@adobe/spacecat-shared-data-access 2.8.4 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-data-access-v2.9.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.9.0...@adobe/spacecat-shared-data-access-v2.9.1) (2025-02-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * import-schema backwards compatibility ([#630](https://github.com/adobe/spacecat-shared/issues/630)) ([9ef74bf](https://github.com/adobe/spacecat-shared/commit/9ef74bfc89b0ccb9d27d34e5a10eef182b9ee527)), closes [#626](https://github.com/adobe/spacecat-shared/issues/626)
7
+
8
+ # [@adobe/spacecat-shared-data-access-v2.9.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.8.4...@adobe/spacecat-shared-data-access-v2.9.0) (2025-02-25)
9
+
10
+
11
+ ### Features
12
+
13
+ * import config schema ([#626](https://github.com/adobe/spacecat-shared/issues/626)) ([df147d8](https://github.com/adobe/spacecat-shared/commit/df147d8c8f83cc45f373b1d89823229afe035129))
14
+
1
15
  # [@adobe/spacecat-shared-data-access-v2.8.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.8.3...@adobe/spacecat-shared-data-access-v2.8.4) (2025-02-25)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "2.8.4",
3
+ "version": "2.9.1",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -12,13 +12,76 @@
12
12
 
13
13
  import Joi from 'joi';
14
14
 
15
+ export const IMPORT_TYPES = {
16
+ ORGANIC_KEYWORDS: 'organic-keywords',
17
+ ORGANIC_TRAFFIC: 'organic-traffic',
18
+ TOP_PAGES: 'top-pages',
19
+ };
20
+
21
+ export const IMPORT_DESTINATIONS = {
22
+ DEFAULT: 'default',
23
+ };
24
+
25
+ export const IMPORT_SOURCES = {
26
+ AHREFS: 'ahrefs',
27
+ GSC: 'google',
28
+ };
29
+
30
+ const IMPORT_BASE_KEYS = {
31
+ destinations: Joi.array().items(Joi.string().valid(IMPORT_DESTINATIONS.DEFAULT)).required(),
32
+ sources: Joi.array().items(Joi.string().valid(...Object.values(IMPORT_SOURCES))).required(),
33
+ // not required for now due backward compatibility
34
+ enabled: Joi.boolean().default(true),
35
+ };
36
+
37
+ export const IMPORT_TYPE_SCHEMAS = {
38
+ [IMPORT_TYPES.ORGANIC_KEYWORDS]: Joi.object({
39
+ type: Joi.string().valid(IMPORT_TYPES.ORGANIC_KEYWORDS).required(),
40
+ ...IMPORT_BASE_KEYS,
41
+ pageUrl: Joi.string().uri(),
42
+ }),
43
+ [IMPORT_TYPES.ORGANIC_TRAFFIC]: Joi.object({
44
+ type: Joi.string().valid(IMPORT_TYPES.ORGANIC_TRAFFIC).required(),
45
+ ...IMPORT_BASE_KEYS,
46
+ }),
47
+ [IMPORT_TYPES.TOP_PAGES]: Joi.object({
48
+ type: Joi.string().valid(IMPORT_TYPES.TOP_PAGES).required(),
49
+ ...IMPORT_BASE_KEYS,
50
+ geo: Joi.string(),
51
+ }),
52
+ };
53
+
54
+ export const DEFAULT_IMPORT_CONFIGS = {
55
+ 'organic-keywords': {
56
+ type: 'organic-keywords',
57
+ destinations: ['default'],
58
+ sources: ['ahrefs'],
59
+ enabled: true,
60
+ },
61
+ 'organic-traffic': {
62
+ type: 'organic-traffic',
63
+ destinations: ['default'],
64
+ sources: ['ahrefs'],
65
+ enabled: true,
66
+ },
67
+ 'top-pages': {
68
+ type: 'top-pages',
69
+ destinations: ['default'],
70
+ sources: ['ahrefs'],
71
+ enabled: true,
72
+ geo: 'global',
73
+ },
74
+ };
75
+
15
76
  export const configSchema = Joi.object({
16
77
  slack: Joi.object({
17
78
  workspace: Joi.string(),
18
79
  channel: Joi.string(),
19
80
  invitedUserCount: Joi.number().integer().min(0),
20
81
  }),
21
- imports: Joi.array().items(Joi.object({ type: Joi.string() }).unknown(true)),
82
+ imports: Joi.array().items(
83
+ Joi.alternatives().try(...Object.values(IMPORT_TYPE_SCHEMAS)),
84
+ ),
22
85
  fetchConfig: Joi.object({
23
86
  headers: Joi.object().pattern(Joi.string(), Joi.string()),
24
87
  }).optional(),
@@ -56,7 +119,7 @@ export function validateConfiguration(config) {
56
119
  const { error, value } = configSchema.validate(config);
57
120
 
58
121
  if (error) {
59
- throw new Error(`Configuration validation error: ${error.message}`);
122
+ throw new Error(`Configuration validation error: ${error.message}`, { cause: error });
60
123
  }
61
124
 
62
125
  return value; // Validated and sanitized configuration
@@ -136,6 +199,47 @@ export const Config = (data = {}) => {
136
199
  state.fetchConfig = fetchConfig;
137
200
  };
138
201
 
202
+ self.enableImport = (type, config = {}) => {
203
+ if (!IMPORT_TYPE_SCHEMAS[type]) {
204
+ throw new Error(`Unknown import type: ${type}`);
205
+ }
206
+
207
+ const defaultConfig = DEFAULT_IMPORT_CONFIGS[type];
208
+ const newConfig = {
209
+ ...defaultConfig, ...config, type, enabled: true,
210
+ };
211
+
212
+ // Validate the new config against its schema
213
+ const { error } = IMPORT_TYPE_SCHEMAS[type].validate(newConfig);
214
+ if (error) {
215
+ throw new Error(`Invalid import config: ${error.message}`);
216
+ }
217
+
218
+ state.imports = state.imports || [];
219
+ // Remove existing import of same type if present
220
+ state.imports = state.imports.filter((imp) => imp.type !== type);
221
+ state.imports.push(newConfig);
222
+
223
+ validateConfiguration(state);
224
+ };
225
+
226
+ self.disableImport = (type) => {
227
+ if (!state.imports) return;
228
+
229
+ state.imports = state.imports.map(
230
+ (imp) => (imp.type === type ? { ...imp, enabled: false } : imp),
231
+ );
232
+
233
+ validateConfiguration(state);
234
+ };
235
+
236
+ self.getImportConfig = (type) => state.imports?.find((imp) => imp.type === type);
237
+
238
+ self.isImportEnabled = (type) => {
239
+ const config = self.getImportConfig(type);
240
+ return config?.enabled ?? false;
241
+ };
242
+
139
243
  return Object.freeze(self);
140
244
  };
141
245
 
@@ -23,13 +23,95 @@ import type {
23
23
  SiteTopPage,
24
24
  } from '../index';
25
25
 
26
+ export type IMPORT_TYPES = {
27
+ readonly ORGANIC_KEYWORDS: 'organic-keywords';
28
+ readonly ORGANIC_TRAFFIC: 'organic-traffic';
29
+ readonly TOP_PAGES: 'top-pages';
30
+ };
31
+
32
+ export type IMPORT_DESTINATIONS = {
33
+ readonly DEFAULT: 'default';
34
+ };
35
+
36
+ export type IMPORT_SOURCES = {
37
+ readonly AHREFS: 'ahrefs';
38
+ readonly GSC: 'google';
39
+ };
40
+
41
+ export type ImportType = 'organic-keywords' | 'organic-traffic' | 'top-pages';
42
+ export type ImportDestination = 'default';
43
+ export type ImportSource = 'ahrefs' | 'google';
44
+
45
+ export interface ImportConfig {
46
+ type: ImportType;
47
+ destinations: ImportDestination[];
48
+ sources: ImportSource[];
49
+ enabled: boolean;
50
+ pageUrl?: string;
51
+ geo?: string;
52
+ }
53
+
54
+ export interface SiteConfig {
55
+ state: {
56
+ slack?: {
57
+ workspace?: string;
58
+ channel?: string;
59
+ invitedUserCount?: number;
60
+ };
61
+ imports?: ImportConfig[];
62
+ handlers?: Record<string, {
63
+ mentions?: Record<string, string[]>;
64
+ excludedURLs?: string[];
65
+ manualOverwrites?: Array<{
66
+ brokenTargetURL?: string;
67
+ targetURL?: string;
68
+ }>;
69
+ fixedURLs?: Array<{
70
+ brokenTargetURL?: string;
71
+ targetURL?: string;
72
+ }>;
73
+ includedURLs?: string[];
74
+ groupedURLs?: Array<{
75
+ name: string;
76
+ pattern: string;
77
+ }>;
78
+ latestMetrics?: {
79
+ pageViewsChange: number;
80
+ ctrChange: number;
81
+ projectedTrafficValue: number;
82
+ };
83
+ }>;
84
+ fetchConfig?: {
85
+ headers?: Record<string, string>;
86
+ };
87
+ };
88
+ getSlackConfig(): { workspace?: string; channel?: string; invitedUserCount?: number };
89
+ getImports(): ImportConfig[];
90
+ getImportConfig(type: ImportType): ImportConfig | undefined;
91
+ isImportEnabled(type: ImportType): boolean;
92
+ enableImport(type: ImportType, config?: Partial<ImportConfig>): void;
93
+ disableImport(type: ImportType): void;
94
+ getHandlers(): Record<string, object>;
95
+ getHandlerConfig(type: string): object;
96
+ getSlackMentions(type: string): string[] | undefined;
97
+ getExcludedURLs(type: string): string[] | undefined;
98
+ getManualOverwrites(type: string):
99
+ Array<{ brokenTargetURL?: string; targetURL?: string }> | undefined;
100
+ getFixedURLs(type: string): Array<{ brokenTargetURL?: string; targetURL?: string }> | undefined;
101
+ getIncludedURLs(type: string): string[] | undefined;
102
+ getGroupedURLs(type: string): Array<{ name: string; pattern: string }> | undefined;
103
+ getLatestMetrics(type: string):
104
+ { pageViewsChange: number; ctrChange: number; projectedTrafficValue: number } | undefined;
105
+ getFetchConfig(): { headers?: Record<string, string> } | undefined;
106
+ }
107
+
26
108
  export interface Site extends BaseModel {
27
109
  getAudits(): Promise<Audit>;
28
110
  getAuditsByAuditType(auditType: string): Promise<Audit>;
29
111
  getAuditsByAuditTypeAndAuditedAt(auditType: string, auditedAt: string): Promise<Audit>;
30
112
  getBaseURL(): string;
31
113
  getName(): string;
32
- getConfig(): object;
114
+ getConfig(): SiteConfig;
33
115
  getDeliveryType(): string;
34
116
  getExperiments(): Promise<Experiment[]>;
35
117
  getExperimentsByExpId(expId: string): Promise<Experiment[]>;