@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.8.1 → 3.0.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +42 -36
  3. package/config.d.ts +2 -2
  4. package/dist/constants/aggregationKPIs.cjs.js +2 -2
  5. package/dist/constants/aggregationKPIs.cjs.js.map +1 -1
  6. package/dist/plugin.cjs.js +9 -0
  7. package/dist/plugin.cjs.js.map +1 -1
  8. package/dist/providers/MetricProvidersRegistry.cjs.js +33 -48
  9. package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
  10. package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +67 -115
  11. package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
  12. package/dist/service/CatalogMetricService.cjs.js +3 -2
  13. package/dist/service/CatalogMetricService.cjs.js.map +1 -1
  14. package/dist/service/aggregations/strategies/{AverageAggregationStrategy.cjs.js → WeightedStatusScoreAggregationStrategy.cjs.js} +16 -16
  15. package/dist/service/aggregations/strategies/WeightedStatusScoreAggregationStrategy.cjs.js.map +1 -0
  16. package/dist/service/aggregations/strategies/registerStrategies.cjs.js +5 -2
  17. package/dist/service/aggregations/strategies/registerStrategies.cjs.js.map +1 -1
  18. package/dist/service/router.cjs.js +8 -2
  19. package/dist/service/router.cjs.js.map +1 -1
  20. package/dist/threshold/ThresholdResolver.cjs.js +9 -6
  21. package/dist/threshold/ThresholdResolver.cjs.js.map +1 -1
  22. package/dist/utils/buildAggregationConfig.cjs.js +1 -1
  23. package/dist/utils/buildAggregationConfig.cjs.js.map +1 -1
  24. package/dist/utils/mergeEntityAndProviderThresholds.cjs.js +5 -6
  25. package/dist/utils/mergeEntityAndProviderThresholds.cjs.js.map +1 -1
  26. package/dist/validation/schemas/aggregationConfigSchemas.cjs.js +3 -3
  27. package/dist/validation/schemas/aggregationConfigSchemas.cjs.js.map +1 -1
  28. package/dist/validation/validateAggregationConfig.cjs.js +1 -1
  29. package/dist/validation/validateAggregationConfig.cjs.js.map +1 -1
  30. package/package.json +10 -10
  31. package/dist/service/aggregations/strategies/AverageAggregationStrategy.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-scorecard-backend
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 6ea1575: **BREAKING**: Rename aggregation KPI type `average` to `weightedStatusScore`.
8
+
9
+ ### App config
10
+
11
+ - `scorecard.aggregationKPIs.*.type`: `average` → `weightedStatusScore`
12
+
13
+ ### `GET /aggregations/:aggregationId` API
14
+
15
+ - `metadata.aggregationType`: `average` → `weightedStatusScore`
16
+ - `result.averageScore` → `result.weightedStatusScore`
17
+ - `result.averageWeightedSum` → `result.weightedStatusSum`
18
+ - `result.averageMaxPossible` → `result.weightedStatusMaxPossible`
19
+
20
+ ### Frontend translations (`metric.*`)
21
+
22
+ Update any scorecard translation overrides that used the old `average*` keys:
23
+
24
+ - `metric.averageCenterTooltipTotalLabel` → `metric.weightedStatusScoreCenterTooltipTotalLabel`
25
+ - `metric.averageCenterTooltipMaxLabel` → `metric.weightedStatusScoreCenterTooltipMaxLabel`
26
+ - `metric.averageCenterTooltipBreakdownRow_one` / `_other` → `metric.weightedStatusScoreCenterTooltipBreakdownRow_one` / `_other`
27
+ - `metric.averageLegendTooltipEntitiesEach_one` / `_other` → `metric.weightedStatusScoreLegendTooltipEntitiesEach_one` / `_other`
28
+ - `metric.averageLegendTooltipRowTotal` → `metric.weightedStatusScoreLegendTooltipRowTotal`
29
+
30
+ The `averageLegendTooltip*` keys served the removed side-legend tooltip; the center donut tooltip uses the `weightedStatusScoreCenterTooltip*` keys (including per-status breakdown rows).
31
+
32
+ ### UI `data-testid` values
33
+
34
+ Update downstream e2e selectors that targeted the weighted-status-score card center label:
35
+
36
+ - `average-card-center-percent` → `weighted-status-score-card-center-percent`
37
+ - `average-card-center-percent-hit-area` → `weighted-status-score-card-center-percent-hit-area`
38
+
39
+ - dfb90b7: **BREAKING**: Standardize all metric and provider IDs from `snake_case` to `lowerCamelCase`.
40
+
41
+ This aligns metric IDs with the naming convention used in `app-config.yaml` and the planned Scorecard design. For example, `github.open_prs` is now `github.openPRs`, `sonarqube.quality_gate` is now `sonarqube.qualityGate`, and `dependabot.alerts_critical` is now `dependabot.alertsCritical`.
42
+
43
+ If you reference metric IDs in your `app-config.yaml` (e.g., in `metricId` fields or plugin schedule config keys), update them to use `lowerCamelCase`.
44
+
45
+ ### Minor Changes
46
+
47
+ - c7f89e7: Introduce `scorecardCollectorsExtensionPoint` for registering collectors to fetch data from different datasources, and `scorecardCollectorsServiceRef` for consuming collected data in metric providers `calculateMetrics` through `collect(...)`.
48
+ - 50447ac: Backstage version bump to v1.52.0
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies [c7f89e7]
53
+ - Updated dependencies [50447ac]
54
+ - Updated dependencies [6ea1575]
55
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@3.0.0
56
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@3.0.0
57
+
3
58
  ## 2.8.1
4
59
 
5
60
  ### Patch Changes
package/README.md CHANGED
@@ -62,7 +62,7 @@ conditions:
62
62
  rule: HAS_METRIC_ID
63
63
  resourceType: scorecard-metric
64
64
  params:
65
- metricIds: ['github.open_prs']
65
+ metricIds: ['github.openPRs']
66
66
  ```
67
67
 
68
68
  This policy would allow users to read only the GitHub Open PRs metric, while restricting access to other available metrics.
@@ -71,6 +71,12 @@ This policy would allow users to read only the GitHub Open PRs metric, while res
71
71
 
72
72
  The Scorecard plugin collects metrics from third-party data sources using metric providers. The Scorecard node plugin provides `scorecardMetricsExtensionPoint` extension point that is used to connect your backend plugin module that exports custom metrics via metric providers to the Scorecard backend plugin. For detailed information on creating metric providers, see [providers.md](./docs/providers.md).
73
73
 
74
+ ### Collectors
75
+
76
+ Collectors are reusable data-fetching contracts used by metric providers. They are registered through `scorecardCollectorsExtensionPoint` and consumed through `scorecardCollectorsServiceRef`.
77
+
78
+ For details and examples, see [collectors.md](./docs/collectors.md).
79
+
74
80
  ### Metric Collection Scheduling
75
81
 
76
82
  The Scorecard plugin automatically collects metrics on a scheduled basis. You can customize the schedule for any metric provider in your `app-config.yaml`. If no schedule is configured, metric providers use the following default schedule:
@@ -88,13 +94,13 @@ For more information about schedule configuration options, see the [Metric Colle
88
94
 
89
95
  The following metric providers are available:
90
96
 
91
- | Provider | Metric ID | Title | Description | Type |
92
- | -------------- | ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- |
93
- | **GitHub** | `github.open_prs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
94
- | **Filecheck** | `filecheck.*` | File Checks | Checks whether specific files (e.g., `README.md`, `LICENSE`, `CODEOWNERS`) exist in a repository. | boolean |
95
- | **Jira** | `jira.open_issues` | Jira open issues | The number of opened issues in Jira | number |
96
- | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.code_review`, `openssf.maintained`). Each returns a score from 0-10. | number |
97
- | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
97
+ | Provider | Metric ID | Title | Description | Type |
98
+ | -------------- | ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- |
99
+ | **GitHub** | `github.openPRs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
100
+ | **Filecheck** | `filecheck.*` | File Checks | Checks whether specific files (e.g., `README.md`, `LICENSE`, `CODEOWNERS`) exist in a repository. | boolean |
101
+ | **Jira** | `jira.openIssues` | Jira open issues | The number of opened issues in Jira | number |
102
+ | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.codeReview`, `openssf.maintained`). Each returns a score from 0-10. | number |
103
+ | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
98
104
 
99
105
  To use these providers, install the corresponding backend modules:
100
106
 
@@ -116,9 +122,9 @@ Thresholds define conditions to assign metric values to specific visual categori
116
122
  - **App Configuration**: Override defaults through `app-config.yaml`
117
123
  - **Entity Annotations**: Override specific thresholds per entity using catalog annotations
118
124
 
119
- Thresholds are evaluated in order, and the first matching rule determines the category. The plugin supports various operators for number metrics (`>`, `>=`, `<`, `<=`, `==`, `!=`, `-` (range)) and boolean metrics (`==`, `!=`). For **number** metrics, configurations loaded through validated paths must cover the **entire real line** when two or more rules are defined (no gaps between intervals); **`average`** KPI **`options.thresholds`** follow the same rule.
125
+ Thresholds are evaluated in order, and the first matching rule determines the category. The plugin supports various operators for number metrics (`>`, `>=`, `<`, `<=`, `==`, `!=`, `-` (range)) and boolean metrics (`==`, `!=`). For **number** metrics, configurations loaded through validated paths must cover the **entire real line** when two or more rules are defined (no gaps between intervals); **`weightedStatusScore`** KPI **`options.thresholds`** follow the same rule.
120
126
 
121
- For comprehensive threshold configuration guide, examples, best practices, interval validation, and **aggregation KPI result thresholds** for **`type: average`**, see [thresholds.md](./docs/thresholds.md).
127
+ For comprehensive threshold configuration guide, examples, best practices, interval validation, and **aggregation KPI result thresholds** for **`type: weightedStatusScore`**, see [thresholds.md](./docs/thresholds.md).
122
128
 
123
129
  ## Aggregation KPIs (homepage and `GET /aggregations`)
124
130
 
@@ -131,23 +137,23 @@ scorecard:
131
137
  title: 'Jira open issues KPI'
132
138
  description: 'Open issues across entities you own, grouped by status.'
133
139
  type: statusGrouped
134
- metricId: jira.open_issues
140
+ metricId: jira.openIssues
135
141
  openPrsKpi:
136
142
  title: 'GitHub open PRs KPI'
137
143
  description: 'Open pull requests grouped by status.'
138
144
  type: statusGrouped
139
- metricId: github.open_prs
145
+ metricId: github.openPRs
140
146
  openPrsWeightedKpi:
141
147
  title: 'GitHub open PRs (weighted health)'
142
148
  description: 'Weighted health from status counts using configurable scores.'
143
- type: average
144
- metricId: github.open_prs
149
+ type: weightedStatusScore
150
+ metricId: github.openPRs
145
151
  options:
146
152
  statusScores:
147
153
  success: 100
148
154
  warning: 50
149
155
  error: 0
150
- # Optional: colors for the average-score donut (expressions apply to percentage 0–100)
156
+ # Optional: colors for the weightedStatusScore donut (expressions apply to percentage 0–100)
151
157
  thresholds:
152
158
  rules:
153
159
  - key: success
@@ -161,17 +167,17 @@ scorecard:
161
167
  color: error.main
162
168
  ```
163
169
 
164
- | Field | Description |
165
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
166
- | `title` | Display title for this aggregation (returned in API metadata). |
167
- | `description` | Display description for this aggregation. |
168
- | `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status) or `average` (normalized weighted score). |
169
- | `metricId` | Metric provider id used to load thresholds and compute counts. |
170
- | `options` | Optional for `statusGrouped`. **Required** for `average`: must include **`options.statusScores`** — map status keys to numeric weights (typically one entry per **metric threshold rule key**). Optionally **`options.thresholds`** (same shape as metric thresholds; see [thresholds.md — Aggregation KPI result thresholds](./docs/thresholds.md#4-aggregation-kpi-result-thresholds-average-type)); evaluated on **`averageScore`** (**0–100** portfolio percentage, **one decimal**); first match sets **`aggregationChartDisplayColor`**. The API includes **`averageScore`**, **`averageWeightedSum`**, **`averageMaxPossible`**, and **`aggregationChartDisplayColor`** (from configured or default result thresholds). |
170
+ | Field | Description |
171
+ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
+ | `title` | Display title for this aggregation (returned in API metadata). |
173
+ | `description` | Display description for this aggregation. |
174
+ | `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status) or `weightedStatusScore` (normalized weighted score). |
175
+ | `metricId` | Metric provider id used to load thresholds and compute counts. |
176
+ | `options` | Optional for `statusGrouped`. **Required** for `weightedStatusScore`: must include **`options.statusScores`** — map status keys to numeric weights (typically one entry per **metric threshold rule key**). Optionally **`options.thresholds`** (same shape as metric thresholds; see [thresholds.md — Aggregation KPI result thresholds](./docs/thresholds.md#4-aggregation-kpi-result-thresholds-weightedstatusscore-type)); evaluated on **`weightedStatusScore`** (**0–100** portfolio percentage, **one decimal**); first match sets **`aggregationChartDisplayColor`**. The API includes **`weightedStatusScore`**, **`weightedStatusSum`**, **`weightedStatusMaxPossible`**, and **`aggregationChartDisplayColor`** (from configured or default result thresholds). |
171
177
 
172
178
  - **Path**: `scorecard.aggregationKPIs.<aggregationId>`.
173
- - If **`aggregationKPIs` is omitted** or a given id is not listed, **`GET /aggregations/:aggregationId`** still works when **`aggregationId` equals the metric id** (e.g. `github.open_prs`): the backend uses that metric with the default `statusGrouped` aggregation and metric-defined title/description.
174
- - **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`average`** KPIs without **`options.statusScores`**, bad expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).
179
+ - If **`aggregationKPIs` is omitted** or a given id is not listed, **`GET /aggregations/:aggregationId`** still works when **`aggregationId` equals the metric id** (e.g. `github.openPRs`): the backend uses that metric with the default `statusGrouped` aggregation and metric-defined title/description.
180
+ - **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`weightedStatusScore`** KPIs without **`options.statusScores`**, bad expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).
175
181
 
176
182
  **Homepage cards** are configured in the app (for example Dynamic Home Page mount points). They should pass **`aggregationId`** matching a key in `aggregationKPIs` or the metric id for the default case. See the [Scorecard frontend plugin README](../scorecard/README.md#homepage-scorecard-cards).
177
183
 
@@ -183,10 +189,10 @@ Returns a list of available metrics. Supports filtering by metric IDs or datasou
183
189
 
184
190
  #### Query Parameters
185
191
 
186
- | Parameter | Type | Required | Description |
187
- | ------------ | ------ | -------- | ------------------------------------------------------------------------------------------ |
188
- | `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,jira.open_issues`) |
189
- | `datasource` | string | No | Filter metrics by datasource ID (e.g., `github`, `jira`, `sonar`) |
192
+ | Parameter | Type | Required | Description |
193
+ | ------------ | ------ | -------- | ---------------------------------------------------------------------------------------- |
194
+ | `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.openPRs,jira.openIssues`) |
195
+ | `datasource` | string | No | Filter metrics by datasource ID (e.g., `github`, `jira`, `sonar`) |
190
196
 
191
197
  #### Behavior
192
198
 
@@ -203,7 +209,7 @@ curl -X GET "{{url}}/api/scorecard/metrics" \
203
209
  -H "Authorization: Bearer <token>"
204
210
 
205
211
  # Get specific metrics by IDs
206
- curl -X GET "{{url}}/api/scorecard/metrics?metricIds=github.open_prs,jira.open_issues" \
212
+ curl -X GET "{{url}}/api/scorecard/metrics?metricIds=github.openPRs,jira.openIssues" \
207
213
  -H "Authorization: Bearer <token>"
208
214
 
209
215
  # Get all metrics from a specific datasource
@@ -225,9 +231,9 @@ Returns the latest metric values for a specific catalog entity.
225
231
 
226
232
  #### Query Parameters
227
233
 
228
- | Parameter | Type | Required | Description |
229
- | ----------- | ------ | -------- | ------------------------------------------------------------------------------------------ |
230
- | `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,jira.open_issues`) |
234
+ | Parameter | Type | Required | Description |
235
+ | ----------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
236
+ | `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.openPRs,jira.openIssues`) |
231
237
 
232
238
  #### Permissions
233
239
 
@@ -236,7 +242,7 @@ Requires `scorecard.metric.read` permission and `catalog.entity.read` permission
236
242
  #### Example Request
237
243
 
238
244
  ```bash
239
- curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?metricIds=github.open_prs" \
245
+ curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?metricIds=github.openPRs" \
240
246
  -H "Authorization: Bearer <token>"
241
247
  ```
242
248
 
@@ -266,7 +272,7 @@ curl -X GET "{{url}}/api/scorecard/aggregations/openIssuesKpi" \
266
272
  -H "Authorization: Bearer <token>"
267
273
 
268
274
  # Default aggregation when no KPI is configured (id equals metric id)
269
- curl -X GET "{{url}}/api/scorecard/aggregations/github.open_prs" \
275
+ curl -X GET "{{url}}/api/scorecard/aggregations/github.openPRs" \
270
276
  -H "Authorization: Bearer <token>"
271
277
  ```
272
278
 
@@ -310,8 +316,8 @@ Requires `scorecard.metric.read` permission. Additionally:
310
316
  #### Example Request
311
317
 
312
318
  ```bash
313
- # Deprecated — prefer GET /aggregations/github.open_prs (or your KPI id)
314
- curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregations" \
319
+ # Deprecated — prefer GET /aggregations/github.openPRs (or your KPI id)
320
+ curl -X GET "{{url}}/api/scorecard/metrics/github.openPRs/catalog/aggregations" \
315
321
  -H "Authorization: Bearer <token>"
316
322
  ```
317
323
 
package/config.d.ts CHANGED
@@ -38,13 +38,13 @@ export interface Config {
38
38
  metricId: string;
39
39
  /** Type-specific settings */
40
40
  options?: {
41
- /** Required under `options` when `type` is `average` */
41
+ /** Required under `options` when `type` is `weightedStatusScore` */
42
42
  statusScores?: {
43
43
  [thresholdRuleKey: string]: number;
44
44
  };
45
45
  /**
46
46
  * Optional: threshold rules for coloring the KPI headline value from the aggregation result
47
- * (e.g. average percentage 0–100 for `average` KPIs).
47
+ * (e.g. weighted status score percentage 0–100 for `weightedStatusScore` KPIs).
48
48
  */
49
49
  thresholds?: {
50
50
  rules: AggregationThresholdRule[];
@@ -3,7 +3,7 @@
3
3
  var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
4
4
 
5
5
  const AGGREGATION_KPIS_CONFIG_PATH = "scorecard.aggregationKPIs";
6
- const DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS = {
6
+ const DEFAULT_WEIGHTED_STATUS_SCORE_KPI_RESULT_THRESHOLDS = {
7
7
  rules: [
8
8
  {
9
9
  key: "success",
@@ -24,5 +24,5 @@ const DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS = {
24
24
  };
25
25
 
26
26
  exports.AGGREGATION_KPIS_CONFIG_PATH = AGGREGATION_KPIS_CONFIG_PATH;
27
- exports.DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS = DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS;
27
+ exports.DEFAULT_WEIGHTED_STATUS_SCORE_KPI_RESULT_THRESHOLDS = DEFAULT_WEIGHTED_STATUS_SCORE_KPI_RESULT_THRESHOLDS;
28
28
  //# sourceMappingURL=aggregationKPIs.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aggregationKPIs.cjs.js","sources":["../../src/constants/aggregationKPIs.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 type ThresholdConfig,\n ScorecardThresholdRuleColors,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport const AGGREGATION_KPIS_CONFIG_PATH =\n 'scorecard.aggregationKPIs' as const;\n\n/**\n * Default applied by `AverageAggregationStrategy` when `options.thresholds` is omitted\n * from app-config. Higher headline percentage (0–100) = better. Evaluated in order; first match wins.\n */\nexport const DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS: ThresholdConfig = {\n rules: [\n {\n key: 'success',\n expression: '>=80',\n color: ScorecardThresholdRuleColors.SUCCESS,\n },\n {\n key: 'warning',\n expression: '30-80',\n color: ScorecardThresholdRuleColors.WARNING,\n },\n {\n key: 'error',\n expression: '<30',\n color: ScorecardThresholdRuleColors.ERROR,\n },\n ],\n};\n"],"names":["ScorecardThresholdRuleColors"],"mappings":";;;;AAqBO,MAAM,4BAAA,GACX;AAMK,MAAM,qCAAA,GAAyD;AAAA,EACpE,KAAA,EAAO;AAAA,IACL;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,MAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,OAAA;AAAA,MACL,UAAA,EAAY,KAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA;AACtC;AAEJ;;;;;"}
1
+ {"version":3,"file":"aggregationKPIs.cjs.js","sources":["../../src/constants/aggregationKPIs.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 type ThresholdConfig,\n ScorecardThresholdRuleColors,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport const AGGREGATION_KPIS_CONFIG_PATH =\n 'scorecard.aggregationKPIs' as const;\n\n/**\n * Default applied by `WeightedStatusScoreAggregationStrategy` when `options.thresholds` is omitted\n * from app-config. Higher headline percentage (0–100) = better. Evaluated in order; first match wins.\n */\nexport const DEFAULT_WEIGHTED_STATUS_SCORE_KPI_RESULT_THRESHOLDS: ThresholdConfig =\n {\n rules: [\n {\n key: 'success',\n expression: '>=80',\n color: ScorecardThresholdRuleColors.SUCCESS,\n },\n {\n key: 'warning',\n expression: '30-80',\n color: ScorecardThresholdRuleColors.WARNING,\n },\n {\n key: 'error',\n expression: '<30',\n color: ScorecardThresholdRuleColors.ERROR,\n },\n ],\n };\n"],"names":["ScorecardThresholdRuleColors"],"mappings":";;;;AAqBO,MAAM,4BAAA,GACX;AAMK,MAAM,mDAAA,GACX;AAAA,EACE,KAAA,EAAO;AAAA,IACL;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,MAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,OAAA;AAAA,MACL,UAAA,EAAY,KAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA;AACtC;AAEJ;;;;;"}
@@ -21,7 +21,13 @@ var ThresholdResolver = require('./threshold/ThresholdResolver.cjs.js');
21
21
  const scorecardPlugin = backendPluginApi.createBackendPlugin({
22
22
  pluginId: "scorecard",
23
23
  register(env) {
24
+ const collectors = [];
24
25
  const metricProvidersRegistry = new MetricProvidersRegistry.MetricProvidersRegistry();
26
+ env.registerExtensionPoint(backstagePluginScorecardNode.scorecardCollectorsExtensionPoint, {
27
+ addCollector(...newCollectors) {
28
+ collectors.push(...newCollectors);
29
+ }
30
+ });
25
31
  env.registerExtensionPoint(backstagePluginScorecardNode.scorecardMetricsExtensionPoint, {
26
32
  addMetricProvider(...newMetricProviders) {
27
33
  newMetricProviders.forEach((metricProvider) => {
@@ -34,6 +40,7 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
34
40
  actionsRegistry: alpha.actionsRegistryServiceRef,
35
41
  auth: backendPluginApi.coreServices.auth,
36
42
  catalog: pluginCatalogNode.catalogServiceRef,
43
+ collectorsService: backstagePluginScorecardNode.scorecardCollectorsServiceRef,
37
44
  config: backendPluginApi.coreServices.rootConfig,
38
45
  database: backendPluginApi.coreServices.database,
39
46
  httpRouter: backendPluginApi.coreServices.httpRouter,
@@ -47,6 +54,7 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
47
54
  actionsRegistry,
48
55
  auth,
49
56
  catalog,
57
+ collectorsService,
50
58
  config,
51
59
  database,
52
60
  httpRouter,
@@ -56,6 +64,7 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
56
64
  permissionsRegistry,
57
65
  scheduler
58
66
  }) {
67
+ collectorsService.init({ collectors });
59
68
  permissionsRegistry.addResourceType({
60
69
  resourceRef: rules.scorecardMetricPermissionResourceRef,
61
70
  getResources: async (resourceRefs) => {
@@ -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 { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createScorecardActions } from './actions';\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 actionsRegistry: actionsRegistryServiceRef,\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 actionsRegistry,\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 createScorecardActions({\n actionsRegistry,\n auth,\n permissions,\n catalog,\n metricProvidersRegistry,\n 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","actionsRegistryServiceRef","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createScorecardActions","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 { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createScorecardActions } from './actions';\nimport {\n type Collector,\n type MetricProvider,\n scorecardCollectorsExtensionPoint,\n scorecardCollectorsServiceRef,\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 collectors: Collector[] = [];\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardCollectorsExtensionPoint, {\n addCollector(...newCollectors: Collector[]) {\n collectors.push(...newCollectors);\n },\n });\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 actionsRegistry: actionsRegistryServiceRef,\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n collectorsService: scorecardCollectorsServiceRef,\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 actionsRegistry,\n auth,\n catalog,\n collectorsService,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n collectorsService.init({ collectors });\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 createScorecardActions({\n actionsRegistry,\n auth,\n permissions,\n catalog,\n metricProvidersRegistry,\n 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","scorecardCollectorsExtensionPoint","scorecardMetricsExtensionPoint","actionsRegistryServiceRef","coreServices","catalogServiceRef","scorecardCollectorsServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createScorecardActions","createRouter"],"mappings":"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"}
@@ -8,28 +8,15 @@ class MetricProvidersRegistry {
8
8
  datasourceIndex = /* @__PURE__ */ new Map();
9
9
  register(metricProvider) {
10
10
  const providerDatasource = metricProvider.getProviderDatasourceId();
11
- const metricType = metricProvider.getMetricType();
12
11
  const providerId = metricProvider.getProviderId();
13
- const metricIds = metricProvider.getMetricIds?.() ?? [providerId];
14
- const metrics = metricProvider.getMetrics?.() ?? [
15
- metricProvider.getMetric()
16
- ];
17
- for (const metricId of metricIds) {
18
- const metric = metrics.find((m) => m.id === metricId);
19
- if (!metric) {
20
- throw new Error(
21
- `Invalid metric provider: metric ID '${metricId}' returned by getMetricIds() does not have a corresponding metric in getMetrics()`
22
- );
23
- }
24
- if (metricType !== metric.type) {
25
- throw new Error(
26
- `Invalid metric provider with ID ${metricId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`
27
- );
28
- }
12
+ const metrics = metricProvider.getMetrics();
13
+ const metricIds = metrics.map((m) => m.id);
14
+ for (const metric of metrics) {
15
+ const metricId = metric.id;
29
16
  const expectedPrefix = `${providerDatasource}.`;
30
17
  if (!metricId.startsWith(expectedPrefix) || metricId === expectedPrefix) {
31
18
  throw new Error(
32
- `Invalid metric provider with ID ${metricId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`
19
+ `Invalid metric provider with ID ${metricId}, must have format '${providerDatasource}.<metricName>' where metric name is not empty`
33
20
  );
34
21
  }
35
22
  if (this.metricProviders.has(metricId)) {
@@ -37,17 +24,14 @@ class MetricProvidersRegistry {
37
24
  `Metric provider with ID '${metricId}' has already been registered`
38
25
  );
39
26
  }
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
- );
27
+ try {
28
+ backstagePluginScorecardNode.validateThresholdsForMetric(metric.thresholds, metric.type);
29
+ } catch (error) {
30
+ throw new backstagePluginScorecardNode.ThresholdConfigFormatError(
31
+ `Invalid default thresholds for metric provider '${providerId}', metric '${metricId}'`,
32
+ error
33
+ );
34
+ }
51
35
  }
52
36
  for (const metricId of metricIds) {
53
37
  this.metricProviders.set(metricId, metricProvider);
@@ -73,17 +57,25 @@ class MetricProvidersRegistry {
73
57
  }
74
58
  getMetric(metricId) {
75
59
  const provider = this.getProvider(metricId);
76
- if (provider.getMetrics) {
77
- const metrics = provider.getMetrics();
78
- const metric = metrics.find((m) => m.id === metricId);
79
- if (metric) {
80
- return metric;
81
- }
60
+ const metrics = provider.getMetrics();
61
+ const metric = metrics.find((m) => m.id === metricId);
62
+ if (metric) {
63
+ return metric;
82
64
  }
83
- return provider.getMetric();
65
+ throw new errors.NotFoundError(
66
+ `Metric '${metricId}' not found in provider '${provider.getProviderId()}'`
67
+ );
84
68
  }
85
69
  async calculateMetric(metricId, entity) {
86
- return this.getProvider(metricId).calculateMetric(entity);
70
+ const provider = this.getProvider(metricId);
71
+ const results = await provider.calculateMetrics(entity);
72
+ const value = results.get(metricId);
73
+ if (value === void 0) {
74
+ throw new Error(
75
+ `Provider '${provider.getProviderId()}' did not return a value for metric '${metricId}'`
76
+ );
77
+ }
78
+ return value;
87
79
  }
88
80
  async calculateMetrics(metricIds, entity) {
89
81
  const results = await Promise.allSettled(
@@ -105,16 +97,11 @@ class MetricProvidersRegistry {
105
97
  return metricIds.map((metricId) => {
106
98
  const provider = this.metricProviders.get(metricId);
107
99
  if (!provider) return void 0;
108
- if (provider.getMetrics) {
109
- const metrics = provider.getMetrics();
110
- return metrics.find((m) => m.id === metricId);
111
- }
112
- return provider.getMetric();
100
+ const metrics = provider.getMetrics();
101
+ return metrics.find((m) => m.id === metricId);
113
102
  }).filter((m) => m !== void 0);
114
103
  }
115
- return this.listProviders().flatMap(
116
- (provider) => provider.getMetrics?.() ?? [provider.getMetric()]
117
- );
104
+ return this.listProviders().flatMap((provider) => provider.getMetrics());
118
105
  }
119
106
  listMetricsByDatasource(datasourceId) {
120
107
  const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);
@@ -122,9 +109,7 @@ class MetricProvidersRegistry {
122
109
  return [];
123
110
  }
124
111
  const providers = [...providerIdsOfDatasource].map((id) => this.metricProviders.get(id)).filter((p) => p !== void 0);
125
- return [...new Set(providers)].flatMap(
126
- (provider) => provider.getMetrics?.() ?? [provider.getMetric()]
127
- );
112
+ return [...new Set(providers)].flatMap((provider) => provider.getMetrics());
128
113
  }
129
114
  }
130
115
 
@@ -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 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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"}
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 providerId = metricProvider.getProviderId();\n\n const metrics = metricProvider.getMetrics();\n const metricIds = metrics.map(m => m.id);\n\n for (const metric of metrics) {\n const metricId = metric.id;\n\n // Validate: Provider ID format (datasource.metricName)\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}.<metricName>' 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 try {\n validateThresholdsForMetric(metric.thresholds, metric.type);\n } catch (error) {\n throw new ThresholdConfigFormatError(\n `Invalid default thresholds for metric provider '${providerId}', metric '${metricId}'`,\n error,\n );\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 const metrics = provider.getMetrics();\n const metric = metrics.find(m => m.id === metricId);\n if (metric) {\n return metric;\n }\n\n throw new NotFoundError(\n `Metric '${metricId}' not found in provider '${provider.getProviderId()}'`,\n );\n }\n\n async calculateMetric(\n metricId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n const provider = this.getProvider(metricId);\n const results = await provider.calculateMetrics(entity);\n const value = results.get(metricId);\n if (value === undefined) {\n throw new Error(\n `Provider '${provider.getProviderId()}' did not return a value for metric '${metricId}'`,\n );\n }\n return value;\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 const metrics = provider.getMetrics();\n return metrics.find(m => m.id === metricId);\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(provider => provider.getMetrics());\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(provider => provider.getMetrics());\n }\n}\n"],"names":["ConflictError","validateThresholdsForMetric","ThresholdConfigFormatError","NotFoundError"],"mappings":"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"}