@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.8.1 → 3.0.1
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 +62 -0
- package/README.md +42 -36
- package/config.d.ts +2 -2
- package/dist/constants/aggregationKPIs.cjs.js +2 -2
- package/dist/constants/aggregationKPIs.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +9 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/providers/MetricProvidersRegistry.cjs.js +33 -48
- package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +67 -115
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
- package/dist/service/CatalogMetricService.cjs.js +3 -2
- package/dist/service/CatalogMetricService.cjs.js.map +1 -1
- package/dist/service/aggregations/strategies/{AverageAggregationStrategy.cjs.js → WeightedStatusScoreAggregationStrategy.cjs.js} +16 -16
- package/dist/service/aggregations/strategies/WeightedStatusScoreAggregationStrategy.cjs.js.map +1 -0
- package/dist/service/aggregations/strategies/registerStrategies.cjs.js +5 -2
- package/dist/service/aggregations/strategies/registerStrategies.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +8 -2
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/threshold/ThresholdResolver.cjs.js +9 -6
- package/dist/threshold/ThresholdResolver.cjs.js.map +1 -1
- package/dist/utils/buildAggregationConfig.cjs.js +1 -1
- package/dist/utils/buildAggregationConfig.cjs.js.map +1 -1
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js +5 -6
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js.map +1 -1
- package/dist/validation/schemas/aggregationConfigSchemas.cjs.js +3 -3
- package/dist/validation/schemas/aggregationConfigSchemas.cjs.js.map +1 -1
- package/dist/validation/validateAggregationConfig.cjs.js +1 -1
- package/dist/validation/validateAggregationConfig.cjs.js.map +1 -1
- package/package.json +10 -10
- package/dist/service/aggregations/strategies/AverageAggregationStrategy.cjs.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-scorecard-backend
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@3.0.1
|
|
8
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@3.0.1
|
|
9
|
+
|
|
10
|
+
## 3.0.0
|
|
11
|
+
|
|
12
|
+
### Major Changes
|
|
13
|
+
|
|
14
|
+
- 6ea1575: **BREAKING**: Rename aggregation KPI type `average` to `weightedStatusScore`.
|
|
15
|
+
|
|
16
|
+
### App config
|
|
17
|
+
|
|
18
|
+
- `scorecard.aggregationKPIs.*.type`: `average` → `weightedStatusScore`
|
|
19
|
+
|
|
20
|
+
### `GET /aggregations/:aggregationId` API
|
|
21
|
+
|
|
22
|
+
- `metadata.aggregationType`: `average` → `weightedStatusScore`
|
|
23
|
+
- `result.averageScore` → `result.weightedStatusScore`
|
|
24
|
+
- `result.averageWeightedSum` → `result.weightedStatusSum`
|
|
25
|
+
- `result.averageMaxPossible` → `result.weightedStatusMaxPossible`
|
|
26
|
+
|
|
27
|
+
### Frontend translations (`metric.*`)
|
|
28
|
+
|
|
29
|
+
Update any scorecard translation overrides that used the old `average*` keys:
|
|
30
|
+
|
|
31
|
+
- `metric.averageCenterTooltipTotalLabel` → `metric.weightedStatusScoreCenterTooltipTotalLabel`
|
|
32
|
+
- `metric.averageCenterTooltipMaxLabel` → `metric.weightedStatusScoreCenterTooltipMaxLabel`
|
|
33
|
+
- `metric.averageCenterTooltipBreakdownRow_one` / `_other` → `metric.weightedStatusScoreCenterTooltipBreakdownRow_one` / `_other`
|
|
34
|
+
- `metric.averageLegendTooltipEntitiesEach_one` / `_other` → `metric.weightedStatusScoreLegendTooltipEntitiesEach_one` / `_other`
|
|
35
|
+
- `metric.averageLegendTooltipRowTotal` → `metric.weightedStatusScoreLegendTooltipRowTotal`
|
|
36
|
+
|
|
37
|
+
The `averageLegendTooltip*` keys served the removed side-legend tooltip; the center donut tooltip uses the `weightedStatusScoreCenterTooltip*` keys (including per-status breakdown rows).
|
|
38
|
+
|
|
39
|
+
### UI `data-testid` values
|
|
40
|
+
|
|
41
|
+
Update downstream e2e selectors that targeted the weighted-status-score card center label:
|
|
42
|
+
|
|
43
|
+
- `average-card-center-percent` → `weighted-status-score-card-center-percent`
|
|
44
|
+
- `average-card-center-percent-hit-area` → `weighted-status-score-card-center-percent-hit-area`
|
|
45
|
+
|
|
46
|
+
- dfb90b7: **BREAKING**: Standardize all metric and provider IDs from `snake_case` to `lowerCamelCase`.
|
|
47
|
+
|
|
48
|
+
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`.
|
|
49
|
+
|
|
50
|
+
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`.
|
|
51
|
+
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- c7f89e7: Introduce `scorecardCollectorsExtensionPoint` for registering collectors to fetch data from different datasources, and `scorecardCollectorsServiceRef` for consuming collected data in metric providers `calculateMetrics` through `collect(...)`.
|
|
55
|
+
- 50447ac: Backstage version bump to v1.52.0
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- Updated dependencies [c7f89e7]
|
|
60
|
+
- Updated dependencies [50447ac]
|
|
61
|
+
- Updated dependencies [6ea1575]
|
|
62
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@3.0.0
|
|
63
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@3.0.0
|
|
64
|
+
|
|
3
65
|
## 2.8.1
|
|
4
66
|
|
|
5
67
|
### 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.
|
|
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
|
|
92
|
-
| -------------- |
|
|
93
|
-
| **GitHub** | `github.
|
|
94
|
-
| **Filecheck** | `filecheck.*`
|
|
95
|
-
| **Jira** | `jira.
|
|
96
|
-
| **OpenSSF** | `openssf.*`
|
|
97
|
-
| **Dependabot** | `dependabot.*`
|
|
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); **`
|
|
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:
|
|
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.
|
|
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.
|
|
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:
|
|
144
|
-
metricId: github.
|
|
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
|
|
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 `
|
|
169
|
-
| `metricId` | Metric provider id used to load thresholds and compute counts.
|
|
170
|
-
| `options` | Optional for `statusGrouped`. **Required** for `
|
|
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.
|
|
174
|
-
- **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
314
|
-
curl -X GET "{{url}}/api/scorecard/metrics/github.
|
|
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 `
|
|
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.
|
|
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
|
|
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.
|
|
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 `
|
|
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;;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -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) => {
|
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 { 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":";;;;;;;;;;;;;;;;;;;;
|
|
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
|
|
14
|
-
const
|
|
15
|
-
|
|
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}.<
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
65
|
+
throw new errors.NotFoundError(
|
|
66
|
+
`Metric '${metricId}' not found in provider '${provider.getProviderId()}'`
|
|
67
|
+
);
|
|
84
68
|
}
|
|
85
69
|
async calculateMetric(metricId, entity) {
|
|
86
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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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"}
|