@deenruv/merchant-plugin 1.0.17-dev.17 → 1.0.17-dev.20
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/README.md +14 -0
- package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +20 -1
- package/dist/plugin-server/api/platform-integration-admin-resolver.js +44 -19
- package/dist/plugin-server/entities/merchant-sync-item.entity.d.ts +14 -0
- package/dist/plugin-server/entities/merchant-sync-item.entity.js +56 -0
- package/dist/plugin-server/entities/merchant-sync-run.entity.d.ts +19 -0
- package/dist/plugin-server/entities/merchant-sync-run.entity.js +72 -0
- package/dist/plugin-server/extensions/api-extensions.js +53 -2
- package/dist/plugin-server/index.js +10 -1
- package/dist/plugin-server/services/google-merchant-api.d.ts +48 -6
- package/dist/plugin-server/services/google-merchant-api.js +247 -32
- package/dist/plugin-server/services/google-platform-integration.service.d.ts +8 -7
- package/dist/plugin-server/services/google-platform-integration.service.js +79 -6
- package/dist/plugin-server/services/merchant-sync-history.service.d.ts +25 -0
- package/dist/plugin-server/services/merchant-sync-history.service.js +97 -0
- package/dist/plugin-server/services/platform-integration.service.d.ts +6 -2
- package/dist/plugin-server/services/platform-integration.service.js +53 -29
- package/dist/plugin-server/services/subscriber.service.d.ts +32 -4
- package/dist/plugin-server/services/subscriber.service.js +141 -23
- package/dist/plugin-server/ui/graphql/mutations.ts +7 -0
- package/dist/plugin-server/ui/graphql/queries.ts +99 -0
- package/dist/plugin-server/ui/pages/GooglePage.tsx +158 -16
- package/dist/plugin-ui/graphql/mutations.d.ts +5 -0
- package/dist/plugin-ui/graphql/mutations.js +6 -0
- package/dist/plugin-ui/graphql/queries.d.ts +53 -2
- package/dist/plugin-ui/graphql/queries.js +49 -0
- package/dist/plugin-ui/pages/GooglePage.js +51 -13
- package/package.json +8 -7
|
@@ -2,6 +2,8 @@ import { typedGql } from "../zeus/typedDocumentNode";
|
|
|
2
2
|
import { scalars } from "./scalars";
|
|
3
3
|
import { merchantPlatformSettingsSelector } from "./mutations";
|
|
4
4
|
import { $ } from "../zeus/index";
|
|
5
|
+
import { gql } from "graphql-tag";
|
|
6
|
+
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
|
|
5
7
|
|
|
6
8
|
const query = typedGql("query", { scalars });
|
|
7
9
|
export const getMerchantPlatformSettings = query({
|
|
@@ -16,3 +18,100 @@ export const getMerchantPlatformInfo = query({
|
|
|
16
18
|
{ isValidConnection: true, productsCount: true },
|
|
17
19
|
],
|
|
18
20
|
});
|
|
21
|
+
|
|
22
|
+
export type GoogleMerchantDiagnostic = {
|
|
23
|
+
checkedAt?: string;
|
|
24
|
+
connectionStatus?: string;
|
|
25
|
+
dataSourceVerified?: boolean;
|
|
26
|
+
disapprovedProductsCount?: number;
|
|
27
|
+
isValidConnection: boolean;
|
|
28
|
+
issues?: Array<{
|
|
29
|
+
code: string;
|
|
30
|
+
description: string;
|
|
31
|
+
offerId: string;
|
|
32
|
+
severity: string;
|
|
33
|
+
}>;
|
|
34
|
+
issuesCount?: number;
|
|
35
|
+
lastError?: {
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
retryable: boolean;
|
|
39
|
+
};
|
|
40
|
+
latencyMs?: number;
|
|
41
|
+
productsCount: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type MerchantSyncRunSummary = {
|
|
45
|
+
createdAt: string;
|
|
46
|
+
errorSummary?: string;
|
|
47
|
+
failed: number;
|
|
48
|
+
finishedAt?: string;
|
|
49
|
+
id: string;
|
|
50
|
+
items: Array<{
|
|
51
|
+
attempts: number;
|
|
52
|
+
errorCode?: string;
|
|
53
|
+
errorMessage?: string;
|
|
54
|
+
offerId: string;
|
|
55
|
+
operation: string;
|
|
56
|
+
status: string;
|
|
57
|
+
}>;
|
|
58
|
+
status: string;
|
|
59
|
+
succeeded: number;
|
|
60
|
+
total: number;
|
|
61
|
+
trigger: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const getGoogleMerchantDiagnostic = gql`
|
|
65
|
+
query GetGoogleMerchantDiagnostic {
|
|
66
|
+
getMerchantPlatformInfo(platform: "google") {
|
|
67
|
+
isValidConnection
|
|
68
|
+
productsCount
|
|
69
|
+
connectionStatus
|
|
70
|
+
dataSourceVerified
|
|
71
|
+
checkedAt
|
|
72
|
+
latencyMs
|
|
73
|
+
disapprovedProductsCount
|
|
74
|
+
issuesCount
|
|
75
|
+
lastError {
|
|
76
|
+
code
|
|
77
|
+
message
|
|
78
|
+
retryable
|
|
79
|
+
}
|
|
80
|
+
issues {
|
|
81
|
+
offerId
|
|
82
|
+
code
|
|
83
|
+
description
|
|
84
|
+
severity
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
` as TypedDocumentNode<{
|
|
89
|
+
getMerchantPlatformInfo?: GoogleMerchantDiagnostic[];
|
|
90
|
+
}>;
|
|
91
|
+
|
|
92
|
+
export const getGoogleMerchantSyncHistory = gql`
|
|
93
|
+
query GetGoogleMerchantSyncHistory($take: Int) {
|
|
94
|
+
getMerchantSyncHistory(platform: "google", take: $take) {
|
|
95
|
+
id
|
|
96
|
+
createdAt
|
|
97
|
+
trigger
|
|
98
|
+
status
|
|
99
|
+
total
|
|
100
|
+
succeeded
|
|
101
|
+
failed
|
|
102
|
+
errorSummary
|
|
103
|
+
finishedAt
|
|
104
|
+
items {
|
|
105
|
+
offerId
|
|
106
|
+
operation
|
|
107
|
+
status
|
|
108
|
+
errorCode
|
|
109
|
+
errorMessage
|
|
110
|
+
attempts
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
` as TypedDocumentNode<
|
|
115
|
+
{ getMerchantSyncHistory: MerchantSyncRunSummary[] },
|
|
116
|
+
{ take?: number }
|
|
117
|
+
>;
|
|
@@ -9,10 +9,14 @@ import { NotificationService } from "@deenruv/admin-ui/core";
|
|
|
9
9
|
import {
|
|
10
10
|
removeOrphanItems,
|
|
11
11
|
saveMerchantPlatformSettings,
|
|
12
|
+
sendAllProductsToMerchantPlatform,
|
|
12
13
|
} from "../graphql/mutations";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
+
getGoogleMerchantDiagnostic,
|
|
16
|
+
getGoogleMerchantSyncHistory,
|
|
15
17
|
getMerchantPlatformSettings,
|
|
18
|
+
GoogleMerchantDiagnostic,
|
|
19
|
+
MerchantSyncRunSummary,
|
|
16
20
|
} from "../graphql/queries";
|
|
17
21
|
|
|
18
22
|
export const GooglePage = () => {
|
|
@@ -20,9 +24,16 @@ export const GooglePage = () => {
|
|
|
20
24
|
const [fetchMerchantPlatformSettings] = useLazyQuery(
|
|
21
25
|
getMerchantPlatformSettings,
|
|
22
26
|
);
|
|
23
|
-
const [fetchMerchantPlatformInfo] = useLazyQuery(
|
|
27
|
+
const [fetchMerchantPlatformInfo] = useLazyQuery(
|
|
28
|
+
getGoogleMerchantDiagnostic,
|
|
29
|
+
);
|
|
30
|
+
const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
|
|
24
31
|
const [mutate] = useMutation(saveMerchantPlatformSettings);
|
|
25
32
|
const [removeOldItems] = useMutation(removeOrphanItems);
|
|
33
|
+
const [sendAllProducts] = useMutation(sendAllProductsToMerchantPlatform);
|
|
34
|
+
const [diagnostic, setDiagnostic] =
|
|
35
|
+
useState<GoogleMerchantDiagnostic | null>(null);
|
|
36
|
+
const [syncHistory, setSyncHistory] = useState<MerchantSyncRunSummary[]>([]);
|
|
26
37
|
const [serviceInfo, setServiceInfo] = useState({
|
|
27
38
|
productsCount: 0,
|
|
28
39
|
connectionStatus: false,
|
|
@@ -55,6 +66,8 @@ export const GooglePage = () => {
|
|
|
55
66
|
brand: "",
|
|
56
67
|
merchantId: "",
|
|
57
68
|
dataSource: "",
|
|
69
|
+
contentLanguage: "pl",
|
|
70
|
+
feedLabel: "PL",
|
|
58
71
|
credentials: "",
|
|
59
72
|
autoUpdate: true,
|
|
60
73
|
firstSync: true,
|
|
@@ -63,9 +76,10 @@ export const GooglePage = () => {
|
|
|
63
76
|
const refetch = async () => {
|
|
64
77
|
try {
|
|
65
78
|
setIsLoading(true);
|
|
66
|
-
const [settingsData, infoData] = await Promise.all([
|
|
79
|
+
const [settingsData, infoData, historyData] = await Promise.all([
|
|
67
80
|
fetchMerchantPlatformSettings({ platform: "google" }),
|
|
68
|
-
fetchMerchantPlatformInfo(
|
|
81
|
+
fetchMerchantPlatformInfo(),
|
|
82
|
+
fetchSyncHistory({ take: 10 }),
|
|
69
83
|
]);
|
|
70
84
|
const settings =
|
|
71
85
|
settingsData?.getMerchantPlatformSettings?.entries?.reduce(
|
|
@@ -79,6 +93,12 @@ export const GooglePage = () => {
|
|
|
79
93
|
if (key === "dataSource") {
|
|
80
94
|
acc.dataSource = value;
|
|
81
95
|
}
|
|
96
|
+
if (key === "contentLanguage") {
|
|
97
|
+
acc.contentLanguage = value;
|
|
98
|
+
}
|
|
99
|
+
if (key === "feedLabel") {
|
|
100
|
+
acc.feedLabel = value;
|
|
101
|
+
}
|
|
82
102
|
if (key === "credentials") {
|
|
83
103
|
acc.credentials = value;
|
|
84
104
|
}
|
|
@@ -94,17 +114,20 @@ export const GooglePage = () => {
|
|
|
94
114
|
brand: string;
|
|
95
115
|
merchantId: string;
|
|
96
116
|
dataSource: string;
|
|
117
|
+
contentLanguage: string;
|
|
118
|
+
feedLabel: string;
|
|
97
119
|
credentials: string;
|
|
98
120
|
autoUpdate: boolean;
|
|
99
121
|
firstSync: boolean;
|
|
100
122
|
},
|
|
101
123
|
);
|
|
102
124
|
setSettingsForm((prev) => ({ ...prev, ...settings }));
|
|
125
|
+
const currentDiagnostic = infoData?.getMerchantPlatformInfo?.[0];
|
|
126
|
+
setDiagnostic(currentDiagnostic ?? null);
|
|
127
|
+
setSyncHistory(historyData?.getMerchantSyncHistory ?? []);
|
|
103
128
|
setServiceInfo({
|
|
104
|
-
productsCount:
|
|
105
|
-
|
|
106
|
-
connectionStatus:
|
|
107
|
-
infoData?.getMerchantPlatformInfo?.[0]?.isValidConnection || false,
|
|
129
|
+
productsCount: currentDiagnostic?.productsCount || 0,
|
|
130
|
+
connectionStatus: currentDiagnostic?.isValidConnection || false,
|
|
108
131
|
});
|
|
109
132
|
setIsLoading(false);
|
|
110
133
|
} catch (error) {
|
|
@@ -137,7 +160,11 @@ export const GooglePage = () => {
|
|
|
137
160
|
}
|
|
138
161
|
} catch (error) {
|
|
139
162
|
console.error(error);
|
|
140
|
-
toast.error(
|
|
163
|
+
toast.error(
|
|
164
|
+
`Failed to save settings: ${
|
|
165
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
166
|
+
}`,
|
|
167
|
+
);
|
|
141
168
|
}
|
|
142
169
|
};
|
|
143
170
|
|
|
@@ -214,6 +241,34 @@ export const GooglePage = () => {
|
|
|
214
241
|
}
|
|
215
242
|
/>
|
|
216
243
|
</div>
|
|
244
|
+
<div className="flex justify-between gap-4">
|
|
245
|
+
<div className="w-full flex flex-col gap-2">
|
|
246
|
+
<label>Content language</label>
|
|
247
|
+
<input
|
|
248
|
+
required
|
|
249
|
+
value={settingsForm.contentLanguage}
|
|
250
|
+
onChange={(e) =>
|
|
251
|
+
setSettingsForm({
|
|
252
|
+
...settingsForm,
|
|
253
|
+
contentLanguage: e.target.value,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="w-full flex flex-col gap-2">
|
|
259
|
+
<label>Feed label</label>
|
|
260
|
+
<input
|
|
261
|
+
required
|
|
262
|
+
value={settingsForm.feedLabel}
|
|
263
|
+
onChange={(e) =>
|
|
264
|
+
setSettingsForm({
|
|
265
|
+
...settingsForm,
|
|
266
|
+
feedLabel: e.target.value,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
217
272
|
<div className="flex flex-col gap-4">
|
|
218
273
|
<div className="flex flex-col">
|
|
219
274
|
<label>Google Account Credentials</label>
|
|
@@ -271,7 +326,67 @@ export const GooglePage = () => {
|
|
|
271
326
|
</form>
|
|
272
327
|
<div className="flex gap-2">
|
|
273
328
|
<span>Connection status</span>
|
|
274
|
-
|
|
329
|
+
<strong>
|
|
330
|
+
{serviceInfo.connectionStatus ? "Connected" : "Disconnected"}
|
|
331
|
+
</strong>
|
|
332
|
+
</div>
|
|
333
|
+
<div className="flex gap-2">
|
|
334
|
+
<span>Products count</span>
|
|
335
|
+
<span>{serviceInfo.productsCount}</span>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="flex flex-col gap-1">
|
|
338
|
+
<span>Diagnostic: {diagnostic?.connectionStatus ?? "UNKNOWN"}</span>
|
|
339
|
+
<span>
|
|
340
|
+
Data source verified:{" "}
|
|
341
|
+
{diagnostic?.dataSourceVerified ? "yes" : "no"}
|
|
342
|
+
</span>
|
|
343
|
+
<span>Latency: {diagnostic?.latencyMs ?? 0} ms</span>
|
|
344
|
+
<span>
|
|
345
|
+
Disapproved products: {diagnostic?.disapprovedProductsCount ?? 0}
|
|
346
|
+
</span>
|
|
347
|
+
<span>Product issues: {diagnostic?.issuesCount ?? 0}</span>
|
|
348
|
+
{diagnostic?.lastError ? (
|
|
349
|
+
<div className="alert alert-danger">
|
|
350
|
+
{diagnostic.lastError.code}: {diagnostic.lastError.message}
|
|
351
|
+
</div>
|
|
352
|
+
) : null}
|
|
353
|
+
{(diagnostic?.issues ?? []).slice(0, 20).map((issue) => (
|
|
354
|
+
<div
|
|
355
|
+
className="alert alert-warning"
|
|
356
|
+
key={`${issue.offerId}-${issue.code}`}
|
|
357
|
+
>
|
|
358
|
+
{issue.offerId} · {issue.severity} · {issue.code}:{" "}
|
|
359
|
+
{issue.description}
|
|
360
|
+
</div>
|
|
361
|
+
))}
|
|
362
|
+
</div>
|
|
363
|
+
<div className="mt-4 flex gap-2">
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
className="btn btn-secondary"
|
|
367
|
+
onClick={async () => {
|
|
368
|
+
await refetch();
|
|
369
|
+
toast.success("Connection diagnostic completed");
|
|
370
|
+
}}
|
|
371
|
+
>
|
|
372
|
+
Test connection
|
|
373
|
+
</button>
|
|
374
|
+
<button
|
|
375
|
+
type="button"
|
|
376
|
+
className="btn btn-primary"
|
|
377
|
+
onClick={async () => {
|
|
378
|
+
try {
|
|
379
|
+
await sendAllProducts({ platform: "google" });
|
|
380
|
+
toast.success("Full synchronization queued");
|
|
381
|
+
await refetch();
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error(error);
|
|
384
|
+
toast.error("Failed to queue full synchronization");
|
|
385
|
+
}
|
|
386
|
+
}}
|
|
387
|
+
>
|
|
388
|
+
Synchronize all
|
|
389
|
+
</button>
|
|
275
390
|
</div>
|
|
276
391
|
{serviceInfo.connectionStatus ? (
|
|
277
392
|
<div style={{ marginTop: "12px" }}>
|
|
@@ -280,11 +395,11 @@ export const GooglePage = () => {
|
|
|
280
395
|
onClick={async () => {
|
|
281
396
|
try {
|
|
282
397
|
await removeOldItems({ platform: "google" });
|
|
283
|
-
toast.success("
|
|
398
|
+
toast.success("Orphan cleanup queued");
|
|
284
399
|
refetch();
|
|
285
400
|
} catch (error) {
|
|
286
401
|
console.error(error);
|
|
287
|
-
toast.error("Failed to
|
|
402
|
+
toast.error("Failed to queue orphan cleanup");
|
|
288
403
|
}
|
|
289
404
|
}}
|
|
290
405
|
>
|
|
@@ -292,10 +407,37 @@ export const GooglePage = () => {
|
|
|
292
407
|
</button>
|
|
293
408
|
</div>
|
|
294
409
|
) : null}
|
|
295
|
-
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
410
|
+
<div className="mt-8 flex flex-col gap-2">
|
|
411
|
+
<h3>Recent synchronizations</h3>
|
|
412
|
+
{syncHistory.length === 0 ? (
|
|
413
|
+
<span>No synchronization history</span>
|
|
414
|
+
) : (
|
|
415
|
+
syncHistory.map((run) => (
|
|
416
|
+
<div className="border rounded p-2" key={run.id}>
|
|
417
|
+
<div>
|
|
418
|
+
{run.status} · {run.succeeded}/{run.total} successful ·{" "}
|
|
419
|
+
{run.failed} failed
|
|
420
|
+
</div>
|
|
421
|
+
<div>{new Date(run.createdAt).toLocaleString()}</div>
|
|
422
|
+
{run.errorSummary ? (
|
|
423
|
+
<div className="text-danger">{run.errorSummary}</div>
|
|
424
|
+
) : null}
|
|
425
|
+
{run.items
|
|
426
|
+
.filter((item) => item.status === "FAILED")
|
|
427
|
+
.slice(0, 20)
|
|
428
|
+
.map((item) => (
|
|
429
|
+
<div
|
|
430
|
+
className="text-danger"
|
|
431
|
+
key={`${run.id}-${item.offerId}-${item.operation}`}
|
|
432
|
+
>
|
|
433
|
+
{item.offerId} · {item.errorCode ?? "ERROR"} · attempts:{" "}
|
|
434
|
+
{item.attempts} · {item.errorMessage}
|
|
435
|
+
</div>
|
|
436
|
+
))}
|
|
437
|
+
</div>
|
|
438
|
+
))
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
299
441
|
</div>
|
|
300
442
|
</PageDetailLayout>
|
|
301
443
|
);
|
|
@@ -27,3 +27,8 @@ export declare const removeOrphanItems: import("@graphql-typed-document-node/cor
|
|
|
27
27
|
}, {} & {
|
|
28
28
|
platform: string;
|
|
29
29
|
}>;
|
|
30
|
+
export declare const sendAllProductsToMerchantPlatform: import("@graphql-typed-document-node/core").TypedDocumentNode<{
|
|
31
|
+
sendAllProductsToMerchantPlatform?: boolean | undefined;
|
|
32
|
+
}, {} & {
|
|
33
|
+
platform: string;
|
|
34
|
+
}>;
|
|
@@ -15,3 +15,9 @@ export const saveMerchantPlatformSettings = mutation({
|
|
|
15
15
|
export const removeOrphanItems = mutation({
|
|
16
16
|
removeOrphanItems: [{ platform: $("platform", "String!") }, true],
|
|
17
17
|
});
|
|
18
|
+
export const sendAllProductsToMerchantPlatform = mutation({
|
|
19
|
+
sendAllProductsToMerchantPlatform: [
|
|
20
|
+
{ platform: $("platform", "String!") },
|
|
21
|
+
true,
|
|
22
|
+
],
|
|
23
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
|
|
2
|
+
export declare const getMerchantPlatformSettings: TypedDocumentNode<{
|
|
2
3
|
getMerchantPlatformSettings?: {
|
|
3
4
|
entries?: {
|
|
4
5
|
value: string;
|
|
@@ -9,7 +10,7 @@ export declare const getMerchantPlatformSettings: import("@graphql-typed-documen
|
|
|
9
10
|
}, {} & {
|
|
10
11
|
platform: string;
|
|
11
12
|
}>;
|
|
12
|
-
export declare const getMerchantPlatformInfo:
|
|
13
|
+
export declare const getMerchantPlatformInfo: TypedDocumentNode<{
|
|
13
14
|
getMerchantPlatformInfo?: {
|
|
14
15
|
isValidConnection: boolean;
|
|
15
16
|
productsCount: number;
|
|
@@ -17,3 +18,53 @@ export declare const getMerchantPlatformInfo: import("@graphql-typed-document-no
|
|
|
17
18
|
}, {} & {
|
|
18
19
|
platform: string;
|
|
19
20
|
}>;
|
|
21
|
+
export type GoogleMerchantDiagnostic = {
|
|
22
|
+
checkedAt?: string;
|
|
23
|
+
connectionStatus?: string;
|
|
24
|
+
dataSourceVerified?: boolean;
|
|
25
|
+
disapprovedProductsCount?: number;
|
|
26
|
+
isValidConnection: boolean;
|
|
27
|
+
issues?: Array<{
|
|
28
|
+
code: string;
|
|
29
|
+
description: string;
|
|
30
|
+
offerId: string;
|
|
31
|
+
severity: string;
|
|
32
|
+
}>;
|
|
33
|
+
issuesCount?: number;
|
|
34
|
+
lastError?: {
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
retryable: boolean;
|
|
38
|
+
};
|
|
39
|
+
latencyMs?: number;
|
|
40
|
+
productsCount: number;
|
|
41
|
+
};
|
|
42
|
+
export type MerchantSyncRunSummary = {
|
|
43
|
+
createdAt: string;
|
|
44
|
+
errorSummary?: string;
|
|
45
|
+
failed: number;
|
|
46
|
+
finishedAt?: string;
|
|
47
|
+
id: string;
|
|
48
|
+
items: Array<{
|
|
49
|
+
attempts: number;
|
|
50
|
+
errorCode?: string;
|
|
51
|
+
errorMessage?: string;
|
|
52
|
+
offerId: string;
|
|
53
|
+
operation: string;
|
|
54
|
+
status: string;
|
|
55
|
+
}>;
|
|
56
|
+
status: string;
|
|
57
|
+
succeeded: number;
|
|
58
|
+
total: number;
|
|
59
|
+
trigger: string;
|
|
60
|
+
};
|
|
61
|
+
export declare const getGoogleMerchantDiagnostic: TypedDocumentNode<{
|
|
62
|
+
getMerchantPlatformInfo?: GoogleMerchantDiagnostic[] | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
[key: string]: any;
|
|
65
|
+
}>;
|
|
66
|
+
export declare const getGoogleMerchantSyncHistory: TypedDocumentNode<{
|
|
67
|
+
getMerchantSyncHistory: MerchantSyncRunSummary[];
|
|
68
|
+
}, {
|
|
69
|
+
take?: number | undefined;
|
|
70
|
+
}>;
|
|
@@ -2,6 +2,7 @@ import { typedGql } from "../zeus/typedDocumentNode";
|
|
|
2
2
|
import { scalars } from "./scalars";
|
|
3
3
|
import { merchantPlatformSettingsSelector } from "./mutations";
|
|
4
4
|
import { $ } from "../zeus/index";
|
|
5
|
+
import { gql } from "graphql-tag";
|
|
5
6
|
const query = typedGql("query", { scalars });
|
|
6
7
|
export const getMerchantPlatformSettings = query({
|
|
7
8
|
getMerchantPlatformSettings: [
|
|
@@ -15,3 +16,51 @@ export const getMerchantPlatformInfo = query({
|
|
|
15
16
|
{ isValidConnection: true, productsCount: true },
|
|
16
17
|
],
|
|
17
18
|
});
|
|
19
|
+
export const getGoogleMerchantDiagnostic = gql `
|
|
20
|
+
query GetGoogleMerchantDiagnostic {
|
|
21
|
+
getMerchantPlatformInfo(platform: "google") {
|
|
22
|
+
isValidConnection
|
|
23
|
+
productsCount
|
|
24
|
+
connectionStatus
|
|
25
|
+
dataSourceVerified
|
|
26
|
+
checkedAt
|
|
27
|
+
latencyMs
|
|
28
|
+
disapprovedProductsCount
|
|
29
|
+
issuesCount
|
|
30
|
+
lastError {
|
|
31
|
+
code
|
|
32
|
+
message
|
|
33
|
+
retryable
|
|
34
|
+
}
|
|
35
|
+
issues {
|
|
36
|
+
offerId
|
|
37
|
+
code
|
|
38
|
+
description
|
|
39
|
+
severity
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
export const getGoogleMerchantSyncHistory = gql `
|
|
45
|
+
query GetGoogleMerchantSyncHistory($take: Int) {
|
|
46
|
+
getMerchantSyncHistory(platform: "google", take: $take) {
|
|
47
|
+
id
|
|
48
|
+
createdAt
|
|
49
|
+
trigger
|
|
50
|
+
status
|
|
51
|
+
total
|
|
52
|
+
succeeded
|
|
53
|
+
failed
|
|
54
|
+
errorSummary
|
|
55
|
+
finishedAt
|
|
56
|
+
items {
|
|
57
|
+
offerId
|
|
58
|
+
operation
|
|
59
|
+
status
|
|
60
|
+
errorCode
|
|
61
|
+
errorMessage
|
|
62
|
+
attempts
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, } from "@deenruv/react-ui-devkit";
|
|
4
|
-
import {
|
|
5
|
-
import { removeOrphanItems, saveMerchantPlatformSettings, } from "../graphql/mutations";
|
|
4
|
+
import { getGoogleMerchantDiagnostic, getGoogleMerchantSyncHistory, getMerchantPlatformSettings, } from "../graphql/queries";
|
|
5
|
+
import { removeOrphanItems, saveMerchantPlatformSettings, sendAllProductsToMerchantPlatform, } from "../graphql/mutations";
|
|
6
6
|
import { toast } from "sonner";
|
|
7
7
|
export const GooglePage = () => {
|
|
8
8
|
const [fetchMerchantPlatformSettings] = useLazyQuery(getMerchantPlatformSettings);
|
|
9
|
-
const [fetchMerchantPlatformInfo] = useLazyQuery(
|
|
9
|
+
const [fetchMerchantPlatformInfo] = useLazyQuery(getGoogleMerchantDiagnostic);
|
|
10
|
+
const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
|
|
10
11
|
const [mutate] = useMutation(saveMerchantPlatformSettings);
|
|
11
12
|
const [removeOldItems] = useMutation(removeOrphanItems);
|
|
13
|
+
const [sendAllProducts] = useMutation(sendAllProductsToMerchantPlatform);
|
|
14
|
+
const [diagnostic, setDiagnostic] = useState(null);
|
|
15
|
+
const [syncHistory, setSyncHistory] = useState([]);
|
|
12
16
|
const [serviceInfo, setServiceInfo] = useState({
|
|
13
17
|
productsCount: 0,
|
|
14
18
|
connectionStatus: false,
|
|
@@ -42,6 +46,8 @@ export const GooglePage = () => {
|
|
|
42
46
|
brand: "",
|
|
43
47
|
merchantId: "",
|
|
44
48
|
dataSource: "",
|
|
49
|
+
contentLanguage: "pl",
|
|
50
|
+
feedLabel: "PL",
|
|
45
51
|
credentials: "",
|
|
46
52
|
autoUpdate: true,
|
|
47
53
|
firstSync: true,
|
|
@@ -49,9 +55,10 @@ export const GooglePage = () => {
|
|
|
49
55
|
const refetch = async () => {
|
|
50
56
|
try {
|
|
51
57
|
setIsLoading(true);
|
|
52
|
-
const [settingsData, infoData] = await Promise.all([
|
|
58
|
+
const [settingsData, infoData, historyData] = await Promise.all([
|
|
53
59
|
fetchMerchantPlatformSettings({ platform: "google" }),
|
|
54
|
-
fetchMerchantPlatformInfo(
|
|
60
|
+
fetchMerchantPlatformInfo(),
|
|
61
|
+
fetchSyncHistory({ take: 10 }),
|
|
55
62
|
]);
|
|
56
63
|
const settings = settingsData?.getMerchantPlatformSettings?.entries?.reduce((acc, { key, value }) => {
|
|
57
64
|
if (key === "brand") {
|
|
@@ -63,6 +70,12 @@ export const GooglePage = () => {
|
|
|
63
70
|
if (key === "dataSource") {
|
|
64
71
|
acc.dataSource = value;
|
|
65
72
|
}
|
|
73
|
+
if (key === "contentLanguage") {
|
|
74
|
+
acc.contentLanguage = value;
|
|
75
|
+
}
|
|
76
|
+
if (key === "feedLabel") {
|
|
77
|
+
acc.feedLabel = value;
|
|
78
|
+
}
|
|
66
79
|
if (key === "credentials") {
|
|
67
80
|
acc.credentials = value;
|
|
68
81
|
}
|
|
@@ -75,9 +88,12 @@ export const GooglePage = () => {
|
|
|
75
88
|
return acc;
|
|
76
89
|
}, {});
|
|
77
90
|
setSettingsForm((prev) => ({ ...prev, ...settings }));
|
|
91
|
+
const currentDiagnostic = infoData?.getMerchantPlatformInfo?.[0];
|
|
92
|
+
setDiagnostic(currentDiagnostic ?? null);
|
|
93
|
+
setSyncHistory(historyData?.getMerchantSyncHistory ?? []);
|
|
78
94
|
setServiceInfo({
|
|
79
|
-
productsCount:
|
|
80
|
-
connectionStatus:
|
|
95
|
+
productsCount: currentDiagnostic?.productsCount || 0,
|
|
96
|
+
connectionStatus: currentDiagnostic?.isValidConnection || false,
|
|
81
97
|
});
|
|
82
98
|
setIsLoading(false);
|
|
83
99
|
}
|
|
@@ -111,7 +127,7 @@ export const GooglePage = () => {
|
|
|
111
127
|
}
|
|
112
128
|
catch (error) {
|
|
113
129
|
console.error(error);
|
|
114
|
-
toast.error(
|
|
130
|
+
toast.error(`Failed to save settings: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
115
131
|
}
|
|
116
132
|
};
|
|
117
133
|
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", { style: {
|
|
@@ -138,7 +154,13 @@ export const GooglePage = () => {
|
|
|
138
154
|
}) })] })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Data source" }), _jsx(Input, { className: "w-full", placeholder: "accounts/{merchantId}/dataSources/{id}", required: true, value: settingsForm.dataSource, onChange: (e) => setSettingsForm({
|
|
139
155
|
...settingsForm,
|
|
140
156
|
dataSource: e.target.value,
|
|
141
|
-
}) })] }), _jsxs("div", { className: "flex
|
|
157
|
+
}) })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Content language" }), _jsx(Input, { required: true, value: settingsForm.contentLanguage, onChange: (e) => setSettingsForm({
|
|
158
|
+
...settingsForm,
|
|
159
|
+
contentLanguage: e.target.value,
|
|
160
|
+
}) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Feed label" }), _jsx(Input, { required: true, value: settingsForm.feedLabel, onChange: (e) => setSettingsForm({
|
|
161
|
+
...settingsForm,
|
|
162
|
+
feedLabel: e.target.value,
|
|
163
|
+
}) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: "Google Account Credentials" }), _jsx(Input, { style: {
|
|
142
164
|
border: "none",
|
|
143
165
|
backgroundColor: "transparent",
|
|
144
166
|
background: "none",
|
|
@@ -148,15 +170,31 @@ export const GooglePage = () => {
|
|
|
148
170
|
}) }), _jsx(Label, { htmlFor: "google-account-credentials", children: "Auto update on Product's change" })] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Label, { htmlFor: "auto-update-on-products-change", children: "Update ALL products with saving" }), _jsx(Checkbox, { id: "auto-update-on-products-change", checked: settingsForm.firstSync, onCheckedChange: (checked) => setSettingsForm({
|
|
149
171
|
...settingsForm,
|
|
150
172
|
firstSync: typeof checked === "boolean" ? checked : false,
|
|
151
|
-
}) })] }), _jsx(Button, { children: "Save" })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Connection status" }), serviceInfo.connectionStatus ?
|
|
173
|
+
}) })] }), _jsx(Button, { children: "Save" })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Connection status" }), _jsx("strong", { children: serviceInfo.connectionStatus ? "Connected" : "Disconnected" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Products count" }), _jsx("span", { children: serviceInfo.productsCount })] }), _jsxs("div", { className: "flex flex-col gap-1 text-sm", children: [_jsxs("span", { children: ["Diagnostic: ", diagnostic?.connectionStatus ?? "UNKNOWN"] }), _jsxs("span", { children: ["Data source verified:", " ", diagnostic?.dataSourceVerified ? "yes" : "no"] }), _jsxs("span", { children: ["Latency: ", diagnostic?.latencyMs ?? 0, " ms"] }), _jsxs("span", { children: ["Disapproved products: ", diagnostic?.disapprovedProductsCount ?? 0] }), _jsxs("span", { children: ["Product issues: ", diagnostic?.issuesCount ?? 0] }), diagnostic?.lastError ? (_jsxs("div", { className: "rounded border border-red-400 p-2 text-red-700", children: [diagnostic.lastError.code, ": ", diagnostic.lastError.message] })) : null, (diagnostic?.issues ?? []).slice(0, 20).map((issue) => (_jsxs("div", { className: "rounded border border-amber-400 p-2", children: [issue.offerId, " \u00B7 ", issue.severity, " \u00B7 ", issue.code, ":", " ", issue.description] }, `${issue.offerId}-${issue.code}`)))] }), _jsxs("div", { className: "mt-4 flex gap-2", children: [_jsx(Button, { type: "button", onClick: async () => {
|
|
174
|
+
await refetch();
|
|
175
|
+
toast.success("Connection diagnostic completed");
|
|
176
|
+
}, children: "Test connection" }), _jsx(Button, { type: "button", onClick: async () => {
|
|
177
|
+
try {
|
|
178
|
+
await sendAllProducts({ platform: "google" });
|
|
179
|
+
toast.success("Full synchronization queued");
|
|
180
|
+
await refetch();
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error(error);
|
|
184
|
+
toast.error("Failed to queue full synchronization");
|
|
185
|
+
}
|
|
186
|
+
}, children: "Synchronize all" })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
|
|
152
187
|
try {
|
|
153
188
|
await removeOldItems({ platform: "google" });
|
|
154
|
-
toast.success("
|
|
189
|
+
toast.success("Orphan cleanup queued");
|
|
155
190
|
refetch();
|
|
156
191
|
}
|
|
157
192
|
catch (error) {
|
|
158
193
|
console.error(error);
|
|
159
|
-
toast.error("Failed to
|
|
194
|
+
toast.error("Failed to queue orphan cleanup");
|
|
160
195
|
}
|
|
161
|
-
}, children: "Remove old items" }) })) : null] }) }))
|
|
196
|
+
}, children: "Remove old items" }) })) : null, _jsxs("div", { className: "mt-8 flex flex-col gap-2", children: [_jsx("h3", { className: "font-semibold", children: "Recent synchronizations" }), syncHistory.length === 0 ? (_jsx("span", { children: "No synchronization history" })) : (syncHistory.map((run) => (_jsxs("div", { className: "rounded border p-2 text-sm", children: [_jsxs("div", { children: [run.status, " \u00B7 ", run.succeeded, "/", run.total, " successful \u00B7", " ", run.failed, " failed"] }), _jsx("div", { children: new Date(run.createdAt).toLocaleString() }), run.errorSummary ? (_jsx("div", { className: "text-red-700", children: run.errorSummary })) : null, run.items
|
|
197
|
+
.filter((item) => item.status === "FAILED")
|
|
198
|
+
.slice(0, 20)
|
|
199
|
+
.map((item) => (_jsxs("div", { className: "mt-1 text-red-700", children: [item.offerId, " \u00B7 ", item.errorCode ?? "ERROR", " \u00B7 attempts:", " ", item.attempts, " \u00B7 ", item.errorMessage] }, `${run.id}-${item.offerId}-${item.operation}`)))] }, run.id))))] })] }) }));
|
|
162
200
|
};
|