@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/plugin.cjs.js +2 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/providers/MetricProvidersRegistry.cjs.js +9 -3
- package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
- package/dist/scheduler/index.cjs.js +4 -1
- package/dist/scheduler/index.cjs.js.map +1 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +14 -2
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
- package/dist/service/CatalogMetricService.cjs.js +36 -109
- package/dist/service/CatalogMetricService.cjs.js.map +1 -1
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js +59 -0
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js.map +1 -0
- package/migrations/20251117131443_add_status_column.js +30 -0
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-scorecard-backend
|
|
2
2
|
|
|
3
|
+
## 2.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f8fb8e4: Implemented saving metric `status` to the database. Added logic for saving `status` in the metric puller scheduler.
|
|
8
|
+
|
|
9
|
+
**BREAKING**: Added method `getMetricType` to the `MetricProvider` interface and updated the `getMetric` method to use `getMetricType()` instead of hardcoded `type` values.
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
export class MyMetricProvider implements MetricProvider {
|
|
13
|
+
+ getMetricType(): 'number' {
|
|
14
|
+
+ return 'number';
|
|
15
|
+
+ }
|
|
16
|
+
|
|
17
|
+
getMetric(): Metric<'number'> {
|
|
18
|
+
return {
|
|
19
|
+
id: this.getProviderId(),
|
|
20
|
+
title: 'GitHub open PRs',
|
|
21
|
+
description:
|
|
22
|
+
'Current count of open Pull Requests for a given GitHub repository.',
|
|
23
|
+
- type: 'number',
|
|
24
|
+
+ type: this.getMetricType(),
|
|
25
|
+
history: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- 4c2261f: Backstage version bump to v1.45.2
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [f8fb8e4]
|
|
36
|
+
- Updated dependencies [4c2261f]
|
|
37
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.2.0
|
|
38
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.2.0
|
|
39
|
+
|
|
3
40
|
## 2.1.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -64,7 +64,6 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
64
64
|
catalog,
|
|
65
65
|
auth,
|
|
66
66
|
registry: metricProvidersRegistry,
|
|
67
|
-
thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator(),
|
|
68
67
|
database: dbMetricValues
|
|
69
68
|
});
|
|
70
69
|
index.Scheduler.create({
|
|
@@ -74,7 +73,8 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
74
73
|
logger,
|
|
75
74
|
scheduler,
|
|
76
75
|
database: dbMetricValues,
|
|
77
|
-
metricProvidersRegistry
|
|
76
|
+
metricProvidersRegistry,
|
|
77
|
+
thresholdEvaluator: new ThresholdEvaluator.ThresholdEvaluator()
|
|
78
78
|
}).start();
|
|
79
79
|
httpRouter.use(
|
|
80
80
|
await router.createRouter({
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -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';\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
|
|
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';\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 });\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 httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n httpAuth,\n permissions,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","CatalogMetricService","Scheduler","ThresholdEvaluator","createRouter"],"mappings":";;;;;;;;;;;;;;;AA0CO,MAAM,kBAAkBA,oCAAoB,CAAA;AAAA,EACjD,QAAU,EAAA,WAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,uBAAA,GAA0B,IAAIC,+CAAwB,EAAA;AAE5D,IAAA,GAAA,CAAI,uBAAuBC,2DAAgC,EAAA;AAAA,MACzD,qBAAqB,kBAAsC,EAAA;AACzD,QAAA,kBAAA,CAAmB,QAAQ,CAAkB,cAAA,KAAA;AAC3C,UAAA,uBAAA,CAAwB,SAAS,cAAc,CAAA;AAAA,SAChD,CAAA;AAAA;AACH,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,OAAS,EAAAC,mCAAA;AAAA,QACT,QAAQD,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA,mBAAA;AAAA,QAClC,WAAWA,6BAAa,CAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,mBAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAA,mBAAA,CAAoB,eAAgB,CAAA;AAAA,UAClC,WAAa,EAAAE,0CAAA;AAAA,UACb,YAAA,EAAc,OAAO,YAA2B,KAAA;AAC9C,YAAO,OAAA,uBAAA,CAAwB,YAAY,YAAY,CAAA;AAAA,WACzD;AAAA,UACA,WAAa,EAAAC,mDAAA;AAAA,UACb,KAAA,EAAO,MAAO,CAAA,MAAA,CAAOC,WAAc;AAAA,SACpC,CAAA;AAGD,QAAA,MAAMC,kBAAQ,QAAQ,CAAA;AAEtB,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAM,MAAA,cAAA,GAAiB,IAAIC,yCAAA,CAAqB,MAAM,CAAA;AAEtD,QAAM,MAAA,oBAAA,GAAuB,IAAIC,yCAAqB,CAAA;AAAA,UACpD,OAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAU,EAAA,uBAAA;AAAA,UACV,QAAU,EAAA;AAAA,SACX,CAAA;AAED,QAAAC,eAAA,CAAU,MAAO,CAAA;AAAA,UACf,IAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA;AAAA,UACA,QAAU,EAAA,cAAA;AAAA,UACV,uBAAA;AAAA,UACA,kBAAA,EAAoB,IAAIC,qCAAmB;AAAA,SAC5C,EAAE,KAAM,EAAA;AAET,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,uBAAA;AAAA,YACA,oBAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -8,10 +8,16 @@ class MetricProvidersRegistry {
|
|
|
8
8
|
register(metricProvider) {
|
|
9
9
|
const providerId = metricProvider.getProviderId();
|
|
10
10
|
const providerDatasource = metricProvider.getProviderDatasourceId();
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const metric = metricProvider.getMetric();
|
|
12
|
+
const metricType = metricProvider.getMetricType();
|
|
13
|
+
if (providerId !== metric.id) {
|
|
13
14
|
throw new Error(
|
|
14
|
-
`Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${
|
|
15
|
+
`Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (metricType !== metric.type) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`
|
|
15
21
|
);
|
|
16
22
|
}
|
|
17
23
|
const expectedPrefix = `${providerDatasource}.`;
|
|
@@ -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 providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const
|
|
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 providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metric = metricProvider.getMetric();\n const metricType = metricProvider.getMetricType();\n\n if (providerId !== metric.id) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n const expectedPrefix = `${providerDatasource}.`;\n if (\n !providerId.startsWith(expectedPrefix) ||\n providerId === expectedPrefix\n ) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(providerId)) {\n throw new ConflictError(\n `Metric provider with ID '${providerId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(providerId, metricProvider);\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(providerId);\n }\n\n getProvider(providerId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(providerId);\n if (!metricProvider) {\n throw new NotFoundError(\n `Metric provider with ID '${providerId}' is not registered.`,\n );\n }\n return metricProvider;\n }\n\n getMetric(providerId: string): Metric {\n return this.getProvider(providerId).getMetric();\n }\n\n async calculateMetric(\n providerId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(providerId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n providerIds: string[],\n entity: Entity,\n ): Promise<{ providerId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n providerIds.map(providerId => this.calculateMetric(providerId, entity)),\n );\n\n return results.map((result, index) => {\n const providerId = providerIds[index];\n if (result.status === 'fulfilled') {\n return { providerId, value: result.value };\n }\n return { providerId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n return Array.from(this.metricProviders.values());\n }\n\n listMetrics(providerIds?: string[]): Metric[] {\n if (providerIds && providerIds.length !== 0) {\n return providerIds\n .map(providerId => this.metricProviders.get(providerId)?.getMetric())\n .filter((m): m is Metric => m !== undefined);\n }\n return [...this.metricProviders.values()].map(provider =>\n provider.getMetric(),\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n return Array.from(providerIdsOfDatasource)\n .map(providerId => this.metricProviders.get(providerId))\n .filter((provider): provider is MetricProvider => provider !== undefined)\n .map(provider => provider.getMetric());\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ class Scheduler {
|
|
|
12
12
|
scheduler;
|
|
13
13
|
database;
|
|
14
14
|
metricProvidersRegistry;
|
|
15
|
+
thresholdEvaluator;
|
|
15
16
|
tasks = [];
|
|
16
17
|
constructor(options) {
|
|
17
18
|
this.auth = options.auth;
|
|
@@ -21,6 +22,7 @@ class Scheduler {
|
|
|
21
22
|
this.scheduler = options.scheduler;
|
|
22
23
|
this.database = options.database;
|
|
23
24
|
this.metricProvidersRegistry = options.metricProvidersRegistry;
|
|
25
|
+
this.thresholdEvaluator = options.thresholdEvaluator;
|
|
24
26
|
}
|
|
25
27
|
static create(options) {
|
|
26
28
|
return new Scheduler(options);
|
|
@@ -68,7 +70,8 @@ class Scheduler {
|
|
|
68
70
|
database: this.database,
|
|
69
71
|
config: this.config,
|
|
70
72
|
catalog: this.catalog,
|
|
71
|
-
auth: this.auth
|
|
73
|
+
auth: this.auth,
|
|
74
|
+
thresholdEvaluator: this.thresholdEvaluator
|
|
72
75
|
},
|
|
73
76
|
provider
|
|
74
77
|
)
|
|
@@ -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';\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\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 }\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 },\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":";;;;;;
|
|
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,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var mergeEntityAndProviderThresholds = require('../../utils/mergeEntityAndProviderThresholds.cjs.js');
|
|
4
5
|
var uuid = require('uuid');
|
|
5
6
|
var catalogModel = require('@backstage/catalog-model');
|
|
6
7
|
|
|
@@ -13,6 +14,7 @@ class PullMetricsByProviderTask {
|
|
|
13
14
|
provider;
|
|
14
15
|
scheduler;
|
|
15
16
|
database;
|
|
17
|
+
thresholdEvaluator;
|
|
16
18
|
static CATALOG_BATCH_SIZE = 50;
|
|
17
19
|
static DEFAULT_SCHEDULE = {
|
|
18
20
|
frequency: { hours: 1 },
|
|
@@ -28,6 +30,7 @@ class PullMetricsByProviderTask {
|
|
|
28
30
|
this.provider = provider;
|
|
29
31
|
this.scheduler = options.scheduler;
|
|
30
32
|
this.database = options.database;
|
|
33
|
+
this.thresholdEvaluator = options.thresholdEvaluator;
|
|
31
34
|
}
|
|
32
35
|
async start() {
|
|
33
36
|
const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;
|
|
@@ -61,6 +64,7 @@ class PullMetricsByProviderTask {
|
|
|
61
64
|
logger.info(`Pulling metrics for ${this.providerId}`);
|
|
62
65
|
let totalProcessed = 0;
|
|
63
66
|
let cursor = void 0;
|
|
67
|
+
const metricType = provider.getMetricType();
|
|
64
68
|
try {
|
|
65
69
|
do {
|
|
66
70
|
const entitiesResponse = await this.catalog.queryEntities(
|
|
@@ -75,19 +79,27 @@ class PullMetricsByProviderTask {
|
|
|
75
79
|
const batchResults = await Promise.allSettled(
|
|
76
80
|
entitiesResponse.items.map(async (entity) => {
|
|
77
81
|
try {
|
|
82
|
+
const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
|
|
83
|
+
entity,
|
|
84
|
+
provider
|
|
85
|
+
);
|
|
78
86
|
const value = await provider.calculateMetric(entity);
|
|
87
|
+
const status = this.thresholdEvaluator.getFirstMatchingThreshold(
|
|
88
|
+
value,
|
|
89
|
+
metricType,
|
|
90
|
+
thresholds
|
|
91
|
+
);
|
|
79
92
|
return {
|
|
80
93
|
catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
|
|
81
94
|
metric_id: this.providerId,
|
|
82
95
|
value,
|
|
83
96
|
timestamp: /* @__PURE__ */ new Date(),
|
|
84
|
-
|
|
97
|
+
status
|
|
85
98
|
};
|
|
86
99
|
} catch (error) {
|
|
87
100
|
return {
|
|
88
101
|
catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
|
|
89
102
|
metric_id: this.providerId,
|
|
90
|
-
value: void 0,
|
|
91
103
|
timestamp: /* @__PURE__ */ new Date(),
|
|
92
104
|
error_message: error instanceof Error ? error.message : String(error)
|
|
93
105
|
};
|
|
@@ -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 { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValue } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\n\ntype Options = Pick<\n SchedulerOptions,\n 'scheduler' | 'logger' | 'database' | 'config' | 'catalog' | 'auth'\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\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: { seconds: 3 },\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 }\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 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 try {\n const value = await provider.calculateMetric(entity);\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message: undefined,\n };\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value: undefined,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n };\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as Omit<DbMetricValue, 'id'>[]),\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"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","stringifyEntityRef"],"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 { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValue } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\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: { seconds: 3 },\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\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 try {\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n const value = await provider.calculateMetric(entity);\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 } as Omit<DbMetricValue, 'id'>;\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n } as Omit<DbMetricValue, 'id'>;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as Omit<DbMetricValue, 'id'>[]),\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"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","mergeEntityAndProviderThresholds","stringifyEntityRef"],"mappings":";;;;;;;AA6CO,MAAM,yBAAmD,CAAA;AAAA,EAC7C,MAAA;AAAA,EACA,IAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,kBAAA;AAAA,EAEjB,OAAwB,kBAAqB,GAAA,EAAA;AAAA,EAE7C,OAAwB,gBACtB,GAAA;AAAA,IACE,SAAA,EAAW,EAAE,KAAA,EAAO,CAAE,EAAA;AAAA,IACtB,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,IACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE;AAAA,GAC7B;AAAA,EAEF,WAAA,CAAY,SAAkB,QAA0B,EAAA;AACtD,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAK,IAAA,CAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AACzC,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,qBAAqB,OAAQ,CAAA,kBAAA;AAAA;AACpC,EAEA,MAAM,KAAuB,GAAA;AAC3B,IAAM,MAAA,kBAAA,GAAqB,CAAqB,kBAAA,EAAA,IAAA,CAAK,UAAU,CAAA,SAAA,CAAA;AAC/D,IAAM,MAAA,QAAA,GAAW,IAAK,CAAA,qBAAA,CAAsB,kBAAkB,CAAA;AAE9D,IAAA,MAAM,UAAa,GAAA,IAAA,CAAK,SAAU,CAAA,yBAAA,CAA0B,QAAQ,CAAA;AAEpE,IAAA,MAAM,WAAW,GAAI,CAAA;AAAA,MACnB,IAAI,IAAK,CAAA,UAAA;AAAA,MACT,IAAI,YAAY;AACd,QAAM,MAAA,MAAA,GAAS,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA;AAAA,UAC/B,KAAA,EAAO,KAAK,WAAY,CAAA,IAAA;AAAA,UACxB,QAAQ,IAAK,CAAA,UAAA;AAAA,UACb,gBAAgBA,OAAK;AAAA,SACtB,CAAA;AAED,QAAI,IAAA;AACF,UAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,IAAK,CAAA,QAAA,EAAU,MAAM,CAAA;AAAA,iBAC7C,KAAO,EAAA;AACd,UAAO,MAAA,CAAA,KAAA;AAAA,YACL,CAAG,EAAA,IAAA,CAAK,UAAU,CAAA,yBAAA,EAA4B,KAAK,CAAA,CAAA;AAAA,YACnD;AAAA,WACF;AAAA;AACF;AACF,KACD,CAAA;AAAA;AACH,EAEQ,sBACN,YACwC,EAAA;AACxC,IAAA,OAAO,IAAK,CAAA,MAAA,CAAO,GAAI,CAAA,YAAY,CAC/B,GAAAC,qEAAA;AAAA,MACE,IAAA,CAAK,MAAO,CAAA,SAAA,CAAU,YAAY;AAAA,QAEpC,yBAA0B,CAAA,gBAAA;AAAA;AAChC,EAEA,MAAc,mBACZ,CAAA,QAAA,EACA,MACe,EAAA;AACf,IAAA,MAAA,CAAO,IAAK,CAAA,CAAA,oBAAA,EAAuB,IAAK,CAAA,UAAU,CAAE,CAAA,CAAA;AAEpD,IAAA,IAAI,cAAiB,GAAA,CAAA;AACrB,IAAA,IAAI,MAA6B,GAAA,MAAA;AAEjC,IAAM,MAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AAE1C,IAAI,IAAA;AACF,MAAG,GAAA;AACD,QAAM,MAAA,gBAAA,GAAmB,MAAM,IAAA,CAAK,OAAQ,CAAA,aAAA;AAAA,UAC1C;AAAA,YACE,MAAA,EAAQ,SAAS,gBAAiB,EAAA;AAAA,YAClC,OAAO,yBAA0B,CAAA,kBAAA;AAAA,YACjC,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,WAC7B;AAAA,UACA,EAAE,WAAa,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,0BAA2B;AAAA,SAC5D;AAEA,QAAA,MAAA,GAAS,iBAAiB,QAAS,CAAA,UAAA;AAEnC,QAAM,MAAA,YAAA,GAAe,MAAM,OAAQ,CAAA,UAAA;AAAA,UACjC,gBAAiB,CAAA,KAAA,CAAM,GAAI,CAAA,OAAM,MAAU,KAAA;AACzC,YAAI,IAAA;AACF,cAAA,MAAM,UAAa,GAAAC,iEAAA;AAAA,gBACjB,MAAA;AAAA,gBACA;AAAA,eACF;AACA,cAAA,MAAM,KAAQ,GAAA,MAAM,QAAS,CAAA,eAAA,CAAgB,MAAM,CAAA;AACnD,cAAM,MAAA,MAAA,GAAS,KAAK,kBAAmB,CAAA,yBAAA;AAAA,gBACrC,KAAA;AAAA,gBACA,UAAA;AAAA,gBACA;AAAA,eACF;AAEA,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBC,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB;AAAA,eACF;AAAA,qBACO,KAAO,EAAA;AACd,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBA,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB,eACE,KAAiB,YAAA,KAAA,GAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK;AAAA,eACzD;AAAA;AACF,WACD;AAAA,SACD,CAAA,IAAA;AAAA,UAAK,CACL,QAAA,KAAA,QAAA,CAAS,MAAO,CAAA,CAAC,KAAK,IAAS,KAAA;AAC7B,YAAI,IAAA,IAAA,CAAK,WAAW,WAAa,EAAA;AAC/B,cAAA,OAAO,CAAC,GAAG,GAAK,EAAA,IAAA,CAAK,KAAK,CAAA;AAAA;AAE5B,YAAO,OAAA,GAAA;AAAA,WACT,EAAG,EAAiC;AAAA,SACtC;AAEA,QAAM,MAAA,IAAA,CAAK,QAAS,CAAA,kBAAA,CAAmB,YAAY,CAAA;AACnD,QAAA,cAAA,IAAkB,iBAAiB,KAAM,CAAA,MAAA;AAAA,eAClC,MAAW,KAAA,KAAA,CAAA;AAEpB,MAAO,MAAA,CAAA,IAAA;AAAA,QACL,CAA6B,0BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,YAAA,EAAe,cAAc,CAAA,SAAA;AAAA,OAC3E;AAAA,aACO,KAAO,EAAA;AACd,MAAA,MAAA,CAAO,MAAM,CAA8B,2BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,EAAA,EAAK,KAAK,CAAE,CAAA,CAAA;AAEtE,MAAM,MAAA,KAAA;AAAA;AACR;AAEJ;;;;"}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var catalogModel = require('@backstage/catalog-model');
|
|
4
3
|
var errors = require('@backstage/errors');
|
|
5
|
-
var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
|
|
6
4
|
var permissionUtils = require('../permissions/permissionUtils.cjs.js');
|
|
5
|
+
var mergeEntityAndProviderThresholds = require('../utils/mergeEntityAndProviderThresholds.cjs.js');
|
|
7
6
|
|
|
8
7
|
class CatalogMetricService {
|
|
9
8
|
catalog;
|
|
10
9
|
auth;
|
|
11
10
|
registry;
|
|
12
|
-
thresholdEvaluator;
|
|
13
11
|
database;
|
|
14
12
|
constructor(options) {
|
|
15
13
|
this.catalog = options.catalog;
|
|
16
14
|
this.auth = options.auth;
|
|
17
15
|
this.registry = options.registry;
|
|
18
|
-
this.thresholdEvaluator = options.thresholdEvaluator;
|
|
19
16
|
this.database = options.database;
|
|
20
17
|
}
|
|
21
18
|
/**
|
|
@@ -43,116 +40,46 @@ class CatalogMetricService {
|
|
|
43
40
|
entityRef,
|
|
44
41
|
authorizedMetricsToFetch.map((m) => m.id)
|
|
45
42
|
);
|
|
46
|
-
return rawResults.map(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
thresholds = this.mergeEntityAndProviderThresholds(
|
|
54
|
-
entity,
|
|
55
|
-
provider,
|
|
56
|
-
metric.type
|
|
57
|
-
);
|
|
58
|
-
if (value === void 0) {
|
|
59
|
-
thresholdError = "Unable to evaluate thresholds, metric value is missing";
|
|
60
|
-
} else {
|
|
61
|
-
evaluation = this.thresholdEvaluator.getFirstMatchingThreshold(
|
|
62
|
-
value,
|
|
63
|
-
metric.type,
|
|
64
|
-
thresholds
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
} catch (e) {
|
|
68
|
-
thresholdError = errors.stringifyError(e);
|
|
69
|
-
}
|
|
70
|
-
const isMetricCalcError = error_message || value === void 0;
|
|
71
|
-
return {
|
|
72
|
-
id: metric.id,
|
|
73
|
-
status: isMetricCalcError ? "error" : "success",
|
|
74
|
-
metadata: {
|
|
75
|
-
title: metric.title,
|
|
76
|
-
description: metric.description,
|
|
77
|
-
type: metric.type,
|
|
78
|
-
history: metric.history
|
|
79
|
-
},
|
|
80
|
-
...isMetricCalcError && {
|
|
81
|
-
error: error_message ?? errors.stringifyError(new Error(`Metric value is 'undefined'`))
|
|
82
|
-
},
|
|
83
|
-
result: {
|
|
84
|
-
value,
|
|
85
|
-
timestamp: new Date(timestamp).toISOString(),
|
|
86
|
-
thresholdResult: {
|
|
87
|
-
definition: thresholds,
|
|
88
|
-
status: thresholdError ? "error" : "success",
|
|
89
|
-
evaluation,
|
|
90
|
-
...thresholdError && { error: thresholdError }
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Parse threshold overrides from entity annotations.
|
|
98
|
-
* Looks for annotations in the format:
|
|
99
|
-
* scorecard.io/{providerId}.thresholds.rules.{thresholdName}: "{expression}"
|
|
100
|
-
*
|
|
101
|
-
* @param entity - The catalog entity
|
|
102
|
-
* @param providerId - The metric provider ID (e.g., 'jira.open_issues')
|
|
103
|
-
* @param metricType - The metric type
|
|
104
|
-
* @returns Threshold rules from entity annotations, or empty rules if none found or invalid
|
|
105
|
-
*/
|
|
106
|
-
parseEntityOverrideThresholds(entity, providerId, metricType) {
|
|
107
|
-
const annotations = entity.metadata?.annotations || {};
|
|
108
|
-
const prefix = `scorecard.io/${providerId}.thresholds.rules.`;
|
|
109
|
-
const overrides = [];
|
|
110
|
-
for (const [annotationKey, expression] of Object.entries(annotations)) {
|
|
111
|
-
if (annotationKey.startsWith(prefix) && expression) {
|
|
112
|
-
const key = annotationKey.substring(prefix.length);
|
|
113
|
-
const entityRule = { key, expression };
|
|
43
|
+
return rawResults.map(
|
|
44
|
+
({ metric_id, value, error_message, timestamp, status }) => {
|
|
45
|
+
let thresholds;
|
|
46
|
+
let thresholdError;
|
|
47
|
+
const provider = this.registry.getProvider(metric_id);
|
|
48
|
+
const metric = provider.getMetric();
|
|
114
49
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (errors.isError(e)) {
|
|
119
|
-
throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
|
|
120
|
-
`Invalid threshold annotation '${annotationKey}: ${expression}' in entity '${catalogModel.stringifyEntityRef(
|
|
121
|
-
entity
|
|
122
|
-
)}': ${e.message}`
|
|
123
|
-
);
|
|
50
|
+
thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(entity, provider);
|
|
51
|
+
if (value === void 0) {
|
|
52
|
+
thresholdError = "Unable to evaluate thresholds, metric value is missing";
|
|
124
53
|
}
|
|
125
|
-
|
|
54
|
+
} catch (error) {
|
|
55
|
+
thresholdError = errors.stringifyError(error);
|
|
126
56
|
}
|
|
57
|
+
const isMetricCalcError = error_message || value === void 0;
|
|
58
|
+
return {
|
|
59
|
+
id: metric.id,
|
|
60
|
+
status: isMetricCalcError ? "error" : "success",
|
|
61
|
+
metadata: {
|
|
62
|
+
title: metric.title,
|
|
63
|
+
description: metric.description,
|
|
64
|
+
type: metric.type,
|
|
65
|
+
history: metric.history
|
|
66
|
+
},
|
|
67
|
+
...isMetricCalcError && {
|
|
68
|
+
error: error_message ?? errors.stringifyError(new Error(`Metric value is 'undefined'`))
|
|
69
|
+
},
|
|
70
|
+
result: {
|
|
71
|
+
value,
|
|
72
|
+
timestamp: new Date(timestamp).toISOString(),
|
|
73
|
+
thresholdResult: {
|
|
74
|
+
definition: thresholds,
|
|
75
|
+
status: thresholdError ? "error" : "success",
|
|
76
|
+
evaluation: status,
|
|
77
|
+
...thresholdError && { error: thresholdError }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
127
81
|
}
|
|
128
|
-
}
|
|
129
|
-
return overrides;
|
|
130
|
-
}
|
|
131
|
-
mergeEntityAndProviderThresholds(entity, provider, metricType) {
|
|
132
|
-
const providerThresholds = provider.getMetricThresholds();
|
|
133
|
-
const entityOverrideThresholds = this.parseEntityOverrideThresholds(
|
|
134
|
-
entity,
|
|
135
|
-
provider.getProviderId(),
|
|
136
|
-
metricType
|
|
137
82
|
);
|
|
138
|
-
const mergedRules = [...providerThresholds.rules];
|
|
139
|
-
for (const override of entityOverrideThresholds) {
|
|
140
|
-
const foundKey = mergedRules.findIndex((rule) => rule.key === override.key);
|
|
141
|
-
if (foundKey !== -1) {
|
|
142
|
-
mergedRules[foundKey] = override;
|
|
143
|
-
} else {
|
|
144
|
-
throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
|
|
145
|
-
`Unable to override ${catalogModel.stringifyEntityRef(
|
|
146
|
-
entity
|
|
147
|
-
)} thresholds by ${JSON.stringify(
|
|
148
|
-
override
|
|
149
|
-
)}, metric provider ${provider.getProviderId()} does not support key ${override.key}`
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
rules: mergedRules
|
|
155
|
-
};
|
|
156
83
|
}
|
|
157
84
|
}
|
|
158
85
|
|
|
@@ -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 { stringifyEntityRef, type Entity } from '@backstage/catalog-model';\nimport {\n MetricResult,\n MetricType,\n ThresholdConfig,\n ThresholdRule,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { ThresholdEvaluator } from '../threshold/ThresholdEvaluator';\nimport { isError, NotFoundError, stringifyError } from '@backstage/errors';\nimport {\n MetricProvider,\n ThresholdConfigFormatError,\n validateThresholds,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { AuthService } 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';\n\nexport type CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n thresholdEvaluator: ThresholdEvaluator;\n database: DatabaseMetricValues;\n};\n\nexport class CatalogMetricService {\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n private readonly database: DatabaseMetricValues;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.thresholdEvaluator = options.thresholdEvaluator;\n this.database = options.database;\n }\n\n /**\n * Get latest metric results for a specific catalog entity and metric providers.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param providerIds - Optional array of provider 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 providerIds?: 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 = providerIds\n ? this.registry.listMetrics().filter(m => providerIds.includes(m.id))\n : this.registry.listMetrics();\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(({ metric_id, value, error_message, timestamp }) => {\n const provider = this.registry.getProvider(metric_id);\n const metric = provider.getMetric();\n\n let thresholds: ThresholdConfig | undefined;\n let evaluation: string | undefined;\n let thresholdError: string | undefined;\n try {\n thresholds = this.mergeEntityAndProviderThresholds(\n entity,\n provider,\n metric.type,\n );\n if (value === undefined) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else {\n evaluation = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metric.type,\n thresholds,\n );\n }\n } catch (e) {\n thresholdError = stringifyError(e);\n }\n\n const isMetricCalcError = error_message || value === undefined;\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,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n });\n }\n\n /**\n * Parse threshold overrides from entity annotations.\n * Looks for annotations in the format:\n * scorecard.io/{providerId}.thresholds.rules.{thresholdName}: \"{expression}\"\n *\n * @param entity - The catalog entity\n * @param providerId - The metric provider ID (e.g., 'jira.open_issues')\n * @param metricType - The metric type\n * @returns Threshold rules from entity annotations, or empty rules if none found or invalid\n */\n private parseEntityOverrideThresholds(\n entity: Entity,\n providerId: string,\n metricType: MetricType,\n ): ThresholdRule[] {\n const annotations = entity.metadata?.annotations || {};\n const prefix = `scorecard.io/${providerId}.thresholds.rules.`;\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 const entityRule = { key, expression };\n try {\n validateThresholds({ rules: [entityRule] }, metricType);\n overrides.push(entityRule);\n } catch (e) {\n if (isError(e)) {\n throw new ThresholdConfigFormatError(\n `Invalid threshold annotation '${annotationKey}: ${expression}' in entity '${stringifyEntityRef(\n entity,\n )}': ${e.message}`,\n );\n }\n throw e;\n }\n }\n }\n\n return overrides;\n }\n\n private mergeEntityAndProviderThresholds(\n entity: Entity,\n provider: MetricProvider,\n metricType: MetricType,\n ): ThresholdConfig {\n const providerThresholds = provider.getMetricThresholds();\n const entityOverrideThresholds = this.parseEntityOverrideThresholds(\n entity,\n provider.getProviderId(),\n metricType,\n );\n\n const mergedRules = [...providerThresholds.rules];\n for (const override of entityOverrideThresholds) {\n const foundKey = mergedRules.findIndex(rule => rule.key === override.key);\n if (foundKey !== -1) {\n mergedRules[foundKey] = override;\n } else {\n throw new ThresholdConfigFormatError(\n `Unable to override ${stringifyEntityRef(\n entity,\n )} thresholds by ${JSON.stringify(\n override,\n )}, metric provider ${provider.getProviderId()} does not support key ${\n override.key\n }`,\n );\n }\n }\n\n return {\n rules: mergedRules,\n };\n }\n}\n"],"names":["NotFoundError","filterAuthorizedMetrics","stringifyError","validateThresholds","isError","ThresholdConfigFormatError","stringifyEntityRef"],"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} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport { AuthService } 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';\n\nexport type CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n};\n\nexport class CatalogMetricService {\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\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 }\n\n /**\n * Get latest metric results for a specific catalog entity and metric providers.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param providerIds - Optional array of provider 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 providerIds?: 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 = providerIds\n ? this.registry.listMetrics().filter(m => providerIds.includes(m.id))\n : this.registry.listMetrics();\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 = provider.getMetric();\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === undefined) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = error_message || value === undefined;\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"],"names":["NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
4
|
+
var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
|
|
5
|
+
var errors = require('@backstage/errors');
|
|
6
|
+
|
|
7
|
+
function parseEntityOverrideThresholds(entity, providerId, metricType) {
|
|
8
|
+
const annotations = entity.metadata?.annotations || {};
|
|
9
|
+
const prefix = `scorecard.io/${providerId}.thresholds.rules.`;
|
|
10
|
+
const overrides = [];
|
|
11
|
+
for (const [annotationKey, expression] of Object.entries(annotations)) {
|
|
12
|
+
if (annotationKey.startsWith(prefix) && expression) {
|
|
13
|
+
const key = annotationKey.substring(prefix.length);
|
|
14
|
+
const entityRule = { key, expression };
|
|
15
|
+
try {
|
|
16
|
+
backstagePluginScorecardNode.validateThresholds({ rules: [entityRule] }, metricType);
|
|
17
|
+
overrides.push(entityRule);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (errors.isError(e)) {
|
|
20
|
+
throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
|
|
21
|
+
`Invalid threshold annotation '${annotationKey}: ${expression}' in entity '${catalogModel.stringifyEntityRef(
|
|
22
|
+
entity
|
|
23
|
+
)}': ${e.message}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return overrides;
|
|
31
|
+
}
|
|
32
|
+
function mergeEntityAndProviderThresholds(entity, provider) {
|
|
33
|
+
const providerThresholds = provider.getMetricThresholds();
|
|
34
|
+
const entityOverrideThresholds = parseEntityOverrideThresholds(
|
|
35
|
+
entity,
|
|
36
|
+
provider.getProviderId(),
|
|
37
|
+
provider.getMetricType()
|
|
38
|
+
);
|
|
39
|
+
const mergedRules = [...providerThresholds.rules];
|
|
40
|
+
for (const override of entityOverrideThresholds) {
|
|
41
|
+
const foundKey = mergedRules.findIndex((rule) => rule.key === override.key);
|
|
42
|
+
if (foundKey === -1) {
|
|
43
|
+
throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
|
|
44
|
+
`Unable to override ${catalogModel.stringifyEntityRef(
|
|
45
|
+
entity
|
|
46
|
+
)} thresholds by ${JSON.stringify(
|
|
47
|
+
override
|
|
48
|
+
)}, metric provider ${provider.getProviderId()} does not support key ${override.key}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
mergedRules[foundKey] = override;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
rules: mergedRules
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.mergeEntityAndProviderThresholds = mergeEntityAndProviderThresholds;
|
|
59
|
+
//# sourceMappingURL=mergeEntityAndProviderThresholds.cjs.js.map
|
|
@@ -0,0 +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 MetricType,\n ThresholdConfig,\n ThresholdRule,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n ThresholdConfigFormatError,\n validateThresholds,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { isError } from '@backstage/errors';\nimport type { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\nfunction parseEntityOverrideThresholds(\n entity: Entity,\n providerId: string,\n metricType: MetricType,\n): ThresholdRule[] {\n const annotations = entity.metadata?.annotations || {};\n const prefix = `scorecard.io/${providerId}.thresholds.rules.`;\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 const entityRule = { key, expression };\n try {\n validateThresholds({ rules: [entityRule] }, metricType);\n overrides.push(entityRule);\n } catch (e) {\n if (isError(e)) {\n throw new ThresholdConfigFormatError(\n `Invalid threshold annotation '${annotationKey}: ${expression}' in entity '${stringifyEntityRef(\n entity,\n )}': ${e.message}`,\n );\n }\n throw e;\n }\n }\n }\n\n return overrides;\n}\n\nexport function mergeEntityAndProviderThresholds(\n entity: Entity,\n provider: MetricProvider,\n): ThresholdConfig {\n const providerThresholds = provider.getMetricThresholds();\n const entityOverrideThresholds = parseEntityOverrideThresholds(\n entity,\n provider.getProviderId(),\n provider.getMetricType(),\n );\n\n const mergedRules = [...providerThresholds.rules];\n for (const override of entityOverrideThresholds) {\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 ${provider.getProviderId()} does not support key ${\n override.key\n }`,\n );\n }\n\n mergedRules[foundKey] = override;\n }\n\n return {\n rules: mergedRules,\n };\n}\n"],"names":["validateThresholds","isError","ThresholdConfigFormatError","stringifyEntityRef"],"mappings":";;;;;;AA6BA,SAAS,6BAAA,CACP,MACA,EAAA,UAAA,EACA,UACiB,EAAA;AACjB,EAAA,MAAM,WAAc,GAAA,MAAA,CAAO,QAAU,EAAA,WAAA,IAAe,EAAC;AACrD,EAAM,MAAA,MAAA,GAAS,gBAAgB,UAAU,CAAA,kBAAA,CAAA;AACzC,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,MAAM,MAAA,UAAA,GAAa,EAAE,GAAA,EAAK,UAAW,EAAA;AACrC,MAAI,IAAA;AACF,QAAAA,+CAAA,CAAmB,EAAE,KAAO,EAAA,CAAC,UAAU,CAAA,IAAK,UAAU,CAAA;AACtD,QAAA,SAAA,CAAU,KAAK,UAAU,CAAA;AAAA,eAClB,CAAG,EAAA;AACV,QAAI,IAAAC,cAAA,CAAQ,CAAC,CAAG,EAAA;AACd,UAAA,MAAM,IAAIC,uDAAA;AAAA,YACR,CAAiC,8BAAA,EAAA,aAAa,CAAK,EAAA,EAAA,UAAU,CAAgB,aAAA,EAAAC,+BAAA;AAAA,cAC3E;AAAA,aACD,CAAM,GAAA,EAAA,CAAA,CAAE,OAAO,CAAA;AAAA,WAClB;AAAA;AAEF,QAAM,MAAA,CAAA;AAAA;AACR;AACF;AAGF,EAAO,OAAA,SAAA;AACT;AAEgB,SAAA,gCAAA,CACd,QACA,QACiB,EAAA;AACjB,EAAM,MAAA,kBAAA,GAAqB,SAAS,mBAAoB,EAAA;AACxD,EAAA,MAAM,wBAA2B,GAAA,6BAAA;AAAA,IAC/B,MAAA;AAAA,IACA,SAAS,aAAc,EAAA;AAAA,IACvB,SAAS,aAAc;AAAA,GACzB;AAEA,EAAA,MAAM,WAAc,GAAA,CAAC,GAAG,kBAAA,CAAmB,KAAK,CAAA;AAChD,EAAA,KAAA,MAAW,YAAY,wBAA0B,EAAA;AAC/C,IAAA,MAAM,WAAW,WAAY,CAAA,SAAA,CAAU,UAAQ,IAAK,CAAA,GAAA,KAAQ,SAAS,GAAG,CAAA;AACxE,IAAA,IAAI,aAAa,EAAI,EAAA;AACnB,MAAA,MAAM,IAAID,uDAAA;AAAA,QACR,CAAsB,mBAAA,EAAAC,+BAAA;AAAA,UACpB;AAAA,SACD,kBAAkB,IAAK,CAAA,SAAA;AAAA,UACtB;AAAA,SACD,CAAqB,kBAAA,EAAA,QAAA,CAAS,eAAe,CAAA,sBAAA,EAC5C,SAAS,GACX,CAAA;AAAA,OACF;AAAA;AAGF,IAAA,WAAA,CAAY,QAAQ,CAAI,GAAA,QAAA;AAAA;AAG1B,EAAO,OAAA;AAAA,IACL,KAAO,EAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Red Hat, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
exports.up = async function up(knex) {
|
|
18
|
+
await knex.schema.alterTable('metric_values', table => {
|
|
19
|
+
table
|
|
20
|
+
.string('status')
|
|
21
|
+
.checkIn(['success', 'warning', 'error'], 'status_check')
|
|
22
|
+
.nullable();
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
exports.down = async function down(knex) {
|
|
27
|
+
await knex.schema.alterTable('metric_values', table => {
|
|
28
|
+
table.dropColumn('status');
|
|
29
|
+
});
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
"postpack": "backstage-cli package postpack"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@backstage/backend-defaults": "^0.13.
|
|
42
|
-
"@backstage/backend-plugin-api": "^1.
|
|
43
|
-
"@backstage/catalog-client": "^1.12.
|
|
44
|
-
"@backstage/catalog-model": "^1.7.
|
|
41
|
+
"@backstage/backend-defaults": "^0.13.1",
|
|
42
|
+
"@backstage/backend-plugin-api": "^1.5.0",
|
|
43
|
+
"@backstage/catalog-client": "^1.12.1",
|
|
44
|
+
"@backstage/catalog-model": "^1.7.6",
|
|
45
45
|
"@backstage/errors": "^1.2.7",
|
|
46
|
-
"@backstage/plugin-catalog-common": "^1.1.
|
|
47
|
-
"@backstage/plugin-catalog-node": "^1.
|
|
48
|
-
"@backstage/plugin-permission-common": "^0.9.
|
|
49
|
-
"@backstage/plugin-permission-node": "^0.10.
|
|
50
|
-
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.
|
|
51
|
-
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.
|
|
46
|
+
"@backstage/plugin-catalog-common": "^1.1.7",
|
|
47
|
+
"@backstage/plugin-catalog-node": "^1.20.0",
|
|
48
|
+
"@backstage/plugin-permission-common": "^0.9.3",
|
|
49
|
+
"@backstage/plugin-permission-node": "^0.10.6",
|
|
50
|
+
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.2.0",
|
|
51
|
+
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.2.0",
|
|
52
52
|
"express": "^4.17.1",
|
|
53
53
|
"express-promise-router": "^4.1.0",
|
|
54
54
|
"knex": "^3.1.0",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"zod": "^3.22.4"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@backstage/backend-test-utils": "^1.
|
|
60
|
-
"@backstage/cli": "^0.34.
|
|
59
|
+
"@backstage/backend-test-utils": "^1.10.0",
|
|
60
|
+
"@backstage/cli": "^0.34.5",
|
|
61
61
|
"@types/express": "^4.17.6",
|
|
62
62
|
"@types/supertest": "^2.0.12",
|
|
63
63
|
"supertest": "^6.2.4"
|