@pagerduty/backstage-plugin-backend 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @pagerduty/backstage-plugin-backend
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bee64f1: Release the service mappings feature
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [bee64f1]
12
+ - @pagerduty/backstage-plugin-common@0.5.0
13
+
3
14
  ## 0.13.0
4
15
 
5
16
  ### Minor Changes
package/config.d.ts CHANGED
@@ -50,5 +50,48 @@ export interface Config {
50
50
  * @deepVisibility secret
51
51
  */
52
52
  accounts?: PagerDutyAccountConfig[];
53
+ /**
54
+ * Optional retention and cleanup settings for custom field sync logs.
55
+ * @visibility backend
56
+ */
57
+ customFieldsSyncLogs?: {
58
+ /**
59
+ * Number of days to keep sync logs before they are deleted. Defaults to 30.
60
+ * @visibility backend
61
+ */
62
+ retentionDays?: number;
63
+ /**
64
+ * Maximum number of sync log rows to keep across all accounts; the
65
+ * oldest rows beyond this count are deleted. Defaults to 500000.
66
+ * @visibility backend
67
+ */
68
+ maxRows?: number;
69
+ /**
70
+ * Settings for the scheduled cleanup task.
71
+ * @visibility backend
72
+ */
73
+ cleanup?: {
74
+ /**
75
+ * Whether the scheduled cleanup task runs. Defaults to true.
76
+ * @visibility backend
77
+ */
78
+ enabled?: boolean;
79
+ /**
80
+ * How often the cleanup task runs, in minutes. Defaults to 15.
81
+ * @visibility backend
82
+ */
83
+ frequencyMinutes?: number;
84
+ /**
85
+ * Number of rows deleted per batch. Defaults to 10000.
86
+ * @visibility backend
87
+ */
88
+ batchSize?: number;
89
+ /**
90
+ * Maximum number of delete batches per run. Defaults to 500.
91
+ * @visibility backend
92
+ */
93
+ maxBatchesPerRun?: number;
94
+ };
95
+ };
53
96
  };
54
97
  }
@@ -102,6 +102,31 @@ async function getDefaultHeaders(account) {
102
102
  "X-PagerDuty-Client": clientHeader
103
103
  };
104
104
  }
105
+ const SERVICES_CACHE_TTL_MS = 20 * 60 * 1e3;
106
+ const ALL_SERVICES_CACHE_KEY = "pagerduty:services:all";
107
+ const serviceCacheKey = (serviceId, account) => `pagerduty:service:${account || "default"}:${serviceId}`;
108
+ async function readCache(cache, key) {
109
+ if (!cache) {
110
+ return void 0;
111
+ }
112
+ return await cache.get(key);
113
+ }
114
+ async function writeCache(cache, key, value) {
115
+ if (!cache) {
116
+ return;
117
+ }
118
+ await cache.set(
119
+ key,
120
+ value,
121
+ { ttl: SERVICES_CACHE_TTL_MS }
122
+ );
123
+ }
124
+ async function deleteCache(cache, key) {
125
+ if (!cache) {
126
+ return;
127
+ }
128
+ await cache.delete(key);
129
+ }
105
130
  async function addServiceRelationsToService(serviceRelations, account) {
106
131
  let response;
107
132
  const options = {
@@ -219,7 +244,7 @@ async function getServiceRelationshipsById(serviceId, account) {
219
244
  headers: await getDefaultHeaders(account)
220
245
  };
221
246
  const apiBaseUrl = getApiBaseUrl(account);
222
- const baseUrl = `${apiBaseUrl}/service_dependencies/technical_services/${serviceId}`;
247
+ const baseUrl = `${apiBaseUrl}/service_dependencies/technical_services/${encodeURIComponent(serviceId)}`;
223
248
  try {
224
249
  response = await fetchWithRetries(baseUrl, options);
225
250
  } catch (error) {
@@ -502,7 +527,12 @@ async function getOncallUsers(escalationPolicy, account) {
502
527
  throw new backstagePluginCommon.HttpError(`Failed to parse oncall information: ${error}`, 500);
503
528
  }
504
529
  }
505
- async function getServiceById(serviceId, account) {
530
+ async function getServiceById(serviceId, account, cache, logger) {
531
+ const cacheKey = serviceCacheKey(serviceId, account);
532
+ const cached = await readCache(cache, cacheKey);
533
+ if (cached) {
534
+ return cached;
535
+ }
506
536
  let response;
507
537
  const params = `time_zone=UTC&include[]=integrations&include[]=escalation_policies`;
508
538
  const options = {
@@ -513,7 +543,7 @@ async function getServiceById(serviceId, account) {
513
543
  const baseUrl = `${apiBaseUrl}/services`;
514
544
  try {
515
545
  response = await fetchWithRetries(
516
- `${baseUrl}/${serviceId}?${params}`,
546
+ `${baseUrl}/${encodeURIComponent(serviceId)}?${params}`,
517
547
  options
518
548
  );
519
549
  } catch (error) {
@@ -550,12 +580,21 @@ async function getServiceById(serviceId, account) {
550
580
  let result;
551
581
  try {
552
582
  result = await response.json();
583
+ await writeCache(cache, serviceCacheKey(serviceId, account), result.service);
553
584
  return result.service;
554
585
  } catch (error) {
555
586
  throw new backstagePluginCommon.HttpError(`Failed to parse service information: ${error}`, 500);
556
587
  }
557
588
  }
558
- async function getSerivcesByIdsAndAccount(serviceIds, account) {
589
+ const SERVICE_IDS_BATCH_SIZE = 50;
590
+ const SERVICE_ID_PATTERN = /^[A-Z0-9]{6,}$/;
591
+ function isValidServiceId(id) {
592
+ if (typeof id !== "string") {
593
+ return false;
594
+ }
595
+ return SERVICE_ID_PATTERN.test(id.trim());
596
+ }
597
+ async function getServicesByIdsBatch(serviceIds, account) {
559
598
  let response;
560
599
  const token = await auth.getAuthToken(account);
561
600
  const options = {
@@ -568,11 +607,11 @@ async function getSerivcesByIdsAndAccount(serviceIds, account) {
568
607
  };
569
608
  const apiBaseUrl = getApiBaseUrl(account);
570
609
  const baseUrl = `${apiBaseUrl}/services`;
610
+ const params = new URLSearchParams();
611
+ serviceIds.forEach((id) => params.append("id[]", id));
612
+ params.append("limit", String(SERVICE_IDS_BATCH_SIZE));
571
613
  try {
572
- response = await fetchWithRetries(
573
- `${baseUrl}?id[]=${serviceIds.join("&id[]=")}`,
574
- options
575
- );
614
+ response = await fetchWithRetries(`${baseUrl}?${params}`, options);
576
615
  } catch (error) {
577
616
  throw new Error(`Failed to retrieve service: ${error}`);
578
617
  }
@@ -584,10 +623,7 @@ async function getSerivcesByIdsAndAccount(serviceIds, account) {
584
623
  }
585
624
  switch (response.status) {
586
625
  case 400:
587
- throw new backstagePluginCommon.HttpError(
588
- "Failed to get service. Caller provided invalid arguments.",
589
- 400
590
- );
626
+ return [];
591
627
  case 401:
592
628
  throw new backstagePluginCommon.HttpError(
593
629
  "Failed to get service. Caller did not supply credentials or did not provide the correct credentials.",
@@ -612,6 +648,24 @@ async function getSerivcesByIdsAndAccount(serviceIds, account) {
612
648
  throw new backstagePluginCommon.HttpError(`Failed to parse service information: ${error}`, 500);
613
649
  }
614
650
  }
651
+ async function getSerivcesByIdsAndAccount(serviceIds, account) {
652
+ const sanitizedServiceIds = Array.from(
653
+ new Set(
654
+ serviceIds.map((id) => typeof id === "string" ? id.trim() : "").filter((id) => isValidServiceId(id))
655
+ )
656
+ );
657
+ if (sanitizedServiceIds.length === 0) {
658
+ return [];
659
+ }
660
+ const batches = [];
661
+ for (let i = 0; i < sanitizedServiceIds.length; i += SERVICE_IDS_BATCH_SIZE) {
662
+ batches.push(sanitizedServiceIds.slice(i, i + SERVICE_IDS_BATCH_SIZE));
663
+ }
664
+ const results = await Promise.all(
665
+ batches.map((batch) => getServicesByIdsBatch(batch, account))
666
+ );
667
+ return results.flat();
668
+ }
615
669
  async function getServiceByIntegrationKey(integrationKey, account) {
616
670
  let response;
617
671
  const params = `query=${integrationKey}&time_zone=UTC&include[]=integrations&include[]=escalation_policies`;
@@ -669,15 +723,21 @@ async function getServiceByIntegrationKey(integrationKey, account) {
669
723
  return result.services[0];
670
724
  }
671
725
  async function getServicesByIds(ids) {
672
- let services = [];
673
- await Promise.all(
674
- Object.entries(EndpointConfig).map(async ([account, _]) => {
675
- services = await getSerivcesByIdsAndAccount(ids, account);
676
- })
726
+ const servicesPerAccount = await Promise.all(
727
+ Object.keys(EndpointConfig).map(
728
+ (account) => getSerivcesByIdsAndAccount(ids, account)
729
+ )
677
730
  );
678
- return services;
731
+ return servicesPerAccount.flat();
679
732
  }
680
- async function getAllServices() {
733
+ async function getAllServices(cache) {
734
+ const cached = await readCache(
735
+ cache,
736
+ ALL_SERVICES_CACHE_KEY
737
+ );
738
+ if (cached !== void 0) {
739
+ return cached;
740
+ }
681
741
  const allServices = [];
682
742
  await Promise.all(
683
743
  Object.entries(EndpointConfig).map(async ([account, _]) => {
@@ -733,6 +793,12 @@ async function getAllServices() {
733
793
  }
734
794
  })
735
795
  );
796
+ await writeCache(cache, ALL_SERVICES_CACHE_KEY, allServices);
797
+ await Promise.all(
798
+ allServices.map(
799
+ (service) => writeCache(cache, serviceCacheKey(service.id, service.account), service)
800
+ )
801
+ );
736
802
  return allServices;
737
803
  }
738
804
  async function getServicesByPartialName(partialName) {
@@ -874,7 +940,7 @@ async function getChangeEvents(serviceId, account) {
874
940
  const baseUrl = `${apiBaseUrl}/services`;
875
941
  try {
876
942
  response = await fetchWithRetries(
877
- `${baseUrl}/${serviceId}/change_events?${params}`,
943
+ `${baseUrl}/${encodeURIComponent(serviceId)}/change_events?${params}`,
878
944
  options
879
945
  );
880
946
  } catch (error) {
@@ -921,7 +987,7 @@ async function getChangeEvents(serviceId, account) {
921
987
  }
922
988
  async function getIncidents(serviceId, account) {
923
989
  let response;
924
- const params = `time_zone=UTC&sort_by=created_at&statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${serviceId}`;
990
+ const params = `time_zone=UTC&sort_by=created_at&statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${encodeURIComponent(serviceId)}`;
925
991
  const options = {
926
992
  method: "GET",
927
993
  headers: await getDefaultHeaders(account)
@@ -981,7 +1047,7 @@ async function getServiceStandards(serviceId, account) {
981
1047
  headers: await getDefaultHeaders(account)
982
1048
  };
983
1049
  const apiBaseUrl = getApiBaseUrl(account);
984
- const baseUrl = `${apiBaseUrl}/standards/scores/technical_services/${serviceId}`;
1050
+ const baseUrl = `${apiBaseUrl}/standards/scores/technical_services/${encodeURIComponent(serviceId)}`;
985
1051
  try {
986
1052
  response = await fetchWithRetries(baseUrl, options);
987
1053
  } catch (error) {
@@ -1076,7 +1142,8 @@ async function getServiceMetrics(serviceId, account) {
1076
1142
  async function createServiceIntegration({
1077
1143
  serviceId,
1078
1144
  vendorId,
1079
- account
1145
+ account,
1146
+ cache
1080
1147
  }) {
1081
1148
  let response;
1082
1149
  const apiBaseUrl = getApiBaseUrl(account);
@@ -1100,7 +1167,7 @@ async function createServiceIntegration({
1100
1167
  };
1101
1168
  try {
1102
1169
  response = await fetchWithRetries(
1103
- `${baseUrl}/${serviceId}/integrations`,
1170
+ `${baseUrl}/${encodeURIComponent(serviceId)}/integrations`,
1104
1171
  options
1105
1172
  );
1106
1173
  } catch (error) {
@@ -1132,12 +1199,41 @@ async function createServiceIntegration({
1132
1199
  let result;
1133
1200
  try {
1134
1201
  result = await response.json();
1202
+ await deleteCache(cache, serviceCacheKey(serviceId, account));
1203
+ await deleteCache(cache, ALL_SERVICES_CACHE_KEY);
1135
1204
  return result.integration.integration_key ?? "";
1136
1205
  } catch (error) {
1137
1206
  throw new Error(`Failed to parse service information: ${error}`);
1138
1207
  }
1139
1208
  }
1209
+ function getAllowedApiOrigins() {
1210
+ const origins = /* @__PURE__ */ new Set();
1211
+ const addOrigin = (apiBaseUrl) => {
1212
+ if (!apiBaseUrl) {
1213
+ return;
1214
+ }
1215
+ try {
1216
+ origins.add(new URL(apiBaseUrl).origin);
1217
+ } catch {
1218
+ }
1219
+ };
1220
+ Object.values(EndpointConfig).forEach((cfg) => addOrigin(cfg.apiBaseUrl));
1221
+ addOrigin(fallbackEndpointConfig?.apiBaseUrl);
1222
+ origins.add("https://api.pagerduty.com");
1223
+ return origins;
1224
+ }
1140
1225
  async function fetchWithRetries(url, options) {
1226
+ let requestOrigin;
1227
+ try {
1228
+ requestOrigin = new URL(url).origin;
1229
+ } catch {
1230
+ throw new Error("Refusing to fetch invalid URL.");
1231
+ }
1232
+ if (!getAllowedApiOrigins().has(requestOrigin)) {
1233
+ throw new Error(
1234
+ `Refusing to fetch URL with disallowed origin: ${requestOrigin}`
1235
+ );
1236
+ }
1141
1237
  let response;
1142
1238
  let error = new Error();
1143
1239
  const maxRetries = 5;
@@ -1158,9 +1254,250 @@ async function fetchWithRetries(url, options) {
1158
1254
  `Failed to fetch data after ${maxRetries} retries. Last error: ${error}`
1159
1255
  );
1160
1256
  }
1257
+ async function createCustomField({
1258
+ request,
1259
+ account
1260
+ }) {
1261
+ let response;
1262
+ const apiBaseUrl = getApiBaseUrl(account);
1263
+ const baseUrl = `${apiBaseUrl}/services/custom_fields`;
1264
+ const token = await auth.getAuthToken(account);
1265
+ const options = {
1266
+ method: "POST",
1267
+ body: JSON.stringify(request),
1268
+ headers: {
1269
+ Authorization: token,
1270
+ Accept: "application/vnd.pagerduty+json;version=2",
1271
+ "Content-Type": "application/json"
1272
+ }
1273
+ };
1274
+ try {
1275
+ response = await fetchWithRetries(baseUrl, options);
1276
+ } catch (error) {
1277
+ throw new Error(`Failed to create custom field: ${error}`);
1278
+ }
1279
+ if (response.status >= 500) {
1280
+ throw new backstagePluginCommon.HttpError(
1281
+ `Failed to create custom field. PagerDuty API returned a server error.`,
1282
+ response.status
1283
+ );
1284
+ }
1285
+ switch (response.status) {
1286
+ case 400: {
1287
+ const errorData = await response.json().catch(() => ({}));
1288
+ throw new backstagePluginCommon.HttpError(
1289
+ `Failed to create custom field. Invalid arguments: ${JSON.stringify(errorData)}`,
1290
+ 400
1291
+ );
1292
+ }
1293
+ case 401:
1294
+ throw new backstagePluginCommon.HttpError(
1295
+ `Failed to create custom field. Invalid credentials provided.`,
1296
+ 401
1297
+ );
1298
+ case 403:
1299
+ throw new backstagePluginCommon.HttpError(
1300
+ `Failed to create custom field. Not authorized to perform this action.`,
1301
+ 403
1302
+ );
1303
+ case 409: {
1304
+ const errorData = await response.json().catch(() => ({}));
1305
+ throw new backstagePluginCommon.HttpError(
1306
+ `Custom field with this name already exists: ${JSON.stringify(errorData)}`,
1307
+ 409
1308
+ );
1309
+ }
1310
+ case 429:
1311
+ throw new backstagePluginCommon.HttpError(`Rate limit exceeded.`, 429);
1312
+ }
1313
+ try {
1314
+ const result = await response.json();
1315
+ return result;
1316
+ } catch (error) {
1317
+ throw new Error(`Failed to parse custom field response: ${error}`);
1318
+ }
1319
+ }
1320
+ async function updateCustomField({
1321
+ fieldId,
1322
+ request,
1323
+ account
1324
+ }) {
1325
+ const apiBaseUrl = getApiBaseUrl(account);
1326
+ const baseUrl = `${apiBaseUrl}/services/custom_fields/${encodeURIComponent(fieldId)}`;
1327
+ const token = await auth.getAuthToken(account);
1328
+ const options = {
1329
+ method: "PUT",
1330
+ body: JSON.stringify(request),
1331
+ headers: {
1332
+ Authorization: token,
1333
+ Accept: "application/vnd.pagerduty+json;version=2",
1334
+ "Content-Type": "application/json"
1335
+ }
1336
+ };
1337
+ let response;
1338
+ try {
1339
+ response = await fetchWithRetries(baseUrl, options);
1340
+ } catch (error) {
1341
+ throw new Error(`Failed to update custom field: ${error}`);
1342
+ }
1343
+ if (response.status >= 500) {
1344
+ throw new backstagePluginCommon.HttpError(
1345
+ `Failed to update custom field. PagerDuty API returned a server error.`,
1346
+ response.status
1347
+ );
1348
+ }
1349
+ switch (response.status) {
1350
+ case 400: {
1351
+ const errorData = await response.json().catch(() => ({}));
1352
+ throw new backstagePluginCommon.HttpError(
1353
+ `Failed to update custom field. Invalid arguments: ${JSON.stringify(errorData)}`,
1354
+ 400
1355
+ );
1356
+ }
1357
+ case 401:
1358
+ throw new backstagePluginCommon.HttpError(
1359
+ `Failed to update custom field. Invalid credentials provided.`,
1360
+ 401
1361
+ );
1362
+ case 403:
1363
+ throw new backstagePluginCommon.HttpError(
1364
+ `Failed to update custom field. Not authorized to perform this action.`,
1365
+ 403
1366
+ );
1367
+ case 404:
1368
+ throw new backstagePluginCommon.HttpError(
1369
+ `Failed to update custom field. Custom field not found.`,
1370
+ 404
1371
+ );
1372
+ case 409: {
1373
+ const errorData = await response.json().catch(() => ({}));
1374
+ throw new backstagePluginCommon.HttpError(
1375
+ `Custom field with this name already exists: ${JSON.stringify(errorData)}`,
1376
+ 409
1377
+ );
1378
+ }
1379
+ case 429:
1380
+ throw new backstagePluginCommon.HttpError(`Rate limit exceeded.`, 429);
1381
+ }
1382
+ try {
1383
+ const result = await response.json();
1384
+ return result;
1385
+ } catch (error) {
1386
+ throw new Error(`Failed to parse custom field response: ${error}`);
1387
+ }
1388
+ }
1389
+ async function deleteCustomField({
1390
+ fieldId,
1391
+ account
1392
+ }) {
1393
+ const apiBaseUrl = getApiBaseUrl(account);
1394
+ const baseUrl = `${apiBaseUrl}/services/custom_fields/${encodeURIComponent(fieldId)}`;
1395
+ const token = await auth.getAuthToken(account);
1396
+ const options = {
1397
+ method: "DELETE",
1398
+ headers: {
1399
+ Authorization: token,
1400
+ Accept: "application/vnd.pagerduty+json;version=2"
1401
+ }
1402
+ };
1403
+ let response;
1404
+ try {
1405
+ response = await fetchWithRetries(baseUrl, options);
1406
+ } catch (error) {
1407
+ throw new Error(`Failed to delete custom field: ${error}`);
1408
+ }
1409
+ if (response.status >= 500) {
1410
+ throw new backstagePluginCommon.HttpError(
1411
+ `Failed to delete custom field. PagerDuty API returned a server error.`,
1412
+ response.status
1413
+ );
1414
+ }
1415
+ switch (response.status) {
1416
+ case 401:
1417
+ throw new backstagePluginCommon.HttpError(
1418
+ `Failed to delete custom field. Invalid credentials provided.`,
1419
+ 401
1420
+ );
1421
+ case 403:
1422
+ throw new backstagePluginCommon.HttpError(
1423
+ `Failed to delete custom field. Caller is not authorized to delete this custom field.`,
1424
+ 403
1425
+ );
1426
+ case 404:
1427
+ throw new backstagePluginCommon.HttpError(`Custom field not found.`, 404);
1428
+ case 429:
1429
+ throw new backstagePluginCommon.HttpError(`Rate limit exceeded.`, 429);
1430
+ }
1431
+ }
1432
+ async function setServiceCustomFieldValues({
1433
+ serviceId,
1434
+ request,
1435
+ account
1436
+ }) {
1437
+ const apiBaseUrl = getApiBaseUrl(account);
1438
+ const baseUrl = `${apiBaseUrl}/services/${encodeURIComponent(serviceId)}/custom_fields/values`;
1439
+ const token = await auth.getAuthToken(account);
1440
+ const options = {
1441
+ method: "PUT",
1442
+ body: JSON.stringify(request),
1443
+ headers: {
1444
+ Authorization: token,
1445
+ Accept: "application/vnd.pagerduty+json;version=2",
1446
+ "Content-Type": "application/json"
1447
+ }
1448
+ };
1449
+ let response;
1450
+ try {
1451
+ response = await fetchWithRetries(baseUrl, options);
1452
+ } catch (error) {
1453
+ throw new Error(`Failed to set service custom field values: ${error}`);
1454
+ }
1455
+ if (response.status >= 500) {
1456
+ throw new backstagePluginCommon.HttpError(
1457
+ `Failed to set service custom field values. PagerDuty API returned a server error.`,
1458
+ response.status
1459
+ );
1460
+ }
1461
+ switch (response.status) {
1462
+ case 400: {
1463
+ const errorData = await response.json().catch(() => ({}));
1464
+ throw new backstagePluginCommon.HttpError(
1465
+ `Failed to set service custom field values. Invalid arguments: ${JSON.stringify(errorData)}`,
1466
+ 400
1467
+ );
1468
+ }
1469
+ case 401:
1470
+ throw new backstagePluginCommon.HttpError(
1471
+ `Failed to set service custom field values. Invalid credentials provided.`,
1472
+ 401
1473
+ );
1474
+ case 403:
1475
+ throw new backstagePluginCommon.HttpError(
1476
+ `Failed to set service custom field values. Not authorized to perform this action.`,
1477
+ 403
1478
+ );
1479
+ case 404:
1480
+ throw new backstagePluginCommon.HttpError(
1481
+ `Failed to set service custom field values. Service or custom field not found.`,
1482
+ 404
1483
+ );
1484
+ case 429:
1485
+ throw new backstagePluginCommon.HttpError(`Rate limit exceeded.`, 429);
1486
+ }
1487
+ try {
1488
+ const result = await response.json();
1489
+ return result;
1490
+ } catch (error) {
1491
+ throw new Error(
1492
+ `Failed to parse set service custom field values response: ${error}`
1493
+ );
1494
+ }
1495
+ }
1161
1496
 
1162
1497
  exports.addServiceRelationsToService = addServiceRelationsToService;
1498
+ exports.createCustomField = createCustomField;
1163
1499
  exports.createServiceIntegration = createServiceIntegration;
1500
+ exports.deleteCustomField = deleteCustomField;
1164
1501
  exports.fetchWithRetries = fetchWithRetries;
1165
1502
  exports.getAllEscalationPolicies = getAllEscalationPolicies;
1166
1503
  exports.getAllServices = getAllServices;
@@ -1178,7 +1515,10 @@ exports.getServiceStandards = getServiceStandards;
1178
1515
  exports.getServicesByIds = getServicesByIds;
1179
1516
  exports.getServicesByPartialName = getServicesByPartialName;
1180
1517
  exports.insertAccountConfig = insertAccountConfig;
1518
+ exports.isValidServiceId = isValidServiceId;
1181
1519
  exports.loadPagerDutyEndpointsFromConfig = loadPagerDutyEndpointsFromConfig;
1182
1520
  exports.removeServiceRelationsFromService = removeServiceRelationsFromService;
1183
1521
  exports.setFallbackAccountConfig = setFallbackAccountConfig;
1522
+ exports.setServiceCustomFieldValues = setServiceCustomFieldValues;
1523
+ exports.updateCustomField = updateCustomField;
1184
1524
  //# sourceMappingURL=pagerduty.cjs.js.map