@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.
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const DEFAULTS = {
4
+ enabled: true,
5
+ retentionDays: 30,
6
+ maxRows: 5e5,
7
+ frequencyMinutes: 15,
8
+ batchSize: 1e4,
9
+ maxBatchesPerRun: 500
10
+ };
11
+ const CONFIG_ROOT = "pagerDuty.customFieldsSyncLogs";
12
+ const SYNC_LOGS_CLEANUP_TASK_ID = "pagerduty-custom-field-sync-logs-cleanup";
13
+ const SYNC_LOGS_CLEANUP_TIMEOUT_MINUTES = 10;
14
+ const SYNC_LOGS_CLEANUP_INITIAL_DELAY_MINUTES = 1;
15
+ function readSyncLogsCleanupConfig(config) {
16
+ const clamp = (value, fallback, min) => Math.max(Math.floor(value ?? fallback), min);
17
+ return {
18
+ enabled: config.getOptionalBoolean(`${CONFIG_ROOT}.cleanup.enabled`) ?? DEFAULTS.enabled,
19
+ retentionDays: clamp(
20
+ config.getOptionalNumber(`${CONFIG_ROOT}.retentionDays`),
21
+ DEFAULTS.retentionDays,
22
+ 1
23
+ ),
24
+ maxRows: clamp(
25
+ config.getOptionalNumber(`${CONFIG_ROOT}.maxRows`),
26
+ DEFAULTS.maxRows,
27
+ 1e3
28
+ ),
29
+ frequencyMinutes: clamp(
30
+ config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.frequencyMinutes`),
31
+ DEFAULTS.frequencyMinutes,
32
+ 1
33
+ ),
34
+ batchSize: clamp(
35
+ config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.batchSize`),
36
+ DEFAULTS.batchSize,
37
+ 100
38
+ ),
39
+ maxBatchesPerRun: clamp(
40
+ config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.maxBatchesPerRun`),
41
+ DEFAULTS.maxBatchesPerRun,
42
+ 1
43
+ )
44
+ };
45
+ }
46
+ async function runSyncLogsCleanup(options) {
47
+ const { store, logger, cleanupConfig } = options;
48
+ const startedAt = Date.now();
49
+ try {
50
+ const result = await store.cleanupSyncLogs({
51
+ olderThanDays: cleanupConfig.retentionDays,
52
+ maxRows: cleanupConfig.maxRows,
53
+ batchSize: cleanupConfig.batchSize,
54
+ maxBatchesPerRun: cleanupConfig.maxBatchesPerRun
55
+ });
56
+ const durationMs = Date.now() - startedAt;
57
+ logger.info(
58
+ `Sync log cleanup: deleted ${result.deletedByAge} expired and ${result.deletedByCap} over-cap row(s) in ${result.batchesUsed} batch(es) (${durationMs}ms)`
59
+ );
60
+ if (result.batchesUsed >= cleanupConfig.maxBatchesPerRun) {
61
+ logger.warn(
62
+ `Sync log cleanup exhausted its batch budget (${cleanupConfig.maxBatchesPerRun} batches of ${cleanupConfig.batchSize}); a backlog remains. To catch up faster, increase pagerDuty.customFieldsSyncLogs.cleanup.maxBatchesPerRun or decrease pagerDuty.customFieldsSyncLogs.cleanup.frequencyMinutes.`
63
+ );
64
+ }
65
+ } catch (error) {
66
+ logger.error(
67
+ `Sync log cleanup failed: ${error instanceof Error ? error.message : String(error)}`
68
+ );
69
+ }
70
+ }
71
+
72
+ exports.SYNC_LOGS_CLEANUP_INITIAL_DELAY_MINUTES = SYNC_LOGS_CLEANUP_INITIAL_DELAY_MINUTES;
73
+ exports.SYNC_LOGS_CLEANUP_TASK_ID = SYNC_LOGS_CLEANUP_TASK_ID;
74
+ exports.SYNC_LOGS_CLEANUP_TIMEOUT_MINUTES = SYNC_LOGS_CLEANUP_TIMEOUT_MINUTES;
75
+ exports.readSyncLogsCleanupConfig = readSyncLogsCleanupConfig;
76
+ exports.runSyncLogsCleanup = runSyncLogsCleanup;
77
+ //# sourceMappingURL=syncLogsCleanup.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncLogsCleanup.cjs.js","sources":["../../src/services/syncLogsCleanup.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { PagerDutyBackendStore } from '../db';\n\nexport interface SyncLogsCleanupConfig {\n enabled: boolean;\n retentionDays: number;\n maxRows: number;\n frequencyMinutes: number;\n batchSize: number;\n maxBatchesPerRun: number;\n}\n\nconst DEFAULTS: SyncLogsCleanupConfig = {\n enabled: true,\n retentionDays: 30,\n maxRows: 500_000,\n frequencyMinutes: 15,\n batchSize: 10_000,\n maxBatchesPerRun: 500,\n};\n\nconst CONFIG_ROOT = 'pagerDuty.customFieldsSyncLogs';\n\nexport const SYNC_LOGS_CLEANUP_TASK_ID =\n 'pagerduty-custom-field-sync-logs-cleanup';\nexport const SYNC_LOGS_CLEANUP_TIMEOUT_MINUTES = 10;\nexport const SYNC_LOGS_CLEANUP_INITIAL_DELAY_MINUTES = 1;\n\nexport function readSyncLogsCleanupConfig(\n config: RootConfigService,\n): SyncLogsCleanupConfig {\n const clamp = (value: number | undefined, fallback: number, min: number) =>\n Math.max(Math.floor(value ?? fallback), min);\n\n return {\n enabled:\n config.getOptionalBoolean(`${CONFIG_ROOT}.cleanup.enabled`) ??\n DEFAULTS.enabled,\n retentionDays: clamp(\n config.getOptionalNumber(`${CONFIG_ROOT}.retentionDays`),\n DEFAULTS.retentionDays,\n 1,\n ),\n maxRows: clamp(\n config.getOptionalNumber(`${CONFIG_ROOT}.maxRows`),\n DEFAULTS.maxRows,\n 1000,\n ),\n frequencyMinutes: clamp(\n config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.frequencyMinutes`),\n DEFAULTS.frequencyMinutes,\n 1,\n ),\n batchSize: clamp(\n config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.batchSize`),\n DEFAULTS.batchSize,\n 100,\n ),\n maxBatchesPerRun: clamp(\n config.getOptionalNumber(`${CONFIG_ROOT}.cleanup.maxBatchesPerRun`),\n DEFAULTS.maxBatchesPerRun,\n 1,\n ),\n };\n}\n\nexport async function runSyncLogsCleanup(options: {\n store: PagerDutyBackendStore;\n logger: LoggerService;\n cleanupConfig: SyncLogsCleanupConfig;\n}): Promise<void> {\n const { store, logger, cleanupConfig } = options;\n const startedAt = Date.now();\n\n try {\n const result = await store.cleanupSyncLogs({\n olderThanDays: cleanupConfig.retentionDays,\n maxRows: cleanupConfig.maxRows,\n batchSize: cleanupConfig.batchSize,\n maxBatchesPerRun: cleanupConfig.maxBatchesPerRun,\n });\n\n const durationMs = Date.now() - startedAt;\n logger.info(\n `Sync log cleanup: deleted ${result.deletedByAge} expired and ${result.deletedByCap} over-cap row(s) in ${result.batchesUsed} batch(es) (${durationMs}ms)`,\n );\n\n if (result.batchesUsed >= cleanupConfig.maxBatchesPerRun) {\n logger.warn(\n `Sync log cleanup exhausted its batch budget (${cleanupConfig.maxBatchesPerRun} batches of ${cleanupConfig.batchSize}); ` +\n `a backlog remains. To catch up faster, increase pagerDuty.customFieldsSyncLogs.cleanup.maxBatchesPerRun ` +\n `or decrease pagerDuty.customFieldsSyncLogs.cleanup.frequencyMinutes.`,\n );\n }\n } catch (error) {\n // A failed cleanup must never crash the backend; the scheduler retries on\n // the next cycle.\n logger.error(\n `Sync log cleanup failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @param { import("knex").Knex } knex
3
+ * @returns { Promise<void> }
4
+ */
5
+
6
+
7
+ exports.up = async function up(knex) {
8
+ return knex.schema.createTable('pagerduty_custom_fields', table => {
9
+ table.increments('id').primary();
10
+ table.string('pagerdutyCustomFieldId').notNullable();
11
+ table.string('pagerdutyCustomFieldDisplayName').notNullable();
12
+ table.boolean('pagerdutyCustomFieldEnabled').notNullable().defaultTo(true);
13
+ table.string('backstageEntityMappingPath').notNullable();
14
+ table.string('pagerdutySubdomain').notNullable();
15
+ table.string('description');
16
+ table.index(['pagerdutyCustomFieldId'], 'pagerduty_custom_field_id_idx');
17
+ table.dateTime('updatedAt').defaultTo(knex.fn.now());
18
+ table.dateTime('createdAt').defaultTo(knex.fn.now());
19
+ });
20
+ };
21
+
22
+ /**
23
+ * @param { import("knex").Knex } knex
24
+ * @returns { Promise<void> }
25
+ */
26
+ exports.down = async function down(knex) {
27
+ await knex.schema.alterTable('pagerduty_custom_fields', table => {
28
+ table.dropIndex([], 'pagerduty_custom_field_id_idx');
29
+ });
30
+ return knex.schema.dropTable('pagerduty_custom_fields');
31
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @param { import("knex").Knex } knex
3
+ * @returns { Promise<void> }
4
+ */
5
+ exports.up = async function up(knex) {
6
+ await knex.schema.alterTable('pagerduty_custom_fields', table => {
7
+ table.unique(
8
+ ['backstageEntityMappingPath', 'pagerdutySubdomain'],
9
+ { indexName: 'pagerduty_cf_entitypath_subdomain_unique' },
10
+ );
11
+ table.unique(
12
+ ['pagerdutyCustomFieldDisplayName', 'pagerdutySubdomain'],
13
+ { indexName: 'pagerduty_cf_displayname_subdomain_unique' },
14
+ );
15
+ });
16
+ };
17
+
18
+ /**
19
+ * @param { import("knex").Knex } knex
20
+ * @returns { Promise<void> }
21
+ */
22
+ exports.down = async function down(knex) {
23
+ await knex.schema.alterTable('pagerduty_custom_fields', table => {
24
+ table.dropUnique([], 'pagerduty_cf_entitypath_subdomain_unique');
25
+ table.dropUnique([], 'pagerduty_cf_displayname_subdomain_unique');
26
+ });
27
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @param { import("knex").Knex } knex
3
+ * @returns { Promise<void> }
4
+ */
5
+ exports.up = async function up(knex) {
6
+ return knex.schema.createTable('pagerduty_custom_field_sync_logs', table => {
7
+ table.increments('id').primary();
8
+ table.dateTime('timestamp').notNullable().defaultTo(knex.fn.now());
9
+ table.string('errorCode').notNullable();
10
+ table.string('customFieldId').notNullable();
11
+ table.string('customFieldName').notNullable();
12
+ table.string('entityPath').notNullable();
13
+ table.string('serviceId').notNullable();
14
+ table.string('serviceName').notNullable();
15
+ table.text('errorMessage').notNullable();
16
+ table.string('subdomain').notNullable();
17
+ table.index(['subdomain', 'timestamp'], 'sync_logs_subdomain_timestamp_idx');
18
+ table.index(['serviceId'], 'sync_logs_service_id_idx');
19
+ });
20
+ };
21
+
22
+ /**
23
+ * @param { import("knex").Knex } knex
24
+ * @returns { Promise<void> }
25
+ */
26
+ exports.down = async function down(knex) {
27
+ await knex.schema.alterTable('pagerduty_custom_field_sync_logs', table => {
28
+ table.dropIndex([], 'sync_logs_subdomain_timestamp_idx');
29
+ table.dropIndex([], 'sync_logs_service_id_idx');
30
+ });
31
+ return knex.schema.dropTable('pagerduty_custom_field_sync_logs');
32
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @param { import("knex").Knex } knex
3
+ * @returns { Promise<void> }
4
+ */
5
+ exports.up = async function up(knex) {
6
+ return knex.schema.alterTable('pagerduty_custom_field_sync_logs', table => {
7
+ table.index(['timestamp'], 'sync_logs_timestamp_idx');
8
+ });
9
+ };
10
+
11
+ /**
12
+ * @param { import("knex").Knex } knex
13
+ * @returns { Promise<void> }
14
+ */
15
+ exports.down = async function down(knex) {
16
+ return knex.schema.alterTable('pagerduty_custom_field_sync_logs', table => {
17
+ table.dropIndex([], 'sync_logs_timestamp_idx');
18
+ });
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagerduty/backstage-plugin-backend",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -37,10 +37,10 @@
37
37
  "postpack": "backstage-cli package postpack"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/backend-defaults": "^0.15.1",
41
- "@backstage/backend-plugin-api": "^1.6.2",
42
- "@backstage/catalog-client": "^1.12.1",
43
- "@pagerduty/backstage-plugin-common": "~0.4.0",
40
+ "@backstage/backend-defaults": "^0.17.2",
41
+ "@backstage/backend-plugin-api": "^1.9.1",
42
+ "@backstage/catalog-client": "^1.15.2",
43
+ "@pagerduty/backstage-plugin-common": "~0.5.0",
44
44
  "@skyra/jaro-winkler": "^1.1.1",
45
45
  "@types/express": "^4.17.6",
46
46
  "express": "^4.20.0",
@@ -51,8 +51,8 @@
51
51
  "uuid": "^9.0.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@backstage/backend-test-utils": "^1.10.4",
55
- "@backstage/cli": "^0.35.3",
54
+ "@backstage/backend-test-utils": "^1.11.3",
55
+ "@backstage/cli": "^0.36.2",
56
56
  "jest-mock": "^30.0.2",
57
57
  "supertest": "^7.1.3"
58
58
  },