@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.7.7 → 2.7.9

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,25 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-scorecard-backend
2
2
 
3
+ ## 2.7.9
4
+
5
+ ### Patch Changes
6
+
7
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@2.7.9
8
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@2.7.9
9
+
10
+ ## 2.7.8
11
+
12
+ ### Patch Changes
13
+
14
+ - 6699550: Custom thresholds for filecheck, openssf, and dependabot are now
15
+ configurable. Custom threshold handling has been centralized in
16
+ `scorecard-backend`, you can define custom thresholds under
17
+ `scorecard.plugins.<providerId>.thresholds`. Provider IDs typically
18
+ follow the format `<datasource>.<metric>`.
19
+ - Updated dependencies [6699550]
20
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@2.7.8
21
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@2.7.8
22
+
3
23
  ## 2.7.7
4
24
 
5
25
  ### Patch Changes
@@ -14,6 +14,7 @@ var DatabaseMetricValues = require('./database/DatabaseMetricValues.cjs.js');
14
14
  var index = require('./scheduler/index.cjs.js');
15
15
  var validateAggregationConfig = require('./validation/validateAggregationConfig.cjs.js');
16
16
  var AggregationService = require('./service/aggregations/AggregationService.cjs.js');
17
+ var ThresholdResolver = require('./threshold/ThresholdResolver.cjs.js');
17
18
 
18
19
  const scorecardPlugin = backendPluginApi.createBackendPlugin({
19
20
  pluginId: "scorecard",
@@ -62,12 +63,17 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
62
63
  await migration.migrate(database);
63
64
  const client = await database.getClient();
64
65
  const dbMetricValues = new DatabaseMetricValues.DatabaseMetricValues(client);
66
+ const thresholdResolver = new ThresholdResolver.ThresholdResolver(
67
+ config,
68
+ metricProvidersRegistry.listProviders()
69
+ );
65
70
  const catalogMetricService = new CatalogMetricService.CatalogMetricService({
66
71
  catalog,
67
72
  auth,
68
73
  registry: metricProvidersRegistry,
69
74
  database: dbMetricValues,
70
- logger
75
+ logger,
76
+ thresholdResolver
71
77
  });
72
78
  const aggregationsService = new AggregationService.AggregationsService({
73
79
  config,
@@ -86,7 +92,8 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
86
92
  scheduler,
87
93
  database: dbMetricValues,
88
94
  metricProvidersRegistry,
89
- thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator()
95
+ thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator(),
96
+ thresholdResolver
90
97
  }).start();
91
98
  const service = {
92
99
  aggregationsService,
@@ -99,7 +106,8 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
99
106
  catalog,
100
107
  httpAuth,
101
108
  permissions,
102
- logger
109
+ logger,
110
+ thresholdResolver
103
111
  })
104
112
  );
105
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationService';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createRouter"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationService';\nimport { ThresholdResolver } from './threshold/ThresholdResolver';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n const thresholdResolver = new ThresholdResolver(\n config,\n metricProvidersRegistry.listProviders(),\n );\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n thresholdResolver,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n thresholdResolver,\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createRouter"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
+ var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
4
5
 
5
6
  class MetricProvidersRegistry {
6
7
  metricProviders = /* @__PURE__ */ new Map();
@@ -8,9 +9,8 @@ class MetricProvidersRegistry {
8
9
  register(metricProvider) {
9
10
  const providerDatasource = metricProvider.getProviderDatasourceId();
10
11
  const metricType = metricProvider.getMetricType();
11
- const metricIds = metricProvider.getMetricIds?.() ?? [
12
- metricProvider.getProviderId()
13
- ];
12
+ const providerId = metricProvider.getProviderId();
13
+ const metricIds = metricProvider.getMetricIds?.() ?? [providerId];
14
14
  const metrics = metricProvider.getMetrics?.() ?? [
15
15
  metricProvider.getMetric()
16
16
  ];
@@ -37,6 +37,19 @@ class MetricProvidersRegistry {
37
37
  `Metric provider with ID '${metricId}' has already been registered`
38
38
  );
39
39
  }
40
+ }
41
+ try {
42
+ backstagePluginScorecardNode.validateThresholdsForMetric(
43
+ metricProvider.getMetricThresholds(),
44
+ metricType
45
+ );
46
+ } catch (error) {
47
+ throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
48
+ `Invalid default thresholds for metric provider '${providerId}'`,
49
+ error
50
+ );
51
+ }
52
+ for (const metricId of metricIds) {
40
53
  this.metricProviders.set(metricId, metricProvider);
41
54
  let datasourceProviders = this.datasourceIndex.get(providerDatasource);
42
55
  if (!datasourceProviders) {
@@ -1 +1 @@
1
- {"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metricType = metricProvider.getMetricType();\n\n // Support both single and batch providers\n const metricIds = metricProvider.getMetricIds?.() ?? [\n metricProvider.getProviderId(),\n ];\n const metrics = metricProvider.getMetrics?.() ?? [\n metricProvider.getMetric(),\n ];\n\n // Validate: Each metric ID must have a corresponding metric definition\n for (const metricId of metricIds) {\n const metric = metrics.find(m => m.id === metricId);\n if (!metric) {\n throw new Error(\n `Invalid metric provider: metric ID '${metricId}' returned by getMetricIds() ` +\n `does not have a corresponding metric in getMetrics()`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, getMetricType() must match ` +\n `getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n // Validate: Provider ID format (datasource.metric_name)\n const expectedPrefix = `${providerDatasource}.`;\n if (!metricId.startsWith(expectedPrefix) || metricId === expectedPrefix) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, must have format ` +\n `'${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(metricId)) {\n throw new ConflictError(\n `Metric provider with ID '${metricId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(metricId, metricProvider);\n\n // Index by datasource\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(metricId);\n }\n }\n\n getProvider(metricId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(metricId);\n if (!metricProvider) {\n throw new NotFoundError(\n `No metric provider registered for metric ID '${metricId}'.`,\n );\n }\n return metricProvider;\n }\n\n hasProvider(providerId: string): boolean {\n return this.metricProviders.has(providerId);\n }\n\n getMetric(metricId: string): Metric {\n const provider = this.getProvider(metricId);\n\n // For batch providers, find the specific metric by ID\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n const metric = metrics.find(m => m.id === metricId);\n if (metric) {\n return metric;\n }\n }\n\n return provider.getMetric();\n }\n\n async calculateMetric(\n metricId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(metricId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n metricIds: string[],\n entity: Entity,\n ): Promise<{ metricId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n metricIds.map(metricId => this.calculateMetric(metricId, entity)),\n );\n\n return results.map((result, index) => {\n const metricId = metricIds[index];\n if (result.status === 'fulfilled') {\n return { metricId, value: result.value };\n }\n return { metricId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n // Deduplicate providers since batch providers are stored under multiple metric IDs\n return [...new Set(this.metricProviders.values())];\n }\n\n listMetrics(metricIds?: string[]): Metric[] {\n if (metricIds && metricIds.length !== 0) {\n return metricIds\n .map(metricId => {\n const provider = this.metricProviders.get(metricId);\n if (!provider) return undefined;\n\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n return metrics.find(m => m.id === metricId);\n }\n\n return provider.getMetric();\n })\n .filter((m): m is Metric => m !== undefined);\n }\n\n // List all metrics from all providers (deduplicate batch providers)\n return this.listProviders().flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n // Get unique providers for this datasource, then get their metrics\n const providers = [...providerIdsOfDatasource]\n .map(id => this.metricProviders.get(id))\n .filter((p): p is MetricProvider => p !== undefined);\n\n return [...new Set(providers)].flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":"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"}
1
+ {"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Entity } from '@backstage/catalog-model';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n MetricProvider,\n ThresholdConfigFormatError,\n validateThresholdsForMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metricType = metricProvider.getMetricType();\n const providerId = metricProvider.getProviderId();\n\n // Support both single and batch providers\n const metricIds = metricProvider.getMetricIds?.() ?? [providerId];\n const metrics = metricProvider.getMetrics?.() ?? [\n metricProvider.getMetric(),\n ];\n\n // Validate: Each metric ID must have a corresponding metric definition\n for (const metricId of metricIds) {\n const metric = metrics.find(m => m.id === metricId);\n if (!metric) {\n throw new Error(\n `Invalid metric provider: metric ID '${metricId}' returned by getMetricIds() ` +\n `does not have a corresponding metric in getMetrics()`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, getMetricType() must match ` +\n `getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n // Validate: Provider ID format (datasource.metric_name)\n const expectedPrefix = `${providerDatasource}.`;\n if (!metricId.startsWith(expectedPrefix) || metricId === expectedPrefix) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, must have format ` +\n `'${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(metricId)) {\n throw new ConflictError(\n `Metric provider with ID '${metricId}' has already been registered`,\n );\n }\n }\n\n try {\n validateThresholdsForMetric(\n metricProvider.getMetricThresholds(),\n metricType,\n );\n } catch (error) {\n throw new ThresholdConfigFormatError(\n `Invalid default thresholds for metric provider '${providerId}'`,\n error,\n );\n }\n\n for (const metricId of metricIds) {\n this.metricProviders.set(metricId, metricProvider);\n\n // Index by datasource\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(metricId);\n }\n }\n\n getProvider(metricId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(metricId);\n if (!metricProvider) {\n throw new NotFoundError(\n `No metric provider registered for metric ID '${metricId}'.`,\n );\n }\n return metricProvider;\n }\n\n hasProvider(providerId: string): boolean {\n return this.metricProviders.has(providerId);\n }\n\n getMetric(metricId: string): Metric {\n const provider = this.getProvider(metricId);\n\n // For batch providers, find the specific metric by ID\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n const metric = metrics.find(m => m.id === metricId);\n if (metric) {\n return metric;\n }\n }\n\n return provider.getMetric();\n }\n\n async calculateMetric(\n metricId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(metricId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n metricIds: string[],\n entity: Entity,\n ): Promise<{ metricId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n metricIds.map(metricId => this.calculateMetric(metricId, entity)),\n );\n\n return results.map((result, index) => {\n const metricId = metricIds[index];\n if (result.status === 'fulfilled') {\n return { metricId, value: result.value };\n }\n return { metricId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n // Deduplicate providers since batch providers are stored under multiple metric IDs\n return [...new Set(this.metricProviders.values())];\n }\n\n listMetrics(metricIds?: string[]): Metric[] {\n if (metricIds && metricIds.length !== 0) {\n return metricIds\n .map(metricId => {\n const provider = this.metricProviders.get(metricId);\n if (!provider) return undefined;\n\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n return metrics.find(m => m.id === metricId);\n }\n\n return provider.getMetric();\n })\n .filter((m): m is Metric => m !== undefined);\n }\n\n // List all metrics from all providers (deduplicate batch providers)\n return this.listProviders().flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n // Get unique providers for this datasource, then get their metrics\n const providers = [...providerIdsOfDatasource]\n .map(id => this.metricProviders.get(id))\n .filter((p): p is MetricProvider => p !== undefined);\n\n return [...new Set(providers)].flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n}\n"],"names":["ConflictError","validateThresholdsForMetric","ThresholdConfigFormatError","NotFoundError"],"mappings":"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"}
@@ -13,6 +13,7 @@ class Scheduler {
13
13
  database;
14
14
  metricProvidersRegistry;
15
15
  thresholdEvaluator;
16
+ thresholdResolver;
16
17
  tasks = [];
17
18
  constructor(options) {
18
19
  this.auth = options.auth;
@@ -23,6 +24,7 @@ class Scheduler {
23
24
  this.database = options.database;
24
25
  this.metricProvidersRegistry = options.metricProvidersRegistry;
25
26
  this.thresholdEvaluator = options.thresholdEvaluator;
27
+ this.thresholdResolver = options.thresholdResolver;
26
28
  }
27
29
  static create(options) {
28
30
  return new Scheduler(options);
@@ -71,7 +73,8 @@ class Scheduler {
71
73
  config: this.config,
72
74
  catalog: this.catalog,
73
75
  auth: this.auth,
74
- thresholdEvaluator: this.thresholdEvaluator
76
+ thresholdEvaluator: this.thresholdEvaluator,
77
+ thresholdResolver: this.thresholdResolver
75
78
  },
76
79
  provider
77
80
  )
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../src/scheduler/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport type { Config } from '@backstage/config';\nimport { CLEANUP_EXPIRED_METRICS_ID } from './constants';\nimport { CleanupExpiredMetricsTask } from './tasks/CleanupExpiredMetricsTask';\nimport { PullMetricsByProviderTask } from './tasks/PullMetricsByProviderTask';\nimport { SchedulerOptions, SchedulerTask } from './types';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { ThresholdEvaluator } from '../threshold/ThresholdEvaluator';\n\nexport class Scheduler {\n private readonly auth: AuthService;\n private readonly catalog: CatalogService;\n private readonly config: Config;\n private readonly logger: LoggerService;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly metricProvidersRegistry: MetricProvidersRegistry;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private tasks: Array<{ name: string; task: SchedulerTask }> = [];\n\n private constructor(options: SchedulerOptions) {\n this.auth = options.auth;\n this.catalog = options.catalog;\n this.config = options.config;\n this.logger = options.logger;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.metricProvidersRegistry = options.metricProvidersRegistry;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n static create(options: SchedulerOptions): Scheduler {\n return new Scheduler(options);\n }\n\n async start(): Promise<void> {\n this.initializeTasks();\n this.initializeTasksByProviders();\n\n const results = await Promise.allSettled(\n this.tasks.map(({ name, task }) => this.startTask(name, task)),\n );\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n\n let index = 0;\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.logger.warn(\n `Failed to start task '${this.tasks[index].name}': ${result.reason}`,\n );\n }\n index++;\n }\n\n this.logger.info(`Scheduled: ${successCount}/${this.tasks.length} tasks`);\n }\n\n private initializeTasks(): void {\n this.tasks = [\n {\n name: CLEANUP_EXPIRED_METRICS_ID,\n task: new CleanupExpiredMetricsTask({\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n }),\n },\n ];\n }\n\n private initializeTasksByProviders(): void {\n const providers = this.metricProvidersRegistry.listProviders();\n\n for (const provider of providers) {\n this.tasks.push({\n name: provider.getProviderId(),\n task: new PullMetricsByProviderTask(\n {\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n catalog: this.catalog,\n auth: this.auth,\n thresholdEvaluator: this.thresholdEvaluator,\n },\n provider,\n ),\n });\n }\n }\n\n private async startTask(name: string, task: SchedulerTask): Promise<void> {\n try {\n await task.start();\n } catch (error) {\n this.logger.error(`Failed to start task '${name}': ${error}`, error);\n throw error;\n }\n }\n}\n"],"names":["CLEANUP_EXPIRED_METRICS_ID","CleanupExpiredMetricsTask","PullMetricsByProviderTask"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../src/scheduler/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport type { Config } from '@backstage/config';\nimport { CLEANUP_EXPIRED_METRICS_ID } from './constants';\nimport { CleanupExpiredMetricsTask } from './tasks/CleanupExpiredMetricsTask';\nimport { PullMetricsByProviderTask } from './tasks/PullMetricsByProviderTask';\nimport { SchedulerOptions, SchedulerTask } from './types';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { ThresholdEvaluator } from '../threshold/ThresholdEvaluator';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\n\nexport class Scheduler {\n private readonly auth: AuthService;\n private readonly catalog: CatalogService;\n private readonly config: Config;\n private readonly logger: LoggerService;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly metricProvidersRegistry: MetricProvidersRegistry;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n private readonly thresholdResolver: ThresholdResolver;\n\n private tasks: Array<{ name: string; task: SchedulerTask }> = [];\n\n private constructor(options: SchedulerOptions) {\n this.auth = options.auth;\n this.catalog = options.catalog;\n this.config = options.config;\n this.logger = options.logger;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.metricProvidersRegistry = options.metricProvidersRegistry;\n this.thresholdEvaluator = options.thresholdEvaluator;\n this.thresholdResolver = options.thresholdResolver;\n }\n\n static create(options: SchedulerOptions): Scheduler {\n return new Scheduler(options);\n }\n\n async start(): Promise<void> {\n this.initializeTasks();\n this.initializeTasksByProviders();\n\n const results = await Promise.allSettled(\n this.tasks.map(({ name, task }) => this.startTask(name, task)),\n );\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n\n let index = 0;\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.logger.warn(\n `Failed to start task '${this.tasks[index].name}': ${result.reason}`,\n );\n }\n index++;\n }\n\n this.logger.info(`Scheduled: ${successCount}/${this.tasks.length} tasks`);\n }\n\n private initializeTasks(): void {\n this.tasks = [\n {\n name: CLEANUP_EXPIRED_METRICS_ID,\n task: new CleanupExpiredMetricsTask({\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n }),\n },\n ];\n }\n\n private initializeTasksByProviders(): void {\n const providers = this.metricProvidersRegistry.listProviders();\n\n for (const provider of providers) {\n this.tasks.push({\n name: provider.getProviderId(),\n task: new PullMetricsByProviderTask(\n {\n scheduler: this.scheduler,\n logger: this.logger,\n database: this.database,\n config: this.config,\n catalog: this.catalog,\n auth: this.auth,\n thresholdEvaluator: this.thresholdEvaluator,\n thresholdResolver: this.thresholdResolver,\n },\n provider,\n ),\n });\n }\n }\n\n private async startTask(name: string, task: SchedulerTask): Promise<void> {\n try {\n await task.start();\n } catch (error) {\n this.logger.error(`Failed to start task '${name}': ${error}`, error);\n throw error;\n }\n }\n}\n"],"names":["CLEANUP_EXPIRED_METRICS_ID","CleanupExpiredMetricsTask","PullMetricsByProviderTask"],"mappings":";;;;;;AAgCO,MAAM,SAAU,CAAA;AAAA,EACJ,IAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,uBAAA;AAAA,EACA,kBAAA;AAAA,EACA,iBAAA;AAAA,EAET,QAAsD,EAAC;AAAA,EAEvD,YAAY,OAA2B,EAAA;AAC7C,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,0BAA0B,OAAQ,CAAA,uBAAA;AACvC,IAAA,IAAA,CAAK,qBAAqB,OAAQ,CAAA,kBAAA;AAClC,IAAA,IAAA,CAAK,oBAAoB,OAAQ,CAAA,iBAAA;AAAA;AACnC,EAEA,OAAO,OAAO,OAAsC,EAAA;AAClD,IAAO,OAAA,IAAI,UAAU,OAAO,CAAA;AAAA;AAC9B,EAEA,MAAM,KAAuB,GAAA;AAC3B,IAAA,IAAA,CAAK,eAAgB,EAAA;AACrB,IAAA,IAAA,CAAK,0BAA2B,EAAA;AAEhC,IAAM,MAAA,OAAA,GAAU,MAAM,OAAQ,CAAA,UAAA;AAAA,MAC5B,IAAK,CAAA,KAAA,CAAM,GAAI,CAAA,CAAC,EAAE,IAAA,EAAM,IAAK,EAAA,KAAM,IAAK,CAAA,SAAA,CAAU,IAAM,EAAA,IAAI,CAAC;AAAA,KAC/D;AAEA,IAAA,MAAM,eAAe,OAAQ,CAAA,MAAA,CAAO,OAAK,CAAE,CAAA,MAAA,KAAW,WAAW,CAAE,CAAA,MAAA;AAEnE,IAAA,IAAI,KAAQ,GAAA,CAAA;AAEZ,IAAA,KAAA,MAAW,UAAU,OAAS,EAAA;AAC5B,MAAI,IAAA,MAAA,CAAO,WAAW,UAAY,EAAA;AAChC,QAAA,IAAA,CAAK,MAAO,CAAA,IAAA;AAAA,UACV,CAAA,sBAAA,EAAyB,KAAK,KAAM,CAAA,KAAK,EAAE,IAAI,CAAA,GAAA,EAAM,OAAO,MAAM,CAAA;AAAA,SACpE;AAAA;AAEF,MAAA,KAAA,EAAA;AAAA;AAGF,IAAK,IAAA,CAAA,MAAA,CAAO,KAAK,CAAc,WAAA,EAAA,YAAY,IAAI,IAAK,CAAA,KAAA,CAAM,MAAM,CAAQ,MAAA,CAAA,CAAA;AAAA;AAC1E,EAEQ,eAAwB,GAAA;AAC9B,IAAA,IAAA,CAAK,KAAQ,GAAA;AAAA,MACX;AAAA,QACE,IAAM,EAAAA,oCAAA;AAAA,QACN,IAAA,EAAM,IAAIC,mDAA0B,CAAA;AAAA,UAClC,WAAW,IAAK,CAAA,SAAA;AAAA,UAChB,QAAQ,IAAK,CAAA,MAAA;AAAA,UACb,UAAU,IAAK,CAAA,QAAA;AAAA,UACf,QAAQ,IAAK,CAAA;AAAA,SACd;AAAA;AACH,KACF;AAAA;AACF,EAEQ,0BAAmC,GAAA;AACzC,IAAM,MAAA,SAAA,GAAY,IAAK,CAAA,uBAAA,CAAwB,aAAc,EAAA;AAE7D,IAAA,KAAA,MAAW,YAAY,SAAW,EAAA;AAChC,MAAA,IAAA,CAAK,MAAM,IAAK,CAAA;AAAA,QACd,IAAA,EAAM,SAAS,aAAc,EAAA;AAAA,QAC7B,MAAM,IAAIC,mDAAA;AAAA,UACR;AAAA,YACE,WAAW,IAAK,CAAA,SAAA;AAAA,YAChB,QAAQ,IAAK,CAAA,MAAA;AAAA,YACb,UAAU,IAAK,CAAA,QAAA;AAAA,YACf,QAAQ,IAAK,CAAA,MAAA;AAAA,YACb,SAAS,IAAK,CAAA,OAAA;AAAA,YACd,MAAM,IAAK,CAAA,IAAA;AAAA,YACX,oBAAoB,IAAK,CAAA,kBAAA;AAAA,YACzB,mBAAmB,IAAK,CAAA;AAAA,WAC1B;AAAA,UACA;AAAA;AACF,OACD,CAAA;AAAA;AACH;AACF,EAEA,MAAc,SAAU,CAAA,IAAA,EAAc,IAAoC,EAAA;AACxE,IAAI,IAAA;AACF,MAAA,MAAM,KAAK,KAAM,EAAA;AAAA,aACV,KAAO,EAAA;AACd,MAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,sBAAA,EAAyB,IAAI,CAAM,GAAA,EAAA,KAAK,IAAI,KAAK,CAAA;AACnE,MAAM,MAAA,KAAA;AAAA;AACR;AAEJ;;;;"}
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
- var mergeEntityAndProviderThresholds = require('../../utils/mergeEntityAndProviderThresholds.cjs.js');
5
4
  var metricUtils = require('../../utils/metricUtils.cjs.js');
6
5
  var normalizeOwnerRef = require('../../utils/normalizeOwnerRef.cjs.js');
7
6
  var uuid = require('uuid');
@@ -17,6 +16,7 @@ class PullMetricsByProviderTask {
17
16
  scheduler;
18
17
  database;
19
18
  thresholdEvaluator;
19
+ thresholdResolver;
20
20
  static CATALOG_BATCH_SIZE = 50;
21
21
  static DEFAULT_SCHEDULE = {
22
22
  frequency: { hours: 1 },
@@ -33,6 +33,7 @@ class PullMetricsByProviderTask {
33
33
  this.scheduler = options.scheduler;
34
34
  this.database = options.database;
35
35
  this.thresholdEvaluator = options.thresholdEvaluator;
36
+ this.thresholdResolver = options.thresholdResolver;
36
37
  }
37
38
  async start() {
38
39
  const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;
@@ -110,7 +111,7 @@ class PullMetricsByProviderTask {
110
111
  }
111
112
  const value2 = resultsMap.get(metricId);
112
113
  try {
113
- const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
114
+ const thresholds = this.thresholdResolver.resolveEntityThresholds(
114
115
  entity,
115
116
  provider
116
117
  );
@@ -168,7 +169,7 @@ class PullMetricsByProviderTask {
168
169
  return void 0;
169
170
  }
170
171
  value = await provider.calculateMetric(entity);
171
- const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
172
+ const thresholds = this.thresholdResolver.resolveEntityThresholds(
172
173
  entity,
173
174
  provider
174
175
  );
@@ -1 +1 @@
1
- {"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { isMetricIdDisabled } from '../../utils/metricUtils';\nimport { normalizeOwnerRef } from '../../utils/normalizeOwnerRef';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n const isBatchProvider = typeof provider.calculateMetrics === 'function';\n const metricIds = provider.getMetricIds?.() ?? [provider.getProviderId()];\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n // Handle batch providers\n if (isBatchProvider && provider.calculateMetrics) {\n const entityRef = stringifyEntityRef(entity);\n const entityKind = normalizeField(entity.kind);\n const entityNamespace = normalizeField(entity.metadata.namespace);\n const entityOwner = normalizeOwnerRef(entity?.spec?.owner);\n\n const enabledMetricIds = metricIds.filter(\n metricId =>\n !isMetricIdDisabled(this.config, metricId, entity, logger),\n );\n\n if (enabledMetricIds.length === 0) {\n return undefined;\n }\n\n try {\n const resultsMap = await provider.calculateMetrics(entity);\n\n return enabledMetricIds.map(metricId => {\n if (!resultsMap.has(metricId)) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message: `calculateMetrics() did not return an entry for metric '${metricId}'`,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n\n const value = resultsMap.get(metricId) as MetricValue;\n\n try {\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status =\n this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n } catch (error) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n });\n } catch (error) {\n return enabledMetricIds.map(\n metricId =>\n ({\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate),\n );\n }\n }\n\n let value: MetricValue | undefined;\n\n try {\n if (\n isMetricIdDisabled(\n this.config,\n provider.getProviderId(),\n entity,\n logger,\n )\n ) {\n return undefined;\n }\n\n value = await provider.calculateMetric(entity);\n\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n } catch (error) {\n // status is intentionally omitted — a calculation failure produces a NULL status\n // in the database, which sorts last when sortBy=status is used\n logger.warn(\n `Failed to calculate metric for entity ${stringifyEntityRef(\n entity,\n )}: ${error}`,\n error instanceof Error ? error : undefined,\n );\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled' && curr.value !== undefined) {\n // Batch providers return an array of results, single providers return one result\n const result = curr.value;\n if (Array.isArray(result)) {\n return [...acc, ...result];\n }\n return [...acc, result];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n if (batchResults.length > 0) {\n const errorCount = batchResults.filter(r => r.error_message).length;\n logger.debug(\n `Storing ${batchResults.length} metric values (${errorCount} errors)`,\n );\n }\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n\nfunction normalizeField(field: unknown): string | undefined {\n if (typeof field !== 'string') return undefined;\n const normalized = field.trim().toLowerCase();\n if (!normalized) return undefined;\n\n // Prevent DB insertion failures (limits column length to 255 characters)\n return normalized.length <= 255 ? normalized : normalized.slice(0, 255);\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","stringifyEntityRef","normalizeOwnerRef","isMetricIdDisabled","value","mergeEntityAndProviderThresholds"],"mappings":"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"}
1
+ {"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { isMetricIdDisabled } from '../../utils/metricUtils';\nimport { normalizeOwnerRef } from '../../utils/normalizeOwnerRef';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { ThresholdResolver } from '../../threshold/ThresholdResolver';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n | 'thresholdResolver'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n private readonly thresholdResolver: ThresholdResolver;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n this.thresholdResolver = options.thresholdResolver;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n const isBatchProvider = typeof provider.calculateMetrics === 'function';\n const metricIds = provider.getMetricIds?.() ?? [provider.getProviderId()];\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n // Handle batch providers\n if (isBatchProvider && provider.calculateMetrics) {\n const entityRef = stringifyEntityRef(entity);\n const entityKind = normalizeField(entity.kind);\n const entityNamespace = normalizeField(entity.metadata.namespace);\n const entityOwner = normalizeOwnerRef(entity?.spec?.owner);\n\n const enabledMetricIds = metricIds.filter(\n metricId =>\n !isMetricIdDisabled(this.config, metricId, entity, logger),\n );\n\n if (enabledMetricIds.length === 0) {\n return undefined;\n }\n\n try {\n const resultsMap = await provider.calculateMetrics(entity);\n\n return enabledMetricIds.map(metricId => {\n if (!resultsMap.has(metricId)) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message: `calculateMetrics() did not return an entry for metric '${metricId}'`,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n\n const value = resultsMap.get(metricId) as MetricValue;\n\n try {\n const thresholds =\n this.thresholdResolver.resolveEntityThresholds(\n entity,\n provider,\n );\n\n const status =\n this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n } catch (error) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n });\n } catch (error) {\n return enabledMetricIds.map(\n metricId =>\n ({\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate),\n );\n }\n }\n\n let value: MetricValue | undefined;\n\n try {\n if (\n isMetricIdDisabled(\n this.config,\n provider.getProviderId(),\n entity,\n logger,\n )\n ) {\n return undefined;\n }\n\n value = await provider.calculateMetric(entity);\n\n const thresholds = this.thresholdResolver.resolveEntityThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n } catch (error) {\n // status is intentionally omitted — a calculation failure produces a NULL status\n // in the database, which sorts last when sortBy=status is used\n logger.warn(\n `Failed to calculate metric for entity ${stringifyEntityRef(\n entity,\n )}: ${error}`,\n error instanceof Error ? error : undefined,\n );\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled' && curr.value !== undefined) {\n // Batch providers return an array of results, single providers return one result\n const result = curr.value;\n if (Array.isArray(result)) {\n return [...acc, ...result];\n }\n return [...acc, result];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n if (batchResults.length > 0) {\n const errorCount = batchResults.filter(r => r.error_message).length;\n logger.debug(\n `Storing ${batchResults.length} metric values (${errorCount} errors)`,\n );\n }\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n\nfunction normalizeField(field: unknown): string | undefined {\n if (typeof field !== 'string') return undefined;\n const normalized = field.trim().toLowerCase();\n if (!normalized) return undefined;\n\n // Prevent DB insertion failures (limits column length to 255 characters)\n return normalized.length <= 255 ? normalized : normalized.slice(0, 255);\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","stringifyEntityRef","normalizeOwnerRef","isMetricIdDisabled","value"],"mappings":"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"}
@@ -4,7 +4,6 @@ var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-p
4
4
  var normalizeOwnerRef = require('../utils/normalizeOwnerRef.cjs.js');
5
5
  var errors = require('@backstage/errors');
6
6
  var permissionUtils = require('../permissions/permissionUtils.cjs.js');
7
- var mergeEntityAndProviderThresholds = require('../utils/mergeEntityAndProviderThresholds.cjs.js');
8
7
  var metricCalculationError = require('../utils/metricCalculationError.cjs.js');
9
8
  var mappers = require('./mappers.cjs.js');
10
9
 
@@ -24,6 +23,7 @@ class CatalogMetricService {
24
23
  auth;
25
24
  registry;
26
25
  database;
26
+ thresholdResolver;
27
27
  static MAX_FETCHABLE_ROWS = 1e4;
28
28
  static BATCH_SIZE = 100;
29
29
  constructor(options) {
@@ -32,6 +32,7 @@ class CatalogMetricService {
32
32
  this.registry = options.registry;
33
33
  this.database = options.database;
34
34
  this.logger = options.logger;
35
+ this.thresholdResolver = options.thresholdResolver;
35
36
  }
36
37
  /**
37
38
  * Get latest metric results for a specific catalog entity.
@@ -65,7 +66,10 @@ class CatalogMetricService {
65
66
  const provider = this.registry.getProvider(metric_id);
66
67
  const metric = this.registry.getMetric(metric_id);
67
68
  try {
68
- thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(entity, provider);
69
+ thresholds = this.thresholdResolver.resolveEntityThresholds(
70
+ entity,
71
+ provider
72
+ );
69
73
  if (value === null) {
70
74
  thresholdError = "Unable to evaluate thresholds, metric value is missing";
71
75
  } else if (error_message) {
@@ -1 +1 @@
1
- {"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MetricResult,\n ThresholdConfig,\n EntityMetricDetailResponse,\n EntityMetricDetail,\n ScorecardEntityHealthSummary,\n aggregationTypes,\n AggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { normalizeOwnerRef } from '../utils/normalizeOwnerRef';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\nimport { isMetricCalculationError } from '../utils/metricCalculationError';\nimport { AggregatedMetricMapper } from './mappers';\nimport { DbMetricValue } from '../database/types';\n\ntype CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n logger: LoggerService;\n};\n\nexport class CatalogMetricService {\n private static entityHealthSummary(\n accessibleRows: DbMetricValue[],\n countsArePartial: boolean,\n ): ScorecardEntityHealthSummary {\n const calculationErrorCount = accessibleRows.filter(row =>\n isMetricCalculationError(row),\n ).length;\n return {\n totalEntities: accessibleRows.length,\n calculationErrorCount,\n countsArePartial,\n };\n }\n\n private readonly logger: LoggerService;\n\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n\n private static readonly MAX_FETCHABLE_ROWS = 10_000;\n private static readonly BATCH_SIZE = 100;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n this.logger = options.logger;\n }\n\n /**\n * Get latest metric results for a specific catalog entity.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param metricIds - Optional array of metric IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n metricIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = this.registry.listMetrics(metricIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = this.registry.getMetric(metric_id);\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === null) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else if (error_message) {\n thresholdError = error_message;\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = isMetricCalculationError({\n value,\n error_message,\n });\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n\n /**\n * Get an aggregated metric by status grouped for multiple entities and a single metric ID.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @returns Aggregated metric by status grouped results\n */\n async getStatusGroupedAggregatedMetrics(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n /**\n * Get an aggregated metric by aggregation type.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @param aggregationType - Aggregation type to use.\n * @returns Aggregated metric by aggregation type results\n */\n async getAggregatedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationType: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length !== 0) {\n if (aggregationType === aggregationTypes.statusGrouped) {\n return this.getStatusGroupedAggregatedMetrics(entityRefs, metricId);\n }\n throw new Error(`Unsupported aggregation type: ${aggregationType}`);\n }\n\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n /**\n * Get detailed entity metrics for drill-down with filtering, sorting, and pagination.\n *\n * Fetches individual entity metric values and enriches them with catalog metadata.\n * Supports database-level filtering (status, owner, kind, entityName),\n * database-level sorting, and in-memory pagination over the permission-filtered result set.\n * Returns empty entities if the catalog is unavailable (fail-secure).\n *\n * @param metricId - Metric ID to fetch (e.g., \"github.open_prs\")\n * @param options - Query options for filtering, sorting, and pagination\n * @param options.status - Filter by threshold status (database-level)\n * @param options.owner - Filter by owner entity reference (database-level)\n * @param options.kind - Filter by entity kind (database-level)\n * @param options.entityName - Substring search against the entity ref `kind:namespace/name` (database-level)\n * @param options.namespace - Exact match against the entity namespace (database-level)\n * @param options.sortBy - Field to sort by (default: \"timestamp\")\n * @param options.sortOrder - Sort direction: \"asc\" or \"desc\" (default: \"desc\")\n * @param options.page - Page number (1-indexed)\n * @param options.limit - Entities per page (max: 100)\n * @returns Paginated entity metric details with metadata\n */\n async getEntityMetricDetails(\n metricId: string,\n credentials: BackstageCredentials,\n options: {\n status?: string;\n owner?: string[];\n kind?: string;\n entityName?: string;\n namespace?: string;\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n page: number;\n limit: number;\n },\n ): Promise<EntityMetricDetailResponse> {\n // Get metric metadata\n const metric = this.registry.getMetric(metricId);\n\n // High-page early-exit guard\n if (\n (options.page - 1) * options.limit >=\n CatalogMetricService.MAX_FETCHABLE_ROWS\n ) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n entityHealth: CatalogMetricService.entityHealthSummary([], false),\n };\n }\n\n // Query database with all DB filters first\n // At the moment, this is going to be an O(MAX_FETCHABLE_ROWS) cost and is intentional to avoid leaking\n // pre-auth counts. MAX_FETCHABLE_ROWS and BATCH_SIZE are to be used as a method to of adjustment to\n // find the right amount of performance\n const rows = await this.database.readEntityMetricsWithFilters(metricId, {\n status: options.status,\n entityName: options.entityName,\n entityKind: options.kind,\n entityNamespace: options.namespace,\n entityOwner: options.owner,\n sortBy: options.sortBy,\n sortOrder: options.sortOrder,\n pagination: {\n limit: CatalogMetricService.MAX_FETCHABLE_ROWS,\n offset: 0,\n },\n });\n\n // Filter to authorized rows by batching through catalog.getEntitiesByRefs with user\n // credentials. The catalog enforces auth natively: null = unauthorized or deleted.\n // We also cache the returned Entity objects so we can enrich the page rows without\n // a second catalog round-trip. Sequential processing preserves DB sort order.\n const entityMap = new Map<string, Entity>();\n const accessibleRows: DbMetricValue[] = [];\n try {\n for (let i = 0; i < rows.length; i += CatalogMetricService.BATCH_SIZE) {\n const batch = rows.slice(i, i + CatalogMetricService.BATCH_SIZE);\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: batch.map(row => row.catalog_entity_ref),\n fields: [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'spec.owner',\n ],\n },\n { credentials },\n );\n\n // Filter out the unauthorized entities\n for (let j = 0; j < batch.length; j++) {\n const entity = response.items[j];\n if (!entity) continue; // null = unauthorized or not found, skip\n entityMap.set(batch[j].catalog_entity_ref, entity);\n accessibleRows.push(batch[j]);\n }\n }\n } catch (error) {\n // Fail secure: if the catalog is unavailable we cannot confirm authorization,\n // so return empty rather than potentially unauthorized data.\n this.logger.error('Failed to fetch entities from catalog', { error });\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n entityHealth: CatalogMetricService.entityHealthSummary([], false),\n };\n }\n\n // True when DB results were capped; pagination.total may undercount the full dataset.\n const isCapped = rows.length === CatalogMetricService.MAX_FETCHABLE_ROWS;\n\n // Apply pagination to filtered entities\n const totalFiltered = accessibleRows.length;\n const pageRows = accessibleRows.slice(\n (options.page - 1) * options.limit,\n options.page * options.limit,\n );\n\n // No rows on this page — either no matching results or the requested page is beyond\n // the last page.\n if (pageRows.length === 0) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n entityHealth: CatalogMetricService.entityHealthSummary(\n accessibleRows,\n isCapped,\n ),\n };\n }\n\n // Enrich page rows from the cached entity map\n const enrichedEntities: EntityMetricDetail[] = [];\n for (const row of pageRows) {\n const entity = entityMap.get(row.catalog_entity_ref);\n if (!entity) continue;\n enrichedEntities.push({\n entityRef: row.catalog_entity_ref,\n entityNamespace: entity.metadata.namespace,\n entityName: entity.metadata.name,\n entityKind: entity.kind,\n owner: normalizeOwnerRef(entity.spec?.owner) ?? '',\n metricValue: row.value,\n timestamp: new Date(row.timestamp).toISOString(),\n status: row.status,\n });\n }\n\n // Format and return response\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: enrichedEntities,\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n entityHealth: CatalogMetricService.entityHealthSummary(\n accessibleRows,\n isCapped,\n ),\n };\n }\n}\n"],"names":["isMetricCalculationError","NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError","AggregatedMetricMapper","aggregationTypes","normalizeOwnerRef"],"mappings":"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"}
1
+ {"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MetricResult,\n ThresholdConfig,\n EntityMetricDetailResponse,\n EntityMetricDetail,\n ScorecardEntityHealthSummary,\n aggregationTypes,\n AggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { normalizeOwnerRef } from '../utils/normalizeOwnerRef';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { isMetricCalculationError } from '../utils/metricCalculationError';\nimport { AggregatedMetricMapper } from './mappers';\nimport { DbMetricValue } from '../database/types';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\n\ntype CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n logger: LoggerService;\n thresholdResolver: ThresholdResolver;\n};\n\nexport class CatalogMetricService {\n private static entityHealthSummary(\n accessibleRows: DbMetricValue[],\n countsArePartial: boolean,\n ): ScorecardEntityHealthSummary {\n const calculationErrorCount = accessibleRows.filter(row =>\n isMetricCalculationError(row),\n ).length;\n return {\n totalEntities: accessibleRows.length,\n calculationErrorCount,\n countsArePartial,\n };\n }\n\n private readonly logger: LoggerService;\n\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdResolver: ThresholdResolver;\n\n private static readonly MAX_FETCHABLE_ROWS = 10_000;\n private static readonly BATCH_SIZE = 100;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n this.logger = options.logger;\n this.thresholdResolver = options.thresholdResolver;\n }\n\n /**\n * Get latest metric results for a specific catalog entity.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param metricIds - Optional array of metric IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n metricIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = this.registry.listMetrics(metricIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = this.registry.getMetric(metric_id);\n\n try {\n thresholds = this.thresholdResolver.resolveEntityThresholds(\n entity,\n provider,\n );\n\n if (value === null) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else if (error_message) {\n thresholdError = error_message;\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = isMetricCalculationError({\n value,\n error_message,\n });\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n\n /**\n * Get an aggregated metric by status grouped for multiple entities and a single metric ID.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @returns Aggregated metric by status grouped results\n */\n async getStatusGroupedAggregatedMetrics(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n /**\n * Get an aggregated metric by aggregation type.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @param aggregationType - Aggregation type to use.\n * @returns Aggregated metric by aggregation type results\n */\n async getAggregatedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationType: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length !== 0) {\n if (aggregationType === aggregationTypes.statusGrouped) {\n return this.getStatusGroupedAggregatedMetrics(entityRefs, metricId);\n }\n throw new Error(`Unsupported aggregation type: ${aggregationType}`);\n }\n\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n /**\n * Get detailed entity metrics for drill-down with filtering, sorting, and pagination.\n *\n * Fetches individual entity metric values and enriches them with catalog metadata.\n * Supports database-level filtering (status, owner, kind, entityName),\n * database-level sorting, and in-memory pagination over the permission-filtered result set.\n * Returns empty entities if the catalog is unavailable (fail-secure).\n *\n * @param metricId - Metric ID to fetch (e.g., \"github.open_prs\")\n * @param options - Query options for filtering, sorting, and pagination\n * @param options.status - Filter by threshold status (database-level)\n * @param options.owner - Filter by owner entity reference (database-level)\n * @param options.kind - Filter by entity kind (database-level)\n * @param options.entityName - Substring search against the entity ref `kind:namespace/name` (database-level)\n * @param options.namespace - Exact match against the entity namespace (database-level)\n * @param options.sortBy - Field to sort by (default: \"timestamp\")\n * @param options.sortOrder - Sort direction: \"asc\" or \"desc\" (default: \"desc\")\n * @param options.page - Page number (1-indexed)\n * @param options.limit - Entities per page (max: 100)\n * @returns Paginated entity metric details with metadata\n */\n async getEntityMetricDetails(\n metricId: string,\n credentials: BackstageCredentials,\n options: {\n status?: string;\n owner?: string[];\n kind?: string;\n entityName?: string;\n namespace?: string;\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n page: number;\n limit: number;\n },\n ): Promise<EntityMetricDetailResponse> {\n // Get metric metadata\n const metric = this.registry.getMetric(metricId);\n\n // High-page early-exit guard\n if (\n (options.page - 1) * options.limit >=\n CatalogMetricService.MAX_FETCHABLE_ROWS\n ) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n entityHealth: CatalogMetricService.entityHealthSummary([], false),\n };\n }\n\n // Query database with all DB filters first\n // At the moment, this is going to be an O(MAX_FETCHABLE_ROWS) cost and is intentional to avoid leaking\n // pre-auth counts. MAX_FETCHABLE_ROWS and BATCH_SIZE are to be used as a method to of adjustment to\n // find the right amount of performance\n const rows = await this.database.readEntityMetricsWithFilters(metricId, {\n status: options.status,\n entityName: options.entityName,\n entityKind: options.kind,\n entityNamespace: options.namespace,\n entityOwner: options.owner,\n sortBy: options.sortBy,\n sortOrder: options.sortOrder,\n pagination: {\n limit: CatalogMetricService.MAX_FETCHABLE_ROWS,\n offset: 0,\n },\n });\n\n // Filter to authorized rows by batching through catalog.getEntitiesByRefs with user\n // credentials. The catalog enforces auth natively: null = unauthorized or deleted.\n // We also cache the returned Entity objects so we can enrich the page rows without\n // a second catalog round-trip. Sequential processing preserves DB sort order.\n const entityMap = new Map<string, Entity>();\n const accessibleRows: DbMetricValue[] = [];\n try {\n for (let i = 0; i < rows.length; i += CatalogMetricService.BATCH_SIZE) {\n const batch = rows.slice(i, i + CatalogMetricService.BATCH_SIZE);\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: batch.map(row => row.catalog_entity_ref),\n fields: [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'spec.owner',\n ],\n },\n { credentials },\n );\n\n // Filter out the unauthorized entities\n for (let j = 0; j < batch.length; j++) {\n const entity = response.items[j];\n if (!entity) continue; // null = unauthorized or not found, skip\n entityMap.set(batch[j].catalog_entity_ref, entity);\n accessibleRows.push(batch[j]);\n }\n }\n } catch (error) {\n // Fail secure: if the catalog is unavailable we cannot confirm authorization,\n // so return empty rather than potentially unauthorized data.\n this.logger.error('Failed to fetch entities from catalog', { error });\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n entityHealth: CatalogMetricService.entityHealthSummary([], false),\n };\n }\n\n // True when DB results were capped; pagination.total may undercount the full dataset.\n const isCapped = rows.length === CatalogMetricService.MAX_FETCHABLE_ROWS;\n\n // Apply pagination to filtered entities\n const totalFiltered = accessibleRows.length;\n const pageRows = accessibleRows.slice(\n (options.page - 1) * options.limit,\n options.page * options.limit,\n );\n\n // No rows on this page — either no matching results or the requested page is beyond\n // the last page.\n if (pageRows.length === 0) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n entityHealth: CatalogMetricService.entityHealthSummary(\n accessibleRows,\n isCapped,\n ),\n };\n }\n\n // Enrich page rows from the cached entity map\n const enrichedEntities: EntityMetricDetail[] = [];\n for (const row of pageRows) {\n const entity = entityMap.get(row.catalog_entity_ref);\n if (!entity) continue;\n enrichedEntities.push({\n entityRef: row.catalog_entity_ref,\n entityNamespace: entity.metadata.namespace,\n entityName: entity.metadata.name,\n entityKind: entity.kind,\n owner: normalizeOwnerRef(entity.spec?.owner) ?? '',\n metricValue: row.value,\n timestamp: new Date(row.timestamp).toISOString(),\n status: row.status,\n });\n }\n\n // Format and return response\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: enrichedEntities,\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n entityHealth: CatalogMetricService.entityHealthSummary(\n accessibleRows,\n isCapped,\n ),\n };\n }\n}\n"],"names":["isMetricCalculationError","NotFoundError","filterAuthorizedMetrics","stringifyError","AggregatedMetricMapper","aggregationTypes","normalizeOwnerRef"],"mappings":"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"}
@@ -25,7 +25,8 @@ async function createRouter({
25
25
  catalog,
26
26
  httpAuth,
27
27
  permissions,
28
- logger
28
+ logger,
29
+ thresholdResolver
29
30
  }) {
30
31
  const router = Router__default.default();
31
32
  router.use(express__default.default.json());
@@ -116,7 +117,7 @@ async function createRouter({
116
117
  for (const entityRef of entitiesOwnedByAUser) {
117
118
  await permissionUtils.checkEntityAccess(entityRef, req, permissions, httpAuth);
118
119
  }
119
- const thresholds = provider.getMetricThresholds();
120
+ const thresholds = thresholdResolver.resolveProviderThresholds(provider);
120
121
  logger.warn(
121
122
  `Deprecated Scorecard API: GET /metrics/${metricId}/catalog/aggregations is deprecated; use GET /aggregations/:aggregationId (e.g. when the aggregation id matches the metric id, GET /aggregations/${metricId}).`
122
123
  );
@@ -213,7 +214,7 @@ async function createRouter({
213
214
  `To view the aggregation of a scorecard metric, your administrator must grant you the required permission.`
214
215
  );
215
216
  }
216
- const thresholds = provider.getMetricThresholds();
217
+ const thresholds = thresholdResolver.resolveProviderThresholds(provider);
217
218
  res.json(
218
219
  await aggregationsService.getAggregatedMetricByEntityRefs({
219
220
  metric,
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthenticationError,\n InputError,\n NotAllowedError,\n} from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport type { CatalogMetricService } from './CatalogMetricService';\nimport type { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n LoggerService,\n type HttpAuthService,\n type PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n filterAuthorizedMetrics,\n checkEntityAccess,\n authorizeConditional,\n getUserEntityRef,\n} from '../permissions/permissionUtils';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { validateMetricIdsQueryParams } from '../middlewares/validateMetricIdsQueryParams';\nimport { getEntitiesOwnedByUser } from '../utils/getEntitiesOwnedByUser';\nimport { parseCommaSeparatedString } from '../utils/parseCommaSeparatedString';\nimport { AggregatedMetricMapper } from './mappers';\nimport { validateDrillDownMetricsSchema } from '../validation/validateDrillDownMetricsSchema';\nimport { validateAggregationIdParam } from '../middlewares/validateAggregationIdParam';\nimport { scorecardMetricReadPermission } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { validateDatasourceQueryParams } from '../middlewares/validateDatasourceQueryParams';\nimport { AggregationsService } from './aggregations/AggregationService';\n\nexport type ScorecardRouterOptions = {\n service: {\n aggregationsService: AggregationsService;\n catalogMetricService: CatalogMetricService;\n };\n metricProvidersRegistry: MetricProvidersRegistry;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n permissions: PermissionsService;\n logger: LoggerService;\n};\n\nexport async function createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n}: ScorecardRouterOptions): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n const { aggregationsService, catalogMetricService } = service;\n\n router.get(\n '/metrics',\n validateMetricIdsQueryParams,\n validateDatasourceQueryParams,\n async (req, res) => {\n const { metricIds, datasource } = req.query;\n\n if (metricIds && datasource) {\n throw new InputError('Cannot filter by both metricIds and datasource');\n }\n\n if (metricIds) {\n return res.json({\n metrics: metricProvidersRegistry.listMetrics(\n parseCommaSeparatedString(metricIds as string),\n ),\n });\n }\n\n if (datasource) {\n return res.json({\n metrics: metricProvidersRegistry.listMetricsByDatasource(\n datasource as string,\n ),\n });\n }\n\n return res.json({ metrics: metricProvidersRegistry.listMetrics() });\n },\n );\n\n router.get(\n '/metrics/catalog/:kind/:namespace/:name',\n validateMetricIdsQueryParams,\n async (req, res) => {\n const { metricIds } = req.query;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const { kind, namespace, name } = req.params;\n\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n\n // Check if user has permission to read this specific catalog entity\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n\n const metricIdArray = metricIds\n ? parseCommaSeparatedString(metricIds as string)\n : undefined;\n\n const results = await catalogMetricService.getLatestEntityMetrics(\n entityRef,\n metricIdArray,\n conditions,\n );\n res.json(results);\n },\n );\n\n // Deprecated (RFC 8594): use GET /aggregations/:aggregationId instead.\n router.get(\n '/metrics/:metricId/catalog/aggregations',\n (req, res, next) => {\n const { metricId } = req.params;\n const successorPath = `${req.baseUrl}/aggregations/${encodeURIComponent(\n metricId,\n )}`;\n res.setHeader('Deprecation', 'true');\n res.setHeader('Link', `<${successorPath}>; rel=\"alternate\"`);\n next();\n },\n async (req, res) => {\n const { metricId } = req.params;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const provider = metricProvidersRegistry.getProvider(metricId);\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const thresholds = provider.getMetricThresholds();\n\n logger.warn(\n `Deprecated Scorecard API: GET /metrics/${metricId}/catalog/aggregations is deprecated; use GET /aggregations/:aggregationId (e.g. when the aggregation id matches the metric id, GET /aggregations/${metricId}).`,\n );\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(metricId);\n\n res.json(\n await aggregationsService.getAggregatedMetricByEntityRefs({\n metric,\n thresholds,\n aggregationConfig,\n entityRefs: entitiesOwnedByAUser,\n }),\n );\n },\n );\n\n router.get(\n '/metrics/:metricId/catalog/aggregations/entities',\n async (req, res) => {\n const { metricId } = req.params;\n\n const {\n page,\n pageSize,\n status,\n owner,\n kind,\n namespace,\n entityName,\n sortBy,\n sortOrder,\n } = validateDrillDownMetricsSchema(req.query, logger);\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entityMetrics = await catalogMetricService.getEntityMetricDetails(\n metricId,\n credentials,\n {\n status,\n owner,\n kind,\n entityName,\n namespace,\n sortBy,\n sortOrder,\n page,\n limit: pageSize,\n },\n );\n\n res.json(entityMetrics);\n },\n );\n\n router.get(\n '/aggregations/:aggregationId',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const userEntityRef = await getUserEntityRef(credentials);\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(aggregationId);\n\n const provider = metricProvidersRegistry.getProvider(\n aggregationConfig.metricId,\n );\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the aggregation of a scorecard metric, your administrator must grant you the required permission.`,\n );\n }\n\n const thresholds = provider.getMetricThresholds();\n\n res.json(\n await aggregationsService.getAggregatedMetricByEntityRefs({\n metric,\n thresholds,\n aggregationConfig,\n entityRefs: entitiesOwnedByAUser,\n }),\n );\n },\n );\n\n router.get(\n '/aggregations/:aggregationId/metadata',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(aggregationId);\n\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregationMetadata(metric, aggregationConfig),\n );\n },\n );\n\n return router;\n}\n"],"names":["Router","express","validateMetricIdsQueryParams","validateDatasourceQueryParams","InputError","parseCommaSeparatedString","authorizeConditional","scorecardMetricReadPermission","stringifyEntityRef","checkEntityAccess","filterAuthorizedMetrics","NotAllowedError","AuthenticationError","getEntitiesOwnedByUser","validateDrillDownMetricsSchema","validateAggregationIdParam","getUserEntityRef","AggregatedMetricMapper"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2DA,eAAsB,YAAa,CAAA;AAAA,EACjC,uBAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAoD,EAAA;AAClD,EAAA,MAAM,SAASA,uBAAO,EAAA;AACtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA;AAEzB,EAAM,MAAA,EAAE,mBAAqB,EAAA,oBAAA,EAAyB,GAAA,OAAA;AAEtD,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,UAAA;AAAA,IACAC,yDAAA;AAAA,IACAC,2DAAA;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAA,MAAM,EAAE,SAAA,EAAW,UAAW,EAAA,GAAI,GAAI,CAAA,KAAA;AAEtC,MAAA,IAAI,aAAa,UAAY,EAAA;AAC3B,QAAM,MAAA,IAAIC,kBAAW,gDAAgD,CAAA;AAAA;AAGvE,MAAA,IAAI,SAAW,EAAA;AACb,QAAA,OAAO,IAAI,IAAK,CAAA;AAAA,UACd,SAAS,uBAAwB,CAAA,WAAA;AAAA,YAC/BC,oDAA0B,SAAmB;AAAA;AAC/C,SACD,CAAA;AAAA;AAGH,MAAA,IAAI,UAAY,EAAA;AACd,QAAA,OAAO,IAAI,IAAK,CAAA;AAAA,UACd,SAAS,uBAAwB,CAAA,uBAAA;AAAA,YAC/B;AAAA;AACF,SACD,CAAA;AAAA;AAGH,MAAA,OAAO,IAAI,IAAK,CAAA,EAAE,SAAS,uBAAwB,CAAA,WAAA,IAAe,CAAA;AAAA;AACpE,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,yCAAA;AAAA,IACAH,yDAAA;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,SAAU,EAAA,GAAI,GAAI,CAAA,KAAA;AAE1B,MAAM,MAAA,EAAE,UAAW,EAAA,GAAI,MAAMI,oCAAA;AAAA,QAC3B,MAAM,QAAS,CAAA,WAAA,CAAY,GAAG,CAAA;AAAA,QAC9B,WAAA;AAAA,QACAC;AAAA,OACF;AAEA,MAAA,MAAM,EAAE,IAAA,EAAM,SAAW,EAAA,IAAA,KAAS,GAAI,CAAA,MAAA;AAEtC,MAAA,MAAM,YAAYC,+BAAmB,CAAA,EAAE,IAAM,EAAA,SAAA,EAAW,MAAM,CAAA;AAG9D,MAAA,MAAMC,iCAAkB,CAAA,SAAA,EAAW,GAAK,EAAA,WAAA,EAAa,QAAQ,CAAA;AAE7D,MAAA,MAAM,aAAgB,GAAA,SAAA,GAClBJ,mDAA0B,CAAA,SAAmB,CAC7C,GAAA,MAAA;AAEJ,MAAM,MAAA,OAAA,GAAU,MAAM,oBAAqB,CAAA,sBAAA;AAAA,QACzC,SAAA;AAAA,QACA,aAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,GAAA,CAAI,KAAK,OAAO,CAAA;AAAA;AAClB,GACF;AAGA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,yCAAA;AAAA,IACA,CAAC,GAAK,EAAA,GAAA,EAAK,IAAS,KAAA;AAClB,MAAM,MAAA,EAAE,QAAS,EAAA,GAAI,GAAI,CAAA,MAAA;AACzB,MAAA,MAAM,aAAgB,GAAA,CAAA,EAAG,GAAI,CAAA,OAAO,CAAiB,cAAA,EAAA,kBAAA;AAAA,QACnD;AAAA,OACD,CAAA,CAAA;AACD,MAAI,GAAA,CAAA,SAAA,CAAU,eAAe,MAAM,CAAA;AACnC,MAAA,GAAA,CAAI,SAAU,CAAA,MAAA,EAAQ,CAAI,CAAA,EAAA,aAAa,CAAoB,kBAAA,CAAA,CAAA;AAC3D,MAAK,IAAA,EAAA;AAAA,KACP;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,QAAS,EAAA,GAAI,GAAI,CAAA,MAAA;AAEzB,MAAM,MAAA,EAAE,UAAW,EAAA,GAAI,MAAMC,oCAAA;AAAA,QAC3B,MAAM,QAAS,CAAA,WAAA,CAAY,GAAG,CAAA;AAAA,QAC9B,WAAA;AAAA,QACAC;AAAA,OACF;AAEA,MAAM,MAAA,QAAA,GAAW,uBAAwB,CAAA,WAAA,CAAY,QAAQ,CAAA;AAC7D,MAAM,MAAA,MAAA,GAAS,uBAAwB,CAAA,SAAA,CAAU,QAAQ,CAAA;AACzD,MAAA,MAAM,iBAAoB,GAAAG,uCAAA,CAAwB,CAAC,MAAM,GAAG,UAAU,CAAA;AAEtE,MAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,QAAA,MAAM,IAAIC,sBAAA;AAAA,UACR,CAAA,yFAAA;AAAA,SACF;AAAA;AAGF,MAAM,MAAA,WAAA,GAAc,MAAM,QAAA,CAAS,WAAY,CAAA,GAAA,EAAK,EAAE,KAAO,EAAA,CAAC,MAAM,CAAA,EAAG,CAAA;AACvE,MAAM,MAAA,aAAA,GAAgB,aAAa,SAAW,EAAA,aAAA;AAE9C,MAAA,IAAI,CAAC,aAAe,EAAA;AAClB,QAAM,MAAA,IAAIC,2BAAoB,iCAAiC,CAAA;AAAA;AAGjE,MAAM,MAAA,oBAAA,GAAuB,MAAMC,6CAAA,CAAuB,aAAe,EAAA;AAAA,QACvE,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,KAAA,MAAW,aAAa,oBAAsB,EAAA;AAC5C,QAAA,MAAMJ,iCAAkB,CAAA,SAAA,EAAW,GAAK,EAAA,WAAA,EAAa,QAAQ,CAAA;AAAA;AAG/D,MAAM,MAAA,UAAA,GAAa,SAAS,mBAAoB,EAAA;AAEhD,MAAO,MAAA,CAAA,IAAA;AAAA,QACL,CAAA,uCAAA,EAA0C,QAAQ,CAAA,iJAAA,EAAoJ,QAAQ,CAAA,EAAA;AAAA,OAChN;AAEA,MAAM,MAAA,iBAAA,GACJ,mBAAoB,CAAA,oBAAA,CAAqB,QAAQ,CAAA;AAEnD,MAAI,GAAA,CAAA,IAAA;AAAA,QACF,MAAM,oBAAoB,+BAAgC,CAAA;AAAA,UACxD,MAAA;AAAA,UACA,UAAA;AAAA,UACA,iBAAA;AAAA,UACA,UAAY,EAAA;AAAA,SACb;AAAA,OACH;AAAA;AACF,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,kDAAA;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,QAAS,EAAA,GAAI,GAAI,CAAA,MAAA;AAEzB,MAAM,MAAA;AAAA,QACJ,IAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,KAAA;AAAA,QACA,IAAA;AAAA,QACA,SAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACE,GAAAK,6DAAA,CAA+B,GAAI,CAAA,KAAA,EAAO,MAAM,CAAA;AAEpD,MAAM,MAAA,WAAA,GAAc,MAAM,QAAA,CAAS,WAAY,CAAA,GAAA,EAAK,EAAE,KAAO,EAAA,CAAC,MAAM,CAAA,EAAG,CAAA;AAEvE,MAAM,MAAA,EAAE,UAAW,EAAA,GAAI,MAAMR,oCAAA;AAAA,QAC3B,WAAA;AAAA,QACA,WAAA;AAAA,QACAC;AAAA,OACF;AAEA,MAAM,MAAA,MAAA,GAAS,uBAAwB,CAAA,SAAA,CAAU,QAAQ,CAAA;AACzD,MAAA,MAAM,iBAAoB,GAAAG,uCAAA,CAAwB,CAAC,MAAM,GAAG,UAAU,CAAA;AAEtE,MAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,QAAA,MAAM,IAAIC,sBAAA;AAAA,UACR,CAAA,yFAAA;AAAA,SACF;AAAA;AAGF,MAAM,MAAA,aAAA,GAAgB,aAAa,SAAW,EAAA,aAAA;AAE9C,MAAA,IAAI,CAAC,aAAe,EAAA;AAClB,QAAM,MAAA,IAAIC,2BAAoB,iCAAiC,CAAA;AAAA;AAGjE,MAAM,MAAA,aAAA,GAAgB,MAAM,oBAAqB,CAAA,sBAAA;AAAA,QAC/C,QAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,UACE,MAAA;AAAA,UACA,KAAA;AAAA,UACA,IAAA;AAAA,UACA,UAAA;AAAA,UACA,SAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA;AAAA,UACA,IAAA;AAAA,UACA,KAAO,EAAA;AAAA;AACT,OACF;AAEA,MAAA,GAAA,CAAI,KAAK,aAAa,CAAA;AAAA;AACxB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,8BAAA;AAAA,IACAG,qDAAA;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,aAAc,EAAA,GAAI,GAAI,CAAA,MAAA;AAE9B,MAAM,MAAA,WAAA,GAAc,MAAM,QAAA,CAAS,WAAY,CAAA,GAAA,EAAK,EAAE,KAAO,EAAA,CAAC,MAAM,CAAA,EAAG,CAAA;AAEvE,MAAM,MAAA,EAAE,UAAW,EAAA,GAAI,MAAMT,oCAAA;AAAA,QAC3B,WAAA;AAAA,QACA,WAAA;AAAA,QACAC;AAAA,OACF;AAEA,MAAM,MAAA,aAAA,GAAgB,MAAMS,gCAAA,CAAiB,WAAW,CAAA;AAExD,MAAM,MAAA,iBAAA,GACJ,mBAAoB,CAAA,oBAAA,CAAqB,aAAa,CAAA;AAExD,MAAA,MAAM,WAAW,uBAAwB,CAAA,WAAA;AAAA,QACvC,iBAAkB,CAAA;AAAA,OACpB;AACA,MAAA,MAAM,SAAS,uBAAwB,CAAA,SAAA;AAAA,QACrC,mBAAmB,QAAY,IAAA;AAAA,OACjC;AAEA,MAAM,MAAA,oBAAA,GAAuB,MAAMH,6CAAA,CAAuB,aAAe,EAAA;AAAA,QACvE,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,KAAA,MAAW,aAAa,oBAAsB,EAAA;AAC5C,QAAA,MAAMJ,iCAAkB,CAAA,SAAA,EAAW,GAAK,EAAA,WAAA,EAAa,QAAQ,CAAA;AAAA;AAG/D,MAAA,MAAM,iBAAoB,GAAAC,uCAAA,CAAwB,CAAC,MAAM,GAAG,UAAU,CAAA;AACtE,MAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,QAAA,MAAM,IAAIC,sBAAA;AAAA,UACR,CAAA,yGAAA;AAAA,SACF;AAAA;AAGF,MAAM,MAAA,UAAA,GAAa,SAAS,mBAAoB,EAAA;AAEhD,MAAI,GAAA,CAAA,IAAA;AAAA,QACF,MAAM,oBAAoB,+BAAgC,CAAA;AAAA,UACxD,MAAA;AAAA,UACA,UAAA;AAAA,UACA,iBAAA;AAAA,UACA,UAAY,EAAA;AAAA,SACb;AAAA,OACH;AAAA;AACF,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,uCAAA;AAAA,IACAI,qDAAA;AAAA,IACA,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,aAAc,EAAA,GAAI,GAAI,CAAA,MAAA;AAE9B,MAAM,MAAA,iBAAA,GACJ,mBAAoB,CAAA,oBAAA,CAAqB,aAAa,CAAA;AAExD,MAAA,MAAM,SAAS,uBAAwB,CAAA,SAAA;AAAA,QACrC,mBAAmB,QAAY,IAAA;AAAA,OACjC;AAEA,MAAI,GAAA,CAAA,IAAA;AAAA,QACFE,8BAAA,CAAuB,qBAAsB,CAAA,MAAA,EAAQ,iBAAiB;AAAA,OACxE;AAAA;AACF,GACF;AAEA,EAAO,OAAA,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthenticationError,\n InputError,\n NotAllowedError,\n} from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport type { CatalogMetricService } from './CatalogMetricService';\nimport type { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n LoggerService,\n type HttpAuthService,\n type PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n filterAuthorizedMetrics,\n checkEntityAccess,\n authorizeConditional,\n getUserEntityRef,\n} from '../permissions/permissionUtils';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { validateMetricIdsQueryParams } from '../middlewares/validateMetricIdsQueryParams';\nimport { getEntitiesOwnedByUser } from '../utils/getEntitiesOwnedByUser';\nimport { parseCommaSeparatedString } from '../utils/parseCommaSeparatedString';\nimport { AggregatedMetricMapper } from './mappers';\nimport { validateDrillDownMetricsSchema } from '../validation/validateDrillDownMetricsSchema';\nimport { validateAggregationIdParam } from '../middlewares/validateAggregationIdParam';\nimport { scorecardMetricReadPermission } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { validateDatasourceQueryParams } from '../middlewares/validateDatasourceQueryParams';\nimport { AggregationsService } from './aggregations/AggregationService';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\n\nexport type ScorecardRouterOptions = {\n service: {\n aggregationsService: AggregationsService;\n catalogMetricService: CatalogMetricService;\n };\n metricProvidersRegistry: MetricProvidersRegistry;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n permissions: PermissionsService;\n logger: LoggerService;\n thresholdResolver: ThresholdResolver;\n};\n\nexport async function createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n}: ScorecardRouterOptions): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n const { aggregationsService, catalogMetricService } = service;\n\n router.get(\n '/metrics',\n validateMetricIdsQueryParams,\n validateDatasourceQueryParams,\n async (req, res) => {\n const { metricIds, datasource } = req.query;\n\n if (metricIds && datasource) {\n throw new InputError('Cannot filter by both metricIds and datasource');\n }\n\n if (metricIds) {\n return res.json({\n metrics: metricProvidersRegistry.listMetrics(\n parseCommaSeparatedString(metricIds as string),\n ),\n });\n }\n\n if (datasource) {\n return res.json({\n metrics: metricProvidersRegistry.listMetricsByDatasource(\n datasource as string,\n ),\n });\n }\n\n return res.json({ metrics: metricProvidersRegistry.listMetrics() });\n },\n );\n\n router.get(\n '/metrics/catalog/:kind/:namespace/:name',\n validateMetricIdsQueryParams,\n async (req, res) => {\n const { metricIds } = req.query;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const { kind, namespace, name } = req.params;\n\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n\n // Check if user has permission to read this specific catalog entity\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n\n const metricIdArray = metricIds\n ? parseCommaSeparatedString(metricIds as string)\n : undefined;\n\n const results = await catalogMetricService.getLatestEntityMetrics(\n entityRef,\n metricIdArray,\n conditions,\n );\n res.json(results);\n },\n );\n\n // Deprecated (RFC 8594): use GET /aggregations/:aggregationId instead.\n router.get(\n '/metrics/:metricId/catalog/aggregations',\n (req, res, next) => {\n const { metricId } = req.params;\n const successorPath = `${req.baseUrl}/aggregations/${encodeURIComponent(\n metricId,\n )}`;\n res.setHeader('Deprecation', 'true');\n res.setHeader('Link', `<${successorPath}>; rel=\"alternate\"`);\n next();\n },\n async (req, res) => {\n const { metricId } = req.params;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const provider = metricProvidersRegistry.getProvider(metricId);\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const thresholds = thresholdResolver.resolveProviderThresholds(provider);\n\n logger.warn(\n `Deprecated Scorecard API: GET /metrics/${metricId}/catalog/aggregations is deprecated; use GET /aggregations/:aggregationId (e.g. when the aggregation id matches the metric id, GET /aggregations/${metricId}).`,\n );\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(metricId);\n\n res.json(\n await aggregationsService.getAggregatedMetricByEntityRefs({\n metric,\n thresholds,\n aggregationConfig,\n entityRefs: entitiesOwnedByAUser,\n }),\n );\n },\n );\n\n router.get(\n '/metrics/:metricId/catalog/aggregations/entities',\n async (req, res) => {\n const { metricId } = req.params;\n\n const {\n page,\n pageSize,\n status,\n owner,\n kind,\n namespace,\n entityName,\n sortBy,\n sortOrder,\n } = validateDrillDownMetricsSchema(req.query, logger);\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entityMetrics = await catalogMetricService.getEntityMetricDetails(\n metricId,\n credentials,\n {\n status,\n owner,\n kind,\n entityName,\n namespace,\n sortBy,\n sortOrder,\n page,\n limit: pageSize,\n },\n );\n\n res.json(entityMetrics);\n },\n );\n\n router.get(\n '/aggregations/:aggregationId',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const userEntityRef = await getUserEntityRef(credentials);\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(aggregationId);\n\n const provider = metricProvidersRegistry.getProvider(\n aggregationConfig.metricId,\n );\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the aggregation of a scorecard metric, your administrator must grant you the required permission.`,\n );\n }\n\n const thresholds = thresholdResolver.resolveProviderThresholds(provider);\n\n res.json(\n await aggregationsService.getAggregatedMetricByEntityRefs({\n metric,\n thresholds,\n aggregationConfig,\n entityRefs: entitiesOwnedByAUser,\n }),\n );\n },\n );\n\n router.get(\n '/aggregations/:aggregationId/metadata',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const aggregationConfig =\n aggregationsService.getAggregationConfig(aggregationId);\n\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregationMetadata(metric, aggregationConfig),\n );\n },\n );\n\n return router;\n}\n"],"names":["Router","express","validateMetricIdsQueryParams","validateDatasourceQueryParams","InputError","parseCommaSeparatedString","authorizeConditional","scorecardMetricReadPermission","stringifyEntityRef","checkEntityAccess","filterAuthorizedMetrics","NotAllowedError","AuthenticationError","getEntitiesOwnedByUser","validateDrillDownMetricsSchema","validateAggregationIdParam","getUserEntityRef","AggregatedMetricMapper"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
4
+ var mergeEntityAndProviderThresholds = require('../utils/mergeEntityAndProviderThresholds.cjs.js');
5
+
6
+ class ThresholdResolver {
7
+ // providerId: thresholds
8
+ constructor(config, providers) {
9
+ this.config = config;
10
+ for (const provider of providers) {
11
+ this.setConfiguredThresholds(provider);
12
+ }
13
+ }
14
+ config;
15
+ configuredThresholds = /* @__PURE__ */ new Map();
16
+ resolveProviderThresholds(provider) {
17
+ return this.configuredThresholds.get(provider.getProviderId()) ?? provider.getMetricThresholds();
18
+ }
19
+ resolveEntityThresholds(entity, provider) {
20
+ return mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
21
+ entity,
22
+ provider,
23
+ this.resolveProviderThresholds(provider)
24
+ );
25
+ }
26
+ setConfiguredThresholds(provider) {
27
+ const providerId = provider.getProviderId();
28
+ const thresholds = backstagePluginScorecardNode.getThresholdsFromConfig(
29
+ this.config,
30
+ `scorecard.plugins.${providerId}.thresholds`,
31
+ provider.getMetricType()
32
+ );
33
+ if (thresholds) {
34
+ this.configuredThresholds.set(providerId, thresholds);
35
+ }
36
+ }
37
+ }
38
+
39
+ exports.ThresholdResolver = ThresholdResolver;
40
+ //# sourceMappingURL=ThresholdResolver.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThresholdResolver.cjs.js","sources":["../../src/threshold/ThresholdResolver.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport type { Entity } from '@backstage/catalog-model';\nimport type { ThresholdConfig } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n getThresholdsFromConfig,\n type MetricProvider,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\n\nexport class ThresholdResolver {\n private readonly configuredThresholds = new Map<string, ThresholdConfig>(); // providerId: thresholds\n\n constructor(private readonly config: Config, providers: MetricProvider[]) {\n for (const provider of providers) {\n this.setConfiguredThresholds(provider);\n }\n }\n\n resolveProviderThresholds(provider: MetricProvider): ThresholdConfig {\n return (\n this.configuredThresholds.get(provider.getProviderId()) ??\n provider.getMetricThresholds()\n );\n }\n\n resolveEntityThresholds(\n entity: Entity,\n provider: MetricProvider,\n ): ThresholdConfig {\n return mergeEntityAndProviderThresholds(\n entity,\n provider,\n this.resolveProviderThresholds(provider),\n );\n }\n\n private setConfiguredThresholds(provider: MetricProvider): void {\n const providerId = provider.getProviderId();\n const thresholds = getThresholdsFromConfig(\n this.config,\n `scorecard.plugins.${providerId}.thresholds`,\n provider.getMetricType(),\n );\n\n if (thresholds) {\n this.configuredThresholds.set(providerId, thresholds);\n }\n }\n}\n"],"names":["mergeEntityAndProviderThresholds","getThresholdsFromConfig"],"mappings":";;;;;AAyBO,MAAM,iBAAkB,CAAA;AAAA;AAAA,EAG7B,WAAA,CAA6B,QAAgB,SAA6B,EAAA;AAA7C,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAC3B,IAAA,KAAA,MAAW,YAAY,SAAW,EAAA;AAChC,MAAA,IAAA,CAAK,wBAAwB,QAAQ,CAAA;AAAA;AACvC;AACF,EAJ6B,MAAA;AAAA,EAFZ,oBAAA,uBAA2B,GAA6B,EAAA;AAAA,EAQzE,0BAA0B,QAA2C,EAAA;AACnE,IACE,OAAA,IAAA,CAAK,qBAAqB,GAAI,CAAA,QAAA,CAAS,eAAe,CAAA,IACtD,SAAS,mBAAoB,EAAA;AAAA;AAEjC,EAEA,uBAAA,CACE,QACA,QACiB,EAAA;AACjB,IAAO,OAAAA,iEAAA;AAAA,MACL,MAAA;AAAA,MACA,QAAA;AAAA,MACA,IAAA,CAAK,0BAA0B,QAAQ;AAAA,KACzC;AAAA;AACF,EAEQ,wBAAwB,QAAgC,EAAA;AAC9D,IAAM,MAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AAC1C,IAAA,MAAM,UAAa,GAAAC,oDAAA;AAAA,MACjB,IAAK,CAAA,MAAA;AAAA,MACL,qBAAqB,UAAU,CAAA,WAAA,CAAA;AAAA,MAC/B,SAAS,aAAc;AAAA,KACzB;AAEA,IAAA,IAAI,UAAY,EAAA;AACd,MAAK,IAAA,CAAA,oBAAA,CAAqB,GAAI,CAAA,UAAA,EAAY,UAAU,CAAA;AAAA;AACtD;AAEJ;;;;"}
@@ -17,10 +17,10 @@ function parseEntityAnnotationThresholds(entity, providerId) {
17
17
  }
18
18
  return overrides;
19
19
  }
20
- function mergeEntityAndProviderThresholds(entity, provider) {
20
+ function mergeEntityAndProviderThresholds(entity, provider, baseThresholds) {
21
21
  let isRulesMerged = false;
22
22
  const providerId = provider.getProviderId();
23
- const providerThresholds = provider.getMetricThresholds();
23
+ const providerThresholds = baseThresholds ?? provider.getMetricThresholds();
24
24
  const providerMetricType = provider.getMetricType();
25
25
  const entityAnnotationThresholds = parseEntityAnnotationThresholds(
26
26
  entity,
@@ -1 +1 @@
1
- {"version":3,"file":"mergeEntityAndProviderThresholds.cjs.js","sources":["../../src/utils/mergeEntityAndProviderThresholds.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef, type Entity } from '@backstage/catalog-model';\nimport type {\n ThresholdConfig,\n ThresholdRule,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n type MetricProvider,\n validateThresholdsForMetric,\n ThresholdConfigFormatError,\n validateThresholdNumberIntervals,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { isError } from '@backstage/errors';\n\nconst thresholdRulesAnnotationPrefix = (providerId: string) =>\n `scorecard.io/${providerId}.thresholds.rules.`;\n\n/**\n * Extract threshold override rules from entity annotations for a given provider, doesn't validate rules.\n */\nfunction parseEntityAnnotationThresholds(\n entity: Entity,\n providerId: string,\n): ThresholdRule[] {\n const annotations = entity.metadata?.annotations || {};\n const prefix = thresholdRulesAnnotationPrefix(providerId);\n const overrides: ThresholdRule[] = [];\n\n for (const [annotationKey, expression] of Object.entries(annotations)) {\n if (annotationKey.startsWith(prefix) && expression) {\n const key = annotationKey.substring(prefix.length);\n overrides.push({ key, expression });\n }\n }\n\n return overrides;\n}\n\nexport function mergeEntityAndProviderThresholds(\n entity: Entity,\n provider: MetricProvider,\n): ThresholdConfig {\n let isRulesMerged = false;\n\n const providerId = provider.getProviderId();\n const providerThresholds = provider.getMetricThresholds();\n const providerMetricType = provider.getMetricType();\n const entityAnnotationThresholds = parseEntityAnnotationThresholds(\n entity,\n providerId,\n );\n\n const mergedRules = [...providerThresholds.rules];\n for (const override of entityAnnotationThresholds) {\n const foundKey = mergedRules.findIndex(rule => rule.key === override.key);\n if (foundKey === -1) {\n throw new ThresholdConfigFormatError(\n `Unable to override ${stringifyEntityRef(\n entity,\n )} thresholds by ${JSON.stringify(\n override,\n )}, metric provider ${providerId} does not support key ${override.key}`,\n );\n }\n\n const mergedRule: ThresholdRule = { ...mergedRules[foundKey], ...override };\n try {\n validateThresholdsForMetric({ rules: [mergedRule] }, providerMetricType);\n\n if (!isRulesMerged) isRulesMerged = true;\n } catch (e) {\n if (isError(e)) {\n throw new ThresholdConfigFormatError(\n `Invalid threshold annotation '${thresholdRulesAnnotationPrefix(\n providerId,\n )}${override.key}: ${\n override.expression\n }' in entity '${stringifyEntityRef(entity)}': ${e.message}`,\n );\n }\n throw e;\n }\n\n mergedRules[foundKey] = mergedRule;\n }\n\n if (isRulesMerged) {\n validateThresholdNumberIntervals(mergedRules, providerMetricType);\n }\n\n return {\n rules: mergedRules,\n };\n}\n"],"names":["ThresholdConfigFormatError","stringifyEntityRef","validateThresholdsForMetric","isError","validateThresholdNumberIntervals"],"mappings":";;;;;;AA6BA,MAAM,8BAAiC,GAAA,CAAC,UACtC,KAAA,CAAA,aAAA,EAAgB,UAAU,CAAA,kBAAA,CAAA;AAK5B,SAAS,+BAAA,CACP,QACA,UACiB,EAAA;AACjB,EAAA,MAAM,WAAc,GAAA,MAAA,CAAO,QAAU,EAAA,WAAA,IAAe,EAAC;AACrD,EAAM,MAAA,MAAA,GAAS,+BAA+B,UAAU,CAAA;AACxD,EAAA,MAAM,YAA6B,EAAC;AAEpC,EAAA,KAAA,MAAW,CAAC,aAAe,EAAA,UAAU,KAAK,MAAO,CAAA,OAAA,CAAQ,WAAW,CAAG,EAAA;AACrE,IAAA,IAAI,aAAc,CAAA,UAAA,CAAW,MAAM,CAAA,IAAK,UAAY,EAAA;AAClD,MAAA,MAAM,GAAM,GAAA,aAAA,CAAc,SAAU,CAAA,MAAA,CAAO,MAAM,CAAA;AACjD,MAAA,SAAA,CAAU,IAAK,CAAA,EAAE,GAAK,EAAA,UAAA,EAAY,CAAA;AAAA;AACpC;AAGF,EAAO,OAAA,SAAA;AACT;AAEgB,SAAA,gCAAA,CACd,QACA,QACiB,EAAA;AACjB,EAAA,IAAI,aAAgB,GAAA,KAAA;AAEpB,EAAM,MAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AAC1C,EAAM,MAAA,kBAAA,GAAqB,SAAS,mBAAoB,EAAA;AACxD,EAAM,MAAA,kBAAA,GAAqB,SAAS,aAAc,EAAA;AAClD,EAAA,MAAM,0BAA6B,GAAA,+BAAA;AAAA,IACjC,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,WAAc,GAAA,CAAC,GAAG,kBAAA,CAAmB,KAAK,CAAA;AAChD,EAAA,KAAA,MAAW,YAAY,0BAA4B,EAAA;AACjD,IAAA,MAAM,WAAW,WAAY,CAAA,SAAA,CAAU,UAAQ,IAAK,CAAA,GAAA,KAAQ,SAAS,GAAG,CAAA;AACxE,IAAA,IAAI,aAAa,EAAI,EAAA;AACnB,MAAA,MAAM,IAAIA,uDAAA;AAAA,QACR,CAAsB,mBAAA,EAAAC,+BAAA;AAAA,UACpB;AAAA,SACD,kBAAkB,IAAK,CAAA,SAAA;AAAA,UACtB;AAAA,SACD,CAAA,kBAAA,EAAqB,UAAU,CAAA,sBAAA,EAAyB,SAAS,GAAG,CAAA;AAAA,OACvE;AAAA;AAGF,IAAA,MAAM,aAA4B,EAAE,GAAG,YAAY,QAAQ,CAAA,EAAG,GAAG,QAAS,EAAA;AAC1E,IAAI,IAAA;AACF,MAAAC,wDAAA,CAA4B,EAAE,KAAO,EAAA,CAAC,UAAU,CAAA,IAAK,kBAAkB,CAAA;AAEvE,MAAI,IAAA,CAAC,eAA+B,aAAA,GAAA,IAAA;AAAA,aAC7B,CAAG,EAAA;AACV,MAAI,IAAAC,cAAA,CAAQ,CAAC,CAAG,EAAA;AACd,QAAA,MAAM,IAAIH,uDAAA;AAAA,UACR,CAAiC,8BAAA,EAAA,8BAAA;AAAA,YAC/B;AAAA,WACD,CAAA,EAAG,QAAS,CAAA,GAAG,CACd,EAAA,EAAA,QAAA,CAAS,UACX,CAAA,aAAA,EAAgBC,+BAAmB,CAAA,MAAM,CAAC,CAAA,GAAA,EAAM,EAAE,OAAO,CAAA;AAAA,SAC3D;AAAA;AAEF,MAAM,MAAA,CAAA;AAAA;AAGR,IAAA,WAAA,CAAY,QAAQ,CAAI,GAAA,UAAA;AAAA;AAG1B,EAAA,IAAI,aAAe,EAAA;AACjB,IAAAG,6DAAA,CAAiC,aAAa,kBAAkB,CAAA;AAAA;AAGlE,EAAO,OAAA;AAAA,IACL,KAAO,EAAA;AAAA,GACT;AACF;;;;"}
1
+ {"version":3,"file":"mergeEntityAndProviderThresholds.cjs.js","sources":["../../src/utils/mergeEntityAndProviderThresholds.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef, type Entity } from '@backstage/catalog-model';\nimport type {\n ThresholdConfig,\n ThresholdRule,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n type MetricProvider,\n validateThresholdsForMetric,\n ThresholdConfigFormatError,\n validateThresholdNumberIntervals,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { isError } from '@backstage/errors';\n\nconst thresholdRulesAnnotationPrefix = (providerId: string) =>\n `scorecard.io/${providerId}.thresholds.rules.`;\n\n/**\n * Extract threshold override rules from entity annotations for a given provider, doesn't validate rules.\n */\nfunction parseEntityAnnotationThresholds(\n entity: Entity,\n providerId: string,\n): ThresholdRule[] {\n const annotations = entity.metadata?.annotations || {};\n const prefix = thresholdRulesAnnotationPrefix(providerId);\n const overrides: ThresholdRule[] = [];\n\n for (const [annotationKey, expression] of Object.entries(annotations)) {\n if (annotationKey.startsWith(prefix) && expression) {\n const key = annotationKey.substring(prefix.length);\n overrides.push({ key, expression });\n }\n }\n\n return overrides;\n}\n\nexport function mergeEntityAndProviderThresholds(\n entity: Entity,\n provider: MetricProvider,\n baseThresholds?: ThresholdConfig,\n): ThresholdConfig {\n let isRulesMerged = false;\n\n const providerId = provider.getProviderId();\n const providerThresholds = baseThresholds ?? provider.getMetricThresholds();\n const providerMetricType = provider.getMetricType();\n const entityAnnotationThresholds = parseEntityAnnotationThresholds(\n entity,\n providerId,\n );\n\n const mergedRules = [...providerThresholds.rules];\n for (const override of entityAnnotationThresholds) {\n const foundKey = mergedRules.findIndex(rule => rule.key === override.key);\n if (foundKey === -1) {\n throw new ThresholdConfigFormatError(\n `Unable to override ${stringifyEntityRef(\n entity,\n )} thresholds by ${JSON.stringify(\n override,\n )}, metric provider ${providerId} does not support key ${override.key}`,\n );\n }\n\n const mergedRule: ThresholdRule = { ...mergedRules[foundKey], ...override };\n try {\n validateThresholdsForMetric({ rules: [mergedRule] }, providerMetricType);\n\n if (!isRulesMerged) isRulesMerged = true;\n } catch (e) {\n if (isError(e)) {\n throw new ThresholdConfigFormatError(\n `Invalid threshold annotation '${thresholdRulesAnnotationPrefix(\n providerId,\n )}${override.key}: ${\n override.expression\n }' in entity '${stringifyEntityRef(entity)}': ${e.message}`,\n );\n }\n throw e;\n }\n\n mergedRules[foundKey] = mergedRule;\n }\n\n if (isRulesMerged) {\n validateThresholdNumberIntervals(mergedRules, providerMetricType);\n }\n\n return {\n rules: mergedRules,\n };\n}\n"],"names":["ThresholdConfigFormatError","stringifyEntityRef","validateThresholdsForMetric","isError","validateThresholdNumberIntervals"],"mappings":";;;;;;AA6BA,MAAM,8BAAiC,GAAA,CAAC,UACtC,KAAA,CAAA,aAAA,EAAgB,UAAU,CAAA,kBAAA,CAAA;AAK5B,SAAS,+BAAA,CACP,QACA,UACiB,EAAA;AACjB,EAAA,MAAM,WAAc,GAAA,MAAA,CAAO,QAAU,EAAA,WAAA,IAAe,EAAC;AACrD,EAAM,MAAA,MAAA,GAAS,+BAA+B,UAAU,CAAA;AACxD,EAAA,MAAM,YAA6B,EAAC;AAEpC,EAAA,KAAA,MAAW,CAAC,aAAe,EAAA,UAAU,KAAK,MAAO,CAAA,OAAA,CAAQ,WAAW,CAAG,EAAA;AACrE,IAAA,IAAI,aAAc,CAAA,UAAA,CAAW,MAAM,CAAA,IAAK,UAAY,EAAA;AAClD,MAAA,MAAM,GAAM,GAAA,aAAA,CAAc,SAAU,CAAA,MAAA,CAAO,MAAM,CAAA;AACjD,MAAA,SAAA,CAAU,IAAK,CAAA,EAAE,GAAK,EAAA,UAAA,EAAY,CAAA;AAAA;AACpC;AAGF,EAAO,OAAA,SAAA;AACT;AAEgB,SAAA,gCAAA,CACd,MACA,EAAA,QAAA,EACA,cACiB,EAAA;AACjB,EAAA,IAAI,aAAgB,GAAA,KAAA;AAEpB,EAAM,MAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AAC1C,EAAM,MAAA,kBAAA,GAAqB,cAAkB,IAAA,QAAA,CAAS,mBAAoB,EAAA;AAC1E,EAAM,MAAA,kBAAA,GAAqB,SAAS,aAAc,EAAA;AAClD,EAAA,MAAM,0BAA6B,GAAA,+BAAA;AAAA,IACjC,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,WAAc,GAAA,CAAC,GAAG,kBAAA,CAAmB,KAAK,CAAA;AAChD,EAAA,KAAA,MAAW,YAAY,0BAA4B,EAAA;AACjD,IAAA,MAAM,WAAW,WAAY,CAAA,SAAA,CAAU,UAAQ,IAAK,CAAA,GAAA,KAAQ,SAAS,GAAG,CAAA;AACxE,IAAA,IAAI,aAAa,EAAI,EAAA;AACnB,MAAA,MAAM,IAAIA,uDAAA;AAAA,QACR,CAAsB,mBAAA,EAAAC,+BAAA;AAAA,UACpB;AAAA,SACD,kBAAkB,IAAK,CAAA,SAAA;AAAA,UACtB;AAAA,SACD,CAAA,kBAAA,EAAqB,UAAU,CAAA,sBAAA,EAAyB,SAAS,GAAG,CAAA;AAAA,OACvE;AAAA;AAGF,IAAA,MAAM,aAA4B,EAAE,GAAG,YAAY,QAAQ,CAAA,EAAG,GAAG,QAAS,EAAA;AAC1E,IAAI,IAAA;AACF,MAAAC,wDAAA,CAA4B,EAAE,KAAO,EAAA,CAAC,UAAU,CAAA,IAAK,kBAAkB,CAAA;AAEvE,MAAI,IAAA,CAAC,eAA+B,aAAA,GAAA,IAAA;AAAA,aAC7B,CAAG,EAAA;AACV,MAAI,IAAAC,cAAA,CAAQ,CAAC,CAAG,EAAA;AACd,QAAA,MAAM,IAAIH,uDAAA;AAAA,UACR,CAAiC,8BAAA,EAAA,8BAAA;AAAA,YAC/B;AAAA,WACD,CAAA,EAAG,QAAS,CAAA,GAAG,CACd,EAAA,EAAA,QAAA,CAAS,UACX,CAAA,aAAA,EAAgBC,+BAAmB,CAAA,MAAM,CAAC,CAAA,GAAA,EAAM,EAAE,OAAO,CAAA;AAAA,SAC3D;AAAA;AAEF,MAAM,MAAA,CAAA;AAAA;AAGR,IAAA,WAAA,CAAY,QAAQ,CAAI,GAAA,UAAA;AAAA;AAG1B,EAAA,IAAI,aAAe,EAAA;AACjB,IAAAG,6DAAA,CAAiC,aAAa,kBAAkB,CAAA;AAAA;AAGlE,EAAO,OAAA;AAAA,IACL,KAAO,EAAA;AAAA,GACT;AACF;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
3
- "version": "2.7.7",
3
+ "version": "2.7.9",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,8 +47,8 @@
47
47
  "@backstage/plugin-catalog-node": "^2.1.0",
48
48
  "@backstage/plugin-permission-common": "^0.9.7",
49
49
  "@backstage/plugin-permission-node": "^0.10.11",
50
- "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.7.7",
51
- "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.7.7",
50
+ "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.7.9",
51
+ "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.7.9",
52
52
  "express": "^4.17.1",
53
53
  "express-promise-router": "^4.1.0",
54
54
  "knex": "^3.1.0",