@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.
@@ -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 dataLoader = require('../services/dataLoader.cjs.js');
6
- var matchingEngine = require('../services/matchingEngine.cjs.js');
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 (!isValidSetting(setting.value)) {
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 isValidSetting(value) {
359
- if (value === "backstage" || value === "pagerduty" || value === "both" || value === "disabled") {
360
- return true;
363
+ function isValidSettingValue(id, value) {
364
+ if (id === DATA_SYNC_SETTING_ID) {
365
+ return value === "enabled" || value === "disabled";
361
366
  }
362
- return false;
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(entity.serviceId, entity.account);
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
- try {
629
- const threshold = request.body.threshold ?? 100;
630
- const account = request.body.account;
631
- if (typeof threshold !== "number" || threshold < 0 || threshold > 100) {
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
- const filteredPdServices = account ? pdServices.filter((service) => service.account === account) : pdServices;
643
- const loadTime = Date.now() - loadStartTime;
644
- const matchStartTime = Date.now();
645
- const matchingConfig = { threshold };
646
- let matches = matchingEngine.findMatches(filteredPdServices, bsComponents, matchingConfig);
647
- if (bestOnly) {
648
- matches = matchingEngine.filterToBestMatchPerService(matches);
649
- }
650
- const matchTime = Date.now() - matchStartTime;
651
- const totalComparisons = filteredPdServices.length * bsComponents.length;
652
- const exactMatches = matches.filter((m) => m.score === 100).length;
653
- const highConfidence = matches.filter(
654
- (m) => m.score >= 90 && m.score < 100
655
- ).length;
656
- const mediumConfidence = matches.filter(
657
- (m) => m.score >= 80 && m.score < 90
658
- ).length;
659
- const getConfidenceLevel = (score) => {
660
- if (score === 100) return "exact";
661
- if (score >= 90) return "high";
662
- if (score >= 80) return "medium";
663
- return "low";
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
- } catch (error) {
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(backendCommon.errorHandler());
962
+ router.use(rootHttpRouter.MiddlewareFactory.create({ config, logger }).error());
974
963
  return router;
975
964
  }
976
965