@deenruv/merchant-plugin 1.0.17-dev.18 → 1.0.17-dev.23

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.
Files changed (34) hide show
  1. package/README.md +14 -0
  2. package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +20 -1
  3. package/dist/plugin-server/api/platform-integration-admin-resolver.js +44 -19
  4. package/dist/plugin-server/entities/merchant-sync-item.entity.d.ts +14 -0
  5. package/dist/plugin-server/entities/merchant-sync-item.entity.js +56 -0
  6. package/dist/plugin-server/entities/merchant-sync-run.entity.d.ts +19 -0
  7. package/dist/plugin-server/entities/merchant-sync-run.entity.js +72 -0
  8. package/dist/plugin-server/extensions/api-extensions.js +53 -2
  9. package/dist/plugin-server/index.js +10 -1
  10. package/dist/plugin-server/services/google-merchant-api.d.ts +48 -6
  11. package/dist/plugin-server/services/google-merchant-api.js +247 -32
  12. package/dist/plugin-server/services/google-platform-integration.service.d.ts +8 -7
  13. package/dist/plugin-server/services/google-platform-integration.service.js +79 -6
  14. package/dist/plugin-server/services/merchant-sync-history.service.d.ts +25 -0
  15. package/dist/plugin-server/services/merchant-sync-history.service.js +97 -0
  16. package/dist/plugin-server/services/platform-integration.service.d.ts +6 -2
  17. package/dist/plugin-server/services/platform-integration.service.js +53 -29
  18. package/dist/plugin-server/services/subscriber.service.d.ts +32 -4
  19. package/dist/plugin-server/services/subscriber.service.js +141 -23
  20. package/dist/plugin-server/ui/graphql/mutations.ts +7 -0
  21. package/dist/plugin-server/ui/graphql/queries.ts +99 -0
  22. package/dist/plugin-server/ui/pages/GooglePage.tsx +158 -16
  23. package/dist/plugin-server/ui/zeus/const.ts +106 -1
  24. package/dist/plugin-server/ui/zeus/index.ts +482 -10
  25. package/dist/plugin-server/zeus/const.js +104 -1
  26. package/dist/plugin-server/zeus/index.d.ts +492 -6
  27. package/dist/plugin-ui/graphql/mutations.d.ts +9 -4
  28. package/dist/plugin-ui/graphql/mutations.js +6 -0
  29. package/dist/plugin-ui/graphql/queries.d.ts +55 -4
  30. package/dist/plugin-ui/graphql/queries.js +49 -0
  31. package/dist/plugin-ui/pages/GooglePage.js +51 -13
  32. package/dist/plugin-ui/zeus/const.js +104 -1
  33. package/dist/plugin-ui/zeus/index.d.ts +492 -6
  34. 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
- getMerchantPlatformInfo,
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(getMerchantPlatformInfo);
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({ platform: "google" }),
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
- infoData?.getMerchantPlatformInfo?.[0]?.productsCount || 0,
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("Failed to save settings");
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
- {serviceInfo.connectionStatus ? <div>💚</div> : <div>💔</div>}
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("Old items removed successfully");
398
+ toast.success("Orphan cleanup queued");
284
399
  refetch();
285
400
  } catch (error) {
286
401
  console.error(error);
287
- toast.error("Failed to remove old items");
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
- {/* <div className="flex gap-2">
296
- <span>Products count</span>
297
- <span>{serviceInfo.productsCount}</span>
298
- </div> */}
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
  );
@@ -176,6 +176,15 @@ export const AllTypesProps: Record<string,any> = {
176
176
  },
177
177
  getMerchantPlatformInfo:{
178
178
 
179
+ },
180
+ getMerchantSyncHistory:{
181
+
182
+ },
183
+ chartMetric:{
184
+ input:"ChartMetricInput"
185
+ },
186
+ orderSummaryMetric:{
187
+ input:"OrderSummaryMetricInput"
179
188
  },
180
189
  getInpostOrganizations:{
181
190
  input:"GetInpostOrganizationsInput"
@@ -1465,6 +1474,21 @@ export const AllTypesProps: Record<string,any> = {
1465
1474
  SaveMerchantPlatformSettingInput:{
1466
1475
  entries:"MerchantPlatformSettingInput"
1467
1476
  },
1477
+ MetricRangeType: "enum" as const,
1478
+ MetricIntervalType: "enum" as const,
1479
+ ChartMetricType: "enum" as const,
1480
+ BetterMetricRangeInput:{
1481
+ start:"DateTime",
1482
+ end:"DateTime"
1483
+ },
1484
+ OrderSummaryMetricInput:{
1485
+ range:"BetterMetricRangeInput"
1486
+ },
1487
+ ChartMetricInput:{
1488
+ range:"BetterMetricRangeInput",
1489
+ interval:"MetricIntervalType",
1490
+ types:"ChartMetricType"
1491
+ },
1468
1492
  SetInpostShippingMethodConfigInput:{
1469
1493
 
1470
1494
  },
@@ -1994,6 +2018,10 @@ export const ReturnTypes: Record<string,any> = {
1994
2018
  zone:"Zone",
1995
2019
  getMerchantPlatformSettings:"MerchantPlatformSettingsEntity",
1996
2020
  getMerchantPlatformInfo:"MerchantPlatformInfo",
2021
+ getMerchantSyncHistory:"MerchantSyncRun",
2022
+ additionalOrderStates:"AdditionalOrderState",
2023
+ chartMetric:"ChartMetrics",
2024
+ orderSummaryMetric:"OrderSummaryMetrics",
1997
2025
  getInpostConfig:"InpostConfig",
1998
2026
  getInpostOrganizations:"InpostOrganizationResponse",
1999
2027
  metricSummary:"MetricSummary"
@@ -3902,7 +3930,84 @@ export const ReturnTypes: Record<string,any> = {
3902
3930
  },
3903
3931
  MerchantPlatformInfo:{
3904
3932
  isValidConnection:"Boolean",
3905
- productsCount:"Int"
3933
+ productsCount:"Int",
3934
+ connectionStatus:"String",
3935
+ dataSourceVerified:"Boolean",
3936
+ checkedAt:"DateTime",
3937
+ latencyMs:"Int",
3938
+ disapprovedProductsCount:"Int",
3939
+ issuesCount:"Int",
3940
+ lastError:"MerchantPlatformError",
3941
+ issues:"MerchantProductIssue"
3942
+ },
3943
+ MerchantPlatformError:{
3944
+ code:"String",
3945
+ message:"String",
3946
+ retryable:"Boolean"
3947
+ },
3948
+ MerchantProductIssue:{
3949
+ offerId:"String",
3950
+ code:"String",
3951
+ description:"String",
3952
+ severity:"String"
3953
+ },
3954
+ MerchantSyncItem:{
3955
+ id:"ID",
3956
+ offerId:"String",
3957
+ operation:"String",
3958
+ status:"String",
3959
+ errorCode:"String",
3960
+ errorMessage:"String",
3961
+ attempts:"Int"
3962
+ },
3963
+ MerchantSyncRun:{
3964
+ id:"ID",
3965
+ createdAt:"DateTime",
3966
+ platform:"String",
3967
+ trigger:"String",
3968
+ status:"String",
3969
+ jobId:"String",
3970
+ total:"Int",
3971
+ succeeded:"Int",
3972
+ failed:"Int",
3973
+ errorSummary:"String",
3974
+ startedAt:"DateTime",
3975
+ finishedAt:"DateTime",
3976
+ items:"MerchantSyncItem"
3977
+ },
3978
+ AdditionalOrderState:{
3979
+ state:"String",
3980
+ selectedByDefault:"Boolean"
3981
+ },
3982
+ ChartDataType:{
3983
+ type:"ChartMetricType",
3984
+ title:"String",
3985
+ entries:"ChartEntry"
3986
+ },
3987
+ ChartMetrics:{
3988
+ data:"ChartDataType"
3989
+ },
3990
+ OrderSummaryMetrics:{
3991
+ data:"OrderSummaryDataMetric"
3992
+ },
3993
+ OrderSummaryDataMetric:{
3994
+ currencyCode:"CurrencyCode",
3995
+ total:"Float",
3996
+ totalWithTax:"Float",
3997
+ orderCount:"Float",
3998
+ averageOrderValue:"Float",
3999
+ averageOrderValueWithTax:"Float",
4000
+ productCount:"Float"
4001
+ },
4002
+ ChartEntryAdditionalData:{
4003
+ id:"String",
4004
+ name:"String",
4005
+ quantity:"Float"
4006
+ },
4007
+ ChartEntry:{
4008
+ intervalTick:"Int",
4009
+ value:"Float",
4010
+ additionalData:"ChartEntryAdditionalData"
3906
4011
  },
3907
4012
  InpostConfig:{
3908
4013
  shippingMethodId:"ID",