@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.
Files changed (85) hide show
  1. package/dist/_tests/approvals/expected-value.spec.d.ts +1 -0
  2. package/dist/_tests/charts/chart-query.spec.d.ts +1 -0
  3. package/dist/_tests/dashboards/definition.spec.d.ts +1 -0
  4. package/dist/_tests/types/PreprodClusterTtl.type.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 +65 -51
  13. package/dist/approvals/use-cases.const.js +40 -9
  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 +27 -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/ExternalWebapp.dto.d.ts +12 -0
  38. package/dist/dtos/FeatureFlags.dto.d.ts +85 -58
  39. package/dist/dtos/FeatureFlags.dto.js +0 -10
  40. package/dist/dtos/FeatureFlags.dto.js.map +3 -3
  41. package/dist/dtos/Hdp.dto.d.ts +70 -0
  42. package/dist/dtos/Organization.dto.d.ts +10 -0
  43. package/dist/dtos/Organization.dto.js.map +1 -1
  44. package/dist/dtos/SalesPlan.dto.d.ts +25 -1
  45. package/dist/dtos/SalesPlan.dto.js +19 -6
  46. package/dist/dtos/SalesPlan.dto.js.map +2 -2
  47. package/dist/enums/SystemAuditEntity.enum.d.ts +4 -2
  48. package/dist/enums/SystemAuditEntity.enum.js +2 -0
  49. package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
  50. package/dist/index.d.ts +17 -10
  51. package/dist/index.js +163 -24
  52. package/dist/index.js.map +2 -2
  53. package/dist/permissions/accounts.permissions.d.ts +7 -28
  54. package/dist/permissions/accounts.permissions.js +7 -27
  55. package/dist/permissions/accounts.permissions.js.map +2 -2
  56. package/dist/permissions/combo.js +2 -3
  57. package/dist/permissions/combo.js.map +2 -2
  58. package/dist/search/compiler.d.ts +20 -0
  59. package/dist/search/compiler.js +48 -11
  60. package/dist/search/compiler.js.map +2 -2
  61. package/dist/search/index.d.ts +1 -1
  62. package/dist/search/index.js.map +2 -2
  63. package/dist/search/parser.d.ts +3 -0
  64. package/dist/search/parser.js +3 -1
  65. package/dist/search/parser.js.map +2 -2
  66. package/dist/types/ClusterPlanFields.type.js +74 -74
  67. package/dist/types/ClusterPlanFields.type.js.map +2 -2
  68. package/dist/types/PreprodClusterTtl.type.d.ts +33 -0
  69. package/dist/types/PreprodClusterTtl.type.js +37 -0
  70. package/dist/types/PreprodClusterTtl.type.js.map +7 -0
  71. package/dist/types/hdp.types.d.ts +6 -0
  72. package/dist/types/hdp.types.js +3 -1
  73. package/dist/types/hdp.types.js.map +2 -2
  74. package/dist/utils/elasticsearchPlanPath.utils.d.ts +18 -0
  75. package/dist/utils/elasticsearchPlanPath.utils.js +23 -0
  76. package/dist/utils/elasticsearchPlanPath.utils.js.map +7 -0
  77. package/dist/utils/qa-user.utils.d.ts +14 -0
  78. package/dist/utils/qa-user.utils.js +14 -0
  79. package/dist/utils/qa-user.utils.js.map +7 -0
  80. package/dist/utils/qa-user.utils.spec.d.ts +1 -0
  81. package/package.json +1 -1
  82. package/dist/dtos/FeatureFlagOverride.dto.d.ts +0 -226
  83. package/dist/dtos/FeatureFlagOverride.dto.js +0 -38
  84. package/dist/dtos/FeatureFlagOverride.dto.js.map +0 -7
  85. /package/dist/_tests/{dtos/FeatureFlagOverride.dto.spec.d.ts → approvals/dns-subdomain.spec.d.ts} +0 -0
@@ -0,0 +1,33 @@
1
+ import type { Limits } from '../dtos/SalesPlan.dto.js';
2
+ /**
3
+ * Returns the value of a number limit as a finite number, or `undefined`.
4
+ * Accepts only a finite number and a string with a decimal number, such as
5
+ * `'8'`, `'-1'` or `'1.5'`. `Number` alone also accepts `true` (1), `[8]`
6
+ * (8), `''` (0) and `'0x8'` (8), and limits are JSON that nothing types at
7
+ * run time.
8
+ */
9
+ export declare function parseNumericLimit(value: unknown): number | undefined;
10
+ /**
11
+ * Returns `true` for the stages `dev` and `int`, where the
12
+ * `preprodClusterTtlHours` limit applies. Returns `false` for every other
13
+ * stage, `prod` and `local` included.
14
+ */
15
+ export declare const isPreprodClusterTtlStage: (stage: string) => boolean;
16
+ /**
17
+ * Returns the `preprodClusterTtlHours` limit of an org when it applies, or
18
+ * `undefined`. The limit applies only on dev and int, so a prod org with the
19
+ * limit keeps its clusters. The value goes through `parseNumericLimit`.
20
+ */
21
+ export declare function getPreprodClusterTtlHours({ limits, stage, }: {
22
+ limits: Pick<Limits, 'preprodClusterTtlHours'> | undefined;
23
+ stage: string;
24
+ }): number | undefined;
25
+ /**
26
+ * Returns whether a cluster is older than the TTL of its org. A cluster with
27
+ * no valid `created` is never past the TTL.
28
+ */
29
+ export declare function isPastPreprodClusterTtl({ created, ttlHours, now, }: {
30
+ created: Date | string | undefined;
31
+ ttlHours: number | undefined;
32
+ now?: Date;
33
+ }): boolean;
@@ -0,0 +1,37 @@
1
+ const HOUR = 60 * 60 * 1e3;
2
+ function parseNumericLimit(value) {
3
+ if (typeof value === "number") {
4
+ return Number.isFinite(value) ? value : void 0;
5
+ }
6
+ if (typeof value !== "string" || !/^-?\d+(\.\d+)?$/.test(value.trim())) {
7
+ return void 0;
8
+ }
9
+ const parsed = Number(value);
10
+ return Number.isFinite(parsed) ? parsed : void 0;
11
+ }
12
+ const isPreprodClusterTtlStage = (stage) => stage === "dev" || stage === "int";
13
+ function getPreprodClusterTtlHours({
14
+ limits,
15
+ stage
16
+ }) {
17
+ if (!isPreprodClusterTtlStage(stage)) return void 0;
18
+ const ttlHours = parseNumericLimit(limits?.preprodClusterTtlHours);
19
+ return ttlHours !== void 0 && Number.isInteger(ttlHours) && ttlHours >= 1 ? ttlHours : void 0;
20
+ }
21
+ function isPastPreprodClusterTtl({
22
+ created,
23
+ ttlHours,
24
+ now = /* @__PURE__ */ new Date()
25
+ }) {
26
+ if (ttlHours === void 0 || created === void 0) return false;
27
+ const createdMs = new Date(created).getTime();
28
+ if (Number.isNaN(createdMs)) return false;
29
+ return now.getTime() - createdMs > ttlHours * HOUR;
30
+ }
31
+ export {
32
+ getPreprodClusterTtlHours,
33
+ isPastPreprodClusterTtl,
34
+ isPreprodClusterTtlStage,
35
+ parseNumericLimit
36
+ };
37
+ //# sourceMappingURL=PreprodClusterTtl.type.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/types/PreprodClusterTtl.type.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 { Limits } from '../dtos/SalesPlan.dto.js'\n\nconst HOUR = 60 * 60 * 1000\n\n/**\n * Returns the value of a number limit as a finite number, or `undefined`.\n * Accepts only a finite number and a string with a decimal number, such as\n * `'8'`, `'-1'` or `'1.5'`. `Number` alone also accepts `true` (1), `[8]`\n * (8), `''` (0) and `'0x8'` (8), and limits are JSON that nothing types at\n * run time.\n */\nexport function parseNumericLimit(value: unknown): number | undefined {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : undefined\n }\n if (typeof value !== 'string' || !/^-?\\d+(\\.\\d+)?$/.test(value.trim())) {\n return undefined\n }\n // A string of 309 or more digits is `Infinity`.\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\n/**\n * Returns `true` for the stages `dev` and `int`, where the\n * `preprodClusterTtlHours` limit applies. Returns `false` for every other\n * stage, `prod` and `local` included.\n */\nexport const isPreprodClusterTtlStage = (stage: string): boolean =>\n stage === 'dev' || stage === 'int'\n\n/**\n * Returns the `preprodClusterTtlHours` limit of an org when it applies, or\n * `undefined`. The limit applies only on dev and int, so a prod org with the\n * limit keeps its clusters. The value goes through `parseNumericLimit`.\n */\nexport function getPreprodClusterTtlHours({\n limits,\n stage,\n}: {\n limits: Pick<Limits, 'preprodClusterTtlHours'> | undefined\n stage: string\n}): number | undefined {\n if (!isPreprodClusterTtlStage(stage)) return undefined\n const ttlHours = parseNumericLimit(limits?.preprodClusterTtlHours)\n return ttlHours !== undefined && Number.isInteger(ttlHours) && ttlHours >= 1\n ? ttlHours\n : undefined\n}\n\n/**\n * Returns whether a cluster is older than the TTL of its org. A cluster with\n * no valid `created` is never past the TTL.\n */\nexport function isPastPreprodClusterTtl({\n created,\n ttlHours,\n now = new Date(),\n}: {\n created: Date | string | undefined\n ttlHours: number | undefined\n now?: Date\n}): boolean {\n if (ttlHours === undefined || created === undefined) return false\n const createdMs = new Date(created).getTime()\n if (Number.isNaN(createdMs)) return false\n return now.getTime() - createdMs > ttlHours * HOUR\n}\n"],
5
+ "mappings": "AAQA,MAAM,OAAO,KAAK,KAAK;AAShB,SAAS,kBAAkB,OAAoC;AACpE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,CAAC,kBAAkB,KAAK,MAAM,KAAK,CAAC,GAAG;AACtE,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAOO,MAAM,2BAA2B,CAAC,UACvC,UAAU,SAAS,UAAU;AAOxB,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGuB;AACrB,MAAI,CAAC,yBAAyB,KAAK,EAAG,QAAO;AAC7C,QAAM,WAAW,kBAAkB,QAAQ,sBAAsB;AACjE,SAAO,aAAa,UAAa,OAAO,UAAU,QAAQ,KAAK,YAAY,IACvE,WACA;AACN;AAMO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA,MAAM,oBAAI,KAAK;AACjB,GAIY;AACV,MAAI,aAAa,UAAa,YAAY,OAAW,QAAO;AAC5D,QAAM,YAAY,IAAI,KAAK,OAAO,EAAE,QAAQ;AAC5C,MAAI,OAAO,MAAM,SAAS,EAAG,QAAO;AACpC,SAAO,IAAI,QAAQ,IAAI,YAAY,WAAW;AAChD;",
6
+ "names": []
7
+ }
@@ -246,6 +246,12 @@ export interface LlmSpend {
246
246
  maxBudget: number;
247
247
  }
248
248
  export type HdpEntity = 'cluster' | 'clusterBackup' | 'clusterBackupSchedule' | 'clusterRestore' | 'datacenter' | 'generation' | 'channel' | 'clusterPlan' | 'clusterPlanType' | 'alertSubscription' | 'consoleApiClient' | 'connectorSecret' | 'byomClient' | 'secureConnectivity' | 'clusterClient' | 'organization' | 'user' | 'invite' | 'usageAlert' | 'audit' | 'systemAudit' | 'telemetry' | 'userlogin' | 'clusterBulkOperation' | 'clusterModification' | 'clusterModificationConfig' | 'llmSpend';
249
+ /**
250
+ * The cluster disks that the disk-usage route of accounts-backend accepts.
251
+ * HDP does not ingest the Zeebe gauge yet, so a Zeebe read returns no rows.
252
+ */
253
+ export declare const HDP_DISKS: readonly ["elasticsearch", "zeebe"];
254
+ export type HdpDisk = (typeof HDP_DISKS)[number];
249
255
  export interface HdpPrometheusDataCapacityDataPoint {
250
256
  clusterId?: string;
251
257
  ts: number;
@@ -7,8 +7,10 @@ var AlertSubscriptionReportType = /* @__PURE__ */ ((AlertSubscriptionReportType2
7
7
  AlertSubscriptionReportType2["Webhook"] = "Webhook";
8
8
  return AlertSubscriptionReportType2;
9
9
  })(AlertSubscriptionReportType || {});
10
+ const HDP_DISKS = ["elasticsearch", "zeebe"];
10
11
  export {
11
12
  AlertSubscriptionReportType,
12
- AlertSubscriptionType
13
+ AlertSubscriptionType,
14
+ HDP_DISKS
13
15
  };
14
16
  //# sourceMappingURL=hdp.types.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/types/hdp.types.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 { Limits } from '../dtos/SalesPlan.dto.js'\nimport type { UsageAlertMetric } from '../dtos/UsageAlert.dto.js'\nimport type { ClusterClientPermission } from '../enums/ClusterClientPermissions.enum.js'\nimport type { ConsoleAPIClientPermission } from '../enums/ConsoleApiClientPermission.enum.js'\nimport type { OrganizationRole } from '../enums/OrganizationRole.enum.js'\nimport type { DisplayedSalesPlan, SalesPlanType } from './SalesPlanType.type.js'\n\nexport interface Cluster {\n uuid: string\n name: string\n clusterPlanType: ClusterPlanType\n clusterPlan: ClusterPlan\n generation: Generation\n channel: Channel\n datacenter?: Datacenter // is not fetched eagerly, if we do not have it we just gonna omit it\n amountOfIpWhitelistRules: number\n ownerUuid: string\n created: Date\n autoUpdate: boolean\n mcpEnabled?: boolean\n businessIdUniquenessEnabled?: boolean\n optimizeEnabled?: boolean\n tags?: string[]\n sleeping?: boolean\n activeModifications: boolean\n encryptionKeysSet?: {\n primaryConfigured: boolean\n secondaryConfigured: boolean\n }\n}\nexport interface ClusterBackup {\n clusterId: string\n backupId?: string\n}\n\nexport interface ClusterModification {\n uuid: string\n clusterUuid: string\n owner: string\n createdAt?: Date\n archivedAt?: Date\n}\n\nexport interface ClusterModificationConfig {\n uuid: string\n clusterUuid: string\n modificationUuid: string\n creator: string\n description: string\n url?: string\n createdAt?: Date\n sealedAt?: Date\n activatedAt?: Date\n replacedBy?: string\n deactivatedAt?: Date\n deactivatedBy?: string | null\n expiresAt?: Date | null\n expiredNotifiedAt?: Date | null\n yoloJustification?: string\n}\n\n/**\n * HDP-specific interface for ClusterModificationConfig.\n * Used for Holy Data Platform integration - keeps Date objects for timestamp fields.\n */\nexport interface HdpClusterModificationConfig {\n uuid: string\n clusterUuid: string\n modificationUuid: string\n creator: string\n description: string\n url?: string\n createdAt?: Date\n sealedAt?: Date\n activatedAt?: Date\n replacedBy?: string\n deactivatedAt?: Date\n deactivatedBy?: string | null\n expiresAt?: Date | null\n expiredNotifiedAt?: Date | null\n}\n\nexport interface ClusterBackupSchedule {\n clusterId: string\n schedule?: string\n}\n\nexport interface ClusterPlanType {\n uuid: string\n name: string\n availabilityClass: string\n isDevelopment: boolean\n}\n\nexport interface ClusterPlan {\n uuid: string\n name: string\n}\n\nexport interface Datacenter {\n uuid: string\n name: string\n}\nexport interface Generation {\n uuid: string\n name: string\n}\nexport interface Channel {\n uuid: string\n name: string\n}\n\n// eslint-disable-next-line no-shadow\nexport enum AlertSubscriptionType {\n ProcessAlert = 'ProcessAlert',\n}\n\n// eslint-disable-next-line no-shadow\nexport enum AlertSubscriptionReportType {\n Email = 'Email',\n Webhook = 'Webhook',\n}\nexport interface AlertSubscription {\n uuid: string\n clusterUuid?: string\n target: string // userIdOrWebhook\n reportType: AlertSubscriptionReportType\n type: AlertSubscriptionType\n}\n\nexport interface ConsoleApiClient {\n uuid: string\n name: string\n organizationUuid: string\n permissions: ConsoleAPIClientPermission[]\n}\n\nexport interface ConnectorSecret {\n clusterUuid: string\n amountOfSecrets: number\n}\n\nexport interface ByomClient {\n uuid: string\n clusterUuid?: string\n username: string\n}\n\nexport interface SecureConnectivity {\n clusterUuid: string\n allowedRegions: string[]\n allowedPrincipals: string[]\n}\n\nexport interface ClusterClient {\n uuid: string\n name: string\n clusterUuid?: string\n clientId: string\n permissions: ClusterClientPermission[]\n}\n\nexport interface Organization {\n uuid: string\n name: string\n salesplan?: {\n type: SalesPlanType // might be undefined when in limbo\n displayedType?: DisplayedSalesPlan // might be undefined when in limbo\n uuid: string // might be undefined when in limbo\n name: string // might be undefined when in limbo\n expireDate?: Date\n }\n reservations?: {\n [key: string]: number\n }\n earlyAccessFeatures?: string[]\n justOptedForTheBetaTerms?: boolean\n salesForceProjectId?: string\n awsOnly?: boolean\n limits?: Limits\n}\n\nexport interface User {\n name: string\n email: string\n userId: string\n orgMemberships?: Array<{ orgUuid: string; roles: OrganizationRole[] }> // can be undefined which means: no new info ;)\n}\n\nexport interface UserLogin {\n userId: string\n}\n\nexport interface Invite {\n email: string\n orgUuid: string\n roles: OrganizationRole[]\n}\n\nexport interface UsageAlert {\n orgId: string\n metric: UsageAlertMetric\n threshold: number\n enabled: boolean\n triggered: boolean\n}\n\nexport interface Audit {\n service: string\n orgId: string\n timestamp: number\n audit: string\n auditType: 'c' | 'r' | 'u' | 'd'\n entity: string\n entityId: string\n userId: string\n entityAttribute?: string\n entityAttributeValueFrom?: string\n entityAttributeValueTo?: string\n parentEntity?: string\n parentEntityId?: string\n}\n\nexport interface SystemAudit {\n service: string\n timestamp: number\n audit: string\n auditType: 'c' | 'r' | 'u' | 'd'\n entity: string\n entityId: string\n userId: string\n entityAttribute?: string | null\n entityAttributeValueFrom?: string | null\n entityAttributeValueTo?: string | null\n orgId?: string | null\n clusterId?: string | null\n}\n\nexport interface TelemetryData {\n customerId: string\n installationId: string\n entities: Array<{ id: string; type: string }>\n key: string\n value: string | number | boolean\n tags?: Record<string, string>\n timestampFrom: number\n timestampTo?: number\n}\n\nexport interface ClusterBulkOperation {\n uuid: string\n name: string\n reason: string\n eyes: number\n clusterUuids: string[]\n type: string\n created: Date\n updated?: Date\n executedAt?: Date\n createdBy: string\n}\n\nexport interface LlmSpend {\n orgId: string\n salesPlanType: SalesPlanType\n spend: number\n maxBudget: number\n}\n\nexport type HdpEntity =\n | 'cluster'\n | 'clusterBackup'\n | 'clusterBackupSchedule'\n | 'clusterRestore'\n | 'datacenter'\n | 'generation'\n | 'channel'\n | 'clusterPlan'\n | 'clusterPlanType'\n | 'alertSubscription'\n | 'consoleApiClient'\n | 'connectorSecret'\n | 'byomClient'\n | 'secureConnectivity'\n | 'clusterClient'\n | 'organization'\n | 'user'\n | 'invite'\n | 'usageAlert'\n | 'audit'\n | 'systemAudit'\n | 'telemetry'\n | 'userlogin'\n | 'clusterBulkOperation'\n | 'clusterModification'\n | 'clusterModificationConfig'\n | 'llmSpend'\n\nexport interface HdpPrometheusDataCapacityDataPoint {\n clusterId?: string\n ts: number\n avg: number\n min: number\n max: number\n}\n\nexport interface HdpBackendChangeEvent {\n ts: number\n data: {\n stage: string\n entity: string\n operation: string\n data: any\n }\n}\n"],
5
- "mappings": "AAuHO,IAAK,wBAAL,kBAAKA,2BAAL;AACL,EAAAA,uBAAA,kBAAe;AADL,SAAAA;AAAA,GAAA;AAKL,IAAK,8BAAL,kBAAKC,iCAAL;AACL,EAAAA,6BAAA,WAAQ;AACR,EAAAA,6BAAA,aAAU;AAFA,SAAAA;AAAA,GAAA;",
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 { Limits } from '../dtos/SalesPlan.dto.js'\nimport type { UsageAlertMetric } from '../dtos/UsageAlert.dto.js'\nimport type { ClusterClientPermission } from '../enums/ClusterClientPermissions.enum.js'\nimport type { ConsoleAPIClientPermission } from '../enums/ConsoleApiClientPermission.enum.js'\nimport type { OrganizationRole } from '../enums/OrganizationRole.enum.js'\nimport type { DisplayedSalesPlan, SalesPlanType } from './SalesPlanType.type.js'\n\nexport interface Cluster {\n uuid: string\n name: string\n clusterPlanType: ClusterPlanType\n clusterPlan: ClusterPlan\n generation: Generation\n channel: Channel\n datacenter?: Datacenter // is not fetched eagerly, if we do not have it we just gonna omit it\n amountOfIpWhitelistRules: number\n ownerUuid: string\n created: Date\n autoUpdate: boolean\n mcpEnabled?: boolean\n businessIdUniquenessEnabled?: boolean\n optimizeEnabled?: boolean\n tags?: string[]\n sleeping?: boolean\n activeModifications: boolean\n encryptionKeysSet?: {\n primaryConfigured: boolean\n secondaryConfigured: boolean\n }\n}\nexport interface ClusterBackup {\n clusterId: string\n backupId?: string\n}\n\nexport interface ClusterModification {\n uuid: string\n clusterUuid: string\n owner: string\n createdAt?: Date\n archivedAt?: Date\n}\n\nexport interface ClusterModificationConfig {\n uuid: string\n clusterUuid: string\n modificationUuid: string\n creator: string\n description: string\n url?: string\n createdAt?: Date\n sealedAt?: Date\n activatedAt?: Date\n replacedBy?: string\n deactivatedAt?: Date\n deactivatedBy?: string | null\n expiresAt?: Date | null\n expiredNotifiedAt?: Date | null\n yoloJustification?: string\n}\n\n/**\n * HDP-specific interface for ClusterModificationConfig.\n * Used for Holy Data Platform integration - keeps Date objects for timestamp fields.\n */\nexport interface HdpClusterModificationConfig {\n uuid: string\n clusterUuid: string\n modificationUuid: string\n creator: string\n description: string\n url?: string\n createdAt?: Date\n sealedAt?: Date\n activatedAt?: Date\n replacedBy?: string\n deactivatedAt?: Date\n deactivatedBy?: string | null\n expiresAt?: Date | null\n expiredNotifiedAt?: Date | null\n}\n\nexport interface ClusterBackupSchedule {\n clusterId: string\n schedule?: string\n}\n\nexport interface ClusterPlanType {\n uuid: string\n name: string\n availabilityClass: string\n isDevelopment: boolean\n}\n\nexport interface ClusterPlan {\n uuid: string\n name: string\n}\n\nexport interface Datacenter {\n uuid: string\n name: string\n}\nexport interface Generation {\n uuid: string\n name: string\n}\nexport interface Channel {\n uuid: string\n name: string\n}\n\n// eslint-disable-next-line no-shadow\nexport enum AlertSubscriptionType {\n ProcessAlert = 'ProcessAlert',\n}\n\n// eslint-disable-next-line no-shadow\nexport enum AlertSubscriptionReportType {\n Email = 'Email',\n Webhook = 'Webhook',\n}\nexport interface AlertSubscription {\n uuid: string\n clusterUuid?: string\n target: string // userIdOrWebhook\n reportType: AlertSubscriptionReportType\n type: AlertSubscriptionType\n}\n\nexport interface ConsoleApiClient {\n uuid: string\n name: string\n organizationUuid: string\n permissions: ConsoleAPIClientPermission[]\n}\n\nexport interface ConnectorSecret {\n clusterUuid: string\n amountOfSecrets: number\n}\n\nexport interface ByomClient {\n uuid: string\n clusterUuid?: string\n username: string\n}\n\nexport interface SecureConnectivity {\n clusterUuid: string\n allowedRegions: string[]\n allowedPrincipals: string[]\n}\n\nexport interface ClusterClient {\n uuid: string\n name: string\n clusterUuid?: string\n clientId: string\n permissions: ClusterClientPermission[]\n}\n\nexport interface Organization {\n uuid: string\n name: string\n salesplan?: {\n type: SalesPlanType // might be undefined when in limbo\n displayedType?: DisplayedSalesPlan // might be undefined when in limbo\n uuid: string // might be undefined when in limbo\n name: string // might be undefined when in limbo\n expireDate?: Date\n }\n reservations?: {\n [key: string]: number\n }\n earlyAccessFeatures?: string[]\n justOptedForTheBetaTerms?: boolean\n salesForceProjectId?: string\n awsOnly?: boolean\n limits?: Limits\n}\n\nexport interface User {\n name: string\n email: string\n userId: string\n orgMemberships?: Array<{ orgUuid: string; roles: OrganizationRole[] }> // can be undefined which means: no new info ;)\n}\n\nexport interface UserLogin {\n userId: string\n}\n\nexport interface Invite {\n email: string\n orgUuid: string\n roles: OrganizationRole[]\n}\n\nexport interface UsageAlert {\n orgId: string\n metric: UsageAlertMetric\n threshold: number\n enabled: boolean\n triggered: boolean\n}\n\nexport interface Audit {\n service: string\n orgId: string\n timestamp: number\n audit: string\n auditType: 'c' | 'r' | 'u' | 'd'\n entity: string\n entityId: string\n userId: string\n entityAttribute?: string\n entityAttributeValueFrom?: string\n entityAttributeValueTo?: string\n parentEntity?: string\n parentEntityId?: string\n}\n\nexport interface SystemAudit {\n service: string\n timestamp: number\n audit: string\n auditType: 'c' | 'r' | 'u' | 'd'\n entity: string\n entityId: string\n userId: string\n entityAttribute?: string | null\n entityAttributeValueFrom?: string | null\n entityAttributeValueTo?: string | null\n orgId?: string | null\n clusterId?: string | null\n}\n\nexport interface TelemetryData {\n customerId: string\n installationId: string\n entities: Array<{ id: string; type: string }>\n key: string\n value: string | number | boolean\n tags?: Record<string, string>\n timestampFrom: number\n timestampTo?: number\n}\n\nexport interface ClusterBulkOperation {\n uuid: string\n name: string\n reason: string\n eyes: number\n clusterUuids: string[]\n type: string\n created: Date\n updated?: Date\n executedAt?: Date\n createdBy: string\n}\n\nexport interface LlmSpend {\n orgId: string\n salesPlanType: SalesPlanType\n spend: number\n maxBudget: number\n}\n\nexport type HdpEntity =\n | 'cluster'\n | 'clusterBackup'\n | 'clusterBackupSchedule'\n | 'clusterRestore'\n | 'datacenter'\n | 'generation'\n | 'channel'\n | 'clusterPlan'\n | 'clusterPlanType'\n | 'alertSubscription'\n | 'consoleApiClient'\n | 'connectorSecret'\n | 'byomClient'\n | 'secureConnectivity'\n | 'clusterClient'\n | 'organization'\n | 'user'\n | 'invite'\n | 'usageAlert'\n | 'audit'\n | 'systemAudit'\n | 'telemetry'\n | 'userlogin'\n | 'clusterBulkOperation'\n | 'clusterModification'\n | 'clusterModificationConfig'\n | 'llmSpend'\n\n/**\n * The cluster disks that the disk-usage route of accounts-backend accepts.\n * HDP does not ingest the Zeebe gauge yet, so a Zeebe read returns no rows.\n */\nexport const HDP_DISKS = ['elasticsearch', 'zeebe'] as const\nexport type HdpDisk = (typeof HDP_DISKS)[number]\n\nexport interface HdpPrometheusDataCapacityDataPoint {\n clusterId?: string\n ts: number\n avg: number\n min: number\n max: number\n}\n\nexport interface HdpBackendChangeEvent {\n ts: number\n data: {\n stage: string\n entity: string\n operation: string\n data: any\n }\n}\n"],
5
+ "mappings": "AAuHO,IAAK,wBAAL,kBAAKA,2BAAL;AACL,EAAAA,uBAAA,kBAAe;AADL,SAAAA;AAAA,GAAA;AAKL,IAAK,8BAAL,kBAAKC,iCAAL;AACL,EAAAA,6BAAA,WAAQ;AACR,EAAAA,6BAAA,aAAU;AAFA,SAAAA;AAAA,GAAA;AAwLL,MAAM,YAAY,CAAC,iBAAiB,OAAO;",
6
6
  "names": ["AlertSubscriptionType", "AlertSubscriptionReportType"]
7
7
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * canonicalPlanKey maps a dotted plan key to the path that Console reads. A
3
+ * legacy `operate.elasticsearch.*` key becomes `elasticsearch.*`. Other keys
4
+ * come back unchanged.
5
+ */
6
+ export declare const canonicalPlanKey: (key: string) => string;
7
+ /**
8
+ * canonicalPlan returns a cluster plan config with `operate.elasticsearch`
9
+ * merged under the top-level `elasticsearch`. If a field is set in both places,
10
+ * the top-level value wins. The input does not change. A plan without the
11
+ * legacy block comes back as the same object, and a value that is not a record
12
+ * comes back unchanged.
13
+ *
14
+ * The result is typed as a plain record, not as the input type: the input type
15
+ * still declares `operate.elasticsearch`, which the result does not have.
16
+ */
17
+ export declare function canonicalPlan(plan: Record<string, unknown>): Record<string, unknown>;
18
+ export declare function canonicalPlan(plan: unknown): unknown;
@@ -0,0 +1,23 @@
1
+ import { ObjectMergeUtils } from "./objectMerge.utils.js";
2
+ const LEGACY_ELASTICSEARCH_PREFIX = "operate.elasticsearch.";
3
+ const canonicalPlanKey = (key) => key.startsWith(LEGACY_ELASTICSEARCH_PREFIX) ? key.slice("operate.".length) : key;
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ function canonicalPlan(plan) {
6
+ if (!isRecord(plan) || !isRecord(plan.operate) || plan.operate.elasticsearch === void 0) {
7
+ return plan;
8
+ }
9
+ const { elasticsearch: legacy, ...operate } = plan.operate;
10
+ return {
11
+ ...plan,
12
+ operate,
13
+ elasticsearch: ObjectMergeUtils.merge({
14
+ objectA: legacy,
15
+ objectB: plan.elasticsearch
16
+ })
17
+ };
18
+ }
19
+ export {
20
+ canonicalPlan,
21
+ canonicalPlanKey
22
+ };
23
+ //# sourceMappingURL=elasticsearchPlanPath.utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/elasticsearchPlanPath.utils.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 { ObjectMergeUtils } from './objectMerge.utils.js'\n\n// Older cluster plans and modification configs keep the Elasticsearch config\n// under `operate.elasticsearch`. The ZeebeCluster resource reads it from the\n// top-level `spec.elasticsearch` only (camunda-operator#5124), so the top-level\n// `elasticsearch` is the canonical plan path.\nconst LEGACY_ELASTICSEARCH_PREFIX = 'operate.elasticsearch.'\n\n/**\n * canonicalPlanKey maps a dotted plan key to the path that Console reads. A\n * legacy `operate.elasticsearch.*` key becomes `elasticsearch.*`. Other keys\n * come back unchanged.\n */\nexport const canonicalPlanKey = (key: string): string =>\n key.startsWith(LEGACY_ELASTICSEARCH_PREFIX)\n ? key.slice('operate.'.length)\n : key\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * canonicalPlan returns a cluster plan config with `operate.elasticsearch`\n * merged under the top-level `elasticsearch`. If a field is set in both places,\n * the top-level value wins. The input does not change. A plan without the\n * legacy block comes back as the same object, and a value that is not a record\n * comes back unchanged.\n *\n * The result is typed as a plain record, not as the input type: the input type\n * still declares `operate.elasticsearch`, which the result does not have.\n */\nexport function canonicalPlan(\n plan: Record<string, unknown>,\n): Record<string, unknown>\nexport function canonicalPlan(plan: unknown): unknown\nexport function canonicalPlan(plan: unknown): unknown {\n if (\n !isRecord(plan) ||\n !isRecord(plan.operate) ||\n plan.operate.elasticsearch === undefined\n ) {\n return plan\n }\n const { elasticsearch: legacy, ...operate } = plan.operate\n return {\n ...plan,\n operate,\n elasticsearch: ObjectMergeUtils.merge({\n objectA: legacy,\n objectB: plan.elasticsearch,\n }),\n }\n}\n"],
5
+ "mappings": "AAMA,SAAS,wBAAwB;AAMjC,MAAM,8BAA8B;AAO7B,MAAM,mBAAmB,CAAC,QAC/B,IAAI,WAAW,2BAA2B,IACtC,IAAI,MAAM,WAAW,MAAM,IAC3B;AAEN,MAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAgB9D,SAAS,cAAc,MAAwB;AACpD,MACE,CAAC,SAAS,IAAI,KACd,CAAC,SAAS,KAAK,OAAO,KACtB,KAAK,QAAQ,kBAAkB,QAC/B;AACA,WAAO;AAAA,EACT;AACA,QAAM,EAAE,eAAe,QAAQ,GAAG,QAAQ,IAAI,KAAK;AACnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,eAAe,iBAAiB,MAAM;AAAA,MACpC,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,IAChB,CAAC;AAAA,EACH;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The email of a user that the QA route of accounts-backend creates
3
+ * (`POST /external/qa/organizations`). The route builds the email with this
4
+ * function, and every reader that must find a QA user calls
5
+ * `isQaUserEmail`. Thus the two cannot drift.
6
+ */
7
+ export declare function qaUserEmail(userId: string): string;
8
+ /**
9
+ * True when the email belongs to a user from an automated QA run. A token has
10
+ * no QA claim, but it carries the email, thus the email is the marker. The id
11
+ * in the email must be a UUID, so a person with the address
12
+ * `qa-user-admin@ultrawombat.com` is no QA user.
13
+ */
14
+ export declare function isQaUserEmail(email: string | undefined): boolean;
@@ -0,0 +1,14 @@
1
+ const QA_USER_EMAIL_PREFIX = "qa-user-";
2
+ const QA_USER_EMAIL_DOMAIN = "@ultrawombat.com";
3
+ const QA_USER_EMAIL_PATTERN = /^qa-user-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}@ultrawombat\.com$/;
4
+ function qaUserEmail(userId) {
5
+ return `${QA_USER_EMAIL_PREFIX}${userId}${QA_USER_EMAIL_DOMAIN}`;
6
+ }
7
+ function isQaUserEmail(email) {
8
+ return email !== void 0 && QA_USER_EMAIL_PATTERN.test(email.toLowerCase());
9
+ }
10
+ export {
11
+ isQaUserEmail,
12
+ qaUserEmail
13
+ };
14
+ //# sourceMappingURL=qa-user.utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/qa-user.utils.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\nconst QA_USER_EMAIL_PREFIX = 'qa-user-'\nconst QA_USER_EMAIL_DOMAIN = '@ultrawombat.com'\n\n// The QA route builds the id with `uuidv4()`: five groups of hex digits.\nconst QA_USER_EMAIL_PATTERN =\n /^qa-user-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}@ultrawombat\\.com$/\n\n/**\n * The email of a user that the QA route of accounts-backend creates\n * (`POST /external/qa/organizations`). The route builds the email with this\n * function, and every reader that must find a QA user calls\n * `isQaUserEmail`. Thus the two cannot drift.\n */\nexport function qaUserEmail(userId: string): string {\n return `${QA_USER_EMAIL_PREFIX}${userId}${QA_USER_EMAIL_DOMAIN}`\n}\n\n/**\n * True when the email belongs to a user from an automated QA run. A token has\n * no QA claim, but it carries the email, thus the email is the marker. The id\n * in the email must be a UUID, so a person with the address\n * `qa-user-admin@ultrawombat.com` is no QA user.\n */\nexport function isQaUserEmail(email: string | undefined): boolean {\n return email !== undefined && QA_USER_EMAIL_PATTERN.test(email.toLowerCase())\n}\n"],
5
+ "mappings": "AAMA,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAG7B,MAAM,wBACJ;AAQK,SAAS,YAAY,QAAwB;AAClD,SAAO,GAAG,oBAAoB,GAAG,MAAM,GAAG,oBAAoB;AAChE;AAQO,SAAS,cAAc,OAAoC;AAChE,SAAO,UAAU,UAAa,sBAAsB,KAAK,MAAM,YAAY,CAAC;AAC9E;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/ccma-shared-types",
3
- "version": "4.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Shared TypeScript types for Camunda Cloud Management Apps",
5
5
  "bugs": {
6
6
  "url": "https://github.com/camunda/camunda-cloud-management-apps/issues"
@@ -1,226 +0,0 @@
1
- import type { Stage } from '../constants/misc.const.js';
2
- /**
3
- * The wire shapes of the feature flag surface of control-backend.
4
- *
5
- * They are here and not in `@camunda-cloud/featureflags`, because
6
- * `@camunda/ccma-api` is published and cannot name a type from that private
7
- * package. The RULES stay there: `resolveFlag` decides a value, and
8
- * `FLAG_REGISTRY` holds the metadata. This file carries the answer over HTTP.
9
- *
10
- * `flagPath` is a `string` in every shape below. The typed `FlagPath` needs
11
- * `Features`, and a caller of the API names a flag that the SERVER validates
12
- * against its own registry. A type that the browser holds cannot do that job,
13
- * because the server can be older or newer than the bundle.
14
- */
15
- /** Which readers an override reaches. It is `FlagOverrideScope`. */
16
- export declare const FEATURE_FLAG_OVERRIDE_SCOPES: readonly ["stage", "org"];
17
- export type FeatureFlagOverrideScope = (typeof FEATURE_FLAG_OVERRIDE_SCOPES)[number];
18
- /** Why a flag has the value that it has. It is `FlagReason`. */
19
- export declare const FEATURE_FLAG_REASONS: readonly ["DEFAULT", "OVERRIDE_STAGE", "OVERRIDE_ORG", "OVERRIDE_REFUSED"];
20
- export type FeatureFlagReason = (typeof FEATURE_FLAG_REASONS)[number];
21
- /** How long a flag is meant to live. It is `FlagType`. */
22
- export declare const FEATURE_FLAG_TYPES: readonly ["release", "ops", "perm"];
23
- export type FeatureFlagType = (typeof FEATURE_FLAG_TYPES)[number];
24
- /**
25
- * One condition of the code rule of a flag.
26
- *
27
- * The code rule is a LIST of these, and a reader gets the flag when ANY one
28
- * matches. An empty list is off for every reader. `FLAG_RULES` of
29
- * `@camunda-cloud/featureflags` holds the rules, and `defaults` evaluates the
30
- * same objects, thus the description cannot drift from the value.
31
- */
32
- export type FeatureFlagCondition =
33
- /** On for every reader. */
34
- {
35
- kind: 'always';
36
- }
37
- /** On when the stage of the reader is one of `stages`. */
38
- | {
39
- kind: 'stage';
40
- stages: Stage[];
41
- }
42
- /** On when the email address of the reader is a Camundi address. */
43
- | {
44
- kind: 'camundiEmail';
45
- }
46
- /** On when the email address of the reader has one of `domains`. */
47
- | {
48
- kind: 'emailDomain';
49
- domains: string[];
50
- }
51
- /** On when the organization of the reader is one of `orgIds`. */
52
- | {
53
- kind: 'org';
54
- orgIds: string[];
55
- };
56
- /** One stored override, as a reader of the API sees it. */
57
- export interface FeatureFlagOverrideDto {
58
- id: string;
59
- flagPath: string;
60
- scope: FeatureFlagOverrideScope;
61
- /** The stage for `stage`, and the organization id for `org`. */
62
- target: string;
63
- enabled: boolean;
64
- /** Why the person asked for it. The approval request demands one. */
65
- reason: string;
66
- /** The approval request that wrote this row. Every row names one. */
67
- approvalId: string;
68
- createdBy: {
69
- userId: string;
70
- email: string;
71
- };
72
- createdAt: string;
73
- /** When the override stops by itself. */
74
- expiresAt: string;
75
- }
76
- /**
77
- * One flag, with everything a person needs to decide about it.
78
- *
79
- * The row carries the code value AND the effective value on purpose. A page
80
- * that showed only the effective value would hide the drift, which is the one
81
- * thing an override makes easy to lose.
82
- */
83
- export interface FeatureFlagRowDto {
84
- flagPath: string;
85
- label: string;
86
- type: FeatureFlagType;
87
- owner: string;
88
- reason: string;
89
- /** Whether an override can move this flag at all. */
90
- overridable: boolean;
91
- /** The date after which a `release` flag is late. */
92
- expiresAt?: string;
93
- /**
94
- * What the code answers for the stage of this service, for NOBODY.
95
- *
96
- * The evaluation carries the stage alone: no email address and no
97
- * organization id. A flag whose condition reads one of those answers
98
- * something else for a real reader — `console.clusterSuspendButton` is
99
- * `false` here on prod and `true` for a Camundi, and
100
- * `console.privateLink87` is `false` here and `true` for an allowlisted
101
- * organization.
102
- *
103
- * It is a BASELINE, and that is the right answer for this surface: one
104
- * value that every reader of the page sees, which is what makes two
105
- * Camundis able to talk about it. A per-viewer value would answer a
106
- * different thing to each of them, and a per-organization value needs an
107
- * organization that the route does not take.
108
- */
109
- codeValue: boolean;
110
- /**
111
- * The code rule of the flag: it is on when ANY condition matches.
112
- *
113
- * This is what `codeValue` cannot say. It names the stages, the email
114
- * rules and the organization allowlist that decide the value for a real
115
- * reader.
116
- */
117
- rule: FeatureFlagCondition[];
118
- /**
119
- * The code value on each known stage, for NOBODY.
120
- *
121
- * The same baseline as `codeValue`, for every stage and not only this one.
122
- * It shows how far the flag stands on its rollout ladder.
123
- */
124
- codeValueByStage: Record<Stage, boolean>;
125
- /**
126
- * The baseline of `codeValue` with a live override in front of it.
127
- *
128
- * Same evaluation context, thus the same limit: it is what a reader with
129
- * no email address and no organization gets. Use it to see the DRIFT
130
- * between the store and the source, which is what this surface exists
131
- * for, and not as the answer for one person.
132
- */
133
- effectiveValue: boolean;
134
- /** Why `effectiveValue` is what it is. */
135
- effectiveReason: FeatureFlagReason;
136
- /** The live override of the stage, if there is one. */
137
- stageOverride?: FeatureFlagOverrideDto;
138
- /**
139
- * How many live overrides name an organization.
140
- *
141
- * A COUNT, and not the rows. The rows carry an organization id, an email
142
- * address and a reason for every organization that holds an override, and
143
- * this answer has no page, thus it would grow with the number of customers
144
- * and hand all of it to every reader. The page shows the number.
145
- */
146
- orgOverrideCount: number;
147
- }
148
- /** The answer of `GET /api/feature-flags`. */
149
- export interface FeatureFlagsDto {
150
- /** The stage that this service runs on. It decides `codeValue`. */
151
- stage: string;
152
- /** The moment when the service built this answer. */
153
- observedAt: string;
154
- flags: FeatureFlagRowDto[];
155
- }
156
- /**
157
- * The permissions that one override payload needs, on top of nothing.
158
- *
159
- * It is here, and shared, because BOTH sides run it: `permissionsFor` of the
160
- * use case stores the answer on the row, and the dialog of control-frontend
161
- * passes the same answer to `canCreate`. Two copies of this rule would let a
162
- * button offer an override that the service then refuses with a 403.
163
- *
164
- * A prod stage override reaches every customer, thus it asks for the narrower
165
- * permission as well — to approve AND to ask.
166
- */
167
- export declare function featureFlagOverridePermissions(payload: {
168
- scope: FeatureFlagOverrideScope;
169
- target: string;
170
- }): string[];
171
- /**
172
- * The one written form of a target.
173
- *
174
- * A GUID reads the same in either case, and `ABC…` and `abc…` are the same
175
- * organization. The store and the resolver compare TEXT, so two forms of one
176
- * id would be two overrides: the unique index would allow both to be live,
177
- * and a row written in capitals would never match the id that a reader
178
- * carries. Everything that writes a target passes it through here first, so
179
- * only one form ever reaches the table.
180
- *
181
- * A stage name is lowercase already, thus this does not change one.
182
- */
183
- export declare function normalizeFeatureFlagOverrideTarget(target: string): string;
184
- /**
185
- * What is wrong with the target of an override, if anything.
186
- *
187
- * It answers `undefined` when the target is good, and a sentence for a person
188
- * when it is not.
189
- *
190
- * It is here, and shared, for the same reason as
191
- * `featureFlagOverridePermissions` above: BOTH sides run it. The dialog of
192
- * control-frontend keeps the button disabled while it answers, and the create
193
- * route of control-backend refuses the call. The dialog accepted any text
194
- * that was not empty before this, thus it offered `prod` on dev and
195
- * `not-an-org` as an organization, and both ended in a 400 after the person
196
- * had filled the whole form.
197
- *
198
- * TWO rules, and the first surprises people:
199
- *
200
- * 1. A stage override names THE STAGE OF THE SERVICE, and no other. Each
201
- * deployed stage of control-backend holds its own database, thus an
202
- * override written on dev reaches dev readers alone. A request for
203
- * another stage would collect its approvals and move nothing.
204
- * 2. An org override names a GUID.
205
- */
206
- export declare function featureFlagOverrideTargetProblem(args: {
207
- scope: FeatureFlagOverrideScope;
208
- target: string;
209
- /** The stage that the service runs on. `FeatureFlagsDto.stage` carries it. */
210
- stage: string;
211
- }): string | undefined;
212
- /** The payload of a `feature-flag-override` approval request. */
213
- export interface FeatureFlagOverridePayloadDto {
214
- flagPath: string;
215
- scope: FeatureFlagOverrideScope;
216
- target: string;
217
- enabled: boolean;
218
- /**
219
- * How many days the override holds, from the moment the action RUNS.
220
- *
221
- * A number of days and not a date, for the same reason as `extend-trial`:
222
- * days pass between the request and the run, and a stored date would lose
223
- * part of its life while the request waited for its approvals.
224
- */
225
- days: number;
226
- }
@@ -1,38 +0,0 @@
1
- import { AccountsPermissions } from "../permissions/accounts.permissions.js";
2
- const ACCOUNTS_CONTROL_FLAG_OVERRIDE = AccountsPermissions.CONTROL_FLAG_OVERRIDE;
3
- const ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD = AccountsPermissions.CONTROL_FLAG_OVERRIDE_PROD;
4
- const FEATURE_FLAG_OVERRIDE_SCOPES = ["stage", "org"];
5
- const FEATURE_FLAG_REASONS = [
6
- "DEFAULT",
7
- "OVERRIDE_STAGE",
8
- "OVERRIDE_ORG",
9
- "OVERRIDE_REFUSED"
10
- ];
11
- const FEATURE_FLAG_TYPES = ["release", "ops", "perm"];
12
- function featureFlagOverridePermissions(payload) {
13
- const base = [ACCOUNTS_CONTROL_FLAG_OVERRIDE];
14
- return payload.scope === "stage" && payload.target === "prod" ? [...base, ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD] : base;
15
- }
16
- const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17
- function normalizeFeatureFlagOverrideTarget(target) {
18
- return target.trim().toLowerCase();
19
- }
20
- function featureFlagOverrideTargetProblem(args) {
21
- const target = normalizeFeatureFlagOverrideTarget(args.target);
22
- if (target.length === 0) {
23
- return args.scope === "stage" ? "Name the stage to move." : "Name the organization to move.";
24
- }
25
- if (args.scope === "stage") {
26
- return target === args.stage ? void 0 : `This Camunda Control runs on ${args.stage} and can move only that stage. Open the request on the Camunda Control of ${target}.`;
27
- }
28
- return GUID.test(target) ? void 0 : "An organization id is a GUID, like 11111111-1111-1111-1111-111111111111.";
29
- }
30
- export {
31
- FEATURE_FLAG_OVERRIDE_SCOPES,
32
- FEATURE_FLAG_REASONS,
33
- FEATURE_FLAG_TYPES,
34
- featureFlagOverridePermissions,
35
- featureFlagOverrideTargetProblem,
36
- normalizeFeatureFlagOverrideTarget
37
- };
38
- //# sourceMappingURL=FeatureFlagOverride.dto.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/dtos/FeatureFlagOverride.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\nimport type { Stage } from '../constants/misc.const.js'\nimport { AccountsPermissions } from '../permissions/accounts.permissions.js'\n\nconst ACCOUNTS_CONTROL_FLAG_OVERRIDE = AccountsPermissions.CONTROL_FLAG_OVERRIDE\nconst ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD =\n AccountsPermissions.CONTROL_FLAG_OVERRIDE_PROD\n\n/**\n * The wire shapes of the feature flag surface of control-backend.\n *\n * They are here and not in `@camunda-cloud/featureflags`, because\n * `@camunda/ccma-api` is published and cannot name a type from that private\n * package. The RULES stay there: `resolveFlag` decides a value, and\n * `FLAG_REGISTRY` holds the metadata. This file carries the answer over HTTP.\n *\n * `flagPath` is a `string` in every shape below. The typed `FlagPath` needs\n * `Features`, and a caller of the API names a flag that the SERVER validates\n * against its own registry. A type that the browser holds cannot do that job,\n * because the server can be older or newer than the bundle.\n */\n\n/** Which readers an override reaches. It is `FlagOverrideScope`. */\nexport const FEATURE_FLAG_OVERRIDE_SCOPES = ['stage', 'org'] as const\n\nexport type FeatureFlagOverrideScope =\n (typeof FEATURE_FLAG_OVERRIDE_SCOPES)[number]\n\n/** Why a flag has the value that it has. It is `FlagReason`. */\nexport const FEATURE_FLAG_REASONS = [\n 'DEFAULT',\n 'OVERRIDE_STAGE',\n 'OVERRIDE_ORG',\n 'OVERRIDE_REFUSED',\n] as const\n\nexport type FeatureFlagReason = (typeof FEATURE_FLAG_REASONS)[number]\n\n/** How long a flag is meant to live. It is `FlagType`. */\nexport const FEATURE_FLAG_TYPES = ['release', 'ops', 'perm'] as const\n\nexport type FeatureFlagType = (typeof FEATURE_FLAG_TYPES)[number]\n\n/**\n * One condition of the code rule of a flag.\n *\n * The code rule is a LIST of these, and a reader gets the flag when ANY one\n * matches. An empty list is off for every reader. `FLAG_RULES` of\n * `@camunda-cloud/featureflags` holds the rules, and `defaults` evaluates the\n * same objects, thus the description cannot drift from the value.\n */\nexport type FeatureFlagCondition =\n /** On for every reader. */\n | { kind: 'always' }\n /** On when the stage of the reader is one of `stages`. */\n | { kind: 'stage'; stages: Stage[] }\n /** On when the email address of the reader is a Camundi address. */\n | { kind: 'camundiEmail' }\n /** On when the email address of the reader has one of `domains`. */\n | { kind: 'emailDomain'; domains: string[] }\n /** On when the organization of the reader is one of `orgIds`. */\n | { kind: 'org'; orgIds: string[] }\n\n/** One stored override, as a reader of the API sees it. */\nexport interface FeatureFlagOverrideDto {\n id: string\n flagPath: string\n scope: FeatureFlagOverrideScope\n /** The stage for `stage`, and the organization id for `org`. */\n target: string\n enabled: boolean\n /** Why the person asked for it. The approval request demands one. */\n reason: string\n /** The approval request that wrote this row. Every row names one. */\n approvalId: string\n createdBy: { userId: string; email: string }\n createdAt: string\n /** When the override stops by itself. */\n expiresAt: string\n}\n\n/**\n * One flag, with everything a person needs to decide about it.\n *\n * The row carries the code value AND the effective value on purpose. A page\n * that showed only the effective value would hide the drift, which is the one\n * thing an override makes easy to lose.\n */\nexport interface FeatureFlagRowDto {\n flagPath: string\n label: string\n type: FeatureFlagType\n owner: string\n reason: string\n /** Whether an override can move this flag at all. */\n overridable: boolean\n /** The date after which a `release` flag is late. */\n expiresAt?: string\n /**\n * What the code answers for the stage of this service, for NOBODY.\n *\n * The evaluation carries the stage alone: no email address and no\n * organization id. A flag whose condition reads one of those answers\n * something else for a real reader \u2014 `console.clusterSuspendButton` is\n * `false` here on prod and `true` for a Camundi, and\n * `console.privateLink87` is `false` here and `true` for an allowlisted\n * organization.\n *\n * It is a BASELINE, and that is the right answer for this surface: one\n * value that every reader of the page sees, which is what makes two\n * Camundis able to talk about it. A per-viewer value would answer a\n * different thing to each of them, and a per-organization value needs an\n * organization that the route does not take.\n */\n codeValue: boolean\n /**\n * The code rule of the flag: it is on when ANY condition matches.\n *\n * This is what `codeValue` cannot say. It names the stages, the email\n * rules and the organization allowlist that decide the value for a real\n * reader.\n */\n rule: FeatureFlagCondition[]\n /**\n * The code value on each known stage, for NOBODY.\n *\n * The same baseline as `codeValue`, for every stage and not only this one.\n * It shows how far the flag stands on its rollout ladder.\n */\n codeValueByStage: Record<Stage, boolean>\n /**\n * The baseline of `codeValue` with a live override in front of it.\n *\n * Same evaluation context, thus the same limit: it is what a reader with\n * no email address and no organization gets. Use it to see the DRIFT\n * between the store and the source, which is what this surface exists\n * for, and not as the answer for one person.\n */\n effectiveValue: boolean\n /** Why `effectiveValue` is what it is. */\n effectiveReason: FeatureFlagReason\n /** The live override of the stage, if there is one. */\n stageOverride?: FeatureFlagOverrideDto\n /**\n * How many live overrides name an organization.\n *\n * A COUNT, and not the rows. The rows carry an organization id, an email\n * address and a reason for every organization that holds an override, and\n * this answer has no page, thus it would grow with the number of customers\n * and hand all of it to every reader. The page shows the number.\n */\n orgOverrideCount: number\n}\n\n/** The answer of `GET /api/feature-flags`. */\nexport interface FeatureFlagsDto {\n /** The stage that this service runs on. It decides `codeValue`. */\n stage: string\n /** The moment when the service built this answer. */\n observedAt: string\n flags: FeatureFlagRowDto[]\n}\n\n/**\n * The permissions that one override payload needs, on top of nothing.\n *\n * It is here, and shared, because BOTH sides run it: `permissionsFor` of the\n * use case stores the answer on the row, and the dialog of control-frontend\n * passes the same answer to `canCreate`. Two copies of this rule would let a\n * button offer an override that the service then refuses with a 403.\n *\n * A prod stage override reaches every customer, thus it asks for the narrower\n * permission as well \u2014 to approve AND to ask.\n */\nexport function featureFlagOverridePermissions(payload: {\n scope: FeatureFlagOverrideScope\n target: string\n}): string[] {\n const base = [ACCOUNTS_CONTROL_FLAG_OVERRIDE]\n return payload.scope === 'stage' && payload.target === 'prod'\n ? [...base, ACCOUNTS_CONTROL_FLAG_OVERRIDE_PROD]\n : base\n}\n\n/**\n * A GUID, as the create route reads one.\n *\n * The server says `z.guid()`. This is the same shape for the browser, which\n * holds no zod: 8-4-4-4-12 hexadecimal characters.\n */\nconst GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * The one written form of a target.\n *\n * A GUID reads the same in either case, and `ABC\u2026` and `abc\u2026` are the same\n * organization. The store and the resolver compare TEXT, so two forms of one\n * id would be two overrides: the unique index would allow both to be live,\n * and a row written in capitals would never match the id that a reader\n * carries. Everything that writes a target passes it through here first, so\n * only one form ever reaches the table.\n *\n * A stage name is lowercase already, thus this does not change one.\n */\nexport function normalizeFeatureFlagOverrideTarget(target: string): string {\n return target.trim().toLowerCase()\n}\n\n/**\n * What is wrong with the target of an override, if anything.\n *\n * It answers `undefined` when the target is good, and a sentence for a person\n * when it is not.\n *\n * It is here, and shared, for the same reason as\n * `featureFlagOverridePermissions` above: BOTH sides run it. The dialog of\n * control-frontend keeps the button disabled while it answers, and the create\n * route of control-backend refuses the call. The dialog accepted any text\n * that was not empty before this, thus it offered `prod` on dev and\n * `not-an-org` as an organization, and both ended in a 400 after the person\n * had filled the whole form.\n *\n * TWO rules, and the first surprises people:\n *\n * 1. A stage override names THE STAGE OF THE SERVICE, and no other. Each\n * deployed stage of control-backend holds its own database, thus an\n * override written on dev reaches dev readers alone. A request for\n * another stage would collect its approvals and move nothing.\n * 2. An org override names a GUID.\n */\nexport function featureFlagOverrideTargetProblem(args: {\n scope: FeatureFlagOverrideScope\n target: string\n /** The stage that the service runs on. `FeatureFlagsDto.stage` carries it. */\n stage: string\n}): string | undefined {\n const target = normalizeFeatureFlagOverrideTarget(args.target)\n if (target.length === 0) {\n return args.scope === 'stage'\n ? 'Name the stage to move.'\n : 'Name the organization to move.'\n }\n if (args.scope === 'stage') {\n return target === args.stage\n ? undefined\n : `This Camunda Control runs on ${args.stage} and can move only that ` +\n `stage. Open the request on the Camunda Control of ${target}.`\n }\n return GUID.test(target)\n ? undefined\n : 'An organization id is a GUID, like 11111111-1111-1111-1111-111111111111.'\n}\n\n/** The payload of a `feature-flag-override` approval request. */\nexport interface FeatureFlagOverridePayloadDto {\n flagPath: string\n scope: FeatureFlagOverrideScope\n target: string\n enabled: boolean\n /**\n * How many days the override holds, from the moment the action RUNS.\n *\n * A number of days and not a date, for the same reason as `extend-trial`:\n * days pass between the request and the run, and a stored date would lose\n * part of its life while the request waited for its approvals.\n */\n days: number\n}\n"],
5
- "mappings": "AAOA,SAAS,2BAA2B;AAEpC,MAAM,iCAAiC,oBAAoB;AAC3D,MAAM,sCACJ,oBAAoB;AAiBf,MAAM,+BAA+B,CAAC,SAAS,KAAK;AAMpD,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,MAAM,qBAAqB,CAAC,WAAW,OAAO,MAAM;AAuIpD,SAAS,+BAA+B,SAGlC;AACX,QAAM,OAAO,CAAC,8BAA8B;AAC5C,SAAO,QAAQ,UAAU,WAAW,QAAQ,WAAW,SACnD,CAAC,GAAG,MAAM,mCAAmC,IAC7C;AACN;AAQA,MAAM,OAAO;AAcN,SAAS,mCAAmC,QAAwB;AACzE,SAAO,OAAO,KAAK,EAAE,YAAY;AACnC;AAwBO,SAAS,iCAAiC,MAK1B;AACrB,QAAM,SAAS,mCAAmC,KAAK,MAAM;AAC7D,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,KAAK,UAAU,UAClB,4BACA;AAAA,EACN;AACA,MAAI,KAAK,UAAU,SAAS;AAC1B,WAAO,WAAW,KAAK,QACnB,SACA,gCAAgC,KAAK,KAAK,6EACa,MAAM;AAAA,EACnE;AACA,SAAO,KAAK,KAAK,MAAM,IACnB,SACA;AACN;",
6
- "names": []
7
- }