@camunda/ccma-shared-types 4.0.0 → 6.0.0
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/dist/_tests/approvals/expected-value.spec.d.ts +1 -0
- package/dist/_tests/charts/chart-query.spec.d.ts +1 -0
- package/dist/_tests/dashboards/definition.spec.d.ts +1 -0
- package/dist/_tests/types/PreprodClusterTtl.type.spec.d.ts +1 -0
- package/dist/_tests/utils/elasticsearchPlanPath.utils.spec.d.ts +1 -0
- package/dist/approvals/dns-subdomain.d.ts +12 -0
- package/dist/approvals/dns-subdomain.js +16 -0
- package/dist/approvals/dns-subdomain.js.map +7 -0
- package/dist/approvals/expected-value.d.ts +33 -0
- package/dist/approvals/expected-value.js +26 -0
- package/dist/approvals/expected-value.js.map +7 -0
- package/dist/approvals/use-cases.const.d.ts +65 -51
- package/dist/approvals/use-cases.const.js +40 -9
- package/dist/approvals/use-cases.const.js.map +2 -2
- package/dist/charts/chart-metrics.d.ts +35 -0
- package/dist/charts/chart-metrics.js +188 -0
- package/dist/charts/chart-metrics.js.map +7 -0
- package/dist/charts/chart-query.d.ts +55 -0
- package/dist/charts/chart-query.js +376 -0
- package/dist/charts/chart-query.js.map +7 -0
- package/dist/charts/chart-spec.d.ts +103 -0
- package/dist/charts/chart-spec.js +79 -0
- package/dist/charts/chart-spec.js.map +7 -0
- package/dist/charts/index.d.ts +3 -0
- package/dist/charts/index.js +59 -0
- package/dist/charts/index.js.map +7 -0
- package/dist/dashboards/definition.d.ts +86 -0
- package/dist/dashboards/definition.js +137 -0
- package/dist/dashboards/definition.js.map +7 -0
- package/dist/dtos/Annotation.dto.d.ts +1 -1
- package/dist/dtos/Annotation.dto.js +10 -2
- package/dist/dtos/Annotation.dto.js.map +2 -2
- package/dist/dtos/ControlV2.dto.d.ts +27 -0
- package/dist/dtos/Dashboard.dto.d.ts +336 -0
- package/dist/dtos/Dashboard.dto.js +70 -0
- package/dist/dtos/Dashboard.dto.js.map +7 -0
- package/dist/dtos/ExternalWebapp.dto.d.ts +12 -0
- package/dist/dtos/FeatureFlags.dto.d.ts +85 -58
- package/dist/dtos/FeatureFlags.dto.js +0 -10
- package/dist/dtos/FeatureFlags.dto.js.map +3 -3
- package/dist/dtos/Hdp.dto.d.ts +70 -0
- package/dist/dtos/Organization.dto.d.ts +10 -0
- package/dist/dtos/Organization.dto.js.map +1 -1
- package/dist/dtos/SalesPlan.dto.d.ts +25 -1
- package/dist/dtos/SalesPlan.dto.js +19 -6
- package/dist/dtos/SalesPlan.dto.js.map +2 -2
- package/dist/enums/SystemAuditEntity.enum.d.ts +4 -2
- package/dist/enums/SystemAuditEntity.enum.js +2 -0
- package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
- package/dist/index.d.ts +17 -10
- package/dist/index.js +163 -24
- package/dist/index.js.map +2 -2
- package/dist/permissions/accounts.permissions.d.ts +7 -28
- package/dist/permissions/accounts.permissions.js +7 -27
- package/dist/permissions/accounts.permissions.js.map +2 -2
- package/dist/permissions/combo.js +2 -3
- package/dist/permissions/combo.js.map +2 -2
- package/dist/search/compiler.d.ts +20 -0
- package/dist/search/compiler.js +48 -11
- package/dist/search/compiler.js.map +2 -2
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js.map +2 -2
- package/dist/search/parser.d.ts +3 -0
- package/dist/search/parser.js +3 -1
- package/dist/search/parser.js.map +2 -2
- package/dist/types/ClusterPlanFields.type.js +74 -74
- package/dist/types/ClusterPlanFields.type.js.map +2 -2
- package/dist/types/PreprodClusterTtl.type.d.ts +33 -0
- package/dist/types/PreprodClusterTtl.type.js +37 -0
- package/dist/types/PreprodClusterTtl.type.js.map +7 -0
- package/dist/types/hdp.types.d.ts +6 -0
- package/dist/types/hdp.types.js +3 -1
- package/dist/types/hdp.types.js.map +2 -2
- package/dist/utils/elasticsearchPlanPath.utils.d.ts +18 -0
- package/dist/utils/elasticsearchPlanPath.utils.js +23 -0
- package/dist/utils/elasticsearchPlanPath.utils.js.map +7 -0
- package/dist/utils/qa-user.utils.d.ts +14 -0
- package/dist/utils/qa-user.utils.js +14 -0
- package/dist/utils/qa-user.utils.js.map +7 -0
- package/dist/utils/qa-user.utils.spec.d.ts +1 -0
- package/package.json +1 -1
- package/dist/dtos/FeatureFlagOverride.dto.d.ts +0 -226
- package/dist/dtos/FeatureFlagOverride.dto.js +0 -38
- package/dist/dtos/FeatureFlagOverride.dto.js.map +0 -7
- /package/dist/_tests/{dtos/FeatureFlagOverride.dto.spec.d.ts → approvals/dns-subdomain.spec.d.ts} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LUCENE_QUERY_MAX_LENGTH } from "../search/parser.js";
|
|
2
|
+
const CHART_GRAINS = ["day", "week", "month"];
|
|
3
|
+
const CHART_WINDOW_UNITS = ["d", "w", "m"];
|
|
4
|
+
const CHART_GRAIN_DEFAULT = "day";
|
|
5
|
+
const CHART_WINDOW_DEFAULT = { amount: 30, unit: "d" };
|
|
6
|
+
const CHART_TOP_DEFAULT = 10;
|
|
7
|
+
const CHART_TOP_MAX = 20;
|
|
8
|
+
const CHART_DAY_GRAIN_MAX_DAYS = 366;
|
|
9
|
+
const CHART_QUERY_MAX_LENGTH = 700;
|
|
10
|
+
const CHART_FILTER_MAX_LENGTH = LUCENE_QUERY_MAX_LENGTH;
|
|
11
|
+
const CHART_ID_MAX_LENGTH = 64;
|
|
12
|
+
const CHART_WINDOW_AMOUNT_MAX = 9999;
|
|
13
|
+
const CHART_OTHER_SERIES_KEY = "(other)";
|
|
14
|
+
const CHART_UNKNOWN_SERIES_KEY = "(unknown)";
|
|
15
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
16
|
+
const toDay = (date) => date.toISOString().slice(0, 10);
|
|
17
|
+
const parseDay = (day) => /* @__PURE__ */ new Date(`${day}T00:00:00Z`);
|
|
18
|
+
const addDays = (day, delta) => toDay(new Date(parseDay(day).getTime() + delta * DAY_MS));
|
|
19
|
+
const chartBucketStart = (day, grain) => {
|
|
20
|
+
const date = parseDay(day);
|
|
21
|
+
switch (grain) {
|
|
22
|
+
case "day":
|
|
23
|
+
return day;
|
|
24
|
+
case "week": {
|
|
25
|
+
const sinceMonday = (date.getUTCDay() + 6) % 7;
|
|
26
|
+
return addDays(day, -sinceMonday);
|
|
27
|
+
}
|
|
28
|
+
case "month":
|
|
29
|
+
return `${day.slice(0, 7)}-01`;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const chartWindowRange = (window, grain, today) => {
|
|
33
|
+
let start;
|
|
34
|
+
switch (window.unit) {
|
|
35
|
+
case "d":
|
|
36
|
+
start = addDays(today, -(window.amount - 1));
|
|
37
|
+
break;
|
|
38
|
+
case "w":
|
|
39
|
+
start = addDays(today, -(7 * window.amount - 1));
|
|
40
|
+
break;
|
|
41
|
+
case "m": {
|
|
42
|
+
const date = parseDay(today);
|
|
43
|
+
const shifted = new Date(
|
|
44
|
+
Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - window.amount, 1)
|
|
45
|
+
);
|
|
46
|
+
const lastDay = new Date(
|
|
47
|
+
Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0)
|
|
48
|
+
).getUTCDate();
|
|
49
|
+
shifted.setUTCDate(Math.min(date.getUTCDate(), lastDay));
|
|
50
|
+
start = addDays(toDay(shifted), 1);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const from = chartBucketStart(start, grain);
|
|
55
|
+
const days = Math.round(
|
|
56
|
+
(parseDay(today).getTime() - parseDay(from).getTime()) / DAY_MS
|
|
57
|
+
) + 1;
|
|
58
|
+
return { from, to: today, days };
|
|
59
|
+
};
|
|
60
|
+
const chartToday = (now = /* @__PURE__ */ new Date()) => toDay(now);
|
|
61
|
+
export {
|
|
62
|
+
CHART_DAY_GRAIN_MAX_DAYS,
|
|
63
|
+
CHART_FILTER_MAX_LENGTH,
|
|
64
|
+
CHART_GRAINS,
|
|
65
|
+
CHART_GRAIN_DEFAULT,
|
|
66
|
+
CHART_ID_MAX_LENGTH,
|
|
67
|
+
CHART_OTHER_SERIES_KEY,
|
|
68
|
+
CHART_QUERY_MAX_LENGTH,
|
|
69
|
+
CHART_TOP_DEFAULT,
|
|
70
|
+
CHART_TOP_MAX,
|
|
71
|
+
CHART_UNKNOWN_SERIES_KEY,
|
|
72
|
+
CHART_WINDOW_AMOUNT_MAX,
|
|
73
|
+
CHART_WINDOW_DEFAULT,
|
|
74
|
+
CHART_WINDOW_UNITS,
|
|
75
|
+
chartBucketStart,
|
|
76
|
+
chartToday,
|
|
77
|
+
chartWindowRange
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chart-spec.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/charts/chart-spec.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport { LUCENE_QUERY_MAX_LENGTH } from '../search/parser.js'\n\n// ============================================================================\n// The chart query of a dashboard tile, and the result that the chart route of\n// accounts-backend gives for it. See\n// docs/superpowers/specs/2026-10-02-dashboard-chart-tiles-design.md.\n// ============================================================================\n\n/** The time buckets of a chart. A week starts on Monday (ISO). */\nexport const CHART_GRAINS = ['day', 'week', 'month'] as const\nexport type ChartGrain = (typeof CHART_GRAINS)[number]\n\n/** `d` is days, `w` is weeks and `m` is calendar months. */\nexport const CHART_WINDOW_UNITS = ['d', 'w', 'm'] as const\nexport type ChartWindowUnit = (typeof CHART_WINDOW_UNITS)[number]\n\n/** A window that ends today (UTC). \"The last 90 days\" is `{ amount: 90, unit: 'd' }`. */\nexport interface ChartWindow {\n amount: number\n unit: ChartWindowUnit\n}\n\n/**\n * One chart query. A tile stores this and never the data, so the chart\n * follows the window as the days go by.\n */\nexport interface ChartSpec {\n /** A metric id of `CHART_METRICS`. */\n metric: string\n /** A dimension id of the metric. Each value of it is one series. */\n by?: string\n grain: ChartGrain\n window: ChartWindow\n /** Lucene source text over the filter fields of the metric. */\n filter?: string\n /** The number of series to show. Only with `by`. */\n top?: number\n}\n\nexport const CHART_GRAIN_DEFAULT: ChartGrain = 'day'\nexport const CHART_WINDOW_DEFAULT: ChartWindow = { amount: 30, unit: 'd' }\nexport const CHART_TOP_DEFAULT = 10\nexport const CHART_TOP_MAX = 20\n\n/** The longest window, in days, for `every day`. */\nexport const CHART_DAY_GRAIN_MAX_DAYS = 366\n\n/**\n * The longest text of a whole chart query. The filter has its own limit,\n * `CHART_FILTER_MAX_LENGTH`.\n */\nexport const CHART_QUERY_MAX_LENGTH = 700\n\n/**\n * The longest filter of a chart query. It is the limit of the Lucene parser,\n * which checks every filter on every run.\n */\nexport const CHART_FILTER_MAX_LENGTH = LUCENE_QUERY_MAX_LENGTH\n\n/** The longest metric id or dimension id that a stored chart query holds. */\nexport const CHART_ID_MAX_LENGTH = 64\n\n/** The largest amount of a window, in its unit. */\nexport const CHART_WINDOW_AMOUNT_MAX = 9999\n\n/**\n * The series key of the sum of every series that is not in the top N. When a\n * real value of the dimension has this key, the key gets a `*`, so the keys\n * of a result stay unique.\n */\nexport const CHART_OTHER_SERIES_KEY = '(other)'\n\n/**\n * The series key of the rows with no value for the `by` dimension. It gets a\n * `*` the same way as `CHART_OTHER_SERIES_KEY`.\n */\nexport const CHART_UNKNOWN_SERIES_KEY = '(unknown)'\n\nexport interface ChartPoint {\n /** The first day of the bucket, `YYYY-MM-DD`. */\n ts: string\n value: number\n}\n\nexport interface ChartSeries {\n key: string\n points: ChartPoint[]\n}\n\n/** `percent` is 0 to 100. */\nexport type ChartUnit = 'count' | 'bytes' | 'usd' | 'percent'\n\n/** The answer of `POST /api/hdp/charts/query`. */\nexport interface ChartResult {\n metric: string\n unit: ChartUnit\n grain: ChartGrain\n /** The first day that the query reads, `YYYY-MM-DD`, inclusive. */\n from: string\n /** The last day that the query reads, `YYYY-MM-DD`, inclusive. */\n to: string\n /**\n * Sorted by the total of the series, the largest first. `(other)` is last.\n * The keys are unique.\n */\n series: ChartSeries[]\n /** True when the top N left out series. Their sum is the `(other)` series. */\n truncated: boolean\n bytesProcessed: number\n /** When the result came from the cache: the time of the query, ISO. */\n cachedAt?: string\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nconst toDay = (date: Date): string => date.toISOString().slice(0, 10)\n\nconst parseDay = (day: string): Date => new Date(`${day}T00:00:00Z`)\n\nconst addDays = (day: string, delta: number): string =>\n toDay(new Date(parseDay(day).getTime() + delta * DAY_MS))\n\n/** The first day of the bucket of `day`, as BigQuery's `DATE_TRUNC` gives it. */\nexport const chartBucketStart = (day: string, grain: ChartGrain): string => {\n const date = parseDay(day)\n switch (grain) {\n case 'day':\n return day\n case 'week': {\n // getUTCDay: Sunday is 0. Monday starts the week.\n const sinceMonday = (date.getUTCDay() + 6) % 7\n return addDays(day, -sinceMonday)\n }\n case 'month':\n return `${day.slice(0, 7)}-01`\n }\n}\n\n/**\n * The days that a chart reads. The window ends on `today` and goes back by its\n * amount. The start moves back to the start of its bucket, so the first\n * bucket of the chart is complete.\n */\nexport const chartWindowRange = (\n window: ChartWindow,\n grain: ChartGrain,\n today: string,\n): { from: string; to: string; days: number } => {\n let start: string\n switch (window.unit) {\n case 'd':\n start = addDays(today, -(window.amount - 1))\n break\n case 'w':\n start = addDays(today, -(7 * window.amount - 1))\n break\n case 'm': {\n const date = parseDay(today)\n // Day 0 of a month is the last day of the month before. Thus a short\n // month clamps the day, and 03-31 minus one month gives 02-28.\n const shifted = new Date(\n Date.UTC(date.getUTCFullYear(), date.getUTCMonth() - window.amount, 1),\n )\n const lastDay = new Date(\n Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0),\n ).getUTCDate()\n shifted.setUTCDate(Math.min(date.getUTCDate(), lastDay))\n start = addDays(toDay(shifted), 1)\n break\n }\n }\n const from = chartBucketStart(start, grain)\n const days =\n Math.round(\n (parseDay(today).getTime() - parseDay(from).getTime()) / DAY_MS,\n ) + 1\n return { from, to: today, days }\n}\n\n/** Today in UTC, `YYYY-MM-DD`. */\nexport const chartToday = (now: Date = new Date()): string => toDay(now)\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,+BAA+B;AASjC,MAAM,eAAe,CAAC,OAAO,QAAQ,OAAO;AAI5C,MAAM,qBAAqB,CAAC,KAAK,KAAK,GAAG;AA0BzC,MAAM,sBAAkC;AACxC,MAAM,uBAAoC,EAAE,QAAQ,IAAI,MAAM,IAAI;AAClE,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;AAGtB,MAAM,2BAA2B;AAMjC,MAAM,yBAAyB;AAM/B,MAAM,0BAA0B;AAGhC,MAAM,sBAAsB;AAG5B,MAAM,0BAA0B;AAOhC,MAAM,yBAAyB;AAM/B,MAAM,2BAA2B;AAqCxC,MAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,MAAM,QAAQ,CAAC,SAAuB,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE;AAEpE,MAAM,WAAW,CAAC,QAAsB,oBAAI,KAAK,GAAG,GAAG,YAAY;AAEnE,MAAM,UAAU,CAAC,KAAa,UAC5B,MAAM,IAAI,KAAK,SAAS,GAAG,EAAE,QAAQ,IAAI,QAAQ,MAAM,CAAC;AAGnD,MAAM,mBAAmB,CAAC,KAAa,UAA8B;AAC1E,QAAM,OAAO,SAAS,GAAG;AACzB,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK,QAAQ;AAEX,YAAM,eAAe,KAAK,UAAU,IAAI,KAAK;AAC7C,aAAO,QAAQ,KAAK,CAAC,WAAW;AAAA,IAClC;AAAA,IACA,KAAK;AACH,aAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC;AAAA,EAC7B;AACF;AAOO,MAAM,mBAAmB,CAC9B,QACA,OACA,UAC+C;AAC/C,MAAI;AACJ,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,cAAQ,QAAQ,OAAO,EAAE,OAAO,SAAS,EAAE;AAC3C;AAAA,IACF,KAAK;AACH,cAAQ,QAAQ,OAAO,EAAE,IAAI,OAAO,SAAS,EAAE;AAC/C;AAAA,IACF,KAAK,KAAK;AACR,YAAM,OAAO,SAAS,KAAK;AAG3B,YAAM,UAAU,IAAI;AAAA,QAClB,KAAK,IAAI,KAAK,eAAe,GAAG,KAAK,YAAY,IAAI,OAAO,QAAQ,CAAC;AAAA,MACvE;AACA,YAAM,UAAU,IAAI;AAAA,QAClB,KAAK,IAAI,QAAQ,eAAe,GAAG,QAAQ,YAAY,IAAI,GAAG,CAAC;AAAA,MACjE,EAAE,WAAW;AACb,cAAQ,WAAW,KAAK,IAAI,KAAK,WAAW,GAAG,OAAO,CAAC;AACvD,cAAQ,QAAQ,MAAM,OAAO,GAAG,CAAC;AACjC;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,iBAAiB,OAAO,KAAK;AAC1C,QAAM,OACJ,KAAK;AAAA,KACF,SAAS,KAAK,EAAE,QAAQ,IAAI,SAAS,IAAI,EAAE,QAAQ,KAAK;AAAA,EAC3D,IAAI;AACN,SAAO,EAAE,MAAM,IAAI,OAAO,KAAK;AACjC;AAGO,MAAM,aAAa,CAAC,MAAY,oBAAI,KAAK,MAAc,MAAM,GAAG;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { CHART_METRICS, type ChartDimensionSpec, type ChartMetricSpec, findChartDimension, findChartMetric, } from './chart-metrics.js';
|
|
2
|
+
export { type ChartQueryCheck, ChartQueryError, type ChartQueryErrorKind, type ChartQueryOptions, formatChartSpec, isChartQueryError, parseChartQuery, readChartSpec, validateChartSpec, } from './chart-query.js';
|
|
3
|
+
export { CHART_DAY_GRAIN_MAX_DAYS, CHART_FILTER_MAX_LENGTH, CHART_GRAIN_DEFAULT, CHART_GRAINS, CHART_ID_MAX_LENGTH, CHART_OTHER_SERIES_KEY, CHART_QUERY_MAX_LENGTH, CHART_TOP_DEFAULT, CHART_TOP_MAX, CHART_UNKNOWN_SERIES_KEY, CHART_WINDOW_AMOUNT_MAX, CHART_WINDOW_DEFAULT, CHART_WINDOW_UNITS, type ChartGrain, type ChartPoint, type ChartResult, type ChartSeries, type ChartSpec, type ChartUnit, type ChartWindow, type ChartWindowUnit, chartBucketStart, chartToday, chartWindowRange, } from './chart-spec.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHART_METRICS,
|
|
3
|
+
findChartDimension,
|
|
4
|
+
findChartMetric
|
|
5
|
+
} from "./chart-metrics.js";
|
|
6
|
+
import {
|
|
7
|
+
ChartQueryError,
|
|
8
|
+
formatChartSpec,
|
|
9
|
+
isChartQueryError,
|
|
10
|
+
parseChartQuery,
|
|
11
|
+
readChartSpec,
|
|
12
|
+
validateChartSpec
|
|
13
|
+
} from "./chart-query.js";
|
|
14
|
+
import {
|
|
15
|
+
CHART_DAY_GRAIN_MAX_DAYS,
|
|
16
|
+
CHART_FILTER_MAX_LENGTH,
|
|
17
|
+
CHART_GRAIN_DEFAULT,
|
|
18
|
+
CHART_GRAINS,
|
|
19
|
+
CHART_ID_MAX_LENGTH,
|
|
20
|
+
CHART_OTHER_SERIES_KEY,
|
|
21
|
+
CHART_QUERY_MAX_LENGTH,
|
|
22
|
+
CHART_TOP_DEFAULT,
|
|
23
|
+
CHART_TOP_MAX,
|
|
24
|
+
CHART_UNKNOWN_SERIES_KEY,
|
|
25
|
+
CHART_WINDOW_AMOUNT_MAX,
|
|
26
|
+
CHART_WINDOW_DEFAULT,
|
|
27
|
+
CHART_WINDOW_UNITS,
|
|
28
|
+
chartBucketStart,
|
|
29
|
+
chartToday,
|
|
30
|
+
chartWindowRange
|
|
31
|
+
} from "./chart-spec.js";
|
|
32
|
+
export {
|
|
33
|
+
CHART_DAY_GRAIN_MAX_DAYS,
|
|
34
|
+
CHART_FILTER_MAX_LENGTH,
|
|
35
|
+
CHART_GRAINS,
|
|
36
|
+
CHART_GRAIN_DEFAULT,
|
|
37
|
+
CHART_ID_MAX_LENGTH,
|
|
38
|
+
CHART_METRICS,
|
|
39
|
+
CHART_OTHER_SERIES_KEY,
|
|
40
|
+
CHART_QUERY_MAX_LENGTH,
|
|
41
|
+
CHART_TOP_DEFAULT,
|
|
42
|
+
CHART_TOP_MAX,
|
|
43
|
+
CHART_UNKNOWN_SERIES_KEY,
|
|
44
|
+
CHART_WINDOW_AMOUNT_MAX,
|
|
45
|
+
CHART_WINDOW_DEFAULT,
|
|
46
|
+
CHART_WINDOW_UNITS,
|
|
47
|
+
ChartQueryError,
|
|
48
|
+
chartBucketStart,
|
|
49
|
+
chartToday,
|
|
50
|
+
chartWindowRange,
|
|
51
|
+
findChartDimension,
|
|
52
|
+
findChartMetric,
|
|
53
|
+
formatChartSpec,
|
|
54
|
+
isChartQueryError,
|
|
55
|
+
parseChartQuery,
|
|
56
|
+
readChartSpec,
|
|
57
|
+
validateChartSpec
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/charts/index.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nexport {\n CHART_METRICS,\n type ChartDimensionSpec,\n type ChartMetricSpec,\n findChartDimension,\n findChartMetric,\n} from './chart-metrics.js'\nexport {\n type ChartQueryCheck,\n ChartQueryError,\n type ChartQueryErrorKind,\n type ChartQueryOptions,\n formatChartSpec,\n isChartQueryError,\n parseChartQuery,\n readChartSpec,\n validateChartSpec,\n} from './chart-query.js'\nexport {\n CHART_DAY_GRAIN_MAX_DAYS,\n CHART_FILTER_MAX_LENGTH,\n CHART_GRAIN_DEFAULT,\n CHART_GRAINS,\n CHART_ID_MAX_LENGTH,\n CHART_OTHER_SERIES_KEY,\n CHART_QUERY_MAX_LENGTH,\n CHART_TOP_DEFAULT,\n CHART_TOP_MAX,\n CHART_UNKNOWN_SERIES_KEY,\n CHART_WINDOW_AMOUNT_MAX,\n CHART_WINDOW_DEFAULT,\n CHART_WINDOW_UNITS,\n type ChartGrain,\n type ChartPoint,\n type ChartResult,\n type ChartSeries,\n type ChartSpec,\n type ChartUnit,\n type ChartWindow,\n type ChartWindowUnit,\n chartBucketStart,\n chartToday,\n chartWindowRange,\n} from './chart-spec.js'\n"],
|
|
5
|
+
"mappings": "AAMA;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAEE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EASA;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { DashboardDefinition, DashboardNewTile, DashboardTile, DashboardTileKind, DashboardTileLayout, StoredDashboardDefinition } from '../dtos/Dashboard.dto.js';
|
|
2
|
+
/**
|
|
3
|
+
* The rules of a dashboard definition, as pure functions.
|
|
4
|
+
*
|
|
5
|
+
* control-backend places a tile that a client adds with `placeNewTile`.
|
|
6
|
+
* The grid of control-frontend lays tiles out with its own rules, and reads
|
|
7
|
+
* only `tilesOverlap` from here, so both sides agree on what an overlap is.
|
|
8
|
+
*/
|
|
9
|
+
/** DASHBOARD_TILE_DEFAULT_SIZE is the size of a new tile of each kind. */
|
|
10
|
+
export declare const DASHBOARD_TILE_DEFAULT_SIZE: Record<DashboardTileKind, {
|
|
11
|
+
w: number;
|
|
12
|
+
h: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** dashboardTileDefaultSize is the size of a new tile. */
|
|
15
|
+
export declare const dashboardTileDefaultSize: (tile: DashboardNewTile) => {
|
|
16
|
+
w: number;
|
|
17
|
+
h: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* isSafeDashboardLink reports whether a link tile can hold `url`.
|
|
21
|
+
*
|
|
22
|
+
* A path of Control (`/clusters`) or an `https://` address. A `javascript:`
|
|
23
|
+
* address would run code in the session of each reader, and `//host` leaves
|
|
24
|
+
* Control while it looks like a path, so both are refused.
|
|
25
|
+
*
|
|
26
|
+
* A browser deletes a tab or a line break from an address before it reads
|
|
27
|
+
* it, so `/<tab>/host` is `//host`. Thus a link holds no control character
|
|
28
|
+
* and no space, and a path must resolve to the origin that it started from.
|
|
29
|
+
*/
|
|
30
|
+
export declare const isSafeDashboardLink: (url: string) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* dashboardDefinitionBytes is the size of a definition as the backend
|
|
33
|
+
* measures it: UTF-8 bytes of its JSON text.
|
|
34
|
+
*/
|
|
35
|
+
export declare const dashboardDefinitionBytes: (definition: DashboardDefinition) => number;
|
|
36
|
+
/** dashboardDefinitionFits reports whether the backend stores `definition`. */
|
|
37
|
+
export declare const dashboardDefinitionFits: (definition: DashboardDefinition) => boolean;
|
|
38
|
+
/** emptyDashboardDefinition is the definition of a new dashboard. */
|
|
39
|
+
export declare const emptyDashboardDefinition: () => DashboardDefinition;
|
|
40
|
+
/**
|
|
41
|
+
* upgradeDashboardDefinition brings a stored definition to the current
|
|
42
|
+
* version.
|
|
43
|
+
*
|
|
44
|
+
* It answers `undefined` for a version that this code does not know. That is
|
|
45
|
+
* a row that NEWER code wrote, for example during a rollout. Do not guess at
|
|
46
|
+
* it, and do not write over it.
|
|
47
|
+
*/
|
|
48
|
+
export declare const upgradeDashboardDefinition: (stored: {
|
|
49
|
+
schemaVersion?: unknown;
|
|
50
|
+
}) => DashboardDefinition | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* toStoredDashboardDefinition is the shape that a write stores: the lowest
|
|
53
|
+
* version that holds the tiles.
|
|
54
|
+
*
|
|
55
|
+
* A pod with older code then still reads the row, during a rollout and after
|
|
56
|
+
* a rollback. Only a dashboard that uses a new kind needs the new version.
|
|
57
|
+
*/
|
|
58
|
+
export declare const toStoredDashboardDefinition: (definition: DashboardDefinition) => StoredDashboardDefinition;
|
|
59
|
+
/**
|
|
60
|
+
* placeNewTile gives a tile of `size` the place after the last tile, in
|
|
61
|
+
* reading order.
|
|
62
|
+
*
|
|
63
|
+
* The last row is the row of the tile that starts lowest. The new tile goes
|
|
64
|
+
* to the right of the tiles in that row, if it fits there and covers no cell
|
|
65
|
+
* of another tile. If not, it goes under every tile, at the left edge.
|
|
66
|
+
*
|
|
67
|
+
* A new tile never goes into a gap between two tiles: a reader who added it
|
|
68
|
+
* from another page must find it at the end. Small tiles that a reader adds
|
|
69
|
+
* one after the other fill a row, and do not make a tall column.
|
|
70
|
+
*/
|
|
71
|
+
export declare const placeNewTile: (tiles: readonly Pick<DashboardTile, "layout">[], size: {
|
|
72
|
+
w: number;
|
|
73
|
+
h: number;
|
|
74
|
+
}) => DashboardTileLayout;
|
|
75
|
+
/**
|
|
76
|
+
* placeNewTiles places tiles one after the other, each with
|
|
77
|
+
* `placeNewTile`, and answers the tiles with their layout.
|
|
78
|
+
*/
|
|
79
|
+
export declare const placeNewTiles: (tiles: readonly Pick<DashboardTile, "layout">[], added: readonly DashboardNewTile[]) => DashboardTile[];
|
|
80
|
+
/**
|
|
81
|
+
* tilesOverlap reports whether two tiles cover one cell of the grid.
|
|
82
|
+
*
|
|
83
|
+
* The backend refuses a definition with an overlap. The grid of the
|
|
84
|
+
* frontend never writes one, so an overlap is a client that is wrong.
|
|
85
|
+
*/
|
|
86
|
+
export declare const tilesOverlap: (a: DashboardTileLayout, b: DashboardTileLayout) => boolean;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DASHBOARD_DEFINITION_BYTES_MAX,
|
|
3
|
+
DASHBOARD_DEFINITION_VERSION,
|
|
4
|
+
DASHBOARD_GRID_COLUMNS
|
|
5
|
+
} from "../dtos/Dashboard.dto.js";
|
|
6
|
+
const DASHBOARD_TILE_DEFAULT_SIZE = {
|
|
7
|
+
"fleet-query": { w: 12, h: 8 },
|
|
8
|
+
annotations: { w: 6, h: 6 },
|
|
9
|
+
"cluster-detail": { w: 4, h: 6 },
|
|
10
|
+
"org-detail": { w: 4, h: 6 },
|
|
11
|
+
heading: { w: 12, h: 1 },
|
|
12
|
+
note: { w: 4, h: 3 },
|
|
13
|
+
link: { w: 3, h: 2 },
|
|
14
|
+
chart: { w: 6, h: 6 }
|
|
15
|
+
};
|
|
16
|
+
const CLUSTER_CARD_SIZE = {
|
|
17
|
+
identity: { w: 4, h: 5 },
|
|
18
|
+
"component-status": { w: 4, h: 7 },
|
|
19
|
+
datacenter: { w: 4, h: 5 },
|
|
20
|
+
generation: { w: 4, h: 6 },
|
|
21
|
+
plan: { w: 4, h: 7 },
|
|
22
|
+
"backup-encryption": { w: 4, h: 5 },
|
|
23
|
+
"settings-flags": { w: 4, h: 13 },
|
|
24
|
+
"external-idp": { w: 4, h: 11 },
|
|
25
|
+
"data-filters": { w: 4, h: 6 },
|
|
26
|
+
"auto-pause": { w: 4, h: 6 },
|
|
27
|
+
"change-history": { w: 4, h: 4 },
|
|
28
|
+
"disk-usage": { w: 6, h: 7 }
|
|
29
|
+
};
|
|
30
|
+
const ORG_CARD_SIZE = {
|
|
31
|
+
summary: { w: 12, h: 3 },
|
|
32
|
+
details: { w: 8, h: 8 },
|
|
33
|
+
"sales-plan": { w: 4, h: 5 },
|
|
34
|
+
"queued-operations": { w: 6, h: 5 },
|
|
35
|
+
clusters: { w: 4, h: 7 },
|
|
36
|
+
"managed-llm": { w: 4, h: 6 },
|
|
37
|
+
"recent-activity": { w: 4, h: 6 }
|
|
38
|
+
};
|
|
39
|
+
const dashboardTileDefaultSize = (tile) => {
|
|
40
|
+
switch (tile.kind) {
|
|
41
|
+
case "cluster-detail":
|
|
42
|
+
return CLUSTER_CARD_SIZE[tile.query.card] ?? DASHBOARD_TILE_DEFAULT_SIZE[tile.kind];
|
|
43
|
+
case "org-detail":
|
|
44
|
+
return ORG_CARD_SIZE[tile.query.card] ?? DASHBOARD_TILE_DEFAULT_SIZE[tile.kind];
|
|
45
|
+
default:
|
|
46
|
+
return DASHBOARD_TILE_DEFAULT_SIZE[tile.kind];
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const PATH_PROBE_ORIGIN = "https://dashboard-link.invalid";
|
|
50
|
+
const isSafeDashboardLink = (url) => {
|
|
51
|
+
if (url.length === 0 || /[\u0000-\u0020\u007f]/.test(url)) return false;
|
|
52
|
+
try {
|
|
53
|
+
if (url.startsWith("/")) {
|
|
54
|
+
return !url.startsWith("//") && !url.startsWith("/\\") && new URL(url, PATH_PROBE_ORIGIN).origin === PATH_PROBE_ORIGIN;
|
|
55
|
+
}
|
|
56
|
+
if (!/^https:\/\//i.test(url)) return false;
|
|
57
|
+
const parsed = new URL(url);
|
|
58
|
+
return parsed.protocol === "https:" && parsed.hostname.length > 0;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const dashboardDefinitionBytes = (definition) => new TextEncoder().encode(JSON.stringify(definition)).length;
|
|
64
|
+
const dashboardDefinitionFits = (definition) => dashboardDefinitionBytes(definition) <= DASHBOARD_DEFINITION_BYTES_MAX;
|
|
65
|
+
const emptyDashboardDefinition = () => ({
|
|
66
|
+
schemaVersion: DASHBOARD_DEFINITION_VERSION,
|
|
67
|
+
tiles: []
|
|
68
|
+
});
|
|
69
|
+
const upgradeDashboardDefinition = (stored) => {
|
|
70
|
+
switch (stored.schemaVersion) {
|
|
71
|
+
// Versions 2 and 3 only added kinds of tiles, so every tile of an older
|
|
72
|
+
// version is a tile of version 3.
|
|
73
|
+
case 1:
|
|
74
|
+
case 2:
|
|
75
|
+
return { ...stored, schemaVersion: 3 };
|
|
76
|
+
case 3:
|
|
77
|
+
return stored;
|
|
78
|
+
default:
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const isTileV1 = (tile) => tile.kind === "fleet-query" || tile.kind === "annotations";
|
|
83
|
+
const isTileV2 = (tile) => tile.kind !== "chart";
|
|
84
|
+
const toStoredDashboardDefinition = (definition) => {
|
|
85
|
+
const tiles = definition.tiles;
|
|
86
|
+
if (tiles.every(isTileV1)) {
|
|
87
|
+
return { schemaVersion: 1, tiles };
|
|
88
|
+
}
|
|
89
|
+
if (tiles.every(isTileV2)) {
|
|
90
|
+
return { schemaVersion: 2, tiles };
|
|
91
|
+
}
|
|
92
|
+
return definition;
|
|
93
|
+
};
|
|
94
|
+
const placeNewTile = (tiles, size) => {
|
|
95
|
+
const w = Math.min(size.w, DASHBOARD_GRID_COLUMNS);
|
|
96
|
+
const bottom = tiles.reduce(
|
|
97
|
+
(lowest, tile) => Math.max(lowest, tile.layout.y + tile.layout.h),
|
|
98
|
+
0
|
|
99
|
+
);
|
|
100
|
+
if (tiles.length > 0) {
|
|
101
|
+
const rowTop = Math.max(...tiles.map((tile) => tile.layout.y));
|
|
102
|
+
const right = Math.max(
|
|
103
|
+
...tiles.filter((tile) => tile.layout.y === rowTop).map((tile) => tile.layout.x + tile.layout.w)
|
|
104
|
+
);
|
|
105
|
+
const candidate = { x: right, y: rowTop, w, h: size.h };
|
|
106
|
+
if (right + w <= DASHBOARD_GRID_COLUMNS && tiles.every((tile) => !tilesOverlap(tile.layout, candidate))) {
|
|
107
|
+
return candidate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { x: 0, y: bottom, w, h: size.h };
|
|
111
|
+
};
|
|
112
|
+
const placeNewTiles = (tiles, added) => {
|
|
113
|
+
const placed = [];
|
|
114
|
+
for (const tile of added) {
|
|
115
|
+
const layout = placeNewTile(
|
|
116
|
+
[...tiles, ...placed],
|
|
117
|
+
dashboardTileDefaultSize(tile)
|
|
118
|
+
);
|
|
119
|
+
placed.push({ ...tile, layout });
|
|
120
|
+
}
|
|
121
|
+
return placed;
|
|
122
|
+
};
|
|
123
|
+
const tilesOverlap = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
|
124
|
+
export {
|
|
125
|
+
DASHBOARD_TILE_DEFAULT_SIZE,
|
|
126
|
+
dashboardDefinitionBytes,
|
|
127
|
+
dashboardDefinitionFits,
|
|
128
|
+
dashboardTileDefaultSize,
|
|
129
|
+
emptyDashboardDefinition,
|
|
130
|
+
isSafeDashboardLink,
|
|
131
|
+
placeNewTile,
|
|
132
|
+
placeNewTiles,
|
|
133
|
+
tilesOverlap,
|
|
134
|
+
toStoredDashboardDefinition,
|
|
135
|
+
upgradeDashboardDefinition
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/dashboards/definition.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport type {\n DashboardClusterCard,\n DashboardDefinition,\n DashboardDefinitionV1,\n DashboardDefinitionV2,\n DashboardNewTile,\n DashboardOrgCard,\n DashboardTile,\n DashboardTileKind,\n DashboardTileLayout,\n DashboardTileV1,\n DashboardTileV2,\n StoredDashboardDefinition,\n} from '../dtos/Dashboard.dto.js'\nimport {\n DASHBOARD_DEFINITION_BYTES_MAX,\n DASHBOARD_DEFINITION_VERSION,\n DASHBOARD_GRID_COLUMNS,\n} from '../dtos/Dashboard.dto.js'\n\n/**\n * The rules of a dashboard definition, as pure functions.\n *\n * control-backend places a tile that a client adds with `placeNewTile`.\n * The grid of control-frontend lays tiles out with its own rules, and reads\n * only `tilesOverlap` from here, so both sides agree on what an overlap is.\n */\n\n/** DASHBOARD_TILE_DEFAULT_SIZE is the size of a new tile of each kind. */\nexport const DASHBOARD_TILE_DEFAULT_SIZE: Record<\n DashboardTileKind,\n { w: number; h: number }\n> = {\n 'fleet-query': { w: 12, h: 8 },\n annotations: { w: 6, h: 6 },\n 'cluster-detail': { w: 4, h: 6 },\n 'org-detail': { w: 4, h: 6 },\n heading: { w: 12, h: 1 },\n note: { w: 4, h: 3 },\n link: { w: 3, h: 2 },\n chart: { w: 6, h: 6 },\n}\n\n/**\n * The size of a new card of a cluster. A card with more rows starts taller,\n * so that a reader does not have to resize every card that they add.\n */\nconst CLUSTER_CARD_SIZE: Record<\n DashboardClusterCard,\n { w: number; h: number }\n> = {\n identity: { w: 4, h: 5 },\n 'component-status': { w: 4, h: 7 },\n datacenter: { w: 4, h: 5 },\n generation: { w: 4, h: 6 },\n plan: { w: 4, h: 7 },\n 'backup-encryption': { w: 4, h: 5 },\n 'settings-flags': { w: 4, h: 13 },\n 'external-idp': { w: 4, h: 11 },\n 'data-filters': { w: 4, h: 6 },\n 'auto-pause': { w: 4, h: 6 },\n 'change-history': { w: 4, h: 4 },\n 'disk-usage': { w: 6, h: 7 },\n}\n\n/** The size of a new card of an organization. */\nconst ORG_CARD_SIZE: Record<DashboardOrgCard, { w: number; h: number }> = {\n summary: { w: 12, h: 3 },\n details: { w: 8, h: 8 },\n 'sales-plan': { w: 4, h: 5 },\n 'queued-operations': { w: 6, h: 5 },\n clusters: { w: 4, h: 7 },\n 'managed-llm': { w: 4, h: 6 },\n 'recent-activity': { w: 4, h: 6 },\n}\n\n/** dashboardTileDefaultSize is the size of a new tile. */\nexport const dashboardTileDefaultSize = (\n tile: DashboardNewTile,\n): { w: number; h: number } => {\n switch (tile.kind) {\n case 'cluster-detail':\n return (\n CLUSTER_CARD_SIZE[tile.query.card] ??\n DASHBOARD_TILE_DEFAULT_SIZE[tile.kind]\n )\n case 'org-detail':\n return (\n ORG_CARD_SIZE[tile.query.card] ?? DASHBOARD_TILE_DEFAULT_SIZE[tile.kind]\n )\n default:\n return DASHBOARD_TILE_DEFAULT_SIZE[tile.kind]\n }\n}\n\n/** An origin that no real address has. A path resolves against it. */\nconst PATH_PROBE_ORIGIN = 'https://dashboard-link.invalid'\n\n/**\n * isSafeDashboardLink reports whether a link tile can hold `url`.\n *\n * A path of Control (`/clusters`) or an `https://` address. A `javascript:`\n * address would run code in the session of each reader, and `//host` leaves\n * Control while it looks like a path, so both are refused.\n *\n * A browser deletes a tab or a line break from an address before it reads\n * it, so `/<tab>/host` is `//host`. Thus a link holds no control character\n * and no space, and a path must resolve to the origin that it started from.\n */\nexport const isSafeDashboardLink = (url: string): boolean => {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: the control characters are what this refuses\n if (url.length === 0 || /[\\u0000-\\u0020\\u007f]/.test(url)) return false\n try {\n if (url.startsWith('/')) {\n return (\n !url.startsWith('//') &&\n !url.startsWith('/\\\\') &&\n new URL(url, PATH_PROBE_ORIGIN).origin === PATH_PROBE_ORIGIN\n )\n }\n // `URL` repairs `https:host` and `https:/host` into a full address, so\n // the raw text must hold the `//` itself.\n if (!/^https:\\/\\//i.test(url)) return false\n const parsed = new URL(url)\n return parsed.protocol === 'https:' && parsed.hostname.length > 0\n } catch {\n return false\n }\n}\n\n/**\n * dashboardDefinitionBytes is the size of a definition as the backend\n * measures it: UTF-8 bytes of its JSON text.\n */\nexport const dashboardDefinitionBytes = (\n definition: DashboardDefinition,\n): number => new TextEncoder().encode(JSON.stringify(definition)).length\n\n/** dashboardDefinitionFits reports whether the backend stores `definition`. */\nexport const dashboardDefinitionFits = (\n definition: DashboardDefinition,\n): boolean =>\n dashboardDefinitionBytes(definition) <= DASHBOARD_DEFINITION_BYTES_MAX\n\n/** emptyDashboardDefinition is the definition of a new dashboard. */\nexport const emptyDashboardDefinition = (): DashboardDefinition => ({\n schemaVersion: DASHBOARD_DEFINITION_VERSION,\n tiles: [],\n})\n\n/**\n * upgradeDashboardDefinition brings a stored definition to the current\n * version.\n *\n * It answers `undefined` for a version that this code does not know. That is\n * a row that NEWER code wrote, for example during a rollout. Do not guess at\n * it, and do not write over it.\n */\nexport const upgradeDashboardDefinition = (stored: {\n schemaVersion?: unknown\n}): DashboardDefinition | undefined => {\n switch (stored.schemaVersion) {\n // Versions 2 and 3 only added kinds of tiles, so every tile of an older\n // version is a tile of version 3.\n case 1:\n case 2:\n return { ...(stored as DashboardDefinition), schemaVersion: 3 }\n case 3:\n return stored as DashboardDefinition\n default:\n return undefined\n }\n}\n\nconst isTileV1 = (tile: DashboardTile): tile is DashboardTileV1 =>\n tile.kind === 'fleet-query' || tile.kind === 'annotations'\n\nconst isTileV2 = (tile: DashboardTile): tile is DashboardTileV2 =>\n tile.kind !== 'chart'\n\n/**\n * toStoredDashboardDefinition is the shape that a write stores: the lowest\n * version that holds the tiles.\n *\n * A pod with older code then still reads the row, during a rollout and after\n * a rollback. Only a dashboard that uses a new kind needs the new version.\n */\nexport const toStoredDashboardDefinition = (\n definition: DashboardDefinition,\n): StoredDashboardDefinition => {\n const tiles = definition.tiles\n if (tiles.every(isTileV1)) {\n return { schemaVersion: 1, tiles } satisfies DashboardDefinitionV1\n }\n if (tiles.every(isTileV2)) {\n return { schemaVersion: 2, tiles } satisfies DashboardDefinitionV2\n }\n return definition\n}\n\n/**\n * placeNewTile gives a tile of `size` the place after the last tile, in\n * reading order.\n *\n * The last row is the row of the tile that starts lowest. The new tile goes\n * to the right of the tiles in that row, if it fits there and covers no cell\n * of another tile. If not, it goes under every tile, at the left edge.\n *\n * A new tile never goes into a gap between two tiles: a reader who added it\n * from another page must find it at the end. Small tiles that a reader adds\n * one after the other fill a row, and do not make a tall column.\n */\nexport const placeNewTile = (\n tiles: readonly Pick<DashboardTile, 'layout'>[],\n size: { w: number; h: number },\n): DashboardTileLayout => {\n const w = Math.min(size.w, DASHBOARD_GRID_COLUMNS)\n const bottom = tiles.reduce(\n (lowest, tile) => Math.max(lowest, tile.layout.y + tile.layout.h),\n 0,\n )\n if (tiles.length > 0) {\n const rowTop = Math.max(...tiles.map((tile) => tile.layout.y))\n const right = Math.max(\n ...tiles\n .filter((tile) => tile.layout.y === rowTop)\n .map((tile) => tile.layout.x + tile.layout.w),\n )\n const candidate = { x: right, y: rowTop, w, h: size.h }\n if (\n right + w <= DASHBOARD_GRID_COLUMNS &&\n tiles.every((tile) => !tilesOverlap(tile.layout, candidate))\n ) {\n return candidate\n }\n }\n return { x: 0, y: bottom, w, h: size.h }\n}\n\n/**\n * placeNewTiles places tiles one after the other, each with\n * `placeNewTile`, and answers the tiles with their layout.\n */\nexport const placeNewTiles = (\n tiles: readonly Pick<DashboardTile, 'layout'>[],\n added: readonly DashboardNewTile[],\n): DashboardTile[] => {\n const placed: DashboardTile[] = []\n for (const tile of added) {\n const layout = placeNewTile(\n [...tiles, ...placed],\n dashboardTileDefaultSize(tile),\n )\n placed.push({ ...tile, layout } as DashboardTile)\n }\n return placed\n}\n\n/**\n * tilesOverlap reports whether two tiles cover one cell of the grid.\n *\n * The backend refuses a definition with an overlap. The grid of the\n * frontend never writes one, so an overlap is a client that is wrong.\n */\nexport const tilesOverlap = (\n a: DashboardTileLayout,\n b: DashboardTileLayout,\n): boolean =>\n a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* about to read or correct it. Add a kind that names a PERSON only with those
|
|
36
36
|
* three answered.
|
|
37
37
|
*/
|
|
38
|
-
export declare const ANNOTATION_ENTITY_KINDS: readonly ["cluster", "organization", "cluster-plan", "cluster-plan-type", "generation", "channel", "sales-plan", "k8s-context"];
|
|
38
|
+
export declare const ANNOTATION_ENTITY_KINDS: readonly ["cluster", "organization", "cluster-plan", "cluster-plan-type", "generation", "channel", "sales-plan", "k8s-context", "cluster-modification", "cluster-bulk-operation", "approval", "dashboard"];
|
|
39
39
|
export type AnnotationEntityKind = (typeof ANNOTATION_ENTITY_KINDS)[number];
|
|
40
40
|
/**
|
|
41
41
|
* What a reader sees for each kind, in the singular.
|
|
@@ -6,7 +6,11 @@ const ANNOTATION_ENTITY_KINDS = [
|
|
|
6
6
|
"generation",
|
|
7
7
|
"channel",
|
|
8
8
|
"sales-plan",
|
|
9
|
-
"k8s-context"
|
|
9
|
+
"k8s-context",
|
|
10
|
+
"cluster-modification",
|
|
11
|
+
"cluster-bulk-operation",
|
|
12
|
+
"approval",
|
|
13
|
+
"dashboard"
|
|
10
14
|
];
|
|
11
15
|
const ANNOTATION_ENTITY_KIND_LABELS = {
|
|
12
16
|
cluster: "Cluster",
|
|
@@ -16,7 +20,11 @@ const ANNOTATION_ENTITY_KIND_LABELS = {
|
|
|
16
20
|
generation: "Generation",
|
|
17
21
|
channel: "Channel",
|
|
18
22
|
"sales-plan": "Sales plan",
|
|
19
|
-
"k8s-context": "Kubernetes context"
|
|
23
|
+
"k8s-context": "Kubernetes context",
|
|
24
|
+
"cluster-modification": "Cluster modification",
|
|
25
|
+
"cluster-bulk-operation": "Cluster bulk operation",
|
|
26
|
+
approval: "Approval request",
|
|
27
|
+
dashboard: "Dashboard"
|
|
20
28
|
};
|
|
21
29
|
const ANNOTATION_KINDS = ["note", "tag"];
|
|
22
30
|
const ANNOTATION_LOOKUP_MAX_ENTITIES = 3e3;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/dtos/Annotation.dto.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\n/**\n * The wire types of the annotations of the fleet: the notes and the tags that a\n * Camundi attaches to an entity.\n *\n * An annotation adds CONTEXT that the fleet itself does not hold. A cluster row\n * says how much memory it has; it does not say \"this is the playground of the\n * demo team\". A Camundi writes that here, and the next Camundi reads it.\n *\n * This file holds no logic and no dependency, because control-backend and\n * control-frontend both read it. The rules that read these types are in\n * `../annotations/policy.js`, and they are pure for the same reason.\n *\n * The feature is INTERNAL. No annotation reaches a customer, and no route of\n * another service reads one.\n */\n\n/**\n * Every kind of fleet entity that can carry an annotation.\n *\n * A `const` tuple, not an enum, so it is both the union type and the list that\n * validates a request at run time.\n *\n * The kind is a STRING on the row and not a foreign key, because the entity is\n * in a different database: a cluster belongs to console-backend and an\n * organization to accounts-backend. That is also what keeps an annotation after\n * the entity goes away. See `AnnotationDto.entityLabel`.\n *\n * Add a kind here and in `ANNOTATION_ENTITY_KIND_LABELS` below. The two lists\n * are one `Record`, so a kind with no label does not compile.\n *\n * Every kind here is a THING of the fleet, and none of them is a person. That\n * is a rule and not an accident. A `user` kind was in this list and came out\n * again: an annotation outlives its entity on purpose, which is right for a\n * cluster that somebody deleted and wrong for a colleague who left, and this\n * feature has no retention rule, no reaper, and no way for the person written\n * about to read or correct it. Add a kind that names a PERSON only with those\n * three answered.\n */\nexport const ANNOTATION_ENTITY_KINDS = [\n 'cluster',\n 'organization',\n 'cluster-plan',\n 'cluster-plan-type',\n 'generation',\n 'channel',\n 'sales-plan',\n 'k8s-context',\n] as const\n\nexport type AnnotationEntityKind = (typeof ANNOTATION_ENTITY_KINDS)[number]\n\n/**\n * What a reader sees for each kind, in the singular.\n *\n * The label is here and not in the frontend, because the search page shows\n * results of every kind together and the backend writes the same word into a\n * log line. Two spellings of one kind would read as two kinds.\n */\nexport const ANNOTATION_ENTITY_KIND_LABELS: Record<\n AnnotationEntityKind,\n string\n> = {\n cluster: 'Cluster',\n organization: 'Organization',\n 'cluster-plan': 'Cluster plan',\n 'cluster-plan-type': 'Cluster plan type',\n generation: 'Generation',\n channel: 'Channel',\n 'sales-plan': 'Sales plan',\n 'k8s-context': 'Kubernetes context',\n}\n\n/**\n * What an annotation IS: a written note, or a tag out of the dictionary.\n *\n * One row type carries both, because both answer the same question \u2014 what does\n * a Camundi need to know about this entity? \u2014 and both are read, listed and\n * removed the same way. The difference is what the row points at: a note holds\n * `body`, and a tag holds `tag`.\n */\nexport const ANNOTATION_KINDS = ['note', 'tag'] as const\n\nexport type AnnotationKind = (typeof ANNOTATION_KINDS)[number]\n\n/** Who wrote an annotation. The same shape as `ApprovalActor`. */\nexport interface AnnotationActor {\n userId: string\n email: string\n}\n\n/**\n * One tag of the dictionary.\n *\n * A tag is its own row, and not a string on the annotation, for one reason:\n * a Camundi must be able to ask \"what else carries `partner`?\". A string on\n * each row answers that only with a scan, and it drifts: `Partner`, `partner `\n * and `partner` would be three tags that read as one.\n */\nexport interface AnnotationTagDto {\n id: string\n /** What a reader sees. The first spelling that anybody wrote wins. */\n name: string\n /** `name` through `normalizeTagName`. It is the identity of the tag. */\n normalizedName: string\n createdBy: AnnotationActor\n createdAt: string\n /**\n * How many LIVE entities carry this tag.\n *\n * It is counted at read time and never stored, so a removed assignment\n * lowers it with no second write. A list of the dictionary needs it to sort\n * by use, which is how a Camundi finds the tag that the team already uses\n * instead of inventing a second spelling of it.\n */\n usageCount: number\n}\n\n/**\n * One note, or one tag, on one entity.\n *\n * `entityKind` and `entityId` together name the entity. Neither is checked\n * against the fleet: the entity lives in another service, and an annotation\n * must survive its entity. See `entityLabel`.\n */\nexport interface AnnotationDto {\n id: string\n kind: AnnotationKind\n entityKind: AnnotationEntityKind\n entityId: string\n /**\n * What the entity was CALLED when somebody wrote the annotation.\n *\n * This is the only reason a search result stays readable after the entity is\n * deleted: the id of a cluster that no longer exists says nothing, and the\n * fleet can no longer answer what its name was. The label is a copy on\n * purpose, so it can be stale \u2014 it says what the entity was called then, not\n * what it is called now.\n */\n entityLabel?: string\n /** The text of a note. Absent on a tag. */\n body?: string\n /** The tag of a tag annotation. Absent on a note. */\n tag?: AnnotationTagDto\n createdBy: AnnotationActor\n createdAt: string\n updatedAt: string\n /** True when `body` was changed after it was written. */\n edited: boolean\n /**\n * How many times this row was written. A new one carries 1.\n *\n * An edit sends the version that it read, and the route refuses the write\n * when the row moved on since. Without it the tab that somebody left open at\n * breakfast silently replaces the edit they made at lunch, and neither write\n * tells them that one of the two is gone.\n */\n version: number\n}\n\n/** The annotations of one entity, in one answer. */\nexport interface EntityAnnotationsDto {\n entityKind: AnnotationEntityKind\n entityId: string\n notes: AnnotationDto[]\n tags: AnnotationDto[]\n /**\n * True when the entity carries more than `ANNOTATION_ENTITY_MAX` rows, so\n * the two lists above are the newest of them and not all of them.\n *\n * A panel that showed no sign of this would be a page that quietly stops\n * being the whole story, which is worse for documentation than a page that\n * says where the rest is.\n */\n hasMore: boolean\n}\n\n/**\n * What one entity carries, in the answer of a lookup over many entities.\n *\n * A table of 3000 clusters cannot read 3000 panels. The lookup answers the\n * two questions that a row of such a table needs: which tags, and whether a\n * colleague wrote something that a person must read before they act.\n */\nexport interface AnnotationLookupEntryDto {\n entityId: string\n /**\n * The live tag annotations of the entity, oldest first, and at most\n * `ANNOTATION_LOOKUP_TAGS_MAX` of them.\n */\n tags: AnnotationDto[]\n /** How many live tags the entity carries. More than `tags` holds is cut. */\n tagCount: number\n /** How many live notes the entity carries. */\n noteCount: number\n /** The newest live note, so a row can show it with no second read. */\n latestNote?: AnnotationDto\n}\n\n/**\n * The answer of a lookup. It holds ONLY the entities that carry something,\n * so an entity with no entry carries nothing.\n */\nexport interface AnnotationLookupDto {\n entityKind: AnnotationEntityKind\n entries: AnnotationLookupEntryDto[]\n}\n\n/**\n * The most entities that one lookup names.\n *\n * The same number as the largest cluster bulk operation, which is the table\n * that asks the largest question.\n */\nexport const ANNOTATION_LOOKUP_MAX_ENTITIES = 3000\n\n/**\n * The most tags that a lookup answers for ONE entity.\n *\n * An entity can carry any number of tags. Without this cap, one lookup of\n * 3000 entities has no bound on its rows. A row of a table shows a few chips,\n * and `tagCount` tells it how many it did not get.\n */\nexport const ANNOTATION_LOOKUP_TAGS_MAX = 20\n\n/**\n * The most annotations that the read of ONE entity answers with.\n *\n * The read has no page of its own: a panel is a thread that a person scrolls,\n * not a table they walk. But it cannot be unbounded either \u2014 an entity that\n * collects notes for a year would make every load of that page serialize\n * thousands of rows of up to `ANNOTATION_BODY_MAX` characters each. So the\n * answer is the NEWEST rows up to this number, with `hasMore` to say that\n * older ones exist. The search page reads the rest.\n */\nexport const ANNOTATION_ENTITY_MAX = 200\n\n/**\n * The longest note that a route accepts.\n *\n * A note is context for a colleague and not a document. The ceiling keeps one\n * paste of a log file out of every read of the entity page, and a person who\n * needs more has a handbook page to link to.\n */\nexport const ANNOTATION_BODY_MAX = 4000\n\n/** The longest tag name. A tag is a label, so it is short by design. */\nexport const ANNOTATION_TAG_NAME_MAX = 60\n\n/**\n * The longest free text that the search accepts.\n *\n * The VIEW needs it as much as the route: a field with no ceiling lets a\n * person paste more than the route takes, and the answer is then a 400 that\n * reads like a broken search. The field holds them here instead, and a deep\n * link that carries more is cut to this length rather than breaking the page.\n */\nexport const ANNOTATION_SEARCH_MAX_LENGTH = 200\n\n/** Annotations for each page when the caller asks for no number. */\nexport const ANNOTATION_PAGE_SIZE_DEFAULT = 100\n\n/**\n * The most annotations that one page can hold.\n *\n * The view needs this number too: a page that asked for more rows than the\n * service allows would get a 400 instead of rows.\n */\nexport const ANNOTATION_PAGE_SIZE_MAX = 500\n\n/**\n * The identity of a tag, from what a person typed.\n *\n * The case and the outer spaces go, and every run of inner whitespace becomes\n * one hyphen. `Partner`, ` partner ` and `Partner ` are then one tag, and\n * `key account` is `key-account`.\n *\n * Both sides run this: the backend writes the result into `normalizedName` and\n * holds it unique, and the frontend runs it to tell a person, before they\n * press the button, that the tag they are typing already exists.\n */\nexport function normalizeTagName(name: string): string {\n return name.trim().toLowerCase().replace(/\\s+/g, '-')\n}\n"],
|
|
5
|
-
"mappings": "AA4CO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,MAAM,gCAGT;AAAA,EACF,SAAS;AAAA,EACT,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,cAAc;AAAA,EACd,eAAe;
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\n/**\n * The wire types of the annotations of the fleet: the notes and the tags that a\n * Camundi attaches to an entity.\n *\n * An annotation adds CONTEXT that the fleet itself does not hold. A cluster row\n * says how much memory it has; it does not say \"this is the playground of the\n * demo team\". A Camundi writes that here, and the next Camundi reads it.\n *\n * This file holds no logic and no dependency, because control-backend and\n * control-frontend both read it. The rules that read these types are in\n * `../annotations/policy.js`, and they are pure for the same reason.\n *\n * The feature is INTERNAL. No annotation reaches a customer, and no route of\n * another service reads one.\n */\n\n/**\n * Every kind of fleet entity that can carry an annotation.\n *\n * A `const` tuple, not an enum, so it is both the union type and the list that\n * validates a request at run time.\n *\n * The kind is a STRING on the row and not a foreign key, because the entity is\n * in a different database: a cluster belongs to console-backend and an\n * organization to accounts-backend. That is also what keeps an annotation after\n * the entity goes away. See `AnnotationDto.entityLabel`.\n *\n * Add a kind here and in `ANNOTATION_ENTITY_KIND_LABELS` below. The two lists\n * are one `Record`, so a kind with no label does not compile.\n *\n * Every kind here is a THING of the fleet, and none of them is a person. That\n * is a rule and not an accident. A `user` kind was in this list and came out\n * again: an annotation outlives its entity on purpose, which is right for a\n * cluster that somebody deleted and wrong for a colleague who left, and this\n * feature has no retention rule, no reaper, and no way for the person written\n * about to read or correct it. Add a kind that names a PERSON only with those\n * three answered.\n */\nexport const ANNOTATION_ENTITY_KINDS = [\n 'cluster',\n 'organization',\n 'cluster-plan',\n 'cluster-plan-type',\n 'generation',\n 'channel',\n 'sales-plan',\n 'k8s-context',\n 'cluster-modification',\n 'cluster-bulk-operation',\n 'approval',\n 'dashboard',\n] as const\n\nexport type AnnotationEntityKind = (typeof ANNOTATION_ENTITY_KINDS)[number]\n\n/**\n * What a reader sees for each kind, in the singular.\n *\n * The label is here and not in the frontend, because the search page shows\n * results of every kind together and the backend writes the same word into a\n * log line. Two spellings of one kind would read as two kinds.\n */\nexport const ANNOTATION_ENTITY_KIND_LABELS: Record<\n AnnotationEntityKind,\n string\n> = {\n cluster: 'Cluster',\n organization: 'Organization',\n 'cluster-plan': 'Cluster plan',\n 'cluster-plan-type': 'Cluster plan type',\n generation: 'Generation',\n channel: 'Channel',\n 'sales-plan': 'Sales plan',\n 'k8s-context': 'Kubernetes context',\n 'cluster-modification': 'Cluster modification',\n 'cluster-bulk-operation': 'Cluster bulk operation',\n approval: 'Approval request',\n dashboard: 'Dashboard',\n}\n\n/**\n * What an annotation IS: a written note, or a tag out of the dictionary.\n *\n * One row type carries both, because both answer the same question \u2014 what does\n * a Camundi need to know about this entity? \u2014 and both are read, listed and\n * removed the same way. The difference is what the row points at: a note holds\n * `body`, and a tag holds `tag`.\n */\nexport const ANNOTATION_KINDS = ['note', 'tag'] as const\n\nexport type AnnotationKind = (typeof ANNOTATION_KINDS)[number]\n\n/** Who wrote an annotation. The same shape as `ApprovalActor`. */\nexport interface AnnotationActor {\n userId: string\n email: string\n}\n\n/**\n * One tag of the dictionary.\n *\n * A tag is its own row, and not a string on the annotation, for one reason:\n * a Camundi must be able to ask \"what else carries `partner`?\". A string on\n * each row answers that only with a scan, and it drifts: `Partner`, `partner `\n * and `partner` would be three tags that read as one.\n */\nexport interface AnnotationTagDto {\n id: string\n /** What a reader sees. The first spelling that anybody wrote wins. */\n name: string\n /** `name` through `normalizeTagName`. It is the identity of the tag. */\n normalizedName: string\n createdBy: AnnotationActor\n createdAt: string\n /**\n * How many LIVE entities carry this tag.\n *\n * It is counted at read time and never stored, so a removed assignment\n * lowers it with no second write. A list of the dictionary needs it to sort\n * by use, which is how a Camundi finds the tag that the team already uses\n * instead of inventing a second spelling of it.\n */\n usageCount: number\n}\n\n/**\n * One note, or one tag, on one entity.\n *\n * `entityKind` and `entityId` together name the entity. Neither is checked\n * against the fleet: the entity lives in another service, and an annotation\n * must survive its entity. See `entityLabel`.\n */\nexport interface AnnotationDto {\n id: string\n kind: AnnotationKind\n entityKind: AnnotationEntityKind\n entityId: string\n /**\n * What the entity was CALLED when somebody wrote the annotation.\n *\n * This is the only reason a search result stays readable after the entity is\n * deleted: the id of a cluster that no longer exists says nothing, and the\n * fleet can no longer answer what its name was. The label is a copy on\n * purpose, so it can be stale \u2014 it says what the entity was called then, not\n * what it is called now.\n */\n entityLabel?: string\n /** The text of a note. Absent on a tag. */\n body?: string\n /** The tag of a tag annotation. Absent on a note. */\n tag?: AnnotationTagDto\n createdBy: AnnotationActor\n createdAt: string\n updatedAt: string\n /** True when `body` was changed after it was written. */\n edited: boolean\n /**\n * How many times this row was written. A new one carries 1.\n *\n * An edit sends the version that it read, and the route refuses the write\n * when the row moved on since. Without it the tab that somebody left open at\n * breakfast silently replaces the edit they made at lunch, and neither write\n * tells them that one of the two is gone.\n */\n version: number\n}\n\n/** The annotations of one entity, in one answer. */\nexport interface EntityAnnotationsDto {\n entityKind: AnnotationEntityKind\n entityId: string\n notes: AnnotationDto[]\n tags: AnnotationDto[]\n /**\n * True when the entity carries more than `ANNOTATION_ENTITY_MAX` rows, so\n * the two lists above are the newest of them and not all of them.\n *\n * A panel that showed no sign of this would be a page that quietly stops\n * being the whole story, which is worse for documentation than a page that\n * says where the rest is.\n */\n hasMore: boolean\n}\n\n/**\n * What one entity carries, in the answer of a lookup over many entities.\n *\n * A table of 3000 clusters cannot read 3000 panels. The lookup answers the\n * two questions that a row of such a table needs: which tags, and whether a\n * colleague wrote something that a person must read before they act.\n */\nexport interface AnnotationLookupEntryDto {\n entityId: string\n /**\n * The live tag annotations of the entity, oldest first, and at most\n * `ANNOTATION_LOOKUP_TAGS_MAX` of them.\n */\n tags: AnnotationDto[]\n /** How many live tags the entity carries. More than `tags` holds is cut. */\n tagCount: number\n /** How many live notes the entity carries. */\n noteCount: number\n /** The newest live note, so a row can show it with no second read. */\n latestNote?: AnnotationDto\n}\n\n/**\n * The answer of a lookup. It holds ONLY the entities that carry something,\n * so an entity with no entry carries nothing.\n */\nexport interface AnnotationLookupDto {\n entityKind: AnnotationEntityKind\n entries: AnnotationLookupEntryDto[]\n}\n\n/**\n * The most entities that one lookup names.\n *\n * The same number as the largest cluster bulk operation, which is the table\n * that asks the largest question.\n */\nexport const ANNOTATION_LOOKUP_MAX_ENTITIES = 3000\n\n/**\n * The most tags that a lookup answers for ONE entity.\n *\n * An entity can carry any number of tags. Without this cap, one lookup of\n * 3000 entities has no bound on its rows. A row of a table shows a few chips,\n * and `tagCount` tells it how many it did not get.\n */\nexport const ANNOTATION_LOOKUP_TAGS_MAX = 20\n\n/**\n * The most annotations that the read of ONE entity answers with.\n *\n * The read has no page of its own: a panel is a thread that a person scrolls,\n * not a table they walk. But it cannot be unbounded either \u2014 an entity that\n * collects notes for a year would make every load of that page serialize\n * thousands of rows of up to `ANNOTATION_BODY_MAX` characters each. So the\n * answer is the NEWEST rows up to this number, with `hasMore` to say that\n * older ones exist. The search page reads the rest.\n */\nexport const ANNOTATION_ENTITY_MAX = 200\n\n/**\n * The longest note that a route accepts.\n *\n * A note is context for a colleague and not a document. The ceiling keeps one\n * paste of a log file out of every read of the entity page, and a person who\n * needs more has a handbook page to link to.\n */\nexport const ANNOTATION_BODY_MAX = 4000\n\n/** The longest tag name. A tag is a label, so it is short by design. */\nexport const ANNOTATION_TAG_NAME_MAX = 60\n\n/**\n * The longest free text that the search accepts.\n *\n * The VIEW needs it as much as the route: a field with no ceiling lets a\n * person paste more than the route takes, and the answer is then a 400 that\n * reads like a broken search. The field holds them here instead, and a deep\n * link that carries more is cut to this length rather than breaking the page.\n */\nexport const ANNOTATION_SEARCH_MAX_LENGTH = 200\n\n/** Annotations for each page when the caller asks for no number. */\nexport const ANNOTATION_PAGE_SIZE_DEFAULT = 100\n\n/**\n * The most annotations that one page can hold.\n *\n * The view needs this number too: a page that asked for more rows than the\n * service allows would get a 400 instead of rows.\n */\nexport const ANNOTATION_PAGE_SIZE_MAX = 500\n\n/**\n * The identity of a tag, from what a person typed.\n *\n * The case and the outer spaces go, and every run of inner whitespace becomes\n * one hyphen. `Partner`, ` partner ` and `Partner ` are then one tag, and\n * `key account` is `key-account`.\n *\n * Both sides run this: the backend writes the result into `normalizedName` and\n * holds it unique, and the frontend runs it to tell a person, before they\n * press the button, that the tag they are typing already exists.\n */\nexport function normalizeTagName(name: string): string {\n return name.trim().toLowerCase().replace(/\\s+/g, '-')\n}\n"],
|
|
5
|
+
"mappings": "AA4CO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,MAAM,gCAGT;AAAA,EACF,SAAS;AAAA,EACT,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,cAAc;AAAA,EACd,eAAe;AAAA,EACf,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,UAAU;AAAA,EACV,WAAW;AACb;AAUO,MAAM,mBAAmB,CAAC,QAAQ,KAAK;AAqIvC,MAAM,iCAAiC;AASvC,MAAM,6BAA6B;AAYnC,MAAM,wBAAwB;AAS9B,MAAM,sBAAsB;AAG5B,MAAM,0BAA0B;AAUhC,MAAM,+BAA+B;AAGrC,MAAM,+BAA+B;AAQrC,MAAM,2BAA2B;AAajC,SAAS,iBAAiB,MAAsB;AACrD,SAAO,KAAK,KAAK,EAAE,YAAY,EAAE,QAAQ,QAAQ,GAAG;AACtD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -164,6 +164,14 @@ export interface ClusterListItemV2Dto {
|
|
|
164
164
|
channel?: string;
|
|
165
165
|
/** ISO-8601; part of the cursor key. */
|
|
166
166
|
created: string;
|
|
167
|
+
/**
|
|
168
|
+
* User id of the creator, e.g. `samlp|Okta|jane.doe@camunda.com`. Absent
|
|
169
|
+
* when a background flow created the cluster, for an old cluster that has
|
|
170
|
+
* no record of its creator, or on an older console-backend. Also absent
|
|
171
|
+
* for a caller without `canReadAllClusterInfo`: the org clusters route
|
|
172
|
+
* serves members of the org too (see `canReadClusterCreator`).
|
|
173
|
+
*/
|
|
174
|
+
createdBy?: string;
|
|
167
175
|
/**
|
|
168
176
|
* ISO-8601 instant of the last suspend. Absent when the cluster is awake:
|
|
169
177
|
* `Cluster.wakeUp()` clears the column, thus this is the time since the last
|
|
@@ -178,6 +186,12 @@ export interface ClusterListItemV2Dto {
|
|
|
178
186
|
/** Session idle timeout. */
|
|
179
187
|
sessionMaxInactiveIntervalMinutes?: number;
|
|
180
188
|
sessionHeartbeatEnabled?: boolean;
|
|
189
|
+
/** BYOK: `encryption` is `External`. The key can still be missing. */
|
|
190
|
+
byokEnabled?: boolean;
|
|
191
|
+
/** The cluster has at least one external monitoring (BYOM) client. */
|
|
192
|
+
externalMonitoringEnabled?: boolean;
|
|
193
|
+
/** The cluster has a `secure_connectivity` row (AWS PrivateLink). */
|
|
194
|
+
privateConnectivityEnabled?: boolean;
|
|
181
195
|
}
|
|
182
196
|
/** Detail extends the list shape; richer fields are added in Phase 3. */
|
|
183
197
|
export type ClusterDetailV2Dto = ClusterListItemV2Dto;
|
|
@@ -293,12 +307,18 @@ export interface ListClustersV2Params extends CursorParams {
|
|
|
293
307
|
orgIds?: string[];
|
|
294
308
|
/** Restrict to these cluster uuids (prefilter-by-id). */
|
|
295
309
|
ids?: string[];
|
|
310
|
+
/** Restrict to clusters that these user ids created (`cluster.createdBy`). */
|
|
311
|
+
createdBy?: string[];
|
|
296
312
|
/** Auto-update on/off. */
|
|
297
313
|
autoUpdate?: boolean;
|
|
298
314
|
/** Internal (Camunda-owned) clusters only / excluded. */
|
|
299
315
|
internal?: boolean;
|
|
300
316
|
/** Only clusters with (true) / without (false) an active modification. */
|
|
301
317
|
hasActiveModification?: boolean;
|
|
318
|
+
/** Only clusters with (true) / without (false) the flag of the same name. */
|
|
319
|
+
byokEnabled?: boolean;
|
|
320
|
+
externalMonitoringEnabled?: boolean;
|
|
321
|
+
privateConnectivityEnabled?: boolean;
|
|
302
322
|
/**
|
|
303
323
|
* Zeebe minor-version prefix match on the generation's versions (e.g.
|
|
304
324
|
* "8.7" matches zeebe 8.7.x).
|
|
@@ -405,6 +425,13 @@ export interface ClusterFacetsV2Dto {
|
|
|
405
425
|
};
|
|
406
426
|
/** Clusters with an active (non-archived) modification. */
|
|
407
427
|
activeModifications: number;
|
|
428
|
+
/**
|
|
429
|
+
* Keyed by the user id of the creator. Only Camundi ids (see
|
|
430
|
+
* `isCamundaUserId`): the "Created by" filter lists Camundis, and the id of
|
|
431
|
+
* a customer does not belong in a fleet-wide facet. Absent on an older
|
|
432
|
+
* console-backend.
|
|
433
|
+
*/
|
|
434
|
+
createdBy?: Record<string, number>;
|
|
408
435
|
}
|
|
409
436
|
/** Cluster/owner resolution for a Zeebe client id (cross-backend org search). */
|
|
410
437
|
export interface ClusterByClientV2Dto {
|