@deenruv/merchant-plugin 1.0.17-dev.32 → 1.0.17-dev.34
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/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +3 -0
- package/dist/plugin-server/api/platform-integration-admin-resolver.js +12 -0
- package/dist/plugin-server/extensions/api-extensions.js +1 -0
- package/dist/plugin-server/services/google-merchant-api.d.ts +13 -0
- package/dist/plugin-server/services/google-merchant-api.js +107 -7
- package/dist/plugin-server/services/google-platform-integration.service.d.ts +1 -0
- package/dist/plugin-server/services/google-platform-integration.service.js +9 -0
- package/dist/plugin-ui/graphql/queries.d.ts +5 -0
- package/dist/plugin-ui/graphql/queries.js +5 -0
- package/dist/plugin-ui/locales/en/index.d.ts +7 -0
- package/dist/plugin-ui/locales/en/merchant.json +7 -0
- package/dist/plugin-ui/locales/pl/index.d.ts +7 -0
- package/dist/plugin-ui/locales/pl/merchant.json +7 -0
- package/dist/plugin-ui/pages/GooglePage.js +74 -8
- package/dist/plugin-ui/pages/google-data-source-discovery.d.ts +11 -0
- package/dist/plugin-ui/pages/google-data-source-discovery.js +21 -0
- package/dist/plugin-ui/pages/google-data-source-discovery.spec.d.ts +1 -0
- package/dist/plugin-ui/pages/google-data-source-discovery.spec.js +34 -0
- package/package.json +7 -7
|
@@ -8,6 +8,9 @@ export declare class PlatformIntegrationAdminResolver {
|
|
|
8
8
|
private facebookPlatformIntegrationService;
|
|
9
9
|
private platformIntegrationService;
|
|
10
10
|
constructor(googlePlatformIntegrationService: GooglePlatformIntegrationService, facebookPlatformIntegrationService: FacebookPlatformIntegrationService, platformIntegrationService: PlatformIntegrationService);
|
|
11
|
+
getGoogleMerchantDataSources(ctx: RequestContext, args: {
|
|
12
|
+
merchantId: string;
|
|
13
|
+
}): Promise<string[]>;
|
|
11
14
|
getMerchantPlatformSettings(ctx: RequestContext, args: {
|
|
12
15
|
platform: string;
|
|
13
16
|
}): Promise<MerchantPlatformSettingsEntity | null>;
|
|
@@ -26,6 +26,9 @@ let PlatformIntegrationAdminResolver = class PlatformIntegrationAdminResolver {
|
|
|
26
26
|
this.facebookPlatformIntegrationService = facebookPlatformIntegrationService;
|
|
27
27
|
this.platformIntegrationService = platformIntegrationService;
|
|
28
28
|
}
|
|
29
|
+
async getGoogleMerchantDataSources(ctx, args) {
|
|
30
|
+
return this.googlePlatformIntegrationService.discoverGoogleMerchantDataSources(ctx, args.merchantId);
|
|
31
|
+
}
|
|
29
32
|
async getMerchantPlatformSettings(ctx, args) {
|
|
30
33
|
const settings = await this.platformIntegrationService.getBaseSettings(ctx, args.platform);
|
|
31
34
|
return settings ? this.redactCredentials(settings) : null;
|
|
@@ -85,6 +88,15 @@ let PlatformIntegrationAdminResolver = class PlatformIntegrationAdminResolver {
|
|
|
85
88
|
}
|
|
86
89
|
};
|
|
87
90
|
exports.PlatformIntegrationAdminResolver = PlatformIntegrationAdminResolver;
|
|
91
|
+
__decorate([
|
|
92
|
+
(0, graphql_1.Query)(),
|
|
93
|
+
(0, core_1.Allow)(core_1.Permission.ReadSettings),
|
|
94
|
+
__param(0, (0, core_1.Ctx)()),
|
|
95
|
+
__param(1, (0, graphql_1.Args)()),
|
|
96
|
+
__metadata("design:type", Function),
|
|
97
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
98
|
+
__metadata("design:returntype", Promise)
|
|
99
|
+
], PlatformIntegrationAdminResolver.prototype, "getGoogleMerchantDataSources", null);
|
|
88
100
|
__decorate([
|
|
89
101
|
(0, graphql_1.Query)(),
|
|
90
102
|
__param(0, (0, core_1.Ctx)()),
|
|
@@ -5,6 +5,7 @@ import type { GoogleProcessedProduct, GoogleProduct, GoogleProductInput, RemoteP
|
|
|
5
5
|
export declare const GOOGLE_CONTENT_LANGUAGE = "pl";
|
|
6
6
|
export declare const GOOGLE_FEED_LABEL = "PL";
|
|
7
7
|
export declare const DEFAULT_GOOGLE_WRITE_CONCURRENCY = 4;
|
|
8
|
+
export declare const MAX_GOOGLE_DATA_SOURCE_DISCOVERY_RESULTS = 100;
|
|
8
9
|
type MerchantClientOptions = NonNullable<ConstructorParameters<typeof v1.ProductsServiceClient>[0]>;
|
|
9
10
|
export type GoogleMerchantCredentials = NonNullable<MerchantClientOptions["credentials"]>;
|
|
10
11
|
export type GoogleMerchantSettings = {
|
|
@@ -70,6 +71,15 @@ export interface GoogleDataSourcesReader {
|
|
|
70
71
|
name: string;
|
|
71
72
|
}): Promise<unknown>;
|
|
72
73
|
}
|
|
74
|
+
export interface GoogleDataSourcesDiscoveryClient {
|
|
75
|
+
close(): Promise<void>;
|
|
76
|
+
listDataSourcesAsync(request: {
|
|
77
|
+
parent: string;
|
|
78
|
+
}): AsyncIterable<{
|
|
79
|
+
name?: string | null;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
export type GoogleDataSourcesDiscoveryClientFactory = (credentials: GoogleMerchantCredentials) => GoogleDataSourcesDiscoveryClient;
|
|
73
83
|
export type GoogleMerchantRetryOptions = {
|
|
74
84
|
delay?: (attempt: number) => Promise<void>;
|
|
75
85
|
maxAttempts?: number;
|
|
@@ -106,6 +116,9 @@ export declare function normalizeGoogleMerchantError(error: unknown): Normalized
|
|
|
106
116
|
export declare function parseGoogleCredentials(rawCredentials: string): GoogleMerchantCredentials;
|
|
107
117
|
export declare function normalizeMerchantId(rawMerchantId: string): string;
|
|
108
118
|
export declare function normalizeDataSource(rawDataSource: string, merchantId: string): string;
|
|
119
|
+
export declare function parseGoogleDataSourceDiscoverySettings(requestedMerchantId: string, entries: readonly SettingEntry[]): Pick<GoogleMerchantSettings, "accountId" | "credentials">;
|
|
120
|
+
export declare function createGoogleDataSourcesDiscoveryClient(credentials: GoogleMerchantCredentials): GoogleDataSourcesDiscoveryClient;
|
|
121
|
+
export declare function discoverGoogleMerchantDataSources(requestedMerchantId: string, entries: readonly SettingEntry[], createClient?: GoogleDataSourcesDiscoveryClientFactory): Promise<string[]>;
|
|
109
122
|
export declare function parseGoogleMerchantSettings(entries: readonly SettingEntry[]): GoogleMerchantSettings;
|
|
110
123
|
export declare function createGoogleMerchantClients(credentials: GoogleMerchantCredentials): GoogleMerchantClients;
|
|
111
124
|
export declare function buildGoogleProductIdentifier(offerId: string, contentLanguage?: string, feedLabel?: string): string;
|
|
@@ -18,7 +18,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
18
18
|
return t;
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.selectRemoteOrphanProducts = exports.diagnoseGoogleMerchantConnection = exports.collectGoogleProductsForDataSource = exports.summarizeMerchantOperations = exports.executeGoogleWriteOperations = exports.runBoundedMerchantOperations = exports.createGoogleDeleteOperation = exports.createGoogleUpdateOperation = exports.createGoogleInsertOperation = exports.buildGoogleUpdateMask = exports.validateGoogleProductUrls = exports.toGoogleProductInput = exports.buildGoogleProductInputName = exports.buildGoogleProductName = exports.buildGoogleProductIdentifier = exports.createGoogleMerchantClients = exports.parseGoogleMerchantSettings = exports.normalizeDataSource = exports.normalizeMerchantId = exports.parseGoogleCredentials = exports.normalizeGoogleMerchantError = exports.DEFAULT_GOOGLE_WRITE_CONCURRENCY = exports.GOOGLE_FEED_LABEL = exports.GOOGLE_CONTENT_LANGUAGE = void 0;
|
|
21
|
+
exports.selectRemoteOrphanProducts = exports.diagnoseGoogleMerchantConnection = exports.collectGoogleProductsForDataSource = exports.summarizeMerchantOperations = exports.executeGoogleWriteOperations = exports.runBoundedMerchantOperations = exports.createGoogleDeleteOperation = exports.createGoogleUpdateOperation = exports.createGoogleInsertOperation = exports.buildGoogleUpdateMask = exports.validateGoogleProductUrls = exports.toGoogleProductInput = exports.buildGoogleProductInputName = exports.buildGoogleProductName = exports.buildGoogleProductIdentifier = exports.createGoogleMerchantClients = exports.parseGoogleMerchantSettings = exports.discoverGoogleMerchantDataSources = exports.createGoogleDataSourcesDiscoveryClient = exports.parseGoogleDataSourceDiscoverySettings = exports.normalizeDataSource = exports.normalizeMerchantId = exports.parseGoogleCredentials = exports.normalizeGoogleMerchantError = exports.MAX_GOOGLE_DATA_SOURCE_DISCOVERY_RESULTS = exports.DEFAULT_GOOGLE_WRITE_CONCURRENCY = exports.GOOGLE_FEED_LABEL = exports.GOOGLE_CONTENT_LANGUAGE = void 0;
|
|
22
22
|
const products_1 = require("@google-shopping/products");
|
|
23
23
|
const datasources_1 = require("@google-shopping/datasources");
|
|
24
24
|
const GOOGLE_OAUTH_SCOPE = "https://www.googleapis.com/auth/content";
|
|
@@ -30,6 +30,7 @@ const FEED_LABEL_PATTERN = /^[A-Z0-9_-]{1,20}$/;
|
|
|
30
30
|
exports.GOOGLE_CONTENT_LANGUAGE = "pl";
|
|
31
31
|
exports.GOOGLE_FEED_LABEL = "PL";
|
|
32
32
|
exports.DEFAULT_GOOGLE_WRITE_CONCURRENCY = 4;
|
|
33
|
+
exports.MAX_GOOGLE_DATA_SOURCE_DISCOVERY_RESULTS = 100;
|
|
33
34
|
class MerchantOperationAttemptError {
|
|
34
35
|
constructor(cause, attempts) {
|
|
35
36
|
this.cause = cause;
|
|
@@ -154,6 +155,105 @@ function normalizeDataSource(rawDataSource, merchantId) {
|
|
|
154
155
|
return `accounts/${match[1]}/dataSources/${match[2]}`;
|
|
155
156
|
}
|
|
156
157
|
exports.normalizeDataSource = normalizeDataSource;
|
|
158
|
+
function parseGoogleDataSourceDiscoverySettings(requestedMerchantId, entries) {
|
|
159
|
+
var _a;
|
|
160
|
+
const getValue = (key) => { var _a; return (_a = entries.find((entry) => entry.key === key)) === null || _a === void 0 ? void 0 : _a.value; };
|
|
161
|
+
const accountId = normalizeMerchantId(requestedMerchantId);
|
|
162
|
+
const rawCredentials = (_a = getValue("credentials")) === null || _a === void 0 ? void 0 : _a.trim();
|
|
163
|
+
if (!rawCredentials) {
|
|
164
|
+
throw new Error("Saved Google credentials are required");
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
return {
|
|
168
|
+
accountId,
|
|
169
|
+
credentials: parseGoogleCredentials(rawCredentials),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (_b) {
|
|
173
|
+
throw new Error("Saved Google credentials are invalid");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.parseGoogleDataSourceDiscoverySettings = parseGoogleDataSourceDiscoverySettings;
|
|
177
|
+
function createGoogleDataSourcesDiscoveryClient(credentials) {
|
|
178
|
+
const client = new datasources_1.v1.DataSourcesServiceClient({
|
|
179
|
+
credentials,
|
|
180
|
+
scopes: [GOOGLE_OAUTH_SCOPE],
|
|
181
|
+
});
|
|
182
|
+
// Google exposes data-source reads under the broad content scope. Actual write
|
|
183
|
+
// prevention is IAM/operator-owned; this wrapper narrows the runtime API surface.
|
|
184
|
+
return {
|
|
185
|
+
close: () => client.close(),
|
|
186
|
+
listDataSourcesAsync: (request) => client.listDataSourcesAsync(request),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
exports.createGoogleDataSourcesDiscoveryClient = createGoogleDataSourcesDiscoveryClient;
|
|
190
|
+
function normalizeGoogleDataSourceDiscoveryError(error) {
|
|
191
|
+
var _a;
|
|
192
|
+
const normalized = normalizeGoogleMerchantError(error);
|
|
193
|
+
const messages = {
|
|
194
|
+
AUTHENTICATION_FAILED: "Google Merchant authentication failed",
|
|
195
|
+
PERMISSION_DENIED: "Saved Google credentials cannot read this Merchant account",
|
|
196
|
+
UNAVAILABLE: "Google Merchant data-source discovery is temporarily unavailable",
|
|
197
|
+
};
|
|
198
|
+
return new Error((_a = messages[normalized.status]) !== null && _a !== void 0 ? _a : "Google Merchant data-source discovery failed");
|
|
199
|
+
}
|
|
200
|
+
async function discoverGoogleMerchantDataSources(requestedMerchantId, entries, createClient = createGoogleDataSourcesDiscoveryClient) {
|
|
201
|
+
var _a, e_1, _b, _c;
|
|
202
|
+
const settings = parseGoogleDataSourceDiscoverySettings(requestedMerchantId, entries);
|
|
203
|
+
let client;
|
|
204
|
+
try {
|
|
205
|
+
client = createClient(settings.credentials);
|
|
206
|
+
}
|
|
207
|
+
catch (_d) {
|
|
208
|
+
throw new Error("Google Merchant data-source client could not be created");
|
|
209
|
+
}
|
|
210
|
+
const names = new Set();
|
|
211
|
+
let discoveryError;
|
|
212
|
+
try {
|
|
213
|
+
try {
|
|
214
|
+
for (var _e = true, _f = __asyncValues(client.listDataSourcesAsync({
|
|
215
|
+
parent: `accounts/${settings.accountId}`,
|
|
216
|
+
})), _g; _g = await _f.next(), _a = _g.done, !_a; _e = true) {
|
|
217
|
+
_c = _g.value;
|
|
218
|
+
_e = false;
|
|
219
|
+
const dataSource = _c;
|
|
220
|
+
if (!dataSource.name)
|
|
221
|
+
continue;
|
|
222
|
+
try {
|
|
223
|
+
names.add(normalizeDataSource(dataSource.name, settings.accountId));
|
|
224
|
+
if (names.size === exports.MAX_GOOGLE_DATA_SOURCE_DISCOVERY_RESULTS)
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
catch (_h) {
|
|
228
|
+
// Ignore malformed or cross-account resources returned by the remote API.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
233
|
+
finally {
|
|
234
|
+
try {
|
|
235
|
+
if (!_e && !_a && (_b = _f.return)) await _b.call(_f);
|
|
236
|
+
}
|
|
237
|
+
finally { if (e_1) throw e_1.error; }
|
|
238
|
+
}
|
|
239
|
+
return [...names].sort();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
discoveryError = normalizeGoogleDataSourceDiscoveryError(error);
|
|
243
|
+
throw discoveryError;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
try {
|
|
247
|
+
await client.close();
|
|
248
|
+
}
|
|
249
|
+
catch (_j) {
|
|
250
|
+
if (!discoveryError) {
|
|
251
|
+
throw new Error("Google Merchant data-source client could not be closed");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.discoverGoogleMerchantDataSources = discoverGoogleMerchantDataSources;
|
|
157
257
|
function parseGoogleMerchantSettings(entries) {
|
|
158
258
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
159
259
|
const getValue = (key) => { var _a; return (_a = entries.find((entry) => entry.key === key)) === null || _a === void 0 ? void 0 : _a.value; };
|
|
@@ -407,7 +507,7 @@ function summarizeMerchantOperations(results) {
|
|
|
407
507
|
}
|
|
408
508
|
exports.summarizeMerchantOperations = summarizeMerchantOperations;
|
|
409
509
|
async function collectGoogleProductsForDataSource(client, accountId, dataSource) {
|
|
410
|
-
var _a,
|
|
510
|
+
var _a, e_2, _b, _c;
|
|
411
511
|
var _d, _e;
|
|
412
512
|
const products = new Map();
|
|
413
513
|
try {
|
|
@@ -426,18 +526,18 @@ async function collectGoogleProductsForDataSource(client, accountId, dataSource)
|
|
|
426
526
|
});
|
|
427
527
|
}
|
|
428
528
|
}
|
|
429
|
-
catch (
|
|
529
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
430
530
|
finally {
|
|
431
531
|
try {
|
|
432
532
|
if (!_f && !_a && (_b = _g.return)) await _b.call(_g);
|
|
433
533
|
}
|
|
434
|
-
finally { if (
|
|
534
|
+
finally { if (e_2) throw e_2.error; }
|
|
435
535
|
}
|
|
436
536
|
return [...products.values()];
|
|
437
537
|
}
|
|
438
538
|
exports.collectGoogleProductsForDataSource = collectGoogleProductsForDataSource;
|
|
439
539
|
async function diagnoseGoogleMerchantConnection(client, settings, dataSourcesClient) {
|
|
440
|
-
var _a,
|
|
540
|
+
var _a, e_3, _b, _c;
|
|
441
541
|
var _d, _e, _f, _g, _h, _j, _k;
|
|
442
542
|
const startedAt = Date.now();
|
|
443
543
|
const issues = [];
|
|
@@ -478,12 +578,12 @@ async function diagnoseGoogleMerchantConnection(client, settings, dataSourcesCli
|
|
|
478
578
|
disapprovedProductsCount += 1;
|
|
479
579
|
}
|
|
480
580
|
}
|
|
481
|
-
catch (
|
|
581
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
482
582
|
finally {
|
|
483
583
|
try {
|
|
484
584
|
if (!_l && !_a && (_b = _m.return)) await _b.call(_m);
|
|
485
585
|
}
|
|
486
|
-
finally { if (
|
|
586
|
+
finally { if (e_3) throw e_3.error; }
|
|
487
587
|
}
|
|
488
588
|
}
|
|
489
589
|
catch (error) {
|
|
@@ -19,6 +19,7 @@ export declare class GooglePlatformIntegrationService {
|
|
|
19
19
|
removeOrphanItems(ctx: RequestContext, items: RemoteProduct[]): Promise<void>;
|
|
20
20
|
getAllProducts(ctx: RequestContext): Promise<RemoteProduct[]>;
|
|
21
21
|
getConnectionDiagnostic(ctx: RequestContext): Promise<import("./google-merchant-api.js").GoogleMerchantConnectionDiagnostic>;
|
|
22
|
+
discoverGoogleMerchantDataSources(ctx: RequestContext, requestedMerchantId: string): Promise<string[]>;
|
|
22
23
|
getGoogleProduct(ctx: RequestContext, { communicateID }: {
|
|
23
24
|
communicateID: string;
|
|
24
25
|
}): Promise<GoogleProcessedProduct | null>;
|
|
@@ -100,6 +100,15 @@ let GooglePlatformIntegrationService = class GooglePlatformIntegrationService {
|
|
|
100
100
|
await this.closeClients(clients);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
async discoverGoogleMerchantDataSources(ctx, requestedMerchantId) {
|
|
104
|
+
const rawSettings = await this.connection
|
|
105
|
+
.getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity)
|
|
106
|
+
.findOne({ relations: ["entries"], where: { platform: "google" } });
|
|
107
|
+
if (!rawSettings) {
|
|
108
|
+
throw new Error("Google Merchant settings not found");
|
|
109
|
+
}
|
|
110
|
+
return (0, google_merchant_api_js_1.discoverGoogleMerchantDataSources)(requestedMerchantId, rawSettings.entries);
|
|
111
|
+
}
|
|
103
112
|
async getGoogleProduct(ctx, { communicateID }) {
|
|
104
113
|
const authorization = await this.getAuthorization(ctx);
|
|
105
114
|
try {
|
|
@@ -10,6 +10,11 @@ export declare const getMerchantPlatformSettings: TypedDocumentNode<{
|
|
|
10
10
|
}, {} & {
|
|
11
11
|
platform: string;
|
|
12
12
|
}>;
|
|
13
|
+
export declare const getGoogleMerchantDataSources: TypedDocumentNode<{
|
|
14
|
+
getGoogleMerchantDataSources: string[];
|
|
15
|
+
}, {
|
|
16
|
+
merchantId: string;
|
|
17
|
+
}>;
|
|
13
18
|
export declare const getMerchantPlatformInfo: TypedDocumentNode<{
|
|
14
19
|
getMerchantPlatformInfo?: {
|
|
15
20
|
isValidConnection: boolean;
|
|
@@ -10,6 +10,11 @@ export const getMerchantPlatformSettings = query({
|
|
|
10
10
|
merchantPlatformSettingsSelector,
|
|
11
11
|
],
|
|
12
12
|
});
|
|
13
|
+
export const getGoogleMerchantDataSources = gql `
|
|
14
|
+
query GetGoogleMerchantDataSources($merchantId: String!) {
|
|
15
|
+
getGoogleMerchantDataSources(merchantId: $merchantId)
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
13
18
|
export const getMerchantPlatformInfo = query({
|
|
14
19
|
getMerchantPlatformInfo: [
|
|
15
20
|
{ platform: $("platform", "String!") },
|
|
@@ -27,6 +27,13 @@ declare const _default: {
|
|
|
27
27
|
title: string;
|
|
28
28
|
merchantId: string;
|
|
29
29
|
dataSource: string;
|
|
30
|
+
dataSourceDiscovery: {
|
|
31
|
+
discover: string;
|
|
32
|
+
loading: string;
|
|
33
|
+
empty: string;
|
|
34
|
+
error: string;
|
|
35
|
+
select: string;
|
|
36
|
+
};
|
|
30
37
|
contentLanguage: string;
|
|
31
38
|
feedLabel: string;
|
|
32
39
|
credentials: string;
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
"title": "Google Merchant",
|
|
28
28
|
"merchantId": "Merchant ID",
|
|
29
29
|
"dataSource": "Data source",
|
|
30
|
+
"dataSourceDiscovery": {
|
|
31
|
+
"discover": "Discover data sources",
|
|
32
|
+
"loading": "Discovering data sources…",
|
|
33
|
+
"empty": "No data sources were found for the entered Merchant account.",
|
|
34
|
+
"error": "Data sources could not be discovered. Check the Merchant ID and saved credentials.",
|
|
35
|
+
"select": "Select a discovered data source"
|
|
36
|
+
},
|
|
30
37
|
"contentLanguage": "Content language",
|
|
31
38
|
"feedLabel": "Feed label",
|
|
32
39
|
"credentials": "Google account credentials",
|
|
@@ -27,6 +27,13 @@ declare const _default: {
|
|
|
27
27
|
title: string;
|
|
28
28
|
merchantId: string;
|
|
29
29
|
dataSource: string;
|
|
30
|
+
dataSourceDiscovery: {
|
|
31
|
+
discover: string;
|
|
32
|
+
loading: string;
|
|
33
|
+
empty: string;
|
|
34
|
+
error: string;
|
|
35
|
+
select: string;
|
|
36
|
+
};
|
|
30
37
|
contentLanguage: string;
|
|
31
38
|
feedLabel: string;
|
|
32
39
|
credentials: string;
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
"title": "Google Merchant",
|
|
28
28
|
"merchantId": "Identyfikator sprzedawcy",
|
|
29
29
|
"dataSource": "Źródło danych",
|
|
30
|
+
"dataSourceDiscovery": {
|
|
31
|
+
"discover": "Wykryj źródła danych",
|
|
32
|
+
"loading": "Wykrywanie źródeł danych…",
|
|
33
|
+
"empty": "Nie znaleziono źródeł danych dla podanego konta Merchant.",
|
|
34
|
+
"error": "Nie udało się wykryć źródeł danych. Sprawdź identyfikator Merchant i zapisane dane logowania.",
|
|
35
|
+
"select": "Wybierz wykryte źródło danych"
|
|
36
|
+
},
|
|
30
37
|
"contentLanguage": "Język treści",
|
|
31
38
|
"feedLabel": "Etykieta pliku danych",
|
|
32
39
|
"credentials": "Dane logowania konta Google",
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, useTranslation, } from "@deenruv/react-ui-devkit";
|
|
4
|
-
import { getGoogleMerchantDiagnostic, getGoogleMerchantSyncHistory, getMerchantPlatformSettings, } from "../graphql/queries";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Label, Input, PageBlock, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, useLazyQuery, useMutation, Checkbox, Button, useTranslation, } from "@deenruv/react-ui-devkit";
|
|
4
|
+
import { getGoogleMerchantDiagnostic, getGoogleMerchantDataSources, getGoogleMerchantSyncHistory, getMerchantPlatformSettings, } from "../graphql/queries";
|
|
5
5
|
import { removeOrphanItems, saveMerchantPlatformSettings, sendAllProductsToMerchantPlatform, } from "../graphql/mutations";
|
|
6
6
|
import { toast } from "sonner";
|
|
7
7
|
import { translationNS } from "../translation-ns.js";
|
|
8
|
+
import { createGoogleDataSourceDiscoveryRequestTracker, isValidGoogleMerchantId, selectDiscoveredGoogleDataSource, } from "./google-data-source-discovery.js";
|
|
8
9
|
export const GooglePage = () => {
|
|
9
10
|
const { t, i18n } = useTranslation(translationNS);
|
|
10
11
|
const [fetchMerchantPlatformSettings] = useLazyQuery(getMerchantPlatformSettings);
|
|
11
12
|
const [fetchMerchantPlatformInfo] = useLazyQuery(getGoogleMerchantDiagnostic);
|
|
13
|
+
const [fetchGoogleMerchantDataSources] = useLazyQuery(getGoogleMerchantDataSources);
|
|
12
14
|
const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
|
|
13
15
|
const [mutate] = useMutation(saveMerchantPlatformSettings);
|
|
14
16
|
const [removeOldItems] = useMutation(removeOrphanItems);
|
|
@@ -19,6 +21,12 @@ export const GooglePage = () => {
|
|
|
19
21
|
productsCount: 0,
|
|
20
22
|
connectionStatus: false,
|
|
21
23
|
});
|
|
24
|
+
const [discoveredDataSources, setDiscoveredDataSources] = useState([]);
|
|
25
|
+
const [discoveryAttempted, setDiscoveryAttempted] = useState(false);
|
|
26
|
+
const [discoveryError, setDiscoveryError] = useState(false);
|
|
27
|
+
const [isDiscoveringDataSources, setIsDiscoveringDataSources] = useState(false);
|
|
28
|
+
const discoveryRequestTracker = useRef(createGoogleDataSourceDiscoveryRequestTracker()).current;
|
|
29
|
+
const currentMerchantId = useRef("");
|
|
22
30
|
const handleSelectedFile = (e) => {
|
|
23
31
|
const file = e.target.files?.[0];
|
|
24
32
|
if (file) {
|
|
@@ -112,6 +120,14 @@ export const GooglePage = () => {
|
|
|
112
120
|
useEffect(() => {
|
|
113
121
|
refetch();
|
|
114
122
|
}, []);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
currentMerchantId.current = settingsForm.merchantId;
|
|
125
|
+
discoveryRequestTracker.invalidate();
|
|
126
|
+
setDiscoveredDataSources([]);
|
|
127
|
+
setDiscoveryAttempted(false);
|
|
128
|
+
setDiscoveryError(false);
|
|
129
|
+
setIsDiscoveringDataSources(false);
|
|
130
|
+
}, [settingsForm.merchantId]);
|
|
115
131
|
const onSubmit = async (e) => {
|
|
116
132
|
e.preventDefault();
|
|
117
133
|
try {
|
|
@@ -139,6 +155,34 @@ export const GooglePage = () => {
|
|
|
139
155
|
}));
|
|
140
156
|
}
|
|
141
157
|
};
|
|
158
|
+
const discoverDataSources = async () => {
|
|
159
|
+
const merchantId = settingsForm.merchantId.trim();
|
|
160
|
+
if (!isValidGoogleMerchantId(merchantId))
|
|
161
|
+
return;
|
|
162
|
+
const request = discoveryRequestTracker.begin(merchantId);
|
|
163
|
+
setDiscoveryAttempted(true);
|
|
164
|
+
setDiscoveryError(false);
|
|
165
|
+
setIsDiscoveringDataSources(true);
|
|
166
|
+
try {
|
|
167
|
+
const data = await fetchGoogleMerchantDataSources({ merchantId });
|
|
168
|
+
if (!discoveryRequestTracker.isCurrent(request, currentMerchantId.current)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
setDiscoveredDataSources(data.getGoogleMerchantDataSources);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
if (!discoveryRequestTracker.isCurrent(request, currentMerchantId.current)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
setDiscoveredDataSources([]);
|
|
178
|
+
setDiscoveryError(true);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (discoveryRequestTracker.isCurrent(request, currentMerchantId.current)) {
|
|
182
|
+
setIsDiscoveringDataSources(false);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
142
186
|
return (_jsx(PageBlock, { children: _jsxs("div", { style: { position: "relative" }, className: "flex flex-col p-4 relative max-w-[800px] mx-auto", children: [isLoading && (_jsx("div", { role: "status", "aria-label": t("common.loading"), style: {
|
|
143
187
|
position: "absolute",
|
|
144
188
|
top: 0,
|
|
@@ -157,13 +201,35 @@ export const GooglePage = () => {
|
|
|
157
201
|
alignItems: "center",
|
|
158
202
|
width: "42px",
|
|
159
203
|
height: "42px",
|
|
160
|
-
}, className: "spinner" }) })), _jsx("h2", { className: "mb-4 text-xl font-semibold", children: t("google.title") }), loadError ? (_jsx("div", { className: "mb-4 rounded border border-red-400 p-3 text-red-700", role: "alert", children: t("common.settingsLoadFailed") })) : null, _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("common.brand") }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.merchantId") }), _jsx(Input, { className: "w-full", required: true, value: settingsForm.merchantId, onChange: (e) =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
}, className: "spinner" }) })), _jsx("h2", { className: "mb-4 text-xl font-semibold", children: t("google.title") }), loadError ? (_jsx("div", { className: "mb-4 rounded border border-red-400 p-3 text-red-700", role: "alert", children: t("common.settingsLoadFailed") })) : null, _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("common.brand") }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.merchantId") }), _jsx(Input, { className: "w-full", required: true, value: settingsForm.merchantId, onChange: (e) => {
|
|
205
|
+
currentMerchantId.current = e.target.value;
|
|
206
|
+
discoveryRequestTracker.invalidate();
|
|
207
|
+
setDiscoveredDataSources([]);
|
|
208
|
+
setDiscoveryAttempted(false);
|
|
209
|
+
setDiscoveryError(false);
|
|
210
|
+
setIsDiscoveringDataSources(false);
|
|
211
|
+
setSettingsForm({
|
|
212
|
+
...settingsForm,
|
|
213
|
+
merchantId: e.target.value,
|
|
214
|
+
});
|
|
215
|
+
} })] })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.dataSource") }), _jsx(Input, { className: "w-full", placeholder: "accounts/{merchantId}/dataSources/{id}", required: true, value: settingsForm.dataSource, onChange: (e) => setSettingsForm({
|
|
164
216
|
...settingsForm,
|
|
165
217
|
dataSource: e.target.value,
|
|
166
|
-
}) })
|
|
218
|
+
}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { disabled: isDiscoveringDataSources ||
|
|
219
|
+
!isValidGoogleMerchantId(settingsForm.merchantId), onClick: discoverDataSources, type: "button", children: isDiscoveringDataSources
|
|
220
|
+
? t("google.dataSourceDiscovery.loading")
|
|
221
|
+
: t("google.dataSourceDiscovery.discover") }), discoveredDataSources.length > 0 ? (_jsxs(Select, { onValueChange: (selectedValue) => setSettingsForm((current) => ({
|
|
222
|
+
...current,
|
|
223
|
+
dataSource: selectDiscoveredGoogleDataSource(current.dataSource, selectedValue, discoveredDataSources),
|
|
224
|
+
})), value: discoveredDataSources.includes(settingsForm.dataSource)
|
|
225
|
+
? settingsForm.dataSource
|
|
226
|
+
: undefined, children: [_jsx(SelectTrigger, { className: "flex-1", children: _jsx(SelectValue, { placeholder: t("google.dataSourceDiscovery.select") }) }), _jsx(SelectContent, { children: discoveredDataSources.map((dataSource) => (_jsx(SelectItem, { value: dataSource, children: dataSource }, dataSource))) })] })) : null] }), _jsx("div", { "aria-live": "polite", className: "text-sm", children: discoveryError
|
|
227
|
+
? t("google.dataSourceDiscovery.error")
|
|
228
|
+
: discoveryAttempted &&
|
|
229
|
+
!isDiscoveringDataSources &&
|
|
230
|
+
discoveredDataSources.length === 0
|
|
231
|
+
? t("google.dataSourceDiscovery.empty")
|
|
232
|
+
: null })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.contentLanguage") }), _jsx(Input, { required: true, value: settingsForm.contentLanguage, onChange: (e) => setSettingsForm({
|
|
167
233
|
...settingsForm,
|
|
168
234
|
contentLanguage: e.target.value,
|
|
169
235
|
}) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.feedLabel") }), _jsx(Input, { required: true, value: settingsForm.feedLabel, onChange: (e) => setSettingsForm({
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type GoogleDataSourceDiscoveryRequest = {
|
|
2
|
+
generation: number;
|
|
3
|
+
merchantId: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function isValidGoogleMerchantId(value: string): boolean;
|
|
6
|
+
export declare function createGoogleDataSourceDiscoveryRequestTracker(): {
|
|
7
|
+
begin: (merchantId: string) => GoogleDataSourceDiscoveryRequest;
|
|
8
|
+
invalidate: () => void;
|
|
9
|
+
isCurrent: (request: GoogleDataSourceDiscoveryRequest, currentMerchantId: string) => boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function selectDiscoveredGoogleDataSource(currentValue: string, selectedValue: string, discoveredValues: readonly string[]): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const MERCHANT_ID_PATTERN = /^[1-9]\d*$/;
|
|
2
|
+
export function isValidGoogleMerchantId(value) {
|
|
3
|
+
return MERCHANT_ID_PATTERN.test(value.trim());
|
|
4
|
+
}
|
|
5
|
+
export function createGoogleDataSourceDiscoveryRequestTracker() {
|
|
6
|
+
let generation = 0;
|
|
7
|
+
return {
|
|
8
|
+
begin: (merchantId) => ({
|
|
9
|
+
generation: ++generation,
|
|
10
|
+
merchantId: merchantId.trim(),
|
|
11
|
+
}),
|
|
12
|
+
invalidate: () => {
|
|
13
|
+
generation += 1;
|
|
14
|
+
},
|
|
15
|
+
isCurrent: (request, currentMerchantId) => request.generation === generation &&
|
|
16
|
+
request.merchantId === currentMerchantId.trim(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function selectDiscoveredGoogleDataSource(currentValue, selectedValue, discoveredValues) {
|
|
20
|
+
return discoveredValues.includes(selectedValue) ? selectedValue : currentValue;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { print } from "graphql";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { getGoogleMerchantDataSources } from "../graphql/queries.js";
|
|
4
|
+
import { createGoogleDataSourceDiscoveryRequestTracker, isValidGoogleMerchantId, selectDiscoveredGoogleDataSource, } from "./google-data-source-discovery.js";
|
|
5
|
+
describe("Google data-source discovery selection", () => {
|
|
6
|
+
it("selects only a resource returned by discovery", () => {
|
|
7
|
+
const discovered = [
|
|
8
|
+
"accounts/123/dataSources/456",
|
|
9
|
+
"accounts/123/dataSources/789",
|
|
10
|
+
];
|
|
11
|
+
expect(selectDiscoveredGoogleDataSource("accounts/123/dataSources/manual", "accounts/123/dataSources/789", discovered)).toBe("accounts/123/dataSources/789");
|
|
12
|
+
expect(selectDiscoveredGoogleDataSource("accounts/123/dataSources/manual", "accounts/999/dataSources/789", discovered)).toBe("accounts/123/dataSources/manual");
|
|
13
|
+
});
|
|
14
|
+
it("builds the raw typed discovery query without Zeus regeneration", () => {
|
|
15
|
+
expect(print(getGoogleMerchantDataSources)).toContain("getGoogleMerchantDataSources(merchantId: $merchantId)");
|
|
16
|
+
});
|
|
17
|
+
it("rejects blank or non-numeric account IDs", () => {
|
|
18
|
+
expect(isValidGoogleMerchantId("")).toBe(false);
|
|
19
|
+
expect(isValidGoogleMerchantId("123x")).toBe(false);
|
|
20
|
+
expect(isValidGoogleMerchantId(" 123 ")).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it("invalidates stale and overlapping discovery requests", () => {
|
|
23
|
+
const tracker = createGoogleDataSourceDiscoveryRequestTracker();
|
|
24
|
+
const first = tracker.begin("123");
|
|
25
|
+
const second = tracker.begin("123");
|
|
26
|
+
expect(tracker.isCurrent(first, "123")).toBe(false);
|
|
27
|
+
expect(tracker.isCurrent(second, "123")).toBe(true);
|
|
28
|
+
tracker.invalidate();
|
|
29
|
+
expect(tracker.isCurrent(second, "123")).toBe(false);
|
|
30
|
+
const third = tracker.begin("456");
|
|
31
|
+
expect(tracker.isCurrent(third, "123")).toBe(false);
|
|
32
|
+
expect(tracker.isCurrent(third, "456")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deenruv/merchant-plugin",
|
|
3
|
-
"version": "1.0.17-dev.
|
|
3
|
+
"version": "1.0.17-dev.34",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"react-i18next": "^14.0.5",
|
|
33
33
|
"recharts": "^2.12.7",
|
|
34
34
|
"sonner": "^1.4.41",
|
|
35
|
-
"@deenruv/admin-types": "^1.0.17-dev.
|
|
36
|
-
"@deenruv/
|
|
37
|
-
"@deenruv/ui
|
|
38
|
-
"@deenruv/
|
|
39
|
-
"@deenruv/
|
|
35
|
+
"@deenruv/admin-types": "^1.0.17-dev.34",
|
|
36
|
+
"@deenruv/react-ui-devkit": "^1.0.17-dev.34",
|
|
37
|
+
"@deenruv/admin-ui": "^1.0.17-dev.34",
|
|
38
|
+
"@deenruv/ui-devkit": "^1.0.17-dev.34",
|
|
39
|
+
"@deenruv/common": "^1.0.17-dev.34"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@graphql-typed-document-node/core": "3.2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"rimraf": "^5.0.10",
|
|
49
49
|
"tslib": "^2.6.2",
|
|
50
50
|
"typescript": "5.3.3",
|
|
51
|
-
"@deenruv/core": "^1.0.17-dev.
|
|
51
|
+
"@deenruv/core": "^1.0.17-dev.34"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@deenruv/core": "^1.0.0 || ^1.0.17-dev.0"
|