@pagerduty/backstage-plugin-backend 0.12.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 +30 -0
- package/config.d.ts +43 -0
- package/dist/apis/pagerduty.cjs.js +364 -24
- package/dist/apis/pagerduty.cjs.js.map +1 -1
- package/dist/controllers/mappings-controller.cjs.js +31 -7
- package/dist/controllers/mappings-controller.cjs.js.map +1 -1
- package/dist/db/PagerDutyBackendDatabase.cjs.js +202 -4
- package/dist/db/PagerDutyBackendDatabase.cjs.js.map +1 -1
- package/dist/index.d.ts +44 -3
- package/dist/plugin.cjs.js +30 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/service/customFieldsController.cjs.js +459 -0
- package/dist/service/customFieldsController.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +99 -110
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/services/autoMatchJobs.cjs.js +53 -0
- package/dist/services/autoMatchJobs.cjs.js.map +1 -0
- package/dist/services/autoMatchRunner.cjs.js +73 -0
- package/dist/services/autoMatchRunner.cjs.js.map +1 -0
- package/dist/services/dataLoader.cjs.js +14 -7
- package/dist/services/dataLoader.cjs.js.map +1 -1
- package/dist/services/syncLogsCleanup.cjs.js +77 -0
- package/dist/services/syncLogsCleanup.cjs.js.map +1 -0
- package/migrations/20260225113251_add_pagerduty_custom_fields_table.js +31 -0
- package/migrations/20260313_add_custom_fields_unique_indexes.js +27 -0
- package/migrations/20260515_add_custom_field_sync_logs_table.js +32 -0
- package/migrations/20260612_add_sync_logs_timestamp_index.js +19 -0
- package/package.json +7 -8
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var backendCommon = require('@backstage/backend-common');
|
|
4
3
|
var pagerduty = require('../apis/pagerduty.cjs.js');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
4
|
+
var autoMatchRunner = require('../services/autoMatchRunner.cjs.js');
|
|
5
|
+
var autoMatchJobs = require('../services/autoMatchJobs.cjs.js');
|
|
7
6
|
var backstagePluginCommon = require('@pagerduty/backstage-plugin-common');
|
|
8
7
|
var auth = require('../auth/auth.cjs.js');
|
|
8
|
+
var customFieldsController = require('./customFieldsController.cjs.js');
|
|
9
9
|
var express = require('express');
|
|
10
10
|
var Router = require('express-promise-router');
|
|
11
11
|
var mappingsController = require('../controllers/mappings-controller.cjs.js');
|
|
12
12
|
var catalogEntity = require('../utils/catalog-entity.cjs.js');
|
|
13
|
+
var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
|
|
13
14
|
|
|
14
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
15
16
|
|
|
@@ -34,6 +35,7 @@ function _interopNamespaceCompat(e) {
|
|
|
34
35
|
var express__namespace = /*#__PURE__*/_interopNamespaceCompat(express);
|
|
35
36
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
36
37
|
|
|
38
|
+
const DATA_SYNC_SETTING_ID = "settings::data-sync";
|
|
37
39
|
async function buildEntityMappingsResponse(entityMappings, componentEntitiesDict, componentEntities, pagerDutyServices) {
|
|
38
40
|
const result = {
|
|
39
41
|
mappings: []
|
|
@@ -152,18 +154,23 @@ async function buildEntityMappingsResponse(entityMappings, componentEntitiesDict
|
|
|
152
154
|
return result;
|
|
153
155
|
}
|
|
154
156
|
async function createRouter(options) {
|
|
155
|
-
const { logger, config, store, catalogApi } = options;
|
|
156
|
-
let { auth: auth$1 } = options;
|
|
157
|
-
if (!auth$1) {
|
|
158
|
-
auth$1 = backendCommon.createLegacyAuthAdapters(options).auth;
|
|
159
|
-
}
|
|
157
|
+
const { logger, config, store, catalogApi, cache } = options;
|
|
160
158
|
if (!catalogApi) {
|
|
161
159
|
throw new Error("Catalog API is required to start the PagerDuty plugin backend");
|
|
162
160
|
}
|
|
161
|
+
if (!cache) {
|
|
162
|
+
throw new Error("Cache service is required to start the PagerDuty plugin backend");
|
|
163
|
+
}
|
|
163
164
|
await auth.loadAuthConfig(config, logger);
|
|
164
165
|
pagerduty.loadPagerDutyEndpointsFromConfig(config, logger);
|
|
165
166
|
const router = Router__default.default();
|
|
166
167
|
router.use(express__namespace.json());
|
|
168
|
+
const runAutoMatch = autoMatchRunner.createAutoMatchRunner(catalogApi, cache);
|
|
169
|
+
const autoMatchJobs$1 = new autoMatchJobs.AutoMatchJobRegistry(cache, runAutoMatch);
|
|
170
|
+
const customFieldsController$1 = new customFieldsController.CustomFieldsController({
|
|
171
|
+
logger,
|
|
172
|
+
store
|
|
173
|
+
});
|
|
167
174
|
router.delete(
|
|
168
175
|
"/dependencies/service/:serviceId",
|
|
169
176
|
async (request, response) => {
|
|
@@ -317,10 +324,8 @@ async function createRouter(options) {
|
|
|
317
324
|
response.status(400).json("Bad Request: 'id' and 'value' are required");
|
|
318
325
|
return;
|
|
319
326
|
}
|
|
320
|
-
if (!
|
|
321
|
-
response.status(400).json(
|
|
322
|
-
"Bad Request: 'value' is invalid. Valid options are 'backstage', 'pagerduty', 'both' or 'disabled'"
|
|
323
|
-
);
|
|
327
|
+
if (!isValidSettingValue(setting.id, setting.value)) {
|
|
328
|
+
response.status(400).json(`Bad Request: '${setting.value}' is not a valid value for setting '${setting.id}'`);
|
|
324
329
|
return;
|
|
325
330
|
}
|
|
326
331
|
await store.updateSetting(setting);
|
|
@@ -355,12 +360,36 @@ async function createRouter(options) {
|
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
362
|
});
|
|
358
|
-
function
|
|
359
|
-
if (
|
|
360
|
-
return
|
|
363
|
+
function isValidSettingValue(id, value) {
|
|
364
|
+
if (id === DATA_SYNC_SETTING_ID) {
|
|
365
|
+
return value === "enabled" || value === "disabled";
|
|
361
366
|
}
|
|
362
|
-
return
|
|
367
|
+
return value === "backstage" || value === "pagerduty" || value === "both" || value === "disabled";
|
|
363
368
|
}
|
|
369
|
+
router.post("/custom-fields", async (request, response) => {
|
|
370
|
+
await customFieldsController$1.createCustomField(request, response);
|
|
371
|
+
});
|
|
372
|
+
router.get("/custom-fields", async (request, response) => {
|
|
373
|
+
await customFieldsController$1.getCustomFields(request, response);
|
|
374
|
+
});
|
|
375
|
+
router.put("/custom-fields/:id", async (request, response) => {
|
|
376
|
+
await customFieldsController$1.updateCustomField(request, response);
|
|
377
|
+
});
|
|
378
|
+
router.patch("/custom-fields/:id/enabled", async (request, response) => {
|
|
379
|
+
await customFieldsController$1.toggleCustomFieldEnabled(request, response);
|
|
380
|
+
});
|
|
381
|
+
router.delete("/custom-fields/:id", async (request, response) => {
|
|
382
|
+
await customFieldsController$1.deleteCustomField(request, response);
|
|
383
|
+
});
|
|
384
|
+
router.post("/custom-fields/sync", async (request, response) => {
|
|
385
|
+
await customFieldsController$1.syncCustomFieldValues(request, response);
|
|
386
|
+
});
|
|
387
|
+
router.post("/custom-fields/sync-logs", async (request, response) => {
|
|
388
|
+
await customFieldsController$1.createSyncLog(request, response);
|
|
389
|
+
});
|
|
390
|
+
router.get("/custom-fields/sync-logs", async (request, response) => {
|
|
391
|
+
await customFieldsController$1.getSyncLogs(request, response);
|
|
392
|
+
});
|
|
364
393
|
router.post("/mapping/entity", async (request, response) => {
|
|
365
394
|
try {
|
|
366
395
|
const entity = request.body;
|
|
@@ -376,7 +405,11 @@ async function createRouter(options) {
|
|
|
376
405
|
);
|
|
377
406
|
if (entity.entityRef !== "" && (entity.integrationKey === "" || entity.integrationKey === void 0)) {
|
|
378
407
|
const backstageVendorId = "PRO19CT";
|
|
379
|
-
const service = await pagerduty.getServiceById(
|
|
408
|
+
const service = await pagerduty.getServiceById(
|
|
409
|
+
entity.serviceId,
|
|
410
|
+
entity.account,
|
|
411
|
+
cache
|
|
412
|
+
);
|
|
380
413
|
const backstageIntegration = service.integrations?.find(
|
|
381
414
|
(integration) => integration.vendor?.id === backstageVendorId
|
|
382
415
|
);
|
|
@@ -384,7 +417,8 @@ async function createRouter(options) {
|
|
|
384
417
|
const integrationKey = await pagerduty.createServiceIntegration({
|
|
385
418
|
serviceId: entity.serviceId,
|
|
386
419
|
vendorId: backstageVendorId,
|
|
387
|
-
account: entity.account
|
|
420
|
+
account: entity.account,
|
|
421
|
+
cache
|
|
388
422
|
});
|
|
389
423
|
entity.integrationKey = integrationKey;
|
|
390
424
|
} else {
|
|
@@ -457,7 +491,8 @@ async function createRouter(options) {
|
|
|
457
491
|
const backstageVendorId = "PRO19CT";
|
|
458
492
|
const service = await pagerduty.getServiceById(
|
|
459
493
|
entity.serviceId,
|
|
460
|
-
entity.account
|
|
494
|
+
entity.account,
|
|
495
|
+
cache
|
|
461
496
|
);
|
|
462
497
|
const backstageIntegration = service.integrations?.find(
|
|
463
498
|
(integration) => integration.vendor?.id === backstageVendorId
|
|
@@ -466,7 +501,8 @@ async function createRouter(options) {
|
|
|
466
501
|
const integrationKey = await pagerduty.createServiceIntegration({
|
|
467
502
|
serviceId: entity.serviceId,
|
|
468
503
|
vendorId: backstageVendorId,
|
|
469
|
-
account: entity.account
|
|
504
|
+
account: entity.account,
|
|
505
|
+
cache
|
|
470
506
|
});
|
|
471
507
|
entity.integrationKey = integrationKey;
|
|
472
508
|
} else {
|
|
@@ -548,7 +584,7 @@ async function createRouter(options) {
|
|
|
548
584
|
}
|
|
549
585
|
});
|
|
550
586
|
const componentEntitiesDict = await catalogEntity.createComponentEntitiesReferenceDict(componentEntities);
|
|
551
|
-
const pagerDutyServices = await pagerduty.getAllServices();
|
|
587
|
+
const pagerDutyServices = await pagerduty.getAllServices(cache);
|
|
552
588
|
const result = await buildEntityMappingsResponse(
|
|
553
589
|
entityMappings,
|
|
554
590
|
componentEntitiesDict,
|
|
@@ -564,7 +600,7 @@ async function createRouter(options) {
|
|
|
564
600
|
}
|
|
565
601
|
}
|
|
566
602
|
});
|
|
567
|
-
router.post("/mapping/entities", mappingsController.getMappingEntities(store, catalogApi));
|
|
603
|
+
router.post("/mapping/entities", mappingsController.getMappingEntities(store, catalogApi, logger));
|
|
568
604
|
router.get(
|
|
569
605
|
"/mapping/entity/:type/:namespace/:name",
|
|
570
606
|
async (request, response) => {
|
|
@@ -624,93 +660,45 @@ async function createRouter(options) {
|
|
|
624
660
|
}
|
|
625
661
|
}
|
|
626
662
|
);
|
|
627
|
-
router.post("/mapping/entity/auto-match", async (request, response) => {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
response.status(400).json({
|
|
633
|
-
error: "Invalid threshold. Must be a number between 0 and 100."
|
|
634
|
-
});
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
const bestOnly = request.body.bestOnly ?? false;
|
|
638
|
-
const loadStartTime = Date.now();
|
|
639
|
-
const { pdServices, bsComponents } = await dataLoader.loadBothSources({
|
|
640
|
-
catalogApi
|
|
663
|
+
router.post("/mapping/entity/auto-match/start", async (request, response) => {
|
|
664
|
+
const threshold = request.body.threshold ?? 100;
|
|
665
|
+
if (typeof threshold !== "number" || threshold < 0 || threshold > 100) {
|
|
666
|
+
response.status(400).json({
|
|
667
|
+
error: "Invalid threshold. Must be a number between 0 and 100."
|
|
641
668
|
});
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
};
|
|
665
|
-
response.json({
|
|
666
|
-
matches: matches.map((m) => ({
|
|
667
|
-
pagerDutyService: {
|
|
668
|
-
serviceId: m.pagerDutyService.sourceId,
|
|
669
|
-
name: m.pagerDutyService.rawName,
|
|
670
|
-
team: m.pagerDutyService.teamName,
|
|
671
|
-
account: m.pagerDutyService.account
|
|
672
|
-
},
|
|
673
|
-
backstageComponent: {
|
|
674
|
-
entityRef: m.backstageComponent.sourceId,
|
|
675
|
-
name: m.backstageComponent.rawName,
|
|
676
|
-
owner: m.backstageComponent.teamName
|
|
677
|
-
},
|
|
678
|
-
score: m.score,
|
|
679
|
-
confidence: getConfidenceLevel(m.score),
|
|
680
|
-
scoreBreakdown: m.scoreBreakdown
|
|
681
|
-
})),
|
|
682
|
-
statistics: {
|
|
683
|
-
totalPagerDutyServices: filteredPdServices.length,
|
|
684
|
-
totalBackstageComponents: bsComponents.length,
|
|
685
|
-
totalPossibleComparisons: totalComparisons,
|
|
686
|
-
matchesFound: matches.length,
|
|
687
|
-
exactMatches,
|
|
688
|
-
highConfidenceMatches: highConfidence,
|
|
689
|
-
mediumConfidenceMatches: mediumConfidence,
|
|
690
|
-
threshold,
|
|
691
|
-
loadTimeMs: loadTime,
|
|
692
|
-
matchTimeMs: matchTime,
|
|
693
|
-
totalTimeMs: loadTime + matchTime
|
|
694
|
-
}
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const bestOnly = request.body.bestOnly ?? false;
|
|
672
|
+
const team = request.body.team;
|
|
673
|
+
const account = request.body.account;
|
|
674
|
+
const job = await autoMatchJobs$1.start({
|
|
675
|
+
threshold,
|
|
676
|
+
bestOnly,
|
|
677
|
+
team,
|
|
678
|
+
account
|
|
679
|
+
});
|
|
680
|
+
response.status(202).json({
|
|
681
|
+
jobId: job.id,
|
|
682
|
+
status: job.status
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
router.get("/mapping/entity/auto-match/:jobId", async (request, response) => {
|
|
686
|
+
const jobId = request.params.jobId;
|
|
687
|
+
const job = await autoMatchJobs$1.get(jobId);
|
|
688
|
+
if (!job) {
|
|
689
|
+
response.status(404).json({
|
|
690
|
+
error: `Auto-match job ${jobId} not found.`
|
|
695
691
|
});
|
|
696
|
-
|
|
697
|
-
logger.error(`Auto-match failed: ${error}`);
|
|
698
|
-
if (error instanceof backstagePluginCommon.HttpError) {
|
|
699
|
-
response.status(error.status).json({
|
|
700
|
-
errors: [`${error.message}`]
|
|
701
|
-
});
|
|
702
|
-
} else if (error instanceof dataLoader.ServiceLoadError) {
|
|
703
|
-
response.status(503).json({
|
|
704
|
-
error: "Service temporarily unavailable",
|
|
705
|
-
message: error.message
|
|
706
|
-
});
|
|
707
|
-
} else {
|
|
708
|
-
response.status(500).json({
|
|
709
|
-
error: "Auto-match failed",
|
|
710
|
-
message: error instanceof Error ? error.message : String(error)
|
|
711
|
-
});
|
|
712
|
-
}
|
|
692
|
+
return;
|
|
713
693
|
}
|
|
694
|
+
response.json({
|
|
695
|
+
jobId: job.id,
|
|
696
|
+
status: job.status,
|
|
697
|
+
createdAt: job.createdAt,
|
|
698
|
+
completedAt: job.completedAt,
|
|
699
|
+
result: job.result,
|
|
700
|
+
error: job.error
|
|
701
|
+
});
|
|
714
702
|
});
|
|
715
703
|
router.get("/escalation_policies", async (_, response) => {
|
|
716
704
|
try {
|
|
@@ -773,7 +761,7 @@ async function createRouter(options) {
|
|
|
773
761
|
);
|
|
774
762
|
return;
|
|
775
763
|
}
|
|
776
|
-
const service = await pagerduty.getServiceById(serviceId, account);
|
|
764
|
+
const service = await pagerduty.getServiceById(serviceId, account, cache);
|
|
777
765
|
const serviceResponse = {
|
|
778
766
|
service
|
|
779
767
|
};
|
|
@@ -828,7 +816,7 @@ async function createRouter(options) {
|
|
|
828
816
|
response.json(services2);
|
|
829
817
|
return;
|
|
830
818
|
}
|
|
831
|
-
const services = await pagerduty.getAllServices();
|
|
819
|
+
const services = await pagerduty.getAllServices(cache);
|
|
832
820
|
const servicesResponse = {
|
|
833
821
|
services
|
|
834
822
|
};
|
|
@@ -864,7 +852,8 @@ async function createRouter(options) {
|
|
|
864
852
|
const integrationKey = await pagerduty.createServiceIntegration({
|
|
865
853
|
serviceId,
|
|
866
854
|
vendorId,
|
|
867
|
-
account
|
|
855
|
+
account,
|
|
856
|
+
cache
|
|
868
857
|
});
|
|
869
858
|
response.json(integrationKey);
|
|
870
859
|
} catch (error) {
|
|
@@ -970,7 +959,7 @@ async function createRouter(options) {
|
|
|
970
959
|
router.get("/health", async (_, response) => {
|
|
971
960
|
response.status(200).json({ status: "ok" });
|
|
972
961
|
});
|
|
973
|
-
router.use(
|
|
962
|
+
router.use(rootHttpRouter.MiddlewareFactory.create({ config, logger }).error());
|
|
974
963
|
return router;
|
|
975
964
|
}
|
|
976
965
|
|