zinkee 0.1.19 → 0.1.21
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 +238 -104
- 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 +61 -12
- package/src/utils/examples.ts +120 -16
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,10 +717,10 @@ 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
|
-
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
|
723
|
+
.option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series")
|
|
675
724
|
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
676
725
|
.option("--legend <json>").option("--raw <json>").option("--raw-file <path>")
|
|
677
726
|
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: WidgetMutationOptions) {
|
|
@@ -686,10 +735,10 @@ 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
|
-
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
|
741
|
+
.option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series")
|
|
693
742
|
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
694
743
|
.option("--legend <json>").option("--raw <json>").option("--raw-file <path>")
|
|
695
744
|
.action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string, commandOptions: WidgetMutationOptions) {
|
package/src/utils/examples.ts
CHANGED
|
@@ -1385,9 +1385,40 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1385
1385
|
}`,
|
|
1386
1386
|
},
|
|
1387
1387
|
{
|
|
1388
|
-
description: "Create a
|
|
1388
|
+
description: "Create a CHART widget with a daily time-serie x-axis.",
|
|
1389
1389
|
command:
|
|
1390
|
-
`zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type CHART --name "Revenue by
|
|
1390
|
+
`zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type CHART --name "Revenue by day" --origin-resource 2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e --layout '{"x":0,"y":0,"w":8,"h":6}' --chart-type lines --x-axis-field due-date --x-axis-date-format DATE --x-axis-time-series --y-axis-serie '{"typeCalc":"SUM","fieldCalc":"amount","color":"#4B9CD3","axisType":"PRIMARY"}' --legend '{"position":"bottom"}'`,
|
|
1391
|
+
raw: `{
|
|
1392
|
+
"type": "CHART",
|
|
1393
|
+
"source": {
|
|
1394
|
+
"originResourceId": "2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e",
|
|
1395
|
+
"isOriginSubSchema": false
|
|
1396
|
+
},
|
|
1397
|
+
"placement": {
|
|
1398
|
+
"layout": { "x": 0, "y": 0, "w": 8, "h": 6 }
|
|
1399
|
+
},
|
|
1400
|
+
"presentation": { "name": "Revenue by day" },
|
|
1401
|
+
"chart": {
|
|
1402
|
+
"chartType": "lines",
|
|
1403
|
+
"xAxisFieldId": "due-date",
|
|
1404
|
+
"xAxisDateFormat": "DATE",
|
|
1405
|
+
"xAxisAsTimeSerie": true,
|
|
1406
|
+
"yAxisSeries": [
|
|
1407
|
+
{
|
|
1408
|
+
"typeCalc": "SUM",
|
|
1409
|
+
"fieldCalc": "amount",
|
|
1410
|
+
"color": "#4B9CD3",
|
|
1411
|
+
"axisType": "PRIMARY"
|
|
1412
|
+
}
|
|
1413
|
+
],
|
|
1414
|
+
"legend": { "position": "bottom" }
|
|
1415
|
+
}
|
|
1416
|
+
}`,
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
description: "Create a bars CHART widget with a monthly time-serie x-axis and two y-axis series.",
|
|
1420
|
+
command:
|
|
1421
|
+
`zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type CHART --name "Revenue by month" --origin-resource 2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e --layout '{"x":0,"y":0,"w":8,"h":6}' --chart-type bars --x-axis-field due-date --x-axis-date-format MONTH_YEAR --x-axis-time-series --y-axis-serie '{"typeCalc":"SUM","fieldCalc":"amount","color":"#4B9CD3","axisType":"PRIMARY"}' --y-axis-serie '{"typeCalc":"COUNT","fieldCalc":"id","color":"#E57373","axisType":"SECONDARY"}' --legend '{"position":"bottom"}'`,
|
|
1391
1422
|
raw: `{
|
|
1392
1423
|
"type": "CHART",
|
|
1393
1424
|
"source": {
|
|
@@ -1401,7 +1432,7 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1401
1432
|
"chart": {
|
|
1402
1433
|
"chartType": "bars",
|
|
1403
1434
|
"xAxisFieldId": "due-date",
|
|
1404
|
-
"xAxisDateFormat": "
|
|
1435
|
+
"xAxisDateFormat": "MONTH_YEAR",
|
|
1405
1436
|
"xAxisAsTimeSerie": true,
|
|
1406
1437
|
"yAxisSeries": [
|
|
1407
1438
|
{
|
|
@@ -1419,6 +1450,68 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1419
1450
|
],
|
|
1420
1451
|
"legend": { "position": "bottom" }
|
|
1421
1452
|
}
|
|
1453
|
+
}`,
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
description: "Create a bars CHART widget with a quarterly time-serie x-axis.",
|
|
1457
|
+
command:
|
|
1458
|
+
`zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type CHART --name "Revenue by quarter" --origin-resource 2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e --layout '{"x":0,"y":0,"w":8,"h":6}' --chart-type bars --x-axis-field due-date --x-axis-date-format QUARTER_YEAR --x-axis-time-series --y-axis-serie '{"typeCalc":"SUM","fieldCalc":"amount","color":"#4B9CD3","axisType":"PRIMARY"}' --legend '{"position":"bottom"}'`,
|
|
1459
|
+
raw: `{
|
|
1460
|
+
"type": "CHART",
|
|
1461
|
+
"source": {
|
|
1462
|
+
"originResourceId": "2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e",
|
|
1463
|
+
"isOriginSubSchema": false
|
|
1464
|
+
},
|
|
1465
|
+
"placement": {
|
|
1466
|
+
"layout": { "x": 0, "y": 0, "w": 8, "h": 6 }
|
|
1467
|
+
},
|
|
1468
|
+
"presentation": { "name": "Revenue by quarter" },
|
|
1469
|
+
"chart": {
|
|
1470
|
+
"chartType": "bars",
|
|
1471
|
+
"xAxisFieldId": "due-date",
|
|
1472
|
+
"xAxisDateFormat": "QUARTER_YEAR",
|
|
1473
|
+
"xAxisAsTimeSerie": true,
|
|
1474
|
+
"yAxisSeries": [
|
|
1475
|
+
{
|
|
1476
|
+
"typeCalc": "SUM",
|
|
1477
|
+
"fieldCalc": "amount",
|
|
1478
|
+
"color": "#4B9CD3",
|
|
1479
|
+
"axisType": "PRIMARY"
|
|
1480
|
+
}
|
|
1481
|
+
],
|
|
1482
|
+
"legend": { "position": "bottom" }
|
|
1483
|
+
}
|
|
1484
|
+
}`,
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
description: "Create a CHART widget with a yearly time-serie x-axis.",
|
|
1488
|
+
command:
|
|
1489
|
+
`zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type CHART --name "Revenue by year" --origin-resource 2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e --layout '{"x":0,"y":0,"w":8,"h":6}' --chart-type lines --x-axis-field due-date --x-axis-date-format YEAR --x-axis-time-series --y-axis-serie '{"typeCalc":"SUM","fieldCalc":"amount","color":"#4B9CD3","axisType":"PRIMARY"}' --legend '{"position":"bottom"}'`,
|
|
1490
|
+
raw: `{
|
|
1491
|
+
"type": "CHART",
|
|
1492
|
+
"source": {
|
|
1493
|
+
"originResourceId": "2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e",
|
|
1494
|
+
"isOriginSubSchema": false
|
|
1495
|
+
},
|
|
1496
|
+
"placement": {
|
|
1497
|
+
"layout": { "x": 0, "y": 0, "w": 8, "h": 6 }
|
|
1498
|
+
},
|
|
1499
|
+
"presentation": { "name": "Revenue by year" },
|
|
1500
|
+
"chart": {
|
|
1501
|
+
"chartType": "lines",
|
|
1502
|
+
"xAxisFieldId": "due-date",
|
|
1503
|
+
"xAxisDateFormat": "YEAR",
|
|
1504
|
+
"xAxisAsTimeSerie": true,
|
|
1505
|
+
"yAxisSeries": [
|
|
1506
|
+
{
|
|
1507
|
+
"typeCalc": "SUM",
|
|
1508
|
+
"fieldCalc": "amount",
|
|
1509
|
+
"color": "#4B9CD3",
|
|
1510
|
+
"axisType": "PRIMARY"
|
|
1511
|
+
}
|
|
1512
|
+
],
|
|
1513
|
+
"legend": { "position": "bottom" }
|
|
1514
|
+
}
|
|
1422
1515
|
}`,
|
|
1423
1516
|
},
|
|
1424
1517
|
{
|
|
@@ -1518,30 +1611,41 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1518
1611
|
},
|
|
1519
1612
|
{
|
|
1520
1613
|
description:
|
|
1521
|
-
"Add query filters
|
|
1614
|
+
"Add query filters with --filter. The CLI parses the expression and emits the structured comparisonOperator payload the API expects.",
|
|
1522
1615
|
command:
|
|
1523
|
-
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --
|
|
1616
|
+
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --filter "status:eq:2" --filter "amount:gte:500"`,
|
|
1524
1617
|
raw: `{
|
|
1525
1618
|
"query": {
|
|
1526
1619
|
"filters": [
|
|
1527
1620
|
{
|
|
1528
|
-
"origin": {
|
|
1529
|
-
"schemaId": "contacts",
|
|
1530
|
-
"fieldId": "status"
|
|
1531
|
-
},
|
|
1621
|
+
"origin": { "fieldId": "status" },
|
|
1532
1622
|
"comparisonOperator": "EQUAL",
|
|
1533
1623
|
"values": ["2"]
|
|
1534
1624
|
},
|
|
1535
1625
|
{
|
|
1536
|
-
"origin": {
|
|
1537
|
-
"schemaId": "contacts",
|
|
1538
|
-
"fieldId": "amount"
|
|
1539
|
-
},
|
|
1626
|
+
"origin": { "fieldId": "amount" },
|
|
1540
1627
|
"comparisonOperator": "GREATER_OR_EQUAL",
|
|
1541
1628
|
"values": ["500"]
|
|
1542
1629
|
}
|
|
1543
1630
|
]
|
|
1544
1631
|
}
|
|
1632
|
+
}`,
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
description:
|
|
1636
|
+
"Fall back to --raw for comparators the parser does not cover yet (e.g. NOT_CONTAINS, RANGE).",
|
|
1637
|
+
command:
|
|
1638
|
+
`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"]}]}}'`,
|
|
1639
|
+
raw: `{
|
|
1640
|
+
"query": {
|
|
1641
|
+
"filters": [
|
|
1642
|
+
{
|
|
1643
|
+
"origin": { "fieldId": "due-date" },
|
|
1644
|
+
"comparisonOperator": "RANGE",
|
|
1645
|
+
"values": ["2025-01-01", "2025-12-31"]
|
|
1646
|
+
}
|
|
1647
|
+
]
|
|
1648
|
+
}
|
|
1545
1649
|
}`,
|
|
1546
1650
|
},
|
|
1547
1651
|
{
|
|
@@ -1569,14 +1673,14 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
|
|
|
1569
1673
|
}`,
|
|
1570
1674
|
},
|
|
1571
1675
|
{
|
|
1572
|
-
description: "Replace the chart series with
|
|
1676
|
+
description: "Replace the chart series with quarterly lines.",
|
|
1573
1677
|
command:
|
|
1574
|
-
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 22222222-2222-4222-a222-222222222222 --chart-type lines --x-axis-field due-date --x-axis-date-format
|
|
1678
|
+
`zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 22222222-2222-4222-a222-222222222222 --chart-type lines --x-axis-field due-date --x-axis-date-format QUARTER_YEAR --x-axis-time-series --y-axis-serie '{"typeCalc":"SUM","fieldCalc":"amount","color":"#2E7D32","axisType":"PRIMARY"}'`,
|
|
1575
1679
|
raw: `{
|
|
1576
1680
|
"chart": {
|
|
1577
1681
|
"chartType": "lines",
|
|
1578
1682
|
"xAxisFieldId": "due-date",
|
|
1579
|
-
"xAxisDateFormat": "
|
|
1683
|
+
"xAxisDateFormat": "QUARTER_YEAR",
|
|
1580
1684
|
"xAxisAsTimeSerie": true,
|
|
1581
1685
|
"yAxisSeries": [
|
|
1582
1686
|
{
|