@camunda/ccma-shared-types 5.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.
Files changed (69) hide show
  1. package/dist/_tests/approvals/dns-subdomain.spec.d.ts +1 -0
  2. package/dist/_tests/approvals/expected-value.spec.d.ts +1 -0
  3. package/dist/_tests/charts/chart-query.spec.d.ts +1 -0
  4. package/dist/_tests/dashboards/definition.spec.d.ts +1 -0
  5. package/dist/_tests/utils/elasticsearchPlanPath.utils.spec.d.ts +1 -0
  6. package/dist/approvals/dns-subdomain.d.ts +12 -0
  7. package/dist/approvals/dns-subdomain.js +16 -0
  8. package/dist/approvals/dns-subdomain.js.map +7 -0
  9. package/dist/approvals/expected-value.d.ts +33 -0
  10. package/dist/approvals/expected-value.js +26 -0
  11. package/dist/approvals/expected-value.js.map +7 -0
  12. package/dist/approvals/use-cases.const.d.ts +80 -0
  13. package/dist/approvals/use-cases.const.js +42 -2
  14. package/dist/approvals/use-cases.const.js.map +2 -2
  15. package/dist/charts/chart-metrics.d.ts +35 -0
  16. package/dist/charts/chart-metrics.js +188 -0
  17. package/dist/charts/chart-metrics.js.map +7 -0
  18. package/dist/charts/chart-query.d.ts +55 -0
  19. package/dist/charts/chart-query.js +376 -0
  20. package/dist/charts/chart-query.js.map +7 -0
  21. package/dist/charts/chart-spec.d.ts +103 -0
  22. package/dist/charts/chart-spec.js +79 -0
  23. package/dist/charts/chart-spec.js.map +7 -0
  24. package/dist/charts/index.d.ts +3 -0
  25. package/dist/charts/index.js +59 -0
  26. package/dist/charts/index.js.map +7 -0
  27. package/dist/dashboards/definition.d.ts +86 -0
  28. package/dist/dashboards/definition.js +137 -0
  29. package/dist/dashboards/definition.js.map +7 -0
  30. package/dist/dtos/Annotation.dto.d.ts +1 -1
  31. package/dist/dtos/Annotation.dto.js +10 -2
  32. package/dist/dtos/Annotation.dto.js.map +2 -2
  33. package/dist/dtos/ControlV2.dto.d.ts +17 -0
  34. package/dist/dtos/Dashboard.dto.d.ts +336 -0
  35. package/dist/dtos/Dashboard.dto.js +70 -0
  36. package/dist/dtos/Dashboard.dto.js.map +7 -0
  37. package/dist/dtos/FeatureFlags.dto.d.ts +88 -54
  38. package/dist/dtos/Hdp.dto.d.ts +5 -88
  39. package/dist/dtos/Organization.dto.d.ts +10 -0
  40. package/dist/dtos/Organization.dto.js.map +1 -1
  41. package/dist/enums/SystemAuditEntity.enum.d.ts +4 -2
  42. package/dist/enums/SystemAuditEntity.enum.js +2 -0
  43. package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
  44. package/dist/index.d.ts +12 -3
  45. package/dist/index.js +150 -1
  46. package/dist/index.js.map +2 -2
  47. package/dist/permissions/accounts.permissions.d.ts +12 -0
  48. package/dist/permissions/accounts.permissions.js +11 -0
  49. package/dist/permissions/accounts.permissions.js.map +2 -2
  50. package/dist/permissions/combo.js +5 -0
  51. package/dist/permissions/combo.js.map +2 -2
  52. package/dist/search/compiler.d.ts +20 -0
  53. package/dist/search/compiler.js +48 -11
  54. package/dist/search/compiler.js.map +2 -2
  55. package/dist/search/index.d.ts +1 -1
  56. package/dist/search/index.js.map +2 -2
  57. package/dist/search/parser.d.ts +3 -0
  58. package/dist/search/parser.js +3 -1
  59. package/dist/search/parser.js.map +2 -2
  60. package/dist/types/ClusterPlanFields.type.js +74 -74
  61. package/dist/types/ClusterPlanFields.type.js.map +2 -2
  62. package/dist/utils/elasticsearchPlanPath.utils.d.ts +18 -0
  63. package/dist/utils/elasticsearchPlanPath.utils.js +23 -0
  64. package/dist/utils/elasticsearchPlanPath.utils.js.map +7 -0
  65. package/dist/utils/qa-user.utils.d.ts +14 -0
  66. package/dist/utils/qa-user.utils.js +14 -0
  67. package/dist/utils/qa-user.utils.js.map +7 -0
  68. package/dist/utils/qa-user.utils.spec.d.ts +1 -0
  69. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ /** The longest DNS subdomain that Kubernetes accepts as a resource name. */
2
+ export declare const DNS_SUBDOMAIN_MAX_LENGTH = 253;
3
+ /**
4
+ * True when a name is a DNS-1123 subdomain, as Kubernetes accepts one for
5
+ * the name of a resource.
6
+ *
7
+ * Each dot-separated label is 1 to 63 characters of lower case letters,
8
+ * digits and `-`, and starts and ends with a letter or a digit. A single
9
+ * regular expression over the whole name accepts `a..b` and a label of 64
10
+ * characters, and Kubernetes refuses both.
11
+ */
12
+ export declare function isDnsSubdomain(name: string): boolean;
@@ -0,0 +1,16 @@
1
+ const DNS_SUBDOMAIN_MAX_LENGTH = 253;
2
+ const DNS_LABEL = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
3
+ const DNS_LABEL_MAX_LENGTH = 63;
4
+ function isDnsSubdomain(name) {
5
+ if (name.length === 0 || name.length > DNS_SUBDOMAIN_MAX_LENGTH) {
6
+ return false;
7
+ }
8
+ return name.split(".").every(
9
+ (label) => label.length <= DNS_LABEL_MAX_LENGTH && DNS_LABEL.test(label)
10
+ );
11
+ }
12
+ export {
13
+ DNS_SUBDOMAIN_MAX_LENGTH,
14
+ isDnsSubdomain
15
+ };
16
+ //# sourceMappingURL=dns-subdomain.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/approvals/dns-subdomain.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/** The longest DNS subdomain that Kubernetes accepts as a resource name. */\nexport const DNS_SUBDOMAIN_MAX_LENGTH = 253\n\nconst DNS_LABEL = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/\nconst DNS_LABEL_MAX_LENGTH = 63\n\n/**\n * True when a name is a DNS-1123 subdomain, as Kubernetes accepts one for\n * the name of a resource.\n *\n * Each dot-separated label is 1 to 63 characters of lower case letters,\n * digits and `-`, and starts and ends with a letter or a digit. A single\n * regular expression over the whole name accepts `a..b` and a label of 64\n * characters, and Kubernetes refuses both.\n */\nexport function isDnsSubdomain(name: string): boolean {\n if (name.length === 0 || name.length > DNS_SUBDOMAIN_MAX_LENGTH) {\n return false\n }\n return name\n .split('.')\n .every(\n (label) => label.length <= DNS_LABEL_MAX_LENGTH && DNS_LABEL.test(label),\n )\n}\n"],
5
+ "mappings": "AAOO,MAAM,2BAA2B;AAExC,MAAM,YAAY;AAClB,MAAM,uBAAuB;AAWtB,SAAS,eAAe,MAAuB;AACpD,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,0BAA0B;AAC/D,WAAO;AAAA,EACT;AACA,SAAO,KACJ,MAAM,GAAG,EACT;AAAA,IACC,CAAC,UAAU,MAAM,UAAU,wBAAwB,UAAU,KAAK,KAAK;AAAA,EACzE;AACJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The guard against a stale approval.
3
+ *
4
+ * Days can pass between the request and the run. A request that says "set X
5
+ * from 3 to 5" was approved as a CHANGE from 3. When somebody else set X to 4
6
+ * in the meantime, the run must not write 5 over the 4: the approvers never
7
+ * read that change, and they never agreed to undo it.
8
+ *
9
+ * So a use case that writes a value carries the value that the person who
10
+ * asked READ, as `expected`. The service that owns the value compares it with
11
+ * what it holds when the call ARRIVES, and it refuses the whole change when one
12
+ * value moved. It never applies a part of it: the approvers read the change as
13
+ * one thing.
14
+ *
15
+ * The comparison lives here so that the form of control-frontend, the use case
16
+ * of control-backend and the route of accounts-backend read "the same value"
17
+ * in one way.
18
+ */
19
+ /** One value that a request writes, with the value that the asker read. */
20
+ export interface ApprovalExpectedChange<T = unknown> {
21
+ key: string;
22
+ /** The value that the person who asked read. `undefined` for "not set". */
23
+ expected?: T;
24
+ }
25
+ /** True when the value that the asker read is the value that the service holds. */
26
+ export declare function isSameApprovalValue(expected: unknown, current: unknown): boolean;
27
+ /**
28
+ * The keys of a change whose value moved since the person who asked read it.
29
+ *
30
+ * An empty answer means the change can run. Any other answer means none of it
31
+ * can.
32
+ */
33
+ export declare function staleApprovalKeys(changes: readonly ApprovalExpectedChange[], currentOf: (key: string) => unknown): string[];
@@ -0,0 +1,26 @@
1
+ function canonical(value) {
2
+ if (value === void 0 || value === null) {
3
+ return "null";
4
+ }
5
+ if (Array.isArray(value)) {
6
+ return `[${value.map(canonical).join(",")}]`;
7
+ }
8
+ if (typeof value === "object") {
9
+ const entries = Object.entries(value).filter(([, inner]) => inner !== void 0).sort(([one], [other]) => one < other ? -1 : one > other ? 1 : 0).map(([key, inner]) => `${JSON.stringify(key)}:${canonical(inner)}`);
10
+ return `{${entries.join(",")}}`;
11
+ }
12
+ return JSON.stringify(value);
13
+ }
14
+ function isSameApprovalValue(expected, current) {
15
+ return canonical(expected) === canonical(current);
16
+ }
17
+ function staleApprovalKeys(changes, currentOf) {
18
+ return changes.filter(
19
+ (change) => !isSameApprovalValue(change.expected, currentOf(change.key))
20
+ ).map((change) => change.key);
21
+ }
22
+ export {
23
+ isSameApprovalValue,
24
+ staleApprovalKeys
25
+ };
26
+ //# sourceMappingURL=expected-value.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/approvals/expected-value.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 guard against a stale approval.\n *\n * Days can pass between the request and the run. A request that says \"set X\n * from 3 to 5\" was approved as a CHANGE from 3. When somebody else set X to 4\n * in the meantime, the run must not write 5 over the 4: the approvers never\n * read that change, and they never agreed to undo it.\n *\n * So a use case that writes a value carries the value that the person who\n * asked READ, as `expected`. The service that owns the value compares it with\n * what it holds when the call ARRIVES, and it refuses the whole change when one\n * value moved. It never applies a part of it: the approvers read the change as\n * one thing.\n *\n * The comparison lives here so that the form of control-frontend, the use case\n * of control-backend and the route of accounts-backend read \"the same value\"\n * in one way.\n */\n\n/** One value that a request writes, with the value that the asker read. */\nexport interface ApprovalExpectedChange<T = unknown> {\n key: string\n /** The value that the person who asked read. `undefined` for \"not set\". */\n expected?: T\n}\n\n/**\n * Turns a value into the one text that two equal values share.\n *\n * `undefined` and `null` both mean \"not set\", because a JSON body cannot carry\n * `undefined` and a database column reads as `null`. The keys of an object\n * are sorted, because two objects with the same fields in another order are\n * the same value.\n */\nfunction canonical(value: unknown): string {\n if (value === undefined || value === null) {\n return 'null'\n }\n if (Array.isArray(value)) {\n return `[${value.map(canonical).join(',')}]`\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, inner]) => inner !== undefined)\n .sort(([one], [other]) => (one < other ? -1 : one > other ? 1 : 0))\n .map(([key, inner]) => `${JSON.stringify(key)}:${canonical(inner)}`)\n return `{${entries.join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n/** True when the value that the asker read is the value that the service holds. */\nexport function isSameApprovalValue(\n expected: unknown,\n current: unknown,\n): boolean {\n return canonical(expected) === canonical(current)\n}\n\n/**\n * The keys of a change whose value moved since the person who asked read it.\n *\n * An empty answer means the change can run. Any other answer means none of it\n * can.\n */\nexport function staleApprovalKeys(\n changes: readonly ApprovalExpectedChange[],\n currentOf: (key: string) => unknown,\n): string[] {\n return changes\n .filter(\n (change) => !isSameApprovalValue(change.expected, currentOf(change.key)),\n )\n .map((change) => change.key)\n}\n"],
5
+ "mappings": "AAwCA,SAAS,UAAU,OAAwB;AACzC,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,IAAI,MAAM,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC;AAAA,EAC3C;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,OAAO,QAAQ,KAAgC,EAC5D,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS,EACzC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,MAAO,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI,CAAE,EACjE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,UAAU,KAAK,CAAC,EAAE;AACrE,WAAO,IAAI,QAAQ,KAAK,GAAG,CAAC;AAAA,EAC9B;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;AAGO,SAAS,oBACd,UACA,SACS;AACT,SAAO,UAAU,QAAQ,MAAM,UAAU,OAAO;AAClD;AAQO,SAAS,kBACd,SACA,WACU;AACV,SAAO,QACJ;AAAA,IACC,CAAC,WAAW,CAAC,oBAAoB,OAAO,UAAU,UAAU,OAAO,GAAG,CAAC;AAAA,EACzE,EACC,IAAI,CAAC,WAAW,OAAO,GAAG;AAC/B;",
6
+ "names": []
7
+ }
@@ -23,7 +23,27 @@ export declare const APPROVAL_USE_CASE_TYPES: {
23
23
  readonly clusterBulkSleepWakeup: "cluster-bulk-sleep-wakeup";
24
24
  readonly clusterBulkDeletion: "cluster-bulk-deletion";
25
25
  readonly clusterBulkSpecUpdate: "cluster-bulk-spec-update";
26
+ readonly changePlatformRoles: "change-platform-roles";
27
+ readonly deleteInternalBackup: "delete-internal-backup";
28
+ readonly changeOrgLimits: "change-org-limits";
29
+ readonly deleteSsoConnection: "delete-sso-connection";
30
+ readonly changeSalesPlan: "change-sales-plan";
31
+ readonly deleteOrganization: "delete-organization";
32
+ readonly deleteUser: "delete-user";
33
+ readonly changeUserEmail: "change-user-email";
34
+ readonly purgeUsers: "purge-users";
35
+ readonly updateUserMetadata: "update-user-metadata";
36
+ readonly changeFeatureFlag: "change-feature-flag";
26
37
  };
38
+ /**
39
+ * The approvals that a change of the feature flag table needs on prod.
40
+ *
41
+ * A flag there turns a feature on or off for every customer, and a person who
42
+ * holds `aafw` could do it alone. Two other Camundis stand in every change,
43
+ * the same floor as a cluster bulk operation on prod
44
+ * (`CLUSTER_BULK_OPERATION_MIN_ACKS`). Int needs one.
45
+ */
46
+ export declare const FEATURE_FLAG_MIN_ACKS_PROD = 2;
27
47
  export type ApprovalUseCaseType = (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES];
28
48
  /**
29
49
  * The shortest extension that accounts-backend accepts.
@@ -46,3 +66,63 @@ export declare const EXTEND_TRIAL_MIN_DAYS = 15;
46
66
  * margin as the floor above.
47
67
  */
48
68
  export declare const EXTEND_TRIAL_MAX_DAYS = 61;
69
+ /**
70
+ * The most roles that one change-platform-roles request adds, or removes.
71
+ *
72
+ * The form of control-frontend offers every platform role, and a person who
73
+ * gives a Camundi a new position rarely changes more than a few. A request with
74
+ * more roles than this is a request that nobody reads before they approve it.
75
+ */
76
+ export declare const PLATFORM_ROLE_CHANGE_MAX_ROLES = 10;
77
+ /**
78
+ * The longest text value that one change-org-limits request writes.
79
+ *
80
+ * A text limit holds a short value, such as a list of regions. The bound keeps
81
+ * a request readable for the person who approves it.
82
+ */
83
+ export declare const ORG_LIMIT_TEXT_MAX_LENGTH = 2000;
84
+ /**
85
+ * The most users that one purge-users request deletes.
86
+ *
87
+ * The God Mode purge deletes one user after the other, and Auth0 limits how
88
+ * fast that goes. A list longer than this is also a list that no approver
89
+ * reads before they agree.
90
+ */
91
+ export declare const PURGE_USERS_MAX_USERS = 1000;
92
+ /**
93
+ * What a person must hold to open or to approve a purge-users request.
94
+ *
95
+ * The purge of God Mode is behind `GOD_PURGE_USERS`, and each deletion calls a
96
+ * route behind `DELETE_USER`. The use case of control-backend returns this
97
+ * list from `permissionsFor`, and the form passes it to `canCreate`, so the
98
+ * button and the server never disagree.
99
+ */
100
+ export declare const PURGE_USERS_REQUIRED_PERMISSIONS: readonly string[];
101
+ /**
102
+ * The largest metadata object that one update-user-metadata request writes,
103
+ * in characters of its JSON text.
104
+ *
105
+ * This is a bound of the product, and not the limit of Auth0, which is far
106
+ * larger. The approvers read the whole object before they agree, and an
107
+ * object that is longer than this is an object that nobody reads.
108
+ */
109
+ export declare const USER_METADATA_MAX_LENGTH = 16000;
110
+ /**
111
+ * The largest metadata object that a request carries as the object that the
112
+ * person who asked READ.
113
+ *
114
+ * It is larger than `USER_METADATA_MAX_LENGTH`, so a user whose metadata grew
115
+ * past that bound can still get an edit that makes it smaller. It still has a
116
+ * bound, because the request keeps the copy in its row.
117
+ */
118
+ export declare const USER_METADATA_EXPECTED_MAX_LENGTH = 500000;
119
+ /** The longest email address that a change-user-email request carries. */
120
+ export declare const APPROVAL_EMAIL_MAX_LENGTH = 320;
121
+ /**
122
+ * The email addresses that a change-user-email request accepts.
123
+ *
124
+ * It is the email pattern of zod 4.6. The pattern is here, so the schema of
125
+ * control-backend and the form of control-frontend accept the same addresses,
126
+ * and a form never sends an address that the service refuses.
127
+ */
128
+ export declare const APPROVAL_EMAIL_PATTERN: RegExp;
@@ -1,3 +1,4 @@
1
+ import { AccountsPermissions } from "../permissions/accounts.permissions.js";
1
2
  const APPROVAL_USE_CASE_TYPES = {
2
3
  sendToExpiredTrial: "send-to-expired-trial",
3
4
  extendTrial: "extend-trial",
@@ -10,13 +11,52 @@ const APPROVAL_USE_CASE_TYPES = {
10
11
  clusterBulkPlanTypeUpdate: "cluster-bulk-plan-type-update",
11
12
  clusterBulkSleepWakeup: "cluster-bulk-sleep-wakeup",
12
13
  clusterBulkDeletion: "cluster-bulk-deletion",
13
- clusterBulkSpecUpdate: "cluster-bulk-spec-update"
14
+ clusterBulkSpecUpdate: "cluster-bulk-spec-update",
15
+ // The second group, behind `control.approvalAdminActions`.
16
+ changePlatformRoles: "change-platform-roles",
17
+ deleteInternalBackup: "delete-internal-backup",
18
+ changeOrgLimits: "change-org-limits",
19
+ deleteSsoConnection: "delete-sso-connection",
20
+ changeSalesPlan: "change-sales-plan",
21
+ // The third group, behind `control.approvalAccountActions`.
22
+ deleteOrganization: "delete-organization",
23
+ deleteUser: "delete-user",
24
+ changeUserEmail: "change-user-email",
25
+ purgeUsers: "purge-users",
26
+ updateUserMetadata: "update-user-metadata",
27
+ // A change of the feature flag table, on a stage where
28
+ // `isFeatureFlagApprovalRequired` is true.
29
+ changeFeatureFlag: "change-feature-flag"
14
30
  };
31
+ const FEATURE_FLAG_MIN_ACKS_PROD = 2;
15
32
  const EXTEND_TRIAL_MIN_DAYS = 15;
16
33
  const EXTEND_TRIAL_MAX_DAYS = 61;
34
+ const PLATFORM_ROLE_CHANGE_MAX_ROLES = 10;
35
+ const ORG_LIMIT_TEXT_MAX_LENGTH = 2e3;
36
+ const PURGE_USERS_MAX_USERS = 1e3;
37
+ const PURGE_USERS_REQUIRED_PERMISSIONS = [
38
+ AccountsPermissions.GOD_PURGE_USERS,
39
+ AccountsPermissions.DELETE_USER
40
+ ];
41
+ const USER_METADATA_MAX_LENGTH = 16e3;
42
+ const USER_METADATA_EXPECTED_MAX_LENGTH = 5e5;
43
+ const APPROVAL_EMAIL_MAX_LENGTH = 320;
44
+ const APPROVAL_EMAIL_PATTERN = (
45
+ // biome-ignore lint/complexity/noUselessEscapeInRegex: the same source as zod, character for character
46
+ /^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/
47
+ );
17
48
  export {
49
+ APPROVAL_EMAIL_MAX_LENGTH,
50
+ APPROVAL_EMAIL_PATTERN,
18
51
  APPROVAL_USE_CASE_TYPES,
19
52
  EXTEND_TRIAL_MAX_DAYS,
20
- EXTEND_TRIAL_MIN_DAYS
53
+ EXTEND_TRIAL_MIN_DAYS,
54
+ FEATURE_FLAG_MIN_ACKS_PROD,
55
+ ORG_LIMIT_TEXT_MAX_LENGTH,
56
+ PLATFORM_ROLE_CHANGE_MAX_ROLES,
57
+ PURGE_USERS_MAX_USERS,
58
+ PURGE_USERS_REQUIRED_PERMISSIONS,
59
+ USER_METADATA_EXPECTED_MAX_LENGTH,
60
+ USER_METADATA_MAX_LENGTH
21
61
  };
22
62
  //# sourceMappingURL=use-cases.const.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/approvals/use-cases.const.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 names and the value ranges of the registered use cases.\n *\n * control-backend owns the registry, and a frontend reads it at run time\n * (`GET /api/approvals/use-cases`). Two things cannot wait for that answer:\n *\n * - **The identifier**, because a caller must NAME a use case to find it in the\n * registry, and to open a request of it.\n * - **The range of a payload field**, because a form holds the value before any\n * call goes out.\n *\n * Both live here, so the schema of the service and the form of the browser can\n * never hold different numbers.\n */\n\n/** The identifier of each use case, on the wire and in the table. */\nexport const APPROVAL_USE_CASE_TYPES = {\n sendToExpiredTrial: 'send-to-expired-trial',\n extendTrial: 'extend-trial',\n moveClusterOrg: 'move-cluster-org',\n // One use case for each type of a cluster bulk operation. The types need\n // different permissions and different approval counts, and a use case holds\n // one of each. See `cluster-bulk-operation.ts` beside this file.\n clusterBulkGenerationUpdate: 'cluster-bulk-generation-update',\n clusterBulkPlanUpdate: 'cluster-bulk-plan-update',\n clusterBulkPlanTypeUpdate: 'cluster-bulk-plan-type-update',\n clusterBulkSleepWakeup: 'cluster-bulk-sleep-wakeup',\n clusterBulkDeletion: 'cluster-bulk-deletion',\n clusterBulkSpecUpdate: 'cluster-bulk-spec-update',\n} as const\n\nexport type ApprovalUseCaseType =\n (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES]\n\n/**\n * The shortest extension that accounts-backend accepts.\n *\n * `POST /:orgId/extendtrial` refuses a date that is less than 14 days away\n * when the body carries `onBehalfOf`, and it measures those 14 days when the\n * CALL arrives. The floor here is 15 and not 14, for two reasons:\n *\n * - The two clocks are not the same instant. Accounts builds its own \"14 days\n * from now\" a few milliseconds after control-backend builds the date, so a\n * date of exactly 14 days is already in the past for the comparison.\n * - Accounts counts with `setDate`, which is calendar arithmetic. A change of\n * the daylight saving time moves its limit by an hour.\n */\nexport const EXTEND_TRIAL_MIN_DAYS = 15\n\n/**\n * The longest extension that accounts-backend accepts.\n *\n * The same route refuses a date more than 62 days away. 61 keeps the same\n * margin as the floor above.\n */\nexport const EXTEND_TRIAL_MAX_DAYS = 61\n"],
5
- "mappings": "AAsBO,MAAM,0BAA0B;AAAA,EACrC,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,qBAAqB;AAAA,EACrB,uBAAuB;AACzB;AAkBO,MAAM,wBAAwB;AAQ9B,MAAM,wBAAwB;",
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 { AccountsPermissions } from '../permissions/accounts.permissions.js'\n\n/**\n * The names and the value ranges of the registered use cases.\n *\n * control-backend owns the registry, and a frontend reads it at run time\n * (`GET /api/approvals/use-cases`). Two things cannot wait for that answer:\n *\n * - **The identifier**, because a caller must NAME a use case to find it in the\n * registry, and to open a request of it.\n * - **The range of a payload field**, because a form holds the value before any\n * call goes out.\n *\n * Both live here, so the schema of the service and the form of the browser can\n * never hold different numbers.\n */\n\n/** The identifier of each use case, on the wire and in the table. */\nexport const APPROVAL_USE_CASE_TYPES = {\n sendToExpiredTrial: 'send-to-expired-trial',\n extendTrial: 'extend-trial',\n moveClusterOrg: 'move-cluster-org',\n // One use case for each type of a cluster bulk operation. The types need\n // different permissions and different approval counts, and a use case holds\n // one of each. See `cluster-bulk-operation.ts` beside this file.\n clusterBulkGenerationUpdate: 'cluster-bulk-generation-update',\n clusterBulkPlanUpdate: 'cluster-bulk-plan-update',\n clusterBulkPlanTypeUpdate: 'cluster-bulk-plan-type-update',\n clusterBulkSleepWakeup: 'cluster-bulk-sleep-wakeup',\n clusterBulkDeletion: 'cluster-bulk-deletion',\n clusterBulkSpecUpdate: 'cluster-bulk-spec-update',\n // The second group, behind `control.approvalAdminActions`.\n changePlatformRoles: 'change-platform-roles',\n deleteInternalBackup: 'delete-internal-backup',\n changeOrgLimits: 'change-org-limits',\n deleteSsoConnection: 'delete-sso-connection',\n changeSalesPlan: 'change-sales-plan',\n // The third group, behind `control.approvalAccountActions`.\n deleteOrganization: 'delete-organization',\n deleteUser: 'delete-user',\n changeUserEmail: 'change-user-email',\n purgeUsers: 'purge-users',\n updateUserMetadata: 'update-user-metadata',\n // A change of the feature flag table, on a stage where\n // `isFeatureFlagApprovalRequired` is true.\n changeFeatureFlag: 'change-feature-flag',\n} as const\n\n/**\n * The approvals that a change of the feature flag table needs on prod.\n *\n * A flag there turns a feature on or off for every customer, and a person who\n * holds `aafw` could do it alone. Two other Camundis stand in every change,\n * the same floor as a cluster bulk operation on prod\n * (`CLUSTER_BULK_OPERATION_MIN_ACKS`). Int needs one.\n */\nexport const FEATURE_FLAG_MIN_ACKS_PROD = 2\n\nexport type ApprovalUseCaseType =\n (typeof APPROVAL_USE_CASE_TYPES)[keyof typeof APPROVAL_USE_CASE_TYPES]\n\n/**\n * The shortest extension that accounts-backend accepts.\n *\n * `POST /:orgId/extendtrial` refuses a date that is less than 14 days away\n * when the body carries `onBehalfOf`, and it measures those 14 days when the\n * CALL arrives. The floor here is 15 and not 14, for two reasons:\n *\n * - The two clocks are not the same instant. Accounts builds its own \"14 days\n * from now\" a few milliseconds after control-backend builds the date, so a\n * date of exactly 14 days is already in the past for the comparison.\n * - Accounts counts with `setDate`, which is calendar arithmetic. A change of\n * the daylight saving time moves its limit by an hour.\n */\nexport const EXTEND_TRIAL_MIN_DAYS = 15\n\n/**\n * The longest extension that accounts-backend accepts.\n *\n * The same route refuses a date more than 62 days away. 61 keeps the same\n * margin as the floor above.\n */\nexport const EXTEND_TRIAL_MAX_DAYS = 61\n\n/**\n * The most roles that one change-platform-roles request adds, or removes.\n *\n * The form of control-frontend offers every platform role, and a person who\n * gives a Camundi a new position rarely changes more than a few. A request with\n * more roles than this is a request that nobody reads before they approve it.\n */\nexport const PLATFORM_ROLE_CHANGE_MAX_ROLES = 10\n\n/**\n * The longest text value that one change-org-limits request writes.\n *\n * A text limit holds a short value, such as a list of regions. The bound keeps\n * a request readable for the person who approves it.\n */\nexport const ORG_LIMIT_TEXT_MAX_LENGTH = 2000\n\n/**\n * The most users that one purge-users request deletes.\n *\n * The God Mode purge deletes one user after the other, and Auth0 limits how\n * fast that goes. A list longer than this is also a list that no approver\n * reads before they agree.\n */\nexport const PURGE_USERS_MAX_USERS = 1000\n\n/**\n * What a person must hold to open or to approve a purge-users request.\n *\n * The purge of God Mode is behind `GOD_PURGE_USERS`, and each deletion calls a\n * route behind `DELETE_USER`. The use case of control-backend returns this\n * list from `permissionsFor`, and the form passes it to `canCreate`, so the\n * button and the server never disagree.\n */\nexport const PURGE_USERS_REQUIRED_PERMISSIONS: readonly string[] = [\n AccountsPermissions.GOD_PURGE_USERS,\n AccountsPermissions.DELETE_USER,\n]\n\n/**\n * The largest metadata object that one update-user-metadata request writes,\n * in characters of its JSON text.\n *\n * This is a bound of the product, and not the limit of Auth0, which is far\n * larger. The approvers read the whole object before they agree, and an\n * object that is longer than this is an object that nobody reads.\n */\nexport const USER_METADATA_MAX_LENGTH = 16_000\n\n/**\n * The largest metadata object that a request carries as the object that the\n * person who asked READ.\n *\n * It is larger than `USER_METADATA_MAX_LENGTH`, so a user whose metadata grew\n * past that bound can still get an edit that makes it smaller. It still has a\n * bound, because the request keeps the copy in its row.\n */\nexport const USER_METADATA_EXPECTED_MAX_LENGTH = 500_000\n\n/** The longest email address that a change-user-email request carries. */\nexport const APPROVAL_EMAIL_MAX_LENGTH = 320\n\n/**\n * The email addresses that a change-user-email request accepts.\n *\n * It is the email pattern of zod 4.6. The pattern is here, so the schema of\n * control-backend and the form of control-frontend accept the same addresses,\n * and a form never sends an address that the service refuses.\n */\nexport const APPROVAL_EMAIL_PATTERN =\n // biome-ignore lint/complexity/noUselessEscapeInRegex: the same source as zod, character for character\n /^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/\n"],
5
+ "mappings": "AAMA,SAAS,2BAA2B;AAkB7B,MAAM,0BAA0B;AAAA,EACrC,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA;AAAA,EAEvB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,iBAAiB;AAAA;AAAA,EAEjB,oBAAoB;AAAA,EACpB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,oBAAoB;AAAA;AAAA;AAAA,EAGpB,mBAAmB;AACrB;AAUO,MAAM,6BAA6B;AAkBnC,MAAM,wBAAwB;AAQ9B,MAAM,wBAAwB;AAS9B,MAAM,iCAAiC;AAQvC,MAAM,4BAA4B;AASlC,MAAM,wBAAwB;AAU9B,MAAM,mCAAsD;AAAA,EACjE,oBAAoB;AAAA,EACpB,oBAAoB;AACtB;AAUO,MAAM,2BAA2B;AAUjC,MAAM,oCAAoC;AAG1C,MAAM,4BAA4B;AASlC,MAAM;AAAA;AAAA,EAEX;AAAA;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,35 @@
1
+ import type { SearchFieldSpec } from '../search/fields.js';
2
+ import type { ChartGrain, ChartUnit } from './chart-spec.js';
3
+ export interface ChartDimensionSpec {
4
+ /** As typed after `by`. */
5
+ id: string;
6
+ label: string;
7
+ description: string;
8
+ }
9
+ export interface ChartMetricSpec {
10
+ /** As typed at the start of a query. */
11
+ id: string;
12
+ label: string;
13
+ description: string;
14
+ unit: ChartUnit;
15
+ dimensions: readonly ChartDimensionSpec[];
16
+ /** The fields that `where` can use. */
17
+ filters: readonly SearchFieldSpec[];
18
+ grains: readonly ChartGrain[];
19
+ /** The longest window, in days. */
20
+ maxWindowDays: number;
21
+ /**
22
+ * How one number comes from a chart, for the `stat` render: first over the
23
+ * series of a bucket, then over the buckets of the window. A counter sums
24
+ * both. A gauge must not: a sum of daily load percentages means nothing.
25
+ */
26
+ stat: {
27
+ series: 'sum' | 'mean';
28
+ window: 'sum' | 'max' | 'mean';
29
+ };
30
+ }
31
+ export declare const CHART_METRICS: readonly ChartMetricSpec[];
32
+ /** Case-insensitive. */
33
+ export declare const findChartMetric: (id: string, metrics?: readonly ChartMetricSpec[]) => ChartMetricSpec | undefined;
34
+ /** Case-insensitive. */
35
+ export declare const findChartDimension: (metric: ChartMetricSpec, id: string) => ChartDimensionSpec | undefined;
@@ -0,0 +1,188 @@
1
+ const CLUSTER_DIMENSION = {
2
+ id: "cluster",
3
+ label: "Cluster",
4
+ description: "The cluster id."
5
+ };
6
+ const ORG_DIMENSION = {
7
+ id: "org",
8
+ label: "Organization",
9
+ description: "The organization id."
10
+ };
11
+ const ORG_NAME_DIMENSION = {
12
+ id: "orgName",
13
+ label: "Organization name",
14
+ description: "The name of the organization of the cluster. Prod clusters only: on other stages it is (unknown)."
15
+ };
16
+ const ORG_ID_FILTER = {
17
+ field: "orgId",
18
+ label: "Organization id",
19
+ description: "Exact organization id.",
20
+ type: "string",
21
+ capabilities: ["match"],
22
+ example: "orgId:3f2c\u2026"
23
+ };
24
+ const CLUSTER_ID_FILTER = {
25
+ field: "clusterId",
26
+ label: "Cluster id",
27
+ description: "Exact cluster id.",
28
+ type: "string",
29
+ capabilities: ["match"],
30
+ example: "clusterId:9a1b\u2026"
31
+ };
32
+ const VALUE_METRIC_DIMENSIONS = [
33
+ CLUSTER_DIMENSION,
34
+ ORG_DIMENSION,
35
+ ORG_NAME_DIMENSION,
36
+ {
37
+ id: "salesPlan",
38
+ label: "Sales plan",
39
+ description: "The sales plan of the organization on that day."
40
+ },
41
+ {
42
+ id: "env",
43
+ label: "Environment",
44
+ description: "Production or development cluster."
45
+ }
46
+ ];
47
+ const VALUE_METRIC_FILTERS = [
48
+ ORG_ID_FILTER,
49
+ CLUSTER_ID_FILTER,
50
+ {
51
+ field: "salesPlan",
52
+ label: "Sales plan",
53
+ description: "Sales plan name, case-insensitive. Use * as a wildcard.",
54
+ type: "string",
55
+ capabilities: ["match"],
56
+ example: "salesPlan:enterprise*"
57
+ },
58
+ {
59
+ field: "env",
60
+ label: "Environment",
61
+ description: "Production or development cluster.",
62
+ type: "enum",
63
+ capabilities: ["match"],
64
+ enumValues: ["production", "development"],
65
+ example: "env:production"
66
+ }
67
+ ];
68
+ const ALL_GRAINS = ["day", "week", "month"];
69
+ const VALUE_METRIC_GRAINS = ALL_GRAINS;
70
+ const LARGE_TABLE_MAX_WINDOW_DAYS = 400;
71
+ const gaugeMetric = (id, label, unit, stat, description) => ({
72
+ id,
73
+ label,
74
+ description,
75
+ unit,
76
+ dimensions: [CLUSTER_DIMENSION, ORG_DIMENSION, ORG_NAME_DIMENSION],
77
+ filters: [ORG_ID_FILTER, CLUSTER_ID_FILTER],
78
+ grains: ALL_GRAINS,
79
+ maxWindowDays: LARGE_TABLE_MAX_WINDOW_DAYS,
80
+ stat
81
+ });
82
+ const VALUE_METRIC_MAX_WINDOW_DAYS = 762;
83
+ const valueMetric = (id, label, description) => ({
84
+ id,
85
+ label,
86
+ description,
87
+ unit: "count",
88
+ dimensions: VALUE_METRIC_DIMENSIONS,
89
+ filters: VALUE_METRIC_FILTERS,
90
+ grains: VALUE_METRIC_GRAINS,
91
+ maxWindowDays: VALUE_METRIC_MAX_WINDOW_DAYS,
92
+ stat: { series: "sum", window: "sum" }
93
+ });
94
+ const CHART_METRICS = [
95
+ valueMetric(
96
+ "processInstances",
97
+ "Process instances",
98
+ "Process instances that started, from the HDP value metrics. The sum over each bucket."
99
+ ),
100
+ valueMetric(
101
+ "decisionInstances",
102
+ "Decision instances",
103
+ "Decision instances that ran, from the HDP value metrics. The sum over each bucket."
104
+ ),
105
+ valueMetric(
106
+ "taskUsers",
107
+ "Task users",
108
+ "Task users, from the HDP value metrics. The sum of the daily values over each bucket, as on the Value metrics tab."
109
+ ),
110
+ gaugeMetric(
111
+ "activeTenants",
112
+ "Active tenants",
113
+ "count",
114
+ { series: "sum", window: "max" },
115
+ "Active tenants: the peak of each cluster on a day, summed over the clusters. A week or a month shows its highest day. The table holds every stage."
116
+ ),
117
+ gaugeMetric(
118
+ "clusterLoad",
119
+ "Cluster load",
120
+ "percent",
121
+ { series: "mean", window: "mean" },
122
+ "The load of a cluster, in percent: the mean of a day, then the mean over the clusters and the days of a bucket. The table holds every stage."
123
+ ),
124
+ gaugeMetric(
125
+ "diskUsedBytes",
126
+ "Elasticsearch disk used",
127
+ "bytes",
128
+ { series: "sum", window: "max" },
129
+ "The used Elasticsearch disk: the peak of each cluster on a day, summed over the clusters. A week or a month shows its highest day. The table holds every stage."
130
+ ),
131
+ {
132
+ id: "hostingCost",
133
+ label: "Hosting cost",
134
+ description: "The loaded hosting cost of the clusters, in US dollars, from the cloud bill. The sum over each bucket. The bill holds every stage.",
135
+ unit: "usd",
136
+ dimensions: [CLUSTER_DIMENSION, ORG_DIMENSION, ORG_NAME_DIMENSION],
137
+ filters: [ORG_ID_FILTER, CLUSTER_ID_FILTER],
138
+ grains: ALL_GRAINS,
139
+ maxWindowDays: VALUE_METRIC_MAX_WINDOW_DAYS,
140
+ stat: { series: "sum", window: "sum" }
141
+ },
142
+ {
143
+ id: "changeEvents",
144
+ label: "Change events",
145
+ description: "The change events that the backends of this stage sent to HDP: one for each insert, update or delete of an entity. The count over each bucket.",
146
+ unit: "count",
147
+ dimensions: [
148
+ { id: "entity", label: "Entity", description: "For example cluster." },
149
+ {
150
+ id: "operation",
151
+ label: "Operation",
152
+ description: "insert, update or delete."
153
+ }
154
+ ],
155
+ filters: [
156
+ {
157
+ field: "entity",
158
+ label: "Entity",
159
+ description: "The entity, case-insensitive. Use * as a wildcard.",
160
+ type: "string",
161
+ capabilities: ["match"],
162
+ example: "entity:cluster"
163
+ },
164
+ {
165
+ field: "operation",
166
+ label: "Operation",
167
+ description: "The operation.",
168
+ type: "enum",
169
+ capabilities: ["match"],
170
+ enumValues: ["insert", "update", "delete"],
171
+ example: "operation:delete"
172
+ }
173
+ ],
174
+ grains: ALL_GRAINS,
175
+ maxWindowDays: LARGE_TABLE_MAX_WINDOW_DAYS,
176
+ stat: { series: "sum", window: "sum" }
177
+ }
178
+ ];
179
+ const findChartMetric = (id, metrics = CHART_METRICS) => metrics.find((metric) => metric.id.toLowerCase() === id.toLowerCase());
180
+ const findChartDimension = (metric, id) => metric.dimensions.find(
181
+ (dimension) => dimension.id.toLowerCase() === id.toLowerCase()
182
+ );
183
+ export {
184
+ CHART_METRICS,
185
+ findChartDimension,
186
+ findChartMetric
187
+ };
188
+ //# sourceMappingURL=chart-metrics.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/charts/chart-metrics.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 { SearchFieldSpec } from '../search/fields.js'\nimport type { ChartGrain, ChartUnit } from './chart-spec.js'\n\n// ============================================================================\n// The metric catalogue of the chart tiles, without SQL.\n//\n// The frontend reads it for autocomplete, help and validation as you type.\n// accounts-backend pairs each entry with its SQL (`hdp-data.charts.controller`),\n// and a test there makes sure that the two have the same ids.\n// ============================================================================\n\nexport interface ChartDimensionSpec {\n /** As typed after `by`. */\n id: string\n label: string\n description: string\n}\n\nexport interface ChartMetricSpec {\n /** As typed at the start of a query. */\n id: string\n label: string\n description: string\n unit: ChartUnit\n dimensions: readonly ChartDimensionSpec[]\n /** The fields that `where` can use. */\n filters: readonly SearchFieldSpec[]\n grains: readonly ChartGrain[]\n /** The longest window, in days. */\n maxWindowDays: number\n /**\n * How one number comes from a chart, for the `stat` render: first over the\n * series of a bucket, then over the buckets of the window. A counter sums\n * both. A gauge must not: a sum of daily load percentages means nothing.\n */\n stat: { series: 'sum' | 'mean'; window: 'sum' | 'max' | 'mean' }\n}\n\nconst CLUSTER_DIMENSION: ChartDimensionSpec = {\n id: 'cluster',\n label: 'Cluster',\n description: 'The cluster id.',\n}\n\nconst ORG_DIMENSION: ChartDimensionSpec = {\n id: 'org',\n label: 'Organization',\n description: 'The organization id.',\n}\n\n/**\n * The name of the organization of the cluster. It comes from the clusters\n * table of HDP, which holds the prod clusters only, so on another stage every\n * series is `(unknown)`.\n */\nconst ORG_NAME_DIMENSION: ChartDimensionSpec = {\n id: 'orgName',\n label: 'Organization name',\n description:\n 'The name of the organization of the cluster. Prod clusters only: on other stages it is (unknown).',\n}\n\nconst ORG_ID_FILTER: SearchFieldSpec = {\n field: 'orgId',\n label: 'Organization id',\n description: 'Exact organization id.',\n type: 'string',\n capabilities: ['match'],\n example: 'orgId:3f2c\u2026',\n}\n\nconst CLUSTER_ID_FILTER: SearchFieldSpec = {\n field: 'clusterId',\n label: 'Cluster id',\n description: 'Exact cluster id.',\n type: 'string',\n capabilities: ['match'],\n example: 'clusterId:9a1b\u2026',\n}\n\nconst VALUE_METRIC_DIMENSIONS: readonly ChartDimensionSpec[] = [\n CLUSTER_DIMENSION,\n ORG_DIMENSION,\n ORG_NAME_DIMENSION,\n {\n id: 'salesPlan',\n label: 'Sales plan',\n description: 'The sales plan of the organization on that day.',\n },\n {\n id: 'env',\n label: 'Environment',\n description: 'Production or development cluster.',\n },\n]\n\nconst VALUE_METRIC_FILTERS: readonly SearchFieldSpec[] = [\n ORG_ID_FILTER,\n CLUSTER_ID_FILTER,\n {\n field: 'salesPlan',\n label: 'Sales plan',\n description: 'Sales plan name, case-insensitive. Use * as a wildcard.',\n type: 'string',\n capabilities: ['match'],\n example: 'salesPlan:enterprise*',\n },\n {\n field: 'env',\n label: 'Environment',\n description: 'Production or development cluster.',\n type: 'enum',\n capabilities: ['match'],\n enumValues: ['production', 'development'],\n example: 'env:production',\n },\n]\n\nconst ALL_GRAINS: readonly ChartGrain[] = ['day', 'week', 'month']\nconst VALUE_METRIC_GRAINS = ALL_GRAINS\n\n/**\n * About one year, for a metric on a large table: the cluster gauges, which\n * are scraped about once a minute, and the change events of every backend\n * and stage. `over 12m every month` starts on the 1st of a month and reads up\n * to 396 days, so the limit holds that query.\n */\nconst LARGE_TABLE_MAX_WINDOW_DAYS = 400\n\n/** A gauge of the clusters, from the Prometheus metrics in HDP. */\nconst gaugeMetric = (\n id: string,\n label: string,\n unit: ChartMetricSpec['unit'],\n stat: ChartMetricSpec['stat'],\n description: string,\n): ChartMetricSpec => ({\n id,\n label,\n description,\n unit,\n dimensions: [CLUSTER_DIMENSION, ORG_DIMENSION, ORG_NAME_DIMENSION],\n filters: [ORG_ID_FILTER, CLUSTER_ID_FILTER],\n grains: ALL_GRAINS,\n maxWindowDays: LARGE_TABLE_MAX_WINDOW_DAYS,\n stat,\n})\n\n/**\n * About two years. The table holds one row for each cluster, day and metric.\n * `over 24m every month` starts on the 1st of a month and reads up to 761\n * days, so the limit holds that query.\n */\nconst VALUE_METRIC_MAX_WINDOW_DAYS = 762\n\nconst valueMetric = (\n id: string,\n label: string,\n description: string,\n): ChartMetricSpec => ({\n id,\n label,\n description,\n unit: 'count',\n dimensions: VALUE_METRIC_DIMENSIONS,\n filters: VALUE_METRIC_FILTERS,\n grains: VALUE_METRIC_GRAINS,\n maxWindowDays: VALUE_METRIC_MAX_WINDOW_DAYS,\n stat: { series: 'sum', window: 'sum' },\n})\n\nexport const CHART_METRICS: readonly ChartMetricSpec[] = [\n valueMetric(\n 'processInstances',\n 'Process instances',\n 'Process instances that started, from the HDP value metrics. The sum over each bucket.',\n ),\n valueMetric(\n 'decisionInstances',\n 'Decision instances',\n 'Decision instances that ran, from the HDP value metrics. The sum over each bucket.',\n ),\n valueMetric(\n 'taskUsers',\n 'Task users',\n 'Task users, from the HDP value metrics. The sum of the daily values over each bucket, as on the Value metrics tab.',\n ),\n gaugeMetric(\n 'activeTenants',\n 'Active tenants',\n 'count',\n { series: 'sum', window: 'max' },\n 'Active tenants: the peak of each cluster on a day, summed over the clusters. A week or a month shows its highest day. The table holds every stage.',\n ),\n gaugeMetric(\n 'clusterLoad',\n 'Cluster load',\n 'percent',\n { series: 'mean', window: 'mean' },\n 'The load of a cluster, in percent: the mean of a day, then the mean over the clusters and the days of a bucket. The table holds every stage.',\n ),\n gaugeMetric(\n 'diskUsedBytes',\n 'Elasticsearch disk used',\n 'bytes',\n { series: 'sum', window: 'max' },\n 'The used Elasticsearch disk: the peak of each cluster on a day, summed over the clusters. A week or a month shows its highest day. The table holds every stage.',\n ),\n {\n id: 'hostingCost',\n label: 'Hosting cost',\n description:\n 'The loaded hosting cost of the clusters, in US dollars, from the cloud bill. The sum over each bucket. The bill holds every stage.',\n unit: 'usd',\n dimensions: [CLUSTER_DIMENSION, ORG_DIMENSION, ORG_NAME_DIMENSION],\n filters: [ORG_ID_FILTER, CLUSTER_ID_FILTER],\n grains: ALL_GRAINS,\n maxWindowDays: VALUE_METRIC_MAX_WINDOW_DAYS,\n stat: { series: 'sum', window: 'sum' },\n },\n {\n id: 'changeEvents',\n label: 'Change events',\n description:\n 'The change events that the backends of this stage sent to HDP: one for each insert, update or delete of an entity. The count over each bucket.',\n unit: 'count',\n dimensions: [\n { id: 'entity', label: 'Entity', description: 'For example cluster.' },\n {\n id: 'operation',\n label: 'Operation',\n description: 'insert, update or delete.',\n },\n ],\n filters: [\n {\n field: 'entity',\n label: 'Entity',\n description: 'The entity, case-insensitive. Use * as a wildcard.',\n type: 'string',\n capabilities: ['match'],\n example: 'entity:cluster',\n },\n {\n field: 'operation',\n label: 'Operation',\n description: 'The operation.',\n type: 'enum',\n capabilities: ['match'],\n enumValues: ['insert', 'update', 'delete'],\n example: 'operation:delete',\n },\n ],\n grains: ALL_GRAINS,\n maxWindowDays: LARGE_TABLE_MAX_WINDOW_DAYS,\n stat: { series: 'sum', window: 'sum' },\n },\n]\n\n/** Case-insensitive. */\nexport const findChartMetric = (\n id: string,\n metrics: readonly ChartMetricSpec[] = CHART_METRICS,\n): ChartMetricSpec | undefined =>\n metrics.find((metric) => metric.id.toLowerCase() === id.toLowerCase())\n\n/** Case-insensitive. */\nexport const findChartDimension = (\n metric: ChartMetricSpec,\n id: string,\n): ChartDimensionSpec | undefined =>\n metric.dimensions.find(\n (dimension) => dimension.id.toLowerCase() === id.toLowerCase(),\n )\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ import { type ChartMetricSpec } from './chart-metrics.js';
2
+ import { type ChartSpec } from './chart-spec.js';
3
+ export type ChartQueryErrorKind = 'syntax' | 'unknown-metric' | 'unknown-dimension' | 'unknown-field' | 'bad-value';
4
+ export declare class ChartQueryError extends Error {
5
+ readonly kind: ChartQueryErrorKind;
6
+ /** A name to use instead, for "did you mean". */
7
+ readonly suggestion?: string;
8
+ constructor(message: string, kind: ChartQueryErrorKind, suggestion?: string);
9
+ }
10
+ /** Type guard that works across package boundaries. */
11
+ export declare const isChartQueryError: (error: unknown) => error is ChartQueryError;
12
+ /**
13
+ * The answer of `POST /api/dashboards/chart-queries/check`: the canonical spec
14
+ * of a valid query, or what is wrong with a query and how to fix it. A wrong
15
+ * query is an answer and not a failure, so a client reads `valid`.
16
+ */
17
+ export type ChartQueryCheck = {
18
+ valid: true;
19
+ spec: ChartSpec;
20
+ query: string;
21
+ } | {
22
+ valid: false;
23
+ error: {
24
+ kind: ChartQueryErrorKind;
25
+ message: string;
26
+ suggestion?: string;
27
+ };
28
+ };
29
+ export interface ChartQueryOptions {
30
+ metrics?: readonly ChartMetricSpec[];
31
+ /** `YYYY-MM-DD`. The default is today in UTC. */
32
+ today?: string;
33
+ }
34
+ /**
35
+ * Parse and validate a chart query. Gives the canonical {@link ChartSpec}:
36
+ * the ids in the spelling of the catalogue, and the defaults filled in.
37
+ * Throws {@link ChartQueryError}.
38
+ */
39
+ export declare const parseChartQuery: (text: string, options?: ChartQueryOptions) => ChartSpec;
40
+ /**
41
+ * Check a spec against the catalogue and give it in canonical form. The chart
42
+ * route runs this on every request body, so a spec that a tile stored long ago
43
+ * gets the rules of today. Throws {@link ChartQueryError}.
44
+ */
45
+ export declare const validateChartSpec: (spec: ChartSpec, options?: ChartQueryOptions) => ChartSpec;
46
+ /**
47
+ * The text of a spec, in one canonical form. Parsing it again gives the same
48
+ * spec, so the text field and the builder can never disagree.
49
+ */
50
+ export declare const formatChartSpec: (spec: ChartSpec) => string;
51
+ /**
52
+ * Read a spec from JSON, for example a request body or a stored tile. Checks
53
+ * the shape only. Run {@link validateChartSpec} after it.
54
+ */
55
+ export declare const readChartSpec: (value: unknown) => ChartSpec;