zinkee 0.1.19 → 0.1.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/.claude/settings.local.json +47 -0
- package/README.md +12 -4
- package/dist/index.js +142 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/displays.test.ts +223 -0
- package/src/commands/displays.ts +59 -10
- package/src/utils/examples.ts +21 -10
package/package.json
CHANGED
|
@@ -252,6 +252,229 @@ describe("displays commands", () => {
|
|
|
252
252
|
});
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
+
it("translates --filter into the structured display filter contract", async () => {
|
|
256
|
+
const configPath = createApiConfig();
|
|
257
|
+
const output = createStdoutBuffer();
|
|
258
|
+
const fetchMock = createFetchMock();
|
|
259
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1", widgetId: "widget-f1" }));
|
|
260
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
261
|
+
|
|
262
|
+
await program.parseAsync([
|
|
263
|
+
"node",
|
|
264
|
+
"zinkee",
|
|
265
|
+
"--json",
|
|
266
|
+
"displays",
|
|
267
|
+
"widgets",
|
|
268
|
+
"create",
|
|
269
|
+
"display-1",
|
|
270
|
+
"--type",
|
|
271
|
+
"table",
|
|
272
|
+
"--origin-resource",
|
|
273
|
+
"schema-1",
|
|
274
|
+
"--filter",
|
|
275
|
+
"edad:gt:30",
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
279
|
+
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
280
|
+
type: "TABLE_OR_SUBTABLE",
|
|
281
|
+
query: {
|
|
282
|
+
filters: [
|
|
283
|
+
{
|
|
284
|
+
origin: { fieldId: "edad" },
|
|
285
|
+
comparisonOperator: "GREATER",
|
|
286
|
+
values: ["30"],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("maps the `in` comparator to ANY_OF with a list of values", async () => {
|
|
294
|
+
const configPath = createApiConfig();
|
|
295
|
+
const fetchMock = createFetchMock();
|
|
296
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ widgetId: "widget-f2" }));
|
|
297
|
+
const program = buildProgram({ configPath });
|
|
298
|
+
|
|
299
|
+
await program.parseAsync([
|
|
300
|
+
"node",
|
|
301
|
+
"zinkee",
|
|
302
|
+
"--json",
|
|
303
|
+
"displays",
|
|
304
|
+
"widgets",
|
|
305
|
+
"create",
|
|
306
|
+
"display-1",
|
|
307
|
+
"--type",
|
|
308
|
+
"table",
|
|
309
|
+
"--filter",
|
|
310
|
+
"status:in:1,2,3",
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
314
|
+
expect(JSON.parse(String(init?.body)).query.filters).toEqual([
|
|
315
|
+
{
|
|
316
|
+
origin: { fieldId: "status" },
|
|
317
|
+
comparisonOperator: "ANY_OF",
|
|
318
|
+
values: ["1", "2", "3"],
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("maps the `is_not_empty` comparator with empty values array", async () => {
|
|
324
|
+
const configPath = createApiConfig();
|
|
325
|
+
const fetchMock = createFetchMock();
|
|
326
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ widgetId: "widget-f3" }));
|
|
327
|
+
const program = buildProgram({ configPath });
|
|
328
|
+
|
|
329
|
+
await program.parseAsync([
|
|
330
|
+
"node",
|
|
331
|
+
"zinkee",
|
|
332
|
+
"--json",
|
|
333
|
+
"displays",
|
|
334
|
+
"widgets",
|
|
335
|
+
"create",
|
|
336
|
+
"display-1",
|
|
337
|
+
"--type",
|
|
338
|
+
"table",
|
|
339
|
+
"--filter",
|
|
340
|
+
"email:is_not_empty",
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
344
|
+
expect(JSON.parse(String(init?.body)).query.filters).toEqual([
|
|
345
|
+
{
|
|
346
|
+
origin: { fieldId: "email" },
|
|
347
|
+
comparisonOperator: "IS_NOT_EMPTY",
|
|
348
|
+
values: [],
|
|
349
|
+
},
|
|
350
|
+
]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("accumulates multiple --filter flags in order", async () => {
|
|
354
|
+
const configPath = createApiConfig();
|
|
355
|
+
const fetchMock = createFetchMock();
|
|
356
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ widgetId: "widget-f4" }));
|
|
357
|
+
const program = buildProgram({ configPath });
|
|
358
|
+
|
|
359
|
+
await program.parseAsync([
|
|
360
|
+
"node",
|
|
361
|
+
"zinkee",
|
|
362
|
+
"--json",
|
|
363
|
+
"displays",
|
|
364
|
+
"widgets",
|
|
365
|
+
"create",
|
|
366
|
+
"display-1",
|
|
367
|
+
"--type",
|
|
368
|
+
"table",
|
|
369
|
+
"--filter",
|
|
370
|
+
"status:eq:2",
|
|
371
|
+
"--filter",
|
|
372
|
+
"amount:gte:500",
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
376
|
+
expect(JSON.parse(String(init?.body)).query.filters).toEqual([
|
|
377
|
+
{ origin: { fieldId: "status" }, comparisonOperator: "EQUAL", values: ["2"] },
|
|
378
|
+
{ origin: { fieldId: "amount" }, comparisonOperator: "GREATER_OR_EQUAL", values: ["500"] },
|
|
379
|
+
]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("rejects --filter with an unsupported comparator before issuing a request", async () => {
|
|
383
|
+
const configPath = createApiConfig();
|
|
384
|
+
const fetchMock = createFetchMock();
|
|
385
|
+
const program = buildProgram({ configPath });
|
|
386
|
+
const stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
387
|
+
|
|
388
|
+
await expect(
|
|
389
|
+
program.parseAsync([
|
|
390
|
+
"node",
|
|
391
|
+
"zinkee",
|
|
392
|
+
"--json",
|
|
393
|
+
"displays",
|
|
394
|
+
"widgets",
|
|
395
|
+
"create",
|
|
396
|
+
"display-1",
|
|
397
|
+
"--type",
|
|
398
|
+
"table",
|
|
399
|
+
"--filter",
|
|
400
|
+
"edad:invalido:30",
|
|
401
|
+
]),
|
|
402
|
+
).rejects.toThrow();
|
|
403
|
+
|
|
404
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
405
|
+
expect(stderrSpy.mock.calls.map(([chunk]) => String(chunk)).join("")).toMatch(/Unsupported comparator/);
|
|
406
|
+
stderrSpy.mockRestore();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("preserves --raw query.filters when only --field is passed (no --filter)", async () => {
|
|
410
|
+
const configPath = createApiConfig();
|
|
411
|
+
const fetchMock = createFetchMock();
|
|
412
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ widgetId: "widget-f6" }));
|
|
413
|
+
const program = buildProgram({ configPath });
|
|
414
|
+
|
|
415
|
+
await program.parseAsync([
|
|
416
|
+
"node",
|
|
417
|
+
"zinkee",
|
|
418
|
+
"--json",
|
|
419
|
+
"displays",
|
|
420
|
+
"widgets",
|
|
421
|
+
"create",
|
|
422
|
+
"display-1",
|
|
423
|
+
"--type",
|
|
424
|
+
"table",
|
|
425
|
+
"--field",
|
|
426
|
+
"foo",
|
|
427
|
+
"--raw",
|
|
428
|
+
'{"query":{"filters":[{"origin":{"fieldId":"status"},"comparisonOperator":"EQUAL","values":["2"]}]}}',
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
432
|
+
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
433
|
+
view: { fields: ["foo"] },
|
|
434
|
+
query: {
|
|
435
|
+
filters: [
|
|
436
|
+
{ origin: { fieldId: "status" }, comparisonOperator: "EQUAL", values: ["2"] },
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("translates --filter for variable mutations into the structured filter contract", async () => {
|
|
443
|
+
const configPath = createApiConfig();
|
|
444
|
+
const fetchMock = createFetchMock();
|
|
445
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ variableId: "var-1" }));
|
|
446
|
+
const program = buildProgram({ configPath });
|
|
447
|
+
|
|
448
|
+
await program.parseAsync([
|
|
449
|
+
"node",
|
|
450
|
+
"zinkee",
|
|
451
|
+
"--json",
|
|
452
|
+
"displays",
|
|
453
|
+
"variables",
|
|
454
|
+
"create",
|
|
455
|
+
"display-1",
|
|
456
|
+
"--type",
|
|
457
|
+
"reference",
|
|
458
|
+
"--name",
|
|
459
|
+
"Activos",
|
|
460
|
+
"--filter",
|
|
461
|
+
"estado:eq:activo",
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
const init = fetchMock.mock.calls[0]?.[1];
|
|
465
|
+
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
466
|
+
type: "reference",
|
|
467
|
+
name: "Activos",
|
|
468
|
+
filters: [
|
|
469
|
+
{
|
|
470
|
+
origin: { fieldId: "estado" },
|
|
471
|
+
comparisonOperator: "EQUAL",
|
|
472
|
+
values: ["activo"],
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
255
478
|
it("blocks destructive display commands in read-only mode", async () => {
|
|
256
479
|
const configPath = createApiConfig();
|
|
257
480
|
const fetchMock = createFetchMock();
|
package/src/commands/displays.ts
CHANGED
|
@@ -6,6 +6,11 @@ import { Command, InvalidArgumentError } from "commander";
|
|
|
6
6
|
import { DisplaysApi, type DisplayResource, type DisplaySummary } from "../api/displays.js";
|
|
7
7
|
import { ApiClient } from "../client.js";
|
|
8
8
|
import { defaultConfigPath, readRuntimeConfig } from "../config.js";
|
|
9
|
+
import {
|
|
10
|
+
parseExpression,
|
|
11
|
+
type ExpressionComparator,
|
|
12
|
+
type ParsedExpression,
|
|
13
|
+
} from "../parsers/expressions.js";
|
|
9
14
|
import { normalizeUuidOrSlug, type NormalizedUuidOrSlug } from "../parsers/selectors.js";
|
|
10
15
|
import { resolveRuntimeContext } from "../runtime-context.js";
|
|
11
16
|
import type { RuntimeOverrides } from "../types.js";
|
|
@@ -54,7 +59,7 @@ interface VariableMutationOptions {
|
|
|
54
59
|
defaultValue?: string[];
|
|
55
60
|
sourceSchema?: string;
|
|
56
61
|
sourceField?: string;
|
|
57
|
-
filter?:
|
|
62
|
+
filter?: ParsedExpression[];
|
|
58
63
|
dateMode?: string;
|
|
59
64
|
dateDynamicOption?: string;
|
|
60
65
|
dateSpecific?: string;
|
|
@@ -78,7 +83,7 @@ interface WidgetMutationOptions {
|
|
|
78
83
|
layoutFile?: string;
|
|
79
84
|
field?: string[];
|
|
80
85
|
sort?: string[];
|
|
81
|
-
filter?:
|
|
86
|
+
filter?: ParsedExpression[];
|
|
82
87
|
widgetReadOnly?: boolean;
|
|
83
88
|
hideAddButton?: boolean;
|
|
84
89
|
hideDeleteButton?: boolean;
|
|
@@ -189,6 +194,46 @@ function parseUuidOrSlugSelector(input: string): NormalizedUuidOrSlug {
|
|
|
189
194
|
}
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
const COMPARATOR_TO_DISPLAY_OPERATOR: Record<ExpressionComparator, string> = {
|
|
198
|
+
eq: "EQUAL",
|
|
199
|
+
neq: "NOT_EQUAL",
|
|
200
|
+
gt: "GREATER",
|
|
201
|
+
gte: "GREATER_OR_EQUAL",
|
|
202
|
+
lt: "LESS",
|
|
203
|
+
lte: "LESS_OR_EQUAL",
|
|
204
|
+
contains: "CONTAINS",
|
|
205
|
+
in: "ANY_OF",
|
|
206
|
+
is_empty: "IS_EMPTY",
|
|
207
|
+
is_not_empty: "IS_NOT_EMPTY",
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function toDisplayFilter(parsed: ParsedExpression): Record<string, unknown> {
|
|
211
|
+
const values =
|
|
212
|
+
parsed.value === null
|
|
213
|
+
? []
|
|
214
|
+
: Array.isArray(parsed.value)
|
|
215
|
+
? [...parsed.value]
|
|
216
|
+
: [parsed.value];
|
|
217
|
+
return {
|
|
218
|
+
origin: { fieldId: parsed.field },
|
|
219
|
+
comparisonOperator: COMPARATOR_TO_DISPLAY_OPERATOR[parsed.comparator],
|
|
220
|
+
values,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function parseFilterExpression(input: string): ParsedExpression {
|
|
225
|
+
try {
|
|
226
|
+
return parseExpression(input);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const message = error instanceof Error ? error.message : "Invalid filter expression.";
|
|
229
|
+
throw new InvalidArgumentError(message);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function collectFilters(value: string, previous: ParsedExpression[] = []): ParsedExpression[] {
|
|
234
|
+
return [...previous, parseFilterExpression(value)];
|
|
235
|
+
}
|
|
236
|
+
|
|
192
237
|
function parseBooleanFlag(input: string): boolean {
|
|
193
238
|
const normalized = input.trim().toLowerCase();
|
|
194
239
|
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
@@ -278,7 +323,7 @@ function buildVariableBody(options: VariableMutationOptions): Record<string, unk
|
|
|
278
323
|
};
|
|
279
324
|
}
|
|
280
325
|
if (options.filter && options.filter.length > 0) {
|
|
281
|
-
body.filters = options.filter.map(
|
|
326
|
+
body.filters = options.filter.map(toDisplayFilter);
|
|
282
327
|
}
|
|
283
328
|
if (
|
|
284
329
|
options.dateMode ||
|
|
@@ -348,15 +393,19 @@ function buildWidgetBody(options: WidgetMutationOptions): Record<string, unknown
|
|
|
348
393
|
};
|
|
349
394
|
}
|
|
350
395
|
|
|
351
|
-
if (options.field
|
|
396
|
+
if (options.field && options.field.length > 0) {
|
|
352
397
|
body.view = {
|
|
353
398
|
...(typeof body.view === "object" && body.view ? body.view as Record<string, unknown> : {}),
|
|
354
399
|
fields: options.field,
|
|
355
400
|
};
|
|
401
|
+
}
|
|
402
|
+
const queryMerge: Record<string, unknown> = {};
|
|
403
|
+
if (options.sort && options.sort.length > 0) queryMerge.sorts = options.sort;
|
|
404
|
+
if (options.filter && options.filter.length > 0) queryMerge.filters = options.filter.map(toDisplayFilter);
|
|
405
|
+
if (Object.keys(queryMerge).length > 0) {
|
|
356
406
|
body.query = {
|
|
357
407
|
...(typeof body.query === "object" && body.query ? body.query as Record<string, unknown> : {}),
|
|
358
|
-
|
|
359
|
-
filters: options.filter?.map((entry) => ({ raw: entry })),
|
|
408
|
+
...queryMerge,
|
|
360
409
|
};
|
|
361
410
|
}
|
|
362
411
|
|
|
@@ -624,7 +673,7 @@ export function registerDisplaysCommands(
|
|
|
624
673
|
variables.command("create").description("Create variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
625
674
|
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
626
675
|
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
627
|
-
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter",
|
|
676
|
+
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
628
677
|
.option("--date-mode <mode>").option("--date-dynamic-option <option>").option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
629
678
|
.option("--raw <json>").option("--raw-file <path>")
|
|
630
679
|
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: VariableMutationOptions) {
|
|
@@ -636,7 +685,7 @@ export function registerDisplaysCommands(
|
|
|
636
685
|
variables.command("update").description("Update variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id")
|
|
637
686
|
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
638
687
|
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
639
|
-
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter",
|
|
688
|
+
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
640
689
|
.option("--date-mode <mode>").option("--date-dynamic-option <option>").option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
641
690
|
.option("--raw <json>").option("--raw-file <path>")
|
|
642
691
|
.action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string, commandOptions: VariableMutationOptions) {
|
|
@@ -668,7 +717,7 @@ export function registerDisplaysCommands(
|
|
|
668
717
|
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
669
718
|
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
670
719
|
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
671
|
-
.option("--filter <expr>", "repeatable filter",
|
|
720
|
+
.option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
672
721
|
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
673
722
|
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
674
723
|
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
|
@@ -686,7 +735,7 @@ export function registerDisplaysCommands(
|
|
|
686
735
|
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
687
736
|
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
688
737
|
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
689
|
-
.option("--filter <expr>", "repeatable filter",
|
|
738
|
+
.option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
690
739
|
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
691
740
|
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
692
741
|
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
package/src/utils/examples.ts
CHANGED
|
@@ -1518,30 +1518,41 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1518
1518
|
},
|
|
1519
1519
|
{
|
|
1520
1520
|
description:
|
|
1521
|
-
"Add query filters
|
|
1521
|
+
"Add query filters with --filter. The CLI parses the expression and emits the structured comparisonOperator payload the API expects.",
|
|
1522
1522
|
command:
|
|
1523
|
-
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --
|
|
1523
|
+
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --filter "status:eq:2" --filter "amount:gte:500"`,
|
|
1524
1524
|
raw: `{
|
|
1525
1525
|
"query": {
|
|
1526
1526
|
"filters": [
|
|
1527
1527
|
{
|
|
1528
|
-
"origin": {
|
|
1529
|
-
"schemaId": "contacts",
|
|
1530
|
-
"fieldId": "status"
|
|
1531
|
-
},
|
|
1528
|
+
"origin": { "fieldId": "status" },
|
|
1532
1529
|
"comparisonOperator": "EQUAL",
|
|
1533
1530
|
"values": ["2"]
|
|
1534
1531
|
},
|
|
1535
1532
|
{
|
|
1536
|
-
"origin": {
|
|
1537
|
-
"schemaId": "contacts",
|
|
1538
|
-
"fieldId": "amount"
|
|
1539
|
-
},
|
|
1533
|
+
"origin": { "fieldId": "amount" },
|
|
1540
1534
|
"comparisonOperator": "GREATER_OR_EQUAL",
|
|
1541
1535
|
"values": ["500"]
|
|
1542
1536
|
}
|
|
1543
1537
|
]
|
|
1544
1538
|
}
|
|
1539
|
+
}`,
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
description:
|
|
1543
|
+
"Fall back to --raw for comparators the parser does not cover yet (e.g. NOT_CONTAINS, RANGE).",
|
|
1544
|
+
command:
|
|
1545
|
+
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --raw '{"query":{"filters":[{"origin":{"fieldId":"due-date"},"comparisonOperator":"RANGE","values":["2025-01-01","2025-12-31"]}]}}'`,
|
|
1546
|
+
raw: `{
|
|
1547
|
+
"query": {
|
|
1548
|
+
"filters": [
|
|
1549
|
+
{
|
|
1550
|
+
"origin": { "fieldId": "due-date" },
|
|
1551
|
+
"comparisonOperator": "RANGE",
|
|
1552
|
+
"values": ["2025-01-01", "2025-12-31"]
|
|
1553
|
+
}
|
|
1554
|
+
]
|
|
1555
|
+
}
|
|
1545
1556
|
}`,
|
|
1546
1557
|
},
|
|
1547
1558
|
{
|