@oneuptime/common 10.4.4 → 10.4.6

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 (34) hide show
  1. package/Models/DatabaseModels/Project.ts +25 -0
  2. package/Models/DatabaseModels/Service.ts +42 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.ts +25 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Services/OpenTelemetryIngestService.ts +50 -46
  6. package/Server/Services/UserCallService.ts +20 -0
  7. package/Server/Services/UserIncomingCallNumberService.ts +22 -0
  8. package/Server/Services/UserSmsService.ts +20 -0
  9. package/Types/Telemetry/TelemetryRetentionConfig.ts +105 -0
  10. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +308 -0
  11. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +105 -0
  12. package/build/dist/Models/DatabaseModels/Project.js +25 -0
  13. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  14. package/build/dist/Models/DatabaseModels/Service.js +42 -0
  15. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  16. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.js +14 -0
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.js.map +1 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  20. package/build/dist/Server/Services/OpenTelemetryIngestService.js +27 -23
  21. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  22. package/build/dist/Server/Services/UserCallService.js +14 -2
  23. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  24. package/build/dist/Server/Services/UserIncomingCallNumberService.js +15 -2
  25. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  26. package/build/dist/Server/Services/UserSmsService.js +14 -2
  27. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  28. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js +44 -0
  29. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -0
  30. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +159 -0
  31. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -0
  32. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +53 -0
  33. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +1 -0
  34. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import MetricDownsamplingRetentionDays from "../../Types/Metrics/MetricDownsamplingRetentionDays";
2
+ import TelemetryRetentionConfig from "../../Types/Telemetry/TelemetryRetentionConfig";
2
3
  import Reseller from "./Reseller";
3
4
  import ResellerPlan from "./ResellerPlan";
4
5
  import User from "./User";
@@ -2028,6 +2029,30 @@ export default class Project extends TenantModel {
2028
2029
  })
2029
2030
  public defaultTelemetryRetentionInDays?: number = undefined;
2030
2031
 
2032
+ @ColumnAccessControl({
2033
+ create: [],
2034
+ read: [
2035
+ Permission.ProjectOwner,
2036
+ Permission.ProjectAdmin,
2037
+ Permission.ProjectMember,
2038
+ Permission.Viewer,
2039
+ Permission.ReadProject,
2040
+ ],
2041
+ update: [Permission.ProjectOwner, Permission.ProjectAdmin],
2042
+ })
2043
+ @TableColumn({
2044
+ type: TableColumnType.JSON,
2045
+ required: false,
2046
+ title: "Telemetry Data Retention Overrides",
2047
+ description:
2048
+ "Project-wide per-pillar retention overrides for telemetry data (logs by severity, traces by status, metrics, profiles). Falls back to defaultTelemetryRetentionInDays when a pillar or bucket is not set.",
2049
+ })
2050
+ @Column({
2051
+ type: ColumnType.JSON,
2052
+ nullable: true,
2053
+ })
2054
+ public telemetryRetentionConfig?: TelemetryRetentionConfig = undefined;
2055
+
2031
2056
  @ColumnAccessControl({
2032
2057
  create: [],
2033
2058
  read: [
@@ -25,6 +25,7 @@ import ObjectID from "../../Types/ObjectID";
25
25
  import Permission from "../../Types/Permission";
26
26
  import TechStack from "../../Types/Service/TechStack";
27
27
  import MetricDownsamplingRetentionDays from "../../Types/Metrics/MetricDownsamplingRetentionDays";
28
+ import TelemetryRetentionConfig from "../../Types/Telemetry/TelemetryRetentionConfig";
28
29
  import {
29
30
  Column,
30
31
  Entity,
@@ -795,6 +796,47 @@ export default class Service extends BaseModel {
795
796
  public metricDownsamplingRetentionDays?: MetricDownsamplingRetentionDays =
796
797
  undefined;
797
798
 
799
+ @ColumnAccessControl({
800
+ create: [
801
+ Permission.ProjectOwner,
802
+ Permission.ProjectAdmin,
803
+ Permission.ProjectMember,
804
+ Permission.SettingsAdmin,
805
+ Permission.SettingsMember,
806
+ Permission.CreateService,
807
+ ],
808
+ read: [
809
+ Permission.ProjectOwner,
810
+ Permission.ProjectAdmin,
811
+ Permission.ProjectMember,
812
+ Permission.Viewer,
813
+ Permission.SettingsAdmin,
814
+ Permission.SettingsMember,
815
+ Permission.SettingsViewer,
816
+ Permission.ReadService,
817
+ ],
818
+ update: [
819
+ Permission.ProjectOwner,
820
+ Permission.ProjectAdmin,
821
+ Permission.ProjectMember,
822
+ Permission.SettingsAdmin,
823
+ Permission.SettingsMember,
824
+ Permission.EditService,
825
+ ],
826
+ })
827
+ @TableColumn({
828
+ type: TableColumnType.JSON,
829
+ required: false,
830
+ title: "Telemetry Data Retention Overrides",
831
+ description:
832
+ "Per-pillar retention overrides for this service (logs by severity, traces by status, metrics, profiles). Unset fields inherit the project-level config and umbrella default.",
833
+ })
834
+ @Column({
835
+ type: ColumnType.JSON,
836
+ nullable: true,
837
+ })
838
+ public telemetryRetentionConfig?: TelemetryRetentionConfig = undefined;
839
+
798
840
  @ColumnAccessControl({
799
841
  create: [],
800
842
  read: [
@@ -0,0 +1,25 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddTelemetryRetentionConfig1779199346010
4
+ implements MigrationInterface
5
+ {
6
+ name = "AddTelemetryRetentionConfig1779199346010";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "Project" ADD "telemetryRetentionConfig" jsonb`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "Service" ADD "telemetryRetentionConfig" jsonb`,
14
+ );
15
+ }
16
+
17
+ public async down(queryRunner: QueryRunner): Promise<void> {
18
+ await queryRunner.query(
19
+ `ALTER TABLE "Service" DROP COLUMN "telemetryRetentionConfig"`,
20
+ );
21
+ await queryRunner.query(
22
+ `ALTER TABLE "Project" DROP COLUMN "telemetryRetentionConfig"`,
23
+ );
24
+ }
25
+ }
@@ -337,6 +337,7 @@ import { MigrateLegacyManagerRolesToAdmin1778900000000 } from "./1778900000000-M
337
337
  import { AddOnCallDutyPolicyScheduleOwners1778929624633 } from "./1778929624633-AddOnCallDutyPolicyScheduleOwners";
338
338
  import { AddOnCallIncomingCallOwnersAndRules1778931537020 } from "./1778931537020-AddOnCallIncomingCallOwnersAndRules";
339
339
  import { IncreaseSmtpUsernameLength1779125489830 } from "./1779125489830-IncreaseSmtpUsernameLength";
340
+ import { AddTelemetryRetentionConfig1779199346010 } from "./1779199346010-AddTelemetryRetentionConfig";
340
341
  export default [
341
342
  InitialMigration,
342
343
  MigrationName1717678334852,
@@ -677,4 +678,5 @@ export default [
677
678
  AddOnCallDutyPolicyScheduleOwners1778929624633,
678
679
  AddOnCallIncomingCallOwnersAndRules1778931537020,
679
680
  IncreaseSmtpUsernameLength1779125489830,
681
+ AddTelemetryRetentionConfig1779199346010,
680
682
  ];
@@ -15,6 +15,7 @@ import { extractOneuptimeLabelNames } from "../Utils/Telemetry/OneuptimeLabel";
15
15
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
16
16
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
17
17
  import logger from "../Utils/Logger";
18
+ import TelemetryRetentionConfig from "../../Types/Telemetry/TelemetryRetentionConfig";
18
19
 
19
20
  export enum OtelAggregationTemporality {
20
21
  Cumulative = "AGGREGATION_TEMPORALITY_CUMULATIVE",
@@ -25,26 +26,38 @@ export interface TelemetryServiceMetadata {
25
26
  serviceName: string;
26
27
  serviceId: ObjectID;
27
28
  dataRententionInDays: number;
29
+ serviceRetentionConfig: TelemetryRetentionConfig | null;
30
+ serviceUmbrellaInDays: number | null;
31
+ projectRetentionConfig: TelemetryRetentionConfig | null;
32
+ projectUmbrellaInDays: number;
33
+ }
34
+
35
+ interface ProjectRetentionContext {
36
+ projectRetentionConfig: TelemetryRetentionConfig | null;
37
+ projectUmbrellaInDays: number;
28
38
  }
29
39
 
30
40
  export default class OTelIngestService {
31
41
  @CaptureSpan()
32
- private static async getProjectDefaultRetentionInDays(
42
+ private static async getProjectRetentionContext(
33
43
  projectId: ObjectID,
34
- ): Promise<number> {
44
+ ): Promise<ProjectRetentionContext> {
35
45
  const project: Project | null = await ProjectService.findOneById({
36
46
  id: projectId,
37
47
  select: {
38
48
  defaultTelemetryRetentionInDays: true,
49
+ telemetryRetentionConfig: true,
39
50
  },
40
51
  props: {
41
52
  isRoot: true,
42
53
  },
43
54
  });
44
55
 
45
- return (
46
- project?.defaultTelemetryRetentionInDays || DEFAULT_RETENTION_IN_DAYS
47
- );
56
+ return {
57
+ projectRetentionConfig: project?.telemetryRetentionConfig ?? null,
58
+ projectUmbrellaInDays:
59
+ project?.defaultTelemetryRetentionInDays || DEFAULT_RETENTION_IN_DAYS,
60
+ };
48
61
  }
49
62
 
50
63
  @CaptureSpan()
@@ -52,17 +65,12 @@ export default class OTelIngestService {
52
65
  serviceName: string;
53
66
  projectId: ObjectID;
54
67
  resourceAttributes?: JSONArray | undefined;
55
- }): Promise<{
56
- serviceId: ObjectID;
57
- dataRententionInDays: number;
58
- }> {
59
- const result: {
60
- serviceId: ObjectID;
61
- dataRententionInDays: number;
62
- } = await this.findOrCreateTelemetryService({
63
- serviceName: data.serviceName,
64
- projectId: data.projectId,
65
- });
68
+ }): Promise<TelemetryServiceMetadata> {
69
+ const result: TelemetryServiceMetadata =
70
+ await this.findOrCreateTelemetryService({
71
+ serviceName: data.serviceName,
72
+ projectId: data.projectId,
73
+ });
66
74
 
67
75
  /*
68
76
  * Touch `lastSeenAt` on the service. Throttled per-service inside
@@ -119,10 +127,7 @@ export default class OTelIngestService {
119
127
  private static async findOrCreateTelemetryService(data: {
120
128
  serviceName: string;
121
129
  projectId: ObjectID;
122
- }): Promise<{
123
- serviceId: ObjectID;
124
- dataRententionInDays: number;
125
- }> {
130
+ }): Promise<TelemetryServiceMetadata> {
126
131
  /*
127
132
  * Sort by createdAt ASC for deterministic resolution if the
128
133
  * DB ever ends up with duplicates (defense in depth — the
@@ -140,6 +145,7 @@ export default class OTelIngestService {
140
145
  select: {
141
146
  _id: true,
142
147
  retainTelemetryDataForDays: true,
148
+ telemetryRetentionConfig: true,
143
149
  },
144
150
  sort: {
145
151
  createdAt: SortOrder.Ascending,
@@ -149,10 +155,27 @@ export default class OTelIngestService {
149
155
  },
150
156
  });
151
157
 
152
- if (!service) {
153
- const projectDefaultRetention: number =
154
- await this.getProjectDefaultRetentionInDays(data.projectId);
158
+ const projectContext: ProjectRetentionContext =
159
+ await this.getProjectRetentionContext(data.projectId);
155
160
 
161
+ const buildMetadata: (svc: Service) => TelemetryServiceMetadata = (
162
+ svc: Service,
163
+ ): TelemetryServiceMetadata => {
164
+ const serviceUmbrella: number | null =
165
+ svc.retainTelemetryDataForDays ?? null;
166
+ return {
167
+ serviceName: data.serviceName,
168
+ serviceId: svc.id!,
169
+ dataRententionInDays:
170
+ serviceUmbrella || projectContext.projectUmbrellaInDays,
171
+ serviceRetentionConfig: svc.telemetryRetentionConfig ?? null,
172
+ serviceUmbrellaInDays: serviceUmbrella,
173
+ projectRetentionConfig: projectContext.projectRetentionConfig,
174
+ projectUmbrellaInDays: projectContext.projectUmbrellaInDays,
175
+ };
176
+ };
177
+
178
+ if (!service) {
156
179
  try {
157
180
  const newService: Service = new Service();
158
181
  newService.projectId = data.projectId;
@@ -166,10 +189,7 @@ export default class OTelIngestService {
166
189
  },
167
190
  });
168
191
 
169
- return {
170
- serviceId: createdService.id!,
171
- dataRententionInDays: projectDefaultRetention,
172
- };
192
+ return buildMetadata(createdService);
173
193
  } catch {
174
194
  /*
175
195
  * Race condition: another request created the service concurrently.
@@ -183,6 +203,7 @@ export default class OTelIngestService {
183
203
  select: {
184
204
  _id: true,
185
205
  retainTelemetryDataForDays: true,
206
+ telemetryRetentionConfig: true,
186
207
  },
187
208
  sort: {
188
209
  createdAt: SortOrder.Ascending,
@@ -193,12 +214,7 @@ export default class OTelIngestService {
193
214
  });
194
215
 
195
216
  if (existingService) {
196
- return {
197
- serviceId: existingService.id!,
198
- dataRententionInDays:
199
- existingService.retainTelemetryDataForDays ||
200
- projectDefaultRetention,
201
- };
217
+ return buildMetadata(existingService);
202
218
  }
203
219
 
204
220
  throw new Error(
@@ -207,19 +223,7 @@ export default class OTelIngestService {
207
223
  }
208
224
  }
209
225
 
210
- if (service.retainTelemetryDataForDays) {
211
- return {
212
- serviceId: service.id!,
213
- dataRententionInDays: service.retainTelemetryDataForDays,
214
- };
215
- }
216
-
217
- return {
218
- serviceId: service.id!,
219
- dataRententionInDays: await this.getProjectDefaultRetentionInDays(
220
- data.projectId,
221
- ),
222
- };
226
+ return buildMetadata(service);
223
227
  }
224
228
  @CaptureSpan()
225
229
  public static getMetricFromDatapoint(data: {
@@ -91,7 +91,17 @@ export class Service extends DatabaseService<Model> {
91
91
  );
92
92
  }
93
93
 
94
+ /*
95
+ * If the project has its own default Twilio config, OneUptime does not
96
+ * charge the project's Call/SMS balance, so the low-balance check does not apply.
97
+ */
98
+ const projectTwilioConfig: TwilioConfig | undefined =
99
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
100
+ createBy.data.projectId!,
101
+ );
102
+
94
103
  if (
104
+ !projectTwilioConfig &&
95
105
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
96
106
  IsBillingEnabled
97
107
  ) {
@@ -161,7 +171,17 @@ export class Service extends DatabaseService<Model> {
161
171
  );
162
172
  }
163
173
 
174
+ /*
175
+ * If the project has its own default Twilio config, OneUptime does not
176
+ * charge the project's Call/SMS balance, so the low-balance check does not apply.
177
+ */
178
+ const projectTwilioConfig: TwilioConfig | undefined =
179
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
180
+ item.projectId!,
181
+ );
182
+
164
183
  if (
184
+ !projectTwilioConfig &&
165
185
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
166
186
  IsBillingEnabled
167
187
  ) {
@@ -3,8 +3,10 @@ import CreateBy from "../Types/Database/CreateBy";
3
3
  import { OnCreate } from "../Types/Database/Hooks";
4
4
  import logger from "../Utils/Logger";
5
5
  import DatabaseService from "./DatabaseService";
6
+ import ProjectCallSMSConfigService from "./ProjectCallSMSConfigService";
6
7
  import ProjectService from "./ProjectService";
7
8
  import SmsService from "./SmsService";
9
+ import TwilioConfig from "../../Types/CallAndSMS/TwilioConfig";
8
10
  import BadDataException from "../../Types/Exception/BadDataException";
9
11
  import ObjectID from "../../Types/ObjectID";
10
12
  import Text from "../../Types/Text";
@@ -48,7 +50,17 @@ export class Service extends DatabaseService<Model> {
48
50
  );
49
51
  }
50
52
 
53
+ /*
54
+ * If the project has its own default Twilio config, OneUptime does not
55
+ * charge the project's SMS balance, so the low-balance check does not apply.
56
+ */
57
+ const projectTwilioConfig: TwilioConfig | undefined =
58
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
59
+ createBy.data.projectId!,
60
+ );
61
+
51
62
  if (
63
+ !projectTwilioConfig &&
52
64
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
53
65
  IsBillingEnabled
54
66
  ) {
@@ -141,7 +153,17 @@ export class Service extends DatabaseService<Model> {
141
153
  );
142
154
  }
143
155
 
156
+ /*
157
+ * If the project has its own default Twilio config, OneUptime does not
158
+ * charge the project's SMS balance, so the low-balance check does not apply.
159
+ */
160
+ const projectTwilioConfig: TwilioConfig | undefined =
161
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
162
+ item.projectId!,
163
+ );
164
+
144
165
  if (
166
+ !projectTwilioConfig &&
145
167
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
146
168
  IsBillingEnabled
147
169
  ) {
@@ -90,7 +90,17 @@ export class Service extends DatabaseService<Model> {
90
90
  );
91
91
  }
92
92
 
93
+ /*
94
+ * If the project has its own default Twilio config, OneUptime does not
95
+ * charge the project's SMS balance, so the low-balance check does not apply.
96
+ */
97
+ const projectTwilioConfig: TwilioConfig | undefined =
98
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
99
+ createBy.data.projectId!,
100
+ );
101
+
93
102
  if (
103
+ !projectTwilioConfig &&
94
104
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
95
105
  IsBillingEnabled
96
106
  ) {
@@ -161,7 +171,17 @@ export class Service extends DatabaseService<Model> {
161
171
  );
162
172
  }
163
173
 
174
+ /*
175
+ * If the project has its own default Twilio config, OneUptime does not
176
+ * charge the project's SMS balance, so the low-balance check does not apply.
177
+ */
178
+ const projectTwilioConfig: TwilioConfig | undefined =
179
+ await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(
180
+ item.projectId!,
181
+ );
182
+
164
183
  if (
184
+ !projectTwilioConfig &&
165
185
  (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 &&
166
186
  IsBillingEnabled
167
187
  ) {
@@ -0,0 +1,105 @@
1
+ import LogSeverity from "../Log/LogSeverity";
2
+ import { SpanStatus } from "../../Models/AnalyticsModels/Span";
3
+
4
+ /*
5
+ * Per-pillar telemetry retention overrides.
6
+ *
7
+ * Lives on Project (project-wide defaults) and Service (per-service overrides).
8
+ * When a row is ingested, retention falls through, narrowest-first:
9
+ * 1. service[pillar].byX[bucketKey]
10
+ * 2. service[pillar].default
11
+ * 3. service.retainTelemetryDataForDays (umbrella)
12
+ * 4. project[pillar].byX[bucketKey]
13
+ * 5. project[pillar].default
14
+ * 6. project.defaultTelemetryRetentionInDays (umbrella)
15
+ * 7. HARDCODED_DEFAULT_TELEMETRY_RETENTION_IN_DAYS
16
+ */
17
+ export default interface TelemetryRetentionConfig {
18
+ logs?: {
19
+ default?: number | null;
20
+ bySeverity?: Partial<Record<LogSeverity, number | null>>;
21
+ };
22
+ traces?: {
23
+ default?: number | null;
24
+ byStatus?: Partial<Record<SpanStatus, number | null>>;
25
+ };
26
+ metrics?: {
27
+ default?: number | null;
28
+ };
29
+ profiles?: {
30
+ default?: number | null;
31
+ };
32
+ }
33
+
34
+ export type TelemetryPillar = "logs" | "traces" | "metrics" | "profiles";
35
+
36
+ export const HARDCODED_DEFAULT_TELEMETRY_RETENTION_IN_DAYS: number = 15;
37
+
38
+ function pickPositive(value: number | null | undefined): number | null {
39
+ return typeof value === "number" && value > 0 ? value : null;
40
+ }
41
+
42
+ function getBucketValue(
43
+ config: TelemetryRetentionConfig | null | undefined,
44
+ pillar: TelemetryPillar,
45
+ bucketKey: LogSeverity | SpanStatus | null | undefined,
46
+ ): number | null {
47
+ if (!config || bucketKey === undefined || bucketKey === null) {
48
+ return null;
49
+ }
50
+ if (pillar === "logs") {
51
+ const bySeverity: Partial<Record<LogSeverity, number | null>> | undefined =
52
+ config.logs?.bySeverity;
53
+ return pickPositive(bySeverity?.[bucketKey as LogSeverity]);
54
+ }
55
+ if (pillar === "traces") {
56
+ const byStatus: Partial<Record<SpanStatus, number | null>> | undefined =
57
+ config.traces?.byStatus;
58
+ return pickPositive(byStatus?.[bucketKey as SpanStatus]);
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function getPillarDefault(
64
+ config: TelemetryRetentionConfig | null | undefined,
65
+ pillar: TelemetryPillar,
66
+ ): number | null {
67
+ if (!config) {
68
+ return null;
69
+ }
70
+ return pickPositive(config[pillar]?.default);
71
+ }
72
+
73
+ export function resolveTelemetryRetentionInDays(input: {
74
+ pillar: TelemetryPillar;
75
+ bucketKey?: LogSeverity | SpanStatus | null;
76
+ serviceConfig?: TelemetryRetentionConfig | null;
77
+ serviceUmbrellaInDays?: number | null;
78
+ projectConfig?: TelemetryRetentionConfig | null;
79
+ projectUmbrellaInDays?: number | null;
80
+ }): number {
81
+ const {
82
+ pillar,
83
+ bucketKey,
84
+ serviceConfig,
85
+ serviceUmbrellaInDays,
86
+ projectConfig,
87
+ projectUmbrellaInDays,
88
+ } = input;
89
+
90
+ const candidates: Array<number | null> = [
91
+ getBucketValue(serviceConfig, pillar, bucketKey),
92
+ getPillarDefault(serviceConfig, pillar),
93
+ pickPositive(serviceUmbrellaInDays),
94
+ getBucketValue(projectConfig, pillar, bucketKey),
95
+ getPillarDefault(projectConfig, pillar),
96
+ pickPositive(projectUmbrellaInDays),
97
+ ];
98
+
99
+ for (const value of candidates) {
100
+ if (value !== null) {
101
+ return value;
102
+ }
103
+ }
104
+ return HARDCODED_DEFAULT_TELEMETRY_RETENTION_IN_DAYS;
105
+ }