@adobe/spacecat-shared-data-access 3.72.0 → 3.73.0
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 +12 -0
- package/package.json +1 -1
- package/src/models/brand-semrush-project/brand-semrush-project.collection.js +7 -7
- package/src/models/brand-semrush-project/brand-semrush-project.model.js +6 -4
- package/src/models/brand-semrush-project/brand-semrush-project.schema.js +16 -3
- package/src/models/brand-semrush-project/index.d.ts +6 -6
- package/src/models/fix-entity/fix-entity.collection.js +47 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-data-access-v3.73.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.72.1...@adobe/spacecat-shared-data-access-v3.73.0) (2026-05-28)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **data-access:** rename BrandSemrushProject accessors (LLMO-5190) ([#1635](https://github.com/adobe/spacecat-shared/issues/1635)) ([f97b09d](https://github.com/adobe/spacecat-shared/commit/f97b09df6f15323baa74ede7aa1f431ba7d68293))
|
|
6
|
+
|
|
7
|
+
## [@adobe/spacecat-shared-data-access-v3.72.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.72.0...@adobe/spacecat-shared-data-access-v3.72.1) (2026-05-28)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* (data-access) include all fixes in getAllFixesWithSuggestionsByOpportunityId (SITES-45307) ([#1634](https://github.com/adobe/spacecat-shared/issues/1634)) ([49e073e](https://github.com/adobe/spacecat-shared/commit/49e073e9e79813597fb11a922fc9f561136ce1d8))
|
|
12
|
+
|
|
1
13
|
## [@adobe/spacecat-shared-data-access-v3.72.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.71.2...@adobe/spacecat-shared-data-access-v3.72.0) (2026-05-27)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/package.json
CHANGED
|
@@ -22,17 +22,17 @@ class BrandSemrushProjectCollection extends BaseCollection {
|
|
|
22
22
|
static COLLECTION_NAME = 'BrandSemrushProjectCollection';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Find the single row for a (brand,
|
|
26
|
-
* null. Used by spacecat-api-service POST /v2/orgs/.../
|
|
27
|
-
* 409 on a duplicate slice before calling
|
|
25
|
+
* Find the single row for a (brand, geoTargetId, languageCode) slice, or
|
|
26
|
+
* null. Used by spacecat-api-service POST /v2/orgs/.../serenity/markets to
|
|
27
|
+
* 409 on a duplicate slice before calling the upstream.
|
|
28
28
|
*
|
|
29
29
|
* @param {string} brandId
|
|
30
|
-
* @param {number}
|
|
31
|
-
* @param {string}
|
|
30
|
+
* @param {number} geoTargetId Google Ads Geo Target ID.
|
|
31
|
+
* @param {string} languageCode BCP-47 primary subtag.
|
|
32
32
|
* @returns {Promise<BrandSemrushProject|null>}
|
|
33
33
|
*/
|
|
34
|
-
async findBySlice(brandId,
|
|
35
|
-
return this.findByIndexKeys({ brandId,
|
|
34
|
+
async findBySlice(brandId, geoTargetId, languageCode) {
|
|
35
|
+
return this.findByIndexKeys({ brandId, geoTargetId, languageCode });
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
import BaseModel from '../base/base.model.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* BrandSemrushProject - mapping between an Adobe brand and
|
|
17
|
-
* project. One row per (brand,
|
|
18
|
-
* workspace is reachable via
|
|
19
|
-
* is not duplicated on this
|
|
16
|
+
* BrandSemrushProject - mapping between an Adobe brand and an upstream
|
|
17
|
+
* Semrush AIO project. One row per (brand, geoTargetId, languageCode) slice.
|
|
18
|
+
* The Semrush workspace is reachable via
|
|
19
|
+
* brand -> organization.getSemrushWorkspaceId() and is not duplicated on this
|
|
20
|
+
* entity. Entity + table names retain the `Semrush` prefix as internal
|
|
21
|
+
* storage identifiers; the public JS accessors do not.
|
|
20
22
|
*
|
|
21
23
|
* @class BrandSemrushProject
|
|
22
24
|
* @extends BaseModel
|
|
@@ -16,11 +16,16 @@ import SchemaBuilder from '../base/schema.builder.js';
|
|
|
16
16
|
import BrandSemrushProject from './brand-semrush-project.model.js';
|
|
17
17
|
import BrandSemrushProjectCollection from './brand-semrush-project.collection.js';
|
|
18
18
|
|
|
19
|
+
// "upstream" in the comments below refers to Semrush AIO — the entity name,
|
|
20
|
+
// table name, and `semrushProjectId` accessor stay because the value IS the
|
|
21
|
+
// upstream identifier. The public API surface decouples via the LLMO-5190
|
|
22
|
+
// rename plan (see the schema attributes' `postgrestField` overrides).
|
|
23
|
+
|
|
19
24
|
// Mirrors the CHECK constraint on brand_to_semrush_projects.language
|
|
20
25
|
// (db/migrations/20260528000000_brand_to_semrush_projects.sql). The slice
|
|
21
26
|
// uniqueness gate is an exact DB string match, so inconsistent casing or
|
|
22
27
|
// format between callers would silently bypass the 409 and bill a duplicate
|
|
23
|
-
//
|
|
28
|
+
// upstream project. Accepts BCP-47-shaped tags: 2/3-letter primary subtag
|
|
24
29
|
// (`en`, `de`, `zho`) optionally followed by a 2-4-letter region/script
|
|
25
30
|
// subtag (`de-ch`, `pt-br`, `zh-hant`). All lowercase by contract.
|
|
26
31
|
const LANGUAGE_TAG_REGEX = /^[a-z]{2,3}(-[a-z]{2,4})?$/;
|
|
@@ -45,15 +50,23 @@ const schema = new SchemaBuilder(BrandSemrushProject, BrandSemrushProjectCollect
|
|
|
45
50
|
required: true,
|
|
46
51
|
validate: (value) => hasText(value),
|
|
47
52
|
})
|
|
48
|
-
|
|
53
|
+
// Google Ads Geo Target ID (criterion_id for the country). Semrush re-uses
|
|
54
|
+
// it as their location identifier; the value is owned by Google Ads, not
|
|
55
|
+
// Semrush. DB column stays `semrush_location_id` — the table layout is
|
|
56
|
+
// subject to change independently (see LLMO-5190 plan §Context).
|
|
57
|
+
.addAttribute('geoTargetId', {
|
|
49
58
|
type: 'number',
|
|
50
59
|
required: true,
|
|
51
60
|
validate: (value) => Number.isInteger(value) && value > 0,
|
|
61
|
+
postgrestField: 'semrush_location_id',
|
|
52
62
|
})
|
|
53
|
-
|
|
63
|
+
// BCP-47 primary subtag (`en`, `de`, `fr`), optionally with a region/script
|
|
64
|
+
// subtag (`de-ch`, `pt-br`, `zh-hant`). DB column stays `language`.
|
|
65
|
+
.addAttribute('languageCode', {
|
|
54
66
|
type: 'string',
|
|
55
67
|
required: true,
|
|
56
68
|
validate: (value) => hasText(value) && LANGUAGE_TAG_REGEX.test(value),
|
|
69
|
+
postgrestField: 'language',
|
|
57
70
|
})
|
|
58
71
|
.addAllIndex(['semrushProjectId']);
|
|
59
72
|
|
|
@@ -22,11 +22,11 @@ import type {
|
|
|
22
22
|
export interface BrandSemrushProject extends BaseModel {
|
|
23
23
|
getBrandId(): string;
|
|
24
24
|
getSemrushProjectId(): string;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
getGeoTargetId(): number;
|
|
26
|
+
getLanguageCode(): string;
|
|
27
27
|
setSemrushProjectId(value: string): BrandSemrushProject;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
setGeoTargetId(value: number): BrandSemrushProject;
|
|
29
|
+
setLanguageCode(value: string): BrandSemrushProject;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export interface BrandSemrushProjectCollection extends
|
|
@@ -37,7 +37,7 @@ export interface BrandSemrushProjectCollection extends
|
|
|
37
37
|
findBySemrushProjectId(semrushProjectId: string): Promise<BrandSemrushProject | null>;
|
|
38
38
|
findBySlice(
|
|
39
39
|
brandId: string,
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
geoTargetId: number,
|
|
41
|
+
languageCode: string,
|
|
42
42
|
): Promise<BrandSemrushProject | null>;
|
|
43
43
|
}
|
|
@@ -193,15 +193,15 @@ class FixEntityCollection extends BaseCollection {
|
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
195
|
* Gets all fixes with their suggestions for a specific opportunity.
|
|
196
|
-
* Fetches
|
|
197
|
-
*
|
|
198
|
-
*
|
|
196
|
+
* Fetches fix entities and mapping records in parallel, then batch-loads
|
|
197
|
+
* suggestions. Fixes without mapping records are included with
|
|
198
|
+
* suggestions: [].
|
|
199
199
|
*
|
|
200
200
|
* @async
|
|
201
201
|
* @param {string} opportunityId - The ID of the opportunity.
|
|
202
202
|
* @returns {Promise<Array>} - A promise that resolves to an array of objects containing:
|
|
203
203
|
* - fixEntity: The FixEntity model
|
|
204
|
-
* - suggestions: Array of associated Suggestion models
|
|
204
|
+
* - suggestions: Array of associated Suggestion models (empty if none mapped)
|
|
205
205
|
* @throws {DataAccessError} - Throws an error if the query fails.
|
|
206
206
|
* @throws {ValidationError} - Throws an error if opportunityId is not provided.
|
|
207
207
|
*/
|
|
@@ -211,10 +211,12 @@ class FixEntityCollection extends BaseCollection {
|
|
|
211
211
|
try {
|
|
212
212
|
const fixEntitySuggestionCollection = this.entityRegistry.getCollection('FixEntitySuggestionCollection');
|
|
213
213
|
|
|
214
|
-
const fixEntitySuggestions = await
|
|
215
|
-
.
|
|
214
|
+
const [allFixEntities, fixEntitySuggestions] = await Promise.all([
|
|
215
|
+
this.allByOpportunityId(opportunityId),
|
|
216
|
+
fixEntitySuggestionCollection.allByIndexKeys({ opportunityId }),
|
|
217
|
+
]);
|
|
216
218
|
|
|
217
|
-
return this.#
|
|
219
|
+
return this.#buildAllFixesWithSuggestions(allFixEntities, fixEntitySuggestions);
|
|
218
220
|
} catch (error) {
|
|
219
221
|
if (error instanceof DataAccessError) {
|
|
220
222
|
throw error;
|
|
@@ -276,6 +278,44 @@ class FixEntityCollection extends BaseCollection {
|
|
|
276
278
|
|
|
277
279
|
return result;
|
|
278
280
|
}
|
|
281
|
+
|
|
282
|
+
async #buildAllFixesWithSuggestions(allFixEntities, fixEntitySuggestions) {
|
|
283
|
+
if (allFixEntities.length === 0) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const suggestionIdsByFixEntityId = {};
|
|
288
|
+
for (const junction of fixEntitySuggestions) {
|
|
289
|
+
const fixEntityId = junction.getFixEntityId();
|
|
290
|
+
const suggestionId = junction.getSuggestionId();
|
|
291
|
+
if (!suggestionIdsByFixEntityId[fixEntityId]) {
|
|
292
|
+
suggestionIdsByFixEntityId[fixEntityId] = [];
|
|
293
|
+
}
|
|
294
|
+
suggestionIdsByFixEntityId[fixEntityId].push(suggestionId);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const allSuggestionIds = Object.values(suggestionIdsByFixEntityId).flat();
|
|
298
|
+
const suggestionsById = {};
|
|
299
|
+
|
|
300
|
+
if (allSuggestionIds.length > 0) {
|
|
301
|
+
const suggestionCollection = this.entityRegistry.getCollection('SuggestionCollection');
|
|
302
|
+
const suggestions = await suggestionCollection.batchGetByKeys(
|
|
303
|
+
allSuggestionIds.map((id) => ({ [suggestionCollection.idName]: id })),
|
|
304
|
+
);
|
|
305
|
+
for (const suggestion of suggestions.data) {
|
|
306
|
+
suggestionsById[suggestion.getId()] = suggestion;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return allFixEntities.map((fixEntity) => {
|
|
311
|
+
const fixEntityId = fixEntity.getId();
|
|
312
|
+
const suggestionIds = suggestionIdsByFixEntityId[fixEntityId] || [];
|
|
313
|
+
const suggestions = suggestionIds
|
|
314
|
+
.map((id) => suggestionsById[id])
|
|
315
|
+
.filter(Boolean);
|
|
316
|
+
return { fixEntity, suggestions };
|
|
317
|
+
});
|
|
318
|
+
}
|
|
279
319
|
}
|
|
280
320
|
|
|
281
321
|
export default FixEntityCollection;
|