zinkee 0.1.36 → 0.1.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zinkee",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@ export interface ApiDisplay {
15
15
  variables?: Array<Record<string, unknown>>;
16
16
  widgets?: Array<Record<string, unknown>>;
17
17
  navigationTargets?: Array<Record<string, unknown>>;
18
- crossWidgetFilter?: Record<string, unknown> | null;
18
+ crossWidgetFilter?: Array<Record<string, unknown>> | null;
19
19
  }
20
20
 
21
21
  export interface ApiDisplayTab {
@@ -272,7 +272,7 @@ export class DisplaysApi {
272
272
 
273
273
  setCrossWidgetFilter(
274
274
  display: NormalizedUuidOrSlug,
275
- body: Record<string, unknown>,
275
+ body: unknown[],
276
276
  ): Promise<Record<string, unknown>> {
277
277
  return this.client.requestJson<Record<string, unknown>>(
278
278
  `/api/v2/displays/${selectorValue(display)}/cross-widget-filter`,
@@ -466,7 +466,7 @@ export const commandRegistry = {
466
466
  command: "displays cross-widget-filter set",
467
467
  accessLevel: "write",
468
468
  supportsExample: true,
469
- supportsRaw: false,
469
+ supportsRaw: true,
470
470
  output: writeOutput,
471
471
  },
472
472
  "displays cross-widget-filter delete": {
@@ -797,4 +797,69 @@ describe("displays commands", () => {
797
797
 
798
798
  expect(fetchMock).not.toHaveBeenCalled();
799
799
  });
800
+
801
+ it("wraps a flag-built cross widget filter configuration in an array", async () => {
802
+ const configPath = createApiConfig();
803
+ const output = createStdoutBuffer();
804
+ const fetchMock = createFetchMock();
805
+ fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1" }));
806
+ const program = buildProgram({ configPath, stdout: output.stdout });
807
+
808
+ await program.parseAsync([
809
+ "node",
810
+ "zinkee",
811
+ "--json",
812
+ "displays",
813
+ "cross-widget-filter",
814
+ "set",
815
+ "display-1",
816
+ "--source-widget",
817
+ "w-source",
818
+ "--source-field",
819
+ "f-source",
820
+ "--target",
821
+ "w-target:f-target",
822
+ ]);
823
+
824
+ const init = fetchMock.mock.calls[0]?.[1];
825
+ expect(fetchMock.mock.calls[0]?.[0]).toBe(
826
+ "https://api.zinkee.test/api/v2/displays/display-1/cross-widget-filter",
827
+ );
828
+ expect(init?.method).toBe("PUT");
829
+ expect(JSON.parse(String(init?.body))).toEqual([
830
+ {
831
+ source: { widgetId: "w-source", fieldId: "f-source" },
832
+ targets: [{ widgetId: "w-target", fieldId: "f-target" }],
833
+ },
834
+ ]);
835
+ });
836
+
837
+ it("sends the raw array of cross widget filter configurations verbatim", async () => {
838
+ const configPath = createApiConfig();
839
+ const output = createStdoutBuffer();
840
+ const fetchMock = createFetchMock();
841
+ fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1" }));
842
+ const program = buildProgram({ configPath, stdout: output.stdout });
843
+
844
+ const rawArray = [
845
+ { source: { widgetId: "w1", fieldId: "f1" }, targets: [{ widgetId: "t1", fieldId: "f1" }] },
846
+ { source: { widgetId: "w2", fieldId: "f2" }, targets: [{ widgetId: "t2", fieldId: "f2" }] },
847
+ ];
848
+
849
+ await program.parseAsync([
850
+ "node",
851
+ "zinkee",
852
+ "--json",
853
+ "displays",
854
+ "cross-widget-filter",
855
+ "set",
856
+ "display-1",
857
+ "--raw",
858
+ JSON.stringify(rawArray),
859
+ ]);
860
+
861
+ const init = fetchMock.mock.calls[0]?.[1];
862
+ expect(init?.method).toBe("PUT");
863
+ expect(JSON.parse(String(init?.body))).toEqual(rawArray);
864
+ });
800
865
  });
@@ -149,9 +149,12 @@ interface NavigationTargetMutationOptions {
149
149
  }
150
150
 
151
151
  interface CrossWidgetFilterSetOptions {
152
- sourceWidget: string;
153
- sourceField: string;
154
- target: string[];
152
+ sourceWidget?: string;
153
+ sourceField?: string;
154
+ sourceSchema?: string;
155
+ target?: string[];
156
+ raw?: string;
157
+ rawFile?: string;
155
158
  }
156
159
 
157
160
  function getCommandIo(stdout: CommandIo | undefined): CommandIo {
@@ -744,20 +747,60 @@ function buildNavigationTargetBody(options: NavigationTargetMutationOptions): Re
744
747
  return body;
745
748
  }
746
749
 
747
- function buildCrossWidgetFilterBody(options: CrossWidgetFilterSetOptions): Record<string, unknown> {
748
- return {
749
- source: {
750
- widgetId: options.sourceWidget,
751
- fieldId: options.sourceField,
752
- },
753
- targets: options.target.map((entry) => {
754
- const [widgetId, fieldId] = entry.split(":");
755
- if (!widgetId || !fieldId) {
756
- throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
757
- }
758
- return { widgetId, fieldId };
759
- }),
750
+ function readRawArray(raw?: string, rawFile?: string): unknown[] {
751
+ if (raw && rawFile) {
752
+ throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
753
+ }
754
+ const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
755
+ if (!source) {
756
+ return [];
757
+ }
758
+ try {
759
+ const parsed = JSON.parse(source) as unknown;
760
+ if (!Array.isArray(parsed)) {
761
+ throw new CliError("invalid_cli_usage", "Raw cross widget filter payload must be a JSON array of configurations.");
762
+ }
763
+ return parsed;
764
+ } catch (error) {
765
+ if (error instanceof CliError) throw error;
766
+ throw new CliError("invalid_cli_usage", "Raw JSON payload is invalid.");
767
+ }
768
+ }
769
+
770
+ // The endpoint replaces the whole array of configurations. With flags a single configuration is
771
+ // built and wrapped in an array; use --raw/--raw-file to send several configurations at once.
772
+ function buildCrossWidgetFilterBody(options: CrossWidgetFilterSetOptions): unknown[] {
773
+ if (options.raw !== undefined || options.rawFile !== undefined) {
774
+ return readRawArray(options.raw, options.rawFile);
775
+ }
776
+
777
+ if (!options.sourceWidget || !options.sourceField) {
778
+ throw new CliError(
779
+ "invalid_cli_usage",
780
+ "Provide --source-widget and --source-field, or --raw/--raw-file with the full configuration array.",
781
+ );
782
+ }
783
+
784
+ const source: Record<string, unknown> = {
785
+ widgetId: options.sourceWidget,
786
+ fieldId: options.sourceField,
760
787
  };
788
+ if (options.sourceSchema) {
789
+ source.schemaId = options.sourceSchema;
790
+ }
791
+
792
+ return [
793
+ {
794
+ source,
795
+ targets: (options.target ?? []).map((entry) => {
796
+ const [widgetId, fieldId] = entry.split(":");
797
+ if (!widgetId || !fieldId) {
798
+ throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
799
+ }
800
+ return { widgetId, fieldId };
801
+ }),
802
+ },
803
+ ];
761
804
  }
762
805
 
763
806
  export function registerDisplaysCommands(
@@ -1212,10 +1255,15 @@ export function registerDisplaysCommands(
1212
1255
  const cross = displays.command("cross-widget-filter").description("Manage cross widget filter");
1213
1256
  cross.command("get").description("Get cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
1214
1257
  const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const item = data.crossWidgetFilter ?? null;
1215
- if (json) { emitJson(stdout, "displays cross-widget-filter get", item); return; } printTable(item ? [item] : [], undefined, stdout);
1258
+ if (json) { emitJson(stdout, "displays cross-widget-filter get", item); return; } printTable(item ?? [], undefined, stdout);
1216
1259
  });
1217
- cross.command("set").description("Set cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
1218
- .requiredOption("--source-widget <id>").requiredOption("--source-field <id>").requiredOption("--target <widget:field>", "repeatable target", (v, p: string[] = []) => [...p, v], [])
1260
+ cross.command("set").description("Set cross widget filter (replaces the whole array of configurations)").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
1261
+ .option("--source-widget <id>", "source widget id (single-configuration mode)")
1262
+ .option("--source-field <id>", "source field id (single-configuration mode)")
1263
+ .option("--source-schema <id>", "source schema id, only for HIERARCHY source widgets")
1264
+ .option("--target <widget:field>", "repeatable target (single-configuration mode)", (v, p: string[] = []) => [...p, v], [])
1265
+ .option("--raw <json>", "full array of configurations as JSON (for several configurations)")
1266
+ .option("--raw-file <path>", "path to a JSON file with the full array of configurations")
1219
1267
  .action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: CrossWidgetFilterSetOptions) {
1220
1268
  const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays cross-widget-filter set", readOnly, "write");
1221
1269
  const data = await api.setCrossWidgetFilter(display, buildCrossWidgetFilterBody(commandOptions));
@@ -1967,42 +1967,92 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
1967
1967
  ]),
1968
1968
  "displays cross-widget-filter set": buildCommandExamples([
1969
1969
  {
1970
- description: "Link one source widget/field to several target widgets through the same field.",
1970
+ description:
1971
+ "Single configuration with flags: link one source widget/field to several targets. The whole array is replaced.",
1971
1972
  command:
1972
1973
  'zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --source-widget 11111111-1111-4111-a111-111111111111 --source-field c60b8526-5835-4c9f-aecd-9a16c7704a34 --target 22222222-2222-4222-a222-222222222222:c60b8526-5835-4c9f-aecd-9a16c7704a34 --target 33333333-3333-4333-a333-333333333333:c60b8526-5835-4c9f-aecd-9a16c7704a34',
1973
- raw: `{
1974
- "source": {
1975
- "widgetId": "11111111-1111-4111-a111-111111111111",
1976
- "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
1977
- },
1978
- "targets": [
1979
- {
1980
- "widgetId": "22222222-2222-4222-a222-222222222222",
1974
+ raw: `[
1975
+ {
1976
+ "source": {
1977
+ "widgetId": "11111111-1111-4111-a111-111111111111",
1981
1978
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
1982
1979
  },
1980
+ "targets": [
1981
+ {
1982
+ "widgetId": "22222222-2222-4222-a222-222222222222",
1983
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
1984
+ },
1985
+ {
1986
+ "widgetId": "33333333-3333-4333-a333-333333333333",
1987
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
1988
+ }
1989
+ ]
1990
+ }
1991
+ ]`,
1992
+ },
1983
1993
  {
1984
- "widgetId": "33333333-3333-4333-a333-333333333333",
1994
+ description:
1995
+ "Several independent configurations with --raw (each source filters its own targets). A widget cannot be a target of two configurations.",
1996
+ command:
1997
+ "zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --raw '[{\"source\":{\"widgetId\":\"11111111-1111-4111-a111-111111111111\",\"fieldId\":\"c60b8526-5835-4c9f-aecd-9a16c7704a34\"},\"targets\":[{\"widgetId\":\"22222222-2222-4222-a222-222222222222\",\"fieldId\":\"c60b8526-5835-4c9f-aecd-9a16c7704a34\"}]},{\"source\":{\"widgetId\":\"44444444-4444-4444-a444-444444444444\",\"fieldId\":\"a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890\"},\"targets\":[{\"widgetId\":\"55555555-5555-4555-a555-555555555555\",\"fieldId\":\"a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890\"}]}]'",
1998
+ raw: `[
1999
+ {
2000
+ "source": {
2001
+ "widgetId": "11111111-1111-4111-a111-111111111111",
1985
2002
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
1986
- }
1987
- ]
1988
- }`,
2003
+ },
2004
+ "targets": [
2005
+ {
2006
+ "widgetId": "22222222-2222-4222-a222-222222222222",
2007
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2008
+ }
2009
+ ]
2010
+ },
2011
+ {
2012
+ "source": {
2013
+ "widgetId": "44444444-4444-4444-a444-444444444444",
2014
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
2015
+ },
2016
+ "targets": [
2017
+ {
2018
+ "widgetId": "55555555-5555-4555-a555-555555555555",
2019
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
2020
+ }
2021
+ ]
2022
+ }
2023
+ ]`,
1989
2024
  },
1990
2025
  {
1991
- description: "Filter one target widget by the status field of the source.",
2026
+ description:
2027
+ "Hierarchy chain with --raw-file: a target of one configuration is the source of the next (click Parent → filters Child, click Child → filters Grandchild).",
1992
2028
  command:
1993
- 'zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --source-widget 11111111-1111-4111-a111-111111111111 --source-field a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890 --target 66666666-6666-4666-a666-666666666666:a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890',
1994
- raw: `{
1995
- "source": {
1996
- "widgetId": "11111111-1111-4111-a111-111111111111",
1997
- "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
2029
+ "zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --raw-file cross-widget-filter.json",
2030
+ raw: `[
2031
+ {
2032
+ "source": {
2033
+ "widgetId": "11111111-1111-4111-a111-111111111111",
2034
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2035
+ },
2036
+ "targets": [
2037
+ {
2038
+ "widgetId": "22222222-2222-4222-a222-222222222222",
2039
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2040
+ }
2041
+ ]
1998
2042
  },
1999
- "targets": [
2000
- {
2001
- "widgetId": "66666666-6666-4666-a666-666666666666",
2043
+ {
2044
+ "source": {
2045
+ "widgetId": "22222222-2222-4222-a222-222222222222",
2002
2046
  "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
2003
- }
2004
- ]
2005
- }`,
2047
+ },
2048
+ "targets": [
2049
+ {
2050
+ "widgetId": "33333333-3333-4333-a333-333333333333",
2051
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
2052
+ }
2053
+ ]
2054
+ }
2055
+ ]`,
2006
2056
  },
2007
2057
  ]),
2008
2058
  "schemas list": buildCommandExamples([
@@ -2900,18 +2950,20 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
2900
2950
  }
2901
2951
  }
2902
2952
  ],
2903
- "crossWidgetFilter": {
2904
- "source": {
2905
- "widgetId": "11111111-1111-4111-a111-111111111111",
2906
- "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2907
- },
2908
- "targets": [
2909
- {
2910
- "widgetId": "22222222-2222-4222-a222-222222222222",
2953
+ "crossWidgetFilter": [
2954
+ {
2955
+ "sourceWidget": {
2956
+ "widgetId": "11111111-1111-4111-a111-111111111111",
2911
2957
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2912
- }
2913
- ]
2914
- },
2958
+ },
2959
+ "targetWidgets": [
2960
+ {
2961
+ "widgetId": "22222222-2222-4222-a222-222222222222",
2962
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
2963
+ }
2964
+ ]
2965
+ }
2966
+ ],
2915
2967
  "navigationTargets": [
2916
2968
  {
2917
2969
  "id": "nav-target-customer",