@pagerduty/backstage-plugin-backend 0.11.0 → 0.12.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.
@@ -8,6 +8,8 @@ var backstagePluginCommon = require('@pagerduty/backstage-plugin-common');
8
8
  var auth = require('../auth/auth.cjs.js');
9
9
  var express = require('express');
10
10
  var Router = require('express-promise-router');
11
+ var mappingsController = require('../controllers/mappings-controller.cjs.js');
12
+ var catalogEntity = require('../utils/catalog-entity.cjs.js');
11
13
 
12
14
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
13
15
 
@@ -32,36 +34,6 @@ function _interopNamespaceCompat(e) {
32
34
  var express__namespace = /*#__PURE__*/_interopNamespaceCompat(express);
33
35
  var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
34
36
 
35
- async function createComponentEntitiesReferenceDict({
36
- items: componentEntities
37
- }) {
38
- const componentEntitiesDict = {};
39
- await Promise.all(
40
- componentEntities.map(async (entity) => {
41
- const serviceId = entity.metadata.annotations?.["pagerduty.com/service-id"];
42
- const integrationKey = entity.metadata.annotations?.["pagerduty.com/integration-key"];
43
- const account = entity.metadata.annotations?.["pagerduty.com/account"];
44
- if (serviceId !== void 0 && serviceId !== "") {
45
- componentEntitiesDict[serviceId] = {
46
- ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
47
- name: entity.metadata.name
48
- };
49
- } else if (integrationKey !== void 0 && integrationKey !== "") {
50
- const service = await pagerduty.getServiceByIntegrationKey(
51
- integrationKey,
52
- account
53
- ).catch(() => void 0);
54
- if (service !== void 0) {
55
- componentEntitiesDict[service.id] = {
56
- ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
57
- name: entity.metadata.name
58
- };
59
- }
60
- }
61
- })
62
- );
63
- return componentEntitiesDict;
64
- }
65
37
  async function buildEntityMappingsResponse(entityMappings, componentEntitiesDict, componentEntities, pagerDutyServices) {
66
38
  const result = {
67
39
  mappings: []
@@ -185,6 +157,9 @@ async function createRouter(options) {
185
157
  if (!auth$1) {
186
158
  auth$1 = backendCommon.createLegacyAuthAdapters(options).auth;
187
159
  }
160
+ if (!catalogApi) {
161
+ throw new Error("Catalog API is required to start the PagerDuty plugin backend");
162
+ }
188
163
  await auth.loadAuthConfig(config, logger);
189
164
  pagerduty.loadPagerDutyEndpointsFromConfig(config, logger);
190
165
  const router = Router__default.default();
@@ -439,6 +414,128 @@ async function createRouter(options) {
439
414
  response.status(error.status).json({
440
415
  errors: [`${error.message}`]
441
416
  });
417
+ } else {
418
+ logger.error(
419
+ `Unexpected error occurred while processing request: ${error}`
420
+ );
421
+ response.status(500).json({
422
+ errors: [error instanceof Error ? error.message : String(error)]
423
+ });
424
+ }
425
+ }
426
+ });
427
+ router.post("/mapping/entities/bulk", async (request, response) => {
428
+ try {
429
+ const { mappings } = request.body;
430
+ if (!Array.isArray(mappings)) {
431
+ response.status(400).json({
432
+ error: "Bad Request: 'mappings' must be an array"
433
+ });
434
+ return;
435
+ }
436
+ const existingMappings = await store.getAllEntityMappings();
437
+ const existingServiceIds = new Set(
438
+ existingMappings.map((m) => m.serviceId)
439
+ );
440
+ const newMappings = [];
441
+ const skipped = [];
442
+ const errors = [];
443
+ for (const entity of mappings) {
444
+ if (!entity.serviceId) {
445
+ errors.push({
446
+ entityRef: entity.entityRef,
447
+ error: "Missing serviceId"
448
+ });
449
+ continue;
450
+ }
451
+ if (existingServiceIds.has(entity.serviceId)) {
452
+ skipped.push(entity);
453
+ continue;
454
+ }
455
+ if (entity.entityRef !== "" && (entity.integrationKey === "" || entity.integrationKey === void 0)) {
456
+ try {
457
+ const backstageVendorId = "PRO19CT";
458
+ const service = await pagerduty.getServiceById(
459
+ entity.serviceId,
460
+ entity.account
461
+ );
462
+ const backstageIntegration = service.integrations?.find(
463
+ (integration) => integration.vendor?.id === backstageVendorId
464
+ );
465
+ if (!backstageIntegration) {
466
+ const integrationKey = await pagerduty.createServiceIntegration({
467
+ serviceId: entity.serviceId,
468
+ vendorId: backstageVendorId,
469
+ account: entity.account
470
+ });
471
+ entity.integrationKey = integrationKey;
472
+ } else {
473
+ entity.integrationKey = backstageIntegration.integration_key;
474
+ }
475
+ } catch (error) {
476
+ errors.push({
477
+ entityRef: entity.entityRef,
478
+ serviceId: entity.serviceId,
479
+ error: error instanceof Error ? `Failed to create integration: ${error.message}` : "Failed to create integration"
480
+ });
481
+ continue;
482
+ }
483
+ }
484
+ newMappings.push(entity);
485
+ }
486
+ let insertedIds = [];
487
+ if (newMappings.length > 0) {
488
+ try {
489
+ insertedIds = await store.bulkInsertEntityMappings(newMappings);
490
+ await Promise.all(
491
+ newMappings.map(async (entity) => {
492
+ if (entity.entityRef !== "") {
493
+ await catalogApi?.refreshEntity(entity.entityRef);
494
+ }
495
+ })
496
+ );
497
+ } catch (error) {
498
+ logger.error(`Bulk insert failed: ${error}`);
499
+ response.status(500).json({
500
+ errors: ["Bulk insert failed"]
501
+ });
502
+ return;
503
+ }
504
+ }
505
+ const results = newMappings.map((entity, index) => ({
506
+ id: insertedIds[index],
507
+ entityRef: entity.entityRef,
508
+ integrationKey: entity.integrationKey,
509
+ serviceId: entity.serviceId,
510
+ status: entity.status,
511
+ account: entity.account
512
+ }));
513
+ response.json({
514
+ success: results,
515
+ skipped: skipped.map((entity) => ({
516
+ entityRef: entity.entityRef,
517
+ serviceId: entity.serviceId,
518
+ reason: "Mapping already exists for this service ID"
519
+ })),
520
+ errors,
521
+ total: mappings.length,
522
+ successCount: results.length,
523
+ skippedCount: skipped.length,
524
+ errorCount: errors.length
525
+ });
526
+ } catch (error) {
527
+ if (error instanceof backstagePluginCommon.HttpError) {
528
+ logger.error(
529
+ `Error occurred while processing bulk mappings: ${error.message}`
530
+ );
531
+ response.status(error.status).json({
532
+ errors: [`${error.message}`]
533
+ });
534
+ } else {
535
+ logger.error(`Unexpected error: ${error}`);
536
+ response.status(500).json({
537
+ errors: ["Internal server error"]
538
+ });
442
539
  }
443
540
  }
444
541
  });
@@ -450,7 +547,7 @@ async function createRouter(options) {
450
547
  kind: "Component"
451
548
  }
452
549
  });
453
- const componentEntitiesDict = await createComponentEntitiesReferenceDict(componentEntities);
550
+ const componentEntitiesDict = await catalogEntity.createComponentEntitiesReferenceDict(componentEntities);
454
551
  const pagerDutyServices = await pagerduty.getAllServices();
455
552
  const result = await buildEntityMappingsResponse(
456
553
  entityMappings,
@@ -467,6 +564,7 @@ async function createRouter(options) {
467
564
  }
468
565
  }
469
566
  });
567
+ router.post("/mapping/entities", mappingsController.getMappingEntities(store, catalogApi));
470
568
  router.get(
471
569
  "/mapping/entity/:type/:namespace/:name",
472
570
  async (request, response) => {
@@ -529,6 +627,7 @@ async function createRouter(options) {
529
627
  router.post("/mapping/entity/auto-match", async (request, response) => {
530
628
  try {
531
629
  const threshold = request.body.threshold ?? 100;
630
+ const account = request.body.account;
532
631
  if (typeof threshold !== "number" || threshold < 0 || threshold > 100) {
533
632
  response.status(400).json({
534
633
  error: "Invalid threshold. Must be a number between 0 and 100."
@@ -540,15 +639,16 @@ async function createRouter(options) {
540
639
  const { pdServices, bsComponents } = await dataLoader.loadBothSources({
541
640
  catalogApi
542
641
  });
642
+ const filteredPdServices = account ? pdServices.filter((service) => service.account === account) : pdServices;
543
643
  const loadTime = Date.now() - loadStartTime;
544
644
  const matchStartTime = Date.now();
545
645
  const matchingConfig = { threshold };
546
- let matches = matchingEngine.findMatches(pdServices, bsComponents, matchingConfig);
646
+ let matches = matchingEngine.findMatches(filteredPdServices, bsComponents, matchingConfig);
547
647
  if (bestOnly) {
548
648
  matches = matchingEngine.filterToBestMatchPerService(matches);
549
649
  }
550
650
  const matchTime = Date.now() - matchStartTime;
551
- const totalComparisons = pdServices.length * bsComponents.length;
651
+ const totalComparisons = filteredPdServices.length * bsComponents.length;
552
652
  const exactMatches = matches.filter((m) => m.score === 100).length;
553
653
  const highConfidence = matches.filter(
554
654
  (m) => m.score >= 90 && m.score < 100
@@ -567,7 +667,8 @@ async function createRouter(options) {
567
667
  pagerDutyService: {
568
668
  serviceId: m.pagerDutyService.sourceId,
569
669
  name: m.pagerDutyService.rawName,
570
- team: m.pagerDutyService.teamName
670
+ team: m.pagerDutyService.teamName,
671
+ account: m.pagerDutyService.account
571
672
  },
572
673
  backstageComponent: {
573
674
  entityRef: m.backstageComponent.sourceId,
@@ -579,7 +680,7 @@ async function createRouter(options) {
579
680
  scoreBreakdown: m.scoreBreakdown
580
681
  })),
581
682
  statistics: {
582
- totalPagerDutyServices: pdServices.length,
683
+ totalPagerDutyServices: filteredPdServices.length,
583
684
  totalBackstageComponents: bsComponents.length,
584
685
  totalPossibleComparisons: totalComparisons,
585
686
  matchesFound: matches.length,
@@ -685,31 +786,65 @@ async function createRouter(options) {
685
786
  }
686
787
  }
687
788
  });
789
+ router.get("/teams", async (request, response) => {
790
+ try {
791
+ const account = request.query.account;
792
+ const teams = await pagerduty.getAllTeams(account);
793
+ response.json(teams);
794
+ } catch (error) {
795
+ if (error instanceof backstagePluginCommon.HttpError) {
796
+ response.status(error.status).json({
797
+ errors: [`${error.message}`]
798
+ });
799
+ }
800
+ }
801
+ });
688
802
  router.get("/services", async (request, response) => {
689
803
  try {
690
- const integrationKey = request.query.integration_key || "";
691
- const account = request.query.account || "";
692
- if (integrationKey !== "") {
804
+ const integrationKey = request.query.integration_key;
805
+ const teamId = request.query.team_id;
806
+ const query = request.query.query;
807
+ const limit = request.query.limit ? parseInt(request.query.limit, 10) : void 0;
808
+ const account = request.query.account;
809
+ if (integrationKey) {
693
810
  const service = await pagerduty.getServiceByIntegrationKey(
694
811
  integrationKey,
695
- account
812
+ account || ""
696
813
  );
697
814
  const serviceResponse = {
698
815
  service
699
816
  };
700
817
  response.json(serviceResponse);
701
- } else {
702
- const services = await pagerduty.getAllServices();
703
- const servicesResponse = {
704
- services
705
- };
706
- response.json(servicesResponse);
818
+ return;
707
819
  }
820
+ if (teamId || query || limit) {
821
+ const teamIdsArray = teamId ? [teamId] : void 0;
822
+ const services2 = await pagerduty.getFilteredServices(
823
+ teamIdsArray,
824
+ query,
825
+ limit || 100,
826
+ account
827
+ );
828
+ response.json(services2);
829
+ return;
830
+ }
831
+ const services = await pagerduty.getAllServices();
832
+ const servicesResponse = {
833
+ services
834
+ };
835
+ response.json(servicesResponse);
708
836
  } catch (error) {
709
837
  if (error instanceof backstagePluginCommon.HttpError) {
710
838
  response.status(error.status).json({
711
839
  errors: [`${error.message}`]
712
840
  });
841
+ } else {
842
+ logger.error(
843
+ `Unexpected error occurred while processing request: ${error}`
844
+ );
845
+ response.status(500).json({
846
+ errors: [error instanceof Error ? error.message : String(error)]
847
+ });
713
848
  }
714
849
  }
715
850
  });
@@ -815,6 +950,23 @@ async function createRouter(options) {
815
950
  }
816
951
  }
817
952
  });
953
+ router.get("/accounts", async (_, response) => {
954
+ try {
955
+ const accountsConfig = config.getOptional("pagerDuty.accounts");
956
+ if (accountsConfig && accountsConfig.length > 0) {
957
+ const accounts = accountsConfig.map((account) => ({
958
+ id: account.id,
959
+ isDefault: account.isDefault || false
960
+ }));
961
+ response.status(200).json({ accounts });
962
+ } else {
963
+ response.status(200).json({ accounts: [] });
964
+ }
965
+ } catch (error) {
966
+ logger.error(`Failed to get accounts: ${error}`);
967
+ response.status(500).json({ error: "Failed to get accounts" });
968
+ }
969
+ });
818
970
  router.get("/health", async (_, response) => {
819
971
  response.status(200).json({ status: "ok" });
820
972
  });
@@ -823,6 +975,5 @@ async function createRouter(options) {
823
975
  }
824
976
 
825
977
  exports.buildEntityMappingsResponse = buildEntityMappingsResponse;
826
- exports.createComponentEntitiesReferenceDict = createComponentEntitiesReferenceDict;
827
978
  exports.createRouter = createRouter;
828
979
  //# sourceMappingURL=router.cjs.js.map