@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +105 -4
- package/dist/database/DatabaseMetricValues.cjs.js +37 -0
- package/dist/database/DatabaseMetricValues.cjs.js.map +1 -1
- package/dist/permissions/permissionUtils.cjs.js +1 -1
- package/dist/permissions/permissionUtils.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +1 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +3 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
- package/dist/service/CatalogMetricService.cjs.js +53 -3
- package/dist/service/CatalogMetricService.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +57 -28
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/utils/getEntitiesOwnedByUser.cjs.js +49 -0
- package/dist/utils/getEntitiesOwnedByUser.cjs.js.map +1 -0
- package/dist/utils/parseCommaSeparatedString.cjs.js +8 -0
- package/dist/utils/parseCommaSeparatedString.cjs.js.map +1 -0
- package/dist/validation/validateCatalogMetricsSchema.cjs.js +18 -0
- package/dist/validation/validateCatalogMetricsSchema.cjs.js.map +1 -0
- package/dist/validation/validateMetricsSchema.cjs.js +19 -0
- package/dist/validation/validateMetricsSchema.cjs.js.map +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-scorecard-backend
|
|
2
2
|
|
|
3
|
+
## 2.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 23e21ad: Added `metricIds` query parameter to the `/metrics` endpoint to filter metrics by metric IDs.
|
|
8
|
+
|
|
9
|
+
Scorecard read permission are no longer needed to get available metrics for the `/metrics` endpoint.
|
|
10
|
+
|
|
11
|
+
- 4e360d5: Implemented endpoint to aggregate metrics for scorecard metrics
|
|
12
|
+
|
|
13
|
+
**BREAKING** Update attribute `value` in the `MetricResult` type and update validation to support `null` instead `undefined` for the updated attribute
|
|
14
|
+
|
|
15
|
+
```diff
|
|
16
|
+
export type MetricResult = {
|
|
17
|
+
id: string;
|
|
18
|
+
status: 'success' | 'error';
|
|
19
|
+
metadata: {
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
type: MetricType;
|
|
23
|
+
history?: boolean;
|
|
24
|
+
};
|
|
25
|
+
result: {
|
|
26
|
+
- value?: MetricValue;
|
|
27
|
+
+ value: MetricValue | null;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
thresholdResult: ThresholdResult;
|
|
30
|
+
};
|
|
31
|
+
error?: string;
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**BREAKING** Update attribute `evaluation` in the `ThresholdResult` type and update validation to support `null` instead `undefined` for the updated attribute
|
|
36
|
+
|
|
37
|
+
```diff
|
|
38
|
+
export type ThresholdResult = {
|
|
39
|
+
status: 'success' | 'error';
|
|
40
|
+
- definition: ThresholdConfig | undefined;
|
|
41
|
+
+ definition: ThresholdConfig | null;
|
|
42
|
+
evaluation: string | undefined; // threshold key the expression evaluated to
|
|
43
|
+
error?: string;
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [52b60ee]
|
|
50
|
+
- Updated dependencies [4e360d5]
|
|
51
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.3.0
|
|
52
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.3.0
|
|
53
|
+
|
|
3
54
|
## 2.2.0
|
|
4
55
|
|
|
5
56
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -75,15 +75,17 @@ The Scorecard plugin collects metrics from third-party data sources using metric
|
|
|
75
75
|
|
|
76
76
|
The following metric providers are available:
|
|
77
77
|
|
|
78
|
-
| Provider
|
|
79
|
-
|
|
|
80
|
-
| **GitHub**
|
|
81
|
-
| **Jira**
|
|
78
|
+
| Provider | Metric ID | Title | Description | Type |
|
|
79
|
+
| ----------- | ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------ |
|
|
80
|
+
| **GitHub** | `github.open_prs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
|
|
81
|
+
| **Jira** | `jira.open_issues` | Jira open issues | The number of opened issues in Jira | number |
|
|
82
|
+
| **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 |
|
|
82
83
|
|
|
83
84
|
To use these providers, install the corresponding backend modules:
|
|
84
85
|
|
|
85
86
|
- GitHub: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github`](../scorecard-backend-module-github/README.md)
|
|
86
87
|
- Jira: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira`](../scorecard-backend-module-jira/README.md)
|
|
88
|
+
- OpenSSF: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf`](../scorecard-backend-module-openssf/README.md)
|
|
87
89
|
|
|
88
90
|
## Thresholds
|
|
89
91
|
|
|
@@ -97,6 +99,105 @@ Thresholds are evaluated in order, and the first matching rule determines the ca
|
|
|
97
99
|
|
|
98
100
|
For comprehensive threshold configuration guide, examples, and best practices, see [thresholds.md](./docs/thresholds.md).
|
|
99
101
|
|
|
102
|
+
## API Endpoints
|
|
103
|
+
|
|
104
|
+
### `GET /metrics`
|
|
105
|
+
|
|
106
|
+
Returns a list of available metrics. Supports filtering by metric IDs or datasource.
|
|
107
|
+
|
|
108
|
+
#### Query Parameters
|
|
109
|
+
|
|
110
|
+
| Parameter | Type | Required | Description |
|
|
111
|
+
| ------------ | ------ | -------- | -------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |
|
|
113
|
+
| `datasource` | string | No | Filter metrics by datasource ID (e.g., `github`, `jira`, `sonar`) |
|
|
114
|
+
|
|
115
|
+
#### Behavior
|
|
116
|
+
|
|
117
|
+
- If `metricIds` is provided, returns only the specified metrics
|
|
118
|
+
- If `datasource` is provided (and `metricIds` is not), returns all metrics from that datasource
|
|
119
|
+
- If neither parameter is provided, returns all available metrics
|
|
120
|
+
- **Note**: Providing both `metricIds` and `datasource` will result in a `400 Bad Request` error
|
|
121
|
+
|
|
122
|
+
#### Example Requests
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Get all metrics
|
|
126
|
+
curl -X GET "{{url}}/api/scorecard/metrics" \
|
|
127
|
+
-H "Authorization: Bearer <token>"
|
|
128
|
+
|
|
129
|
+
# Get specific metrics by IDs
|
|
130
|
+
curl -X GET "{{url}}/api/scorecard/metrics?metricIds=github.open_prs,github.open_issues" \
|
|
131
|
+
-H "Authorization: Bearer <token>"
|
|
132
|
+
|
|
133
|
+
# Get all metrics from a specific datasource
|
|
134
|
+
curl -X GET "{{url}}/api/scorecard/metrics?datasource=github" \
|
|
135
|
+
-H "Authorization: Bearer <token>"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `GET /metrics/catalog/:kind/:namespace/:name`
|
|
139
|
+
|
|
140
|
+
Returns the latest metric values for a specific catalog entity.
|
|
141
|
+
|
|
142
|
+
#### Path Parameters
|
|
143
|
+
|
|
144
|
+
| Parameter | Type | Required | Description |
|
|
145
|
+
| ----------- | ------ | -------- | ---------------------------------- |
|
|
146
|
+
| `kind` | string | Yes | Entity kind (e.g., `component`) |
|
|
147
|
+
| `namespace` | string | Yes | Entity namespace (e.g., `default`) |
|
|
148
|
+
| `name` | string | Yes | Entity name |
|
|
149
|
+
|
|
150
|
+
#### Query Parameters
|
|
151
|
+
|
|
152
|
+
| Parameter | Type | Required | Description |
|
|
153
|
+
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |
|
|
155
|
+
|
|
156
|
+
#### Permissions
|
|
157
|
+
|
|
158
|
+
Requires `scorecard.metric.read` permission and `catalog.entity.read` permission for the specific entity.
|
|
159
|
+
|
|
160
|
+
#### Example Request
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?metricIds=github.open_prs" \
|
|
164
|
+
-H "Authorization: Bearer <token>"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `GET /metrics/:metricId/catalog/aggregations`
|
|
168
|
+
|
|
169
|
+
Returns aggregated metrics for a specific metric across all entities owned by the authenticated user. This endpoint aggregates metrics from:
|
|
170
|
+
|
|
171
|
+
- Entities directly owned by the user
|
|
172
|
+
- Entities owned by groups the user is a direct member of (only direct parent groups are considered)
|
|
173
|
+
|
|
174
|
+
#### Path Parameters
|
|
175
|
+
|
|
176
|
+
| Parameter | Type | Required | Description |
|
|
177
|
+
| ---------- | ------ | -------- | --------------------------------- |
|
|
178
|
+
| `metricId` | string | Yes | The ID of the metric to aggregate |
|
|
179
|
+
|
|
180
|
+
#### Authentication
|
|
181
|
+
|
|
182
|
+
Requires user authentication. The endpoint uses the authenticated user's entity reference to determine which entities to aggregate.
|
|
183
|
+
|
|
184
|
+
#### Permissions
|
|
185
|
+
|
|
186
|
+
Requires `scorecard.metric.read` permission. Additionally:
|
|
187
|
+
|
|
188
|
+
- The user must have access to the specific metric (returns `403 Forbidden` if access is denied)
|
|
189
|
+
- The user must have `catalog.entity.read` permission for each entity that will be included in the aggregation
|
|
190
|
+
|
|
191
|
+
#### Example Request
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Get aggregated metrics for a specific metric
|
|
195
|
+
curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregations" \
|
|
196
|
+
-H "Authorization: Bearer <token>"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For comprehensive documentation on how entity aggregation works, including details on transitive parent groups, error handling, and best practices, see [aggregation.md](./docs/aggregation.md).
|
|
200
|
+
|
|
100
201
|
## Configuration cleanup Job
|
|
101
202
|
|
|
102
203
|
The plugin has a predefined job that runs every day to check and clean old metrics. By default, metrics are saved for **365 days**, however, this period can be changed in the `app-config.yaml` file. Here is an example of how to do that:
|
|
@@ -26,6 +26,43 @@ class DatabaseMetricValues {
|
|
|
26
26
|
async cleanupExpiredMetrics(olderThan) {
|
|
27
27
|
return await this.dbClient(this.tableName).where("timestamp", "<", olderThan).del();
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Get aggregated metrics by status for multiple entities and metrics.
|
|
31
|
+
*/
|
|
32
|
+
async readAggregatedMetricsByEntityRefs(catalog_entity_refs, metric_ids) {
|
|
33
|
+
const latestIdsSubquery = this.dbClient(this.tableName).max("id").whereIn("metric_id", metric_ids).whereIn("catalog_entity_ref", catalog_entity_refs).groupBy("metric_id", "catalog_entity_ref");
|
|
34
|
+
const results = await this.dbClient(this.tableName).select("metric_id").count("* as total").max("timestamp as max_timestamp").select(
|
|
35
|
+
this.dbClient.raw(
|
|
36
|
+
"SUM(CASE WHEN status = 'success' AND value IS NOT NULL THEN 1 ELSE 0 END) as success"
|
|
37
|
+
)
|
|
38
|
+
).select(
|
|
39
|
+
this.dbClient.raw(
|
|
40
|
+
"SUM(CASE WHEN status = 'warning' AND value IS NOT NULL THEN 1 ELSE 0 END) as warning"
|
|
41
|
+
)
|
|
42
|
+
).select(
|
|
43
|
+
this.dbClient.raw(
|
|
44
|
+
"SUM(CASE WHEN status = 'error' AND value IS NOT NULL THEN 1 ELSE 0 END) as error"
|
|
45
|
+
)
|
|
46
|
+
).whereIn("id", latestIdsSubquery).whereNotNull("status").whereNotNull("value").groupBy("metric_id");
|
|
47
|
+
return results.map((row) => {
|
|
48
|
+
let maxTimestamp;
|
|
49
|
+
if (row.max_timestamp instanceof Date) {
|
|
50
|
+
maxTimestamp = row.max_timestamp;
|
|
51
|
+
} else if (typeof row.max_timestamp === "number" || typeof row.max_timestamp === "string") {
|
|
52
|
+
maxTimestamp = new Date(row.max_timestamp);
|
|
53
|
+
} else {
|
|
54
|
+
maxTimestamp = /* @__PURE__ */ new Date();
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
metric_id: row.metric_id,
|
|
58
|
+
total: Number(row.total),
|
|
59
|
+
max_timestamp: maxTimestamp,
|
|
60
|
+
success: Number(row.success),
|
|
61
|
+
warning: Number(row.warning),
|
|
62
|
+
error: Number(row.error)
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
29
66
|
}
|
|
30
67
|
|
|
31
68
|
exports.DatabaseMetricValues = DatabaseMetricValues;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatabaseMetricValues.cjs.js","sources":["../../src/database/DatabaseMetricValues.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 { Knex } from 'knex';\nimport {
|
|
1
|
+
{"version":3,"file":"DatabaseMetricValues.cjs.js","sources":["../../src/database/DatabaseMetricValues.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 { Knex } from 'knex';\nimport {\n DbMetricValueCreate,\n DbMetricValue,\n DbAggregatedMetric,\n} from './types';\n\nexport class DatabaseMetricValues {\n private readonly tableName = 'metric_values';\n\n constructor(private readonly dbClient: Knex<any, any[]>) {}\n\n /**\n * Insert multiple metric values\n */\n async createMetricValues(metricValues: DbMetricValueCreate[]): Promise<void> {\n await this.dbClient(this.tableName).insert(metricValues);\n }\n\n /**\n * Get the latest metric values for a specific entity and metrics\n */\n async readLatestEntityMetricValues(\n catalog_entity_ref: string,\n metric_ids: string[],\n ): Promise<DbMetricValue[]> {\n return await this.dbClient(this.tableName)\n .select('*')\n .whereIn(\n 'id',\n this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metric_ids)\n .where('catalog_entity_ref', catalog_entity_ref)\n .groupBy('metric_id'),\n );\n }\n\n /**\n * Delete metric values that are older than the given date\n */\n async cleanupExpiredMetrics(olderThan: Date): Promise<number> {\n return await this.dbClient(this.tableName)\n .where('timestamp', '<', olderThan)\n .del();\n }\n\n /**\n * Get aggregated metrics by status for multiple entities and metrics.\n */\n async readAggregatedMetricsByEntityRefs(\n catalog_entity_refs: string[],\n metric_ids: string[],\n ): Promise<DbAggregatedMetric[]> {\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metric_ids)\n .whereIn('catalog_entity_ref', catalog_entity_refs)\n .groupBy('metric_id', 'catalog_entity_ref');\n\n const results = await this.dbClient(this.tableName)\n .select('metric_id')\n .count('* as total')\n .max('timestamp as max_timestamp')\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'success' AND value IS NOT NULL THEN 1 ELSE 0 END) as success\",\n ),\n )\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'warning' AND value IS NOT NULL THEN 1 ELSE 0 END) as warning\",\n ),\n )\n .select(\n this.dbClient.raw(\n \"SUM(CASE WHEN status = 'error' AND value IS NOT NULL THEN 1 ELSE 0 END) as error\",\n ),\n )\n .whereIn('id', latestIdsSubquery)\n .whereNotNull('status')\n .whereNotNull('value')\n .groupBy('metric_id');\n\n // Normalize types for cross-database compatibility\n // PostgreSQL returns COUNT/SUM as strings, SQLite returns numbers\n // PostgreSQL returns MAX(timestamp) as Date, SQLite returns number (milliseconds)\n return results.map(row => {\n let maxTimestamp: Date;\n if (row.max_timestamp instanceof Date) {\n maxTimestamp = row.max_timestamp;\n } else if (\n typeof row.max_timestamp === 'number' ||\n typeof row.max_timestamp === 'string'\n ) {\n maxTimestamp = new Date(row.max_timestamp as string | number);\n } else {\n maxTimestamp = new Date();\n }\n\n return {\n metric_id: row.metric_id,\n total: Number(row.total),\n max_timestamp: maxTimestamp,\n success: Number(row.success),\n warning: Number(row.warning),\n error: Number(row.error),\n };\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ const checkEntityAccess = async (entityRef, req, permissions, httpAuth) => {
|
|
|
11
11
|
{ credentials: await httpAuth.credentials(req) }
|
|
12
12
|
);
|
|
13
13
|
if (entityAccessDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
14
|
-
throw new errors.NotAllowedError(
|
|
14
|
+
throw new errors.NotAllowedError(`Access to "${entityRef}" entity metrics denied`);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
const matches = (metric, filters) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissionUtils.cjs.js","sources":["../../src/permissions/permissionUtils.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 PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n AuthorizeResult,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\n\nimport { Metric } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nimport { rules as scorecardRules } from './rules';\n\nexport const checkEntityAccess = async (\n entityRef: string,\n req: Request,\n permissions: PermissionsService,\n httpAuth: HttpAuthService,\n): Promise<void> => {\n const entityAccessDecision = await permissions.authorize(\n [{ permission: catalogEntityReadPermission, resourceRef: entityRef }],\n { credentials: await httpAuth.credentials(req) },\n );\n\n if (entityAccessDecision[0].result !== AuthorizeResult.ALLOW) {\n throw new NotAllowedError(
|
|
1
|
+
{"version":3,"file":"permissionUtils.cjs.js","sources":["../../src/permissions/permissionUtils.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 PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n AuthorizeResult,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\n\nimport { Metric } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nimport { rules as scorecardRules } from './rules';\n\nexport const checkEntityAccess = async (\n entityRef: string,\n req: Request,\n permissions: PermissionsService,\n httpAuth: HttpAuthService,\n): Promise<void> => {\n const entityAccessDecision = await permissions.authorize(\n [{ permission: catalogEntityReadPermission, resourceRef: entityRef }],\n { credentials: await httpAuth.credentials(req) },\n );\n\n if (entityAccessDecision[0].result !== AuthorizeResult.ALLOW) {\n throw new NotAllowedError(`Access to \"${entityRef}\" entity metrics denied`);\n }\n};\n\nexport const matches = (\n metric: Metric,\n filters?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n): boolean => {\n if (!filters) {\n return true;\n }\n\n if ('allOf' in filters) {\n return filters.allOf.every(filter => matches(metric, filter));\n }\n\n if ('anyOf' in filters) {\n return filters.anyOf.some(filter => matches(metric, filter));\n }\n\n if ('not' in filters) {\n return !matches(metric, filters.not);\n }\n return (\n Object.values(scorecardRules)\n .find(r => r.name === filters.rule)\n ?.apply(metric, filters.params ?? {}) ?? false\n );\n};\n\nexport const filterAuthorizedMetrics = (\n metrics: Metric[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n) => {\n if (!filter) {\n return metrics;\n }\n\n return metrics.filter(metric => matches(metric, filter));\n};\n"],"names":["catalogEntityReadPermission","AuthorizeResult","NotAllowedError","scorecardRules"],"mappings":";;;;;;;AAkCO,MAAM,iBAAoB,GAAA,OAC/B,SACA,EAAA,GAAA,EACA,aACA,QACkB,KAAA;AAClB,EAAM,MAAA,oBAAA,GAAuB,MAAM,WAAY,CAAA,SAAA;AAAA,IAC7C,CAAC,EAAE,UAAA,EAAYA,iCAA6B,EAAA,WAAA,EAAa,WAAW,CAAA;AAAA,IACpE,EAAE,WAAa,EAAA,MAAM,QAAS,CAAA,WAAA,CAAY,GAAG,CAAE;AAAA,GACjD;AAEA,EAAA,IAAI,oBAAqB,CAAA,CAAC,CAAE,CAAA,MAAA,KAAWC,uCAAgB,KAAO,EAAA;AAC5D,IAAA,MAAM,IAAIC,sBAAA,CAAgB,CAAc,WAAA,EAAA,SAAS,CAAyB,uBAAA,CAAA,CAAA;AAAA;AAE9E;AAEa,MAAA,OAAA,GAAU,CACrB,MAAA,EACA,OAGY,KAAA;AACZ,EAAA,IAAI,CAAC,OAAS,EAAA;AACZ,IAAO,OAAA,IAAA;AAAA;AAGT,EAAA,IAAI,WAAW,OAAS,EAAA;AACtB,IAAA,OAAO,QAAQ,KAAM,CAAA,KAAA,CAAM,YAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA;AAG9D,EAAA,IAAI,WAAW,OAAS,EAAA;AACtB,IAAA,OAAO,QAAQ,KAAM,CAAA,IAAA,CAAK,YAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA;AAG7D,EAAA,IAAI,SAAS,OAAS,EAAA;AACpB,IAAA,OAAO,CAAC,OAAA,CAAQ,MAAQ,EAAA,OAAA,CAAQ,GAAG,CAAA;AAAA;AAErC,EAAA,OACE,OAAO,MAAO,CAAAC,WAAc,CACzB,CAAA,IAAA,CAAK,OAAK,CAAE,CAAA,IAAA,KAAS,OAAQ,CAAA,IAAI,GAChC,KAAM,CAAA,MAAA,EAAQ,QAAQ,MAAU,IAAA,EAAE,CAAK,IAAA,KAAA;AAE/C;AAEa,MAAA,uBAAA,GAA0B,CACrC,OAAA,EACA,MAGG,KAAA;AACH,EAAA,IAAI,CAAC,MAAQ,EAAA;AACX,IAAO,OAAA,OAAA;AAAA;AAGT,EAAA,OAAO,QAAQ,MAAO,CAAA,CAAA,MAAA,KAAU,OAAQ,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AACzD;;;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n }).start();\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n httpAuth,\n permissions,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","CatalogMetricService","Scheduler","ThresholdEvaluator","createRouter"],"mappings":";;;;;;;;;;;;;;;AA0CO,MAAM,kBAAkBA,oCAAoB,CAAA;AAAA,EACjD,QAAU,EAAA,WAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,uBAAA,GAA0B,IAAIC,+CAAwB,EAAA;AAE5D,IAAA,GAAA,CAAI,uBAAuBC,2DAAgC,EAAA;AAAA,MACzD,qBAAqB,kBAAsC,EAAA;AACzD,QAAA,kBAAA,CAAmB,QAAQ,CAAkB,cAAA,KAAA;AAC3C,UAAA,uBAAA,CAAwB,SAAS,cAAc,CAAA;AAAA,SAChD,CAAA;AAAA;AACH,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,OAAS,EAAAC,mCAAA;AAAA,QACT,QAAQD,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA,mBAAA;AAAA,QAClC,WAAWA,6BAAa,CAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,mBAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAA,mBAAA,CAAoB,eAAgB,CAAA;AAAA,UAClC,WAAa,EAAAE,0CAAA;AAAA,UACb,YAAA,EAAc,OAAO,YAA2B,KAAA;AAC9C,YAAO,OAAA,uBAAA,CAAwB,YAAY,YAAY,CAAA;AAAA,WACzD;AAAA,UACA,WAAa,EAAAC,mDAAA;AAAA,UACb,KAAA,EAAO,MAAO,CAAA,MAAA,CAAOC,WAAc;AAAA,SACpC,CAAA;AAGD,QAAA,MAAMC,kBAAQ,QAAQ,CAAA;AAEtB,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAM,MAAA,cAAA,GAAiB,IAAIC,yCAAA,CAAqB,MAAM,CAAA;AAEtD,QAAM,MAAA,oBAAA,GAAuB,IAAIC,yCAAqB,CAAA;AAAA,UACpD,OAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAU,EAAA,uBAAA;AAAA,UACV,QAAU,EAAA;AAAA,SACX,CAAA;AAED,QAAAC,eAAA,CAAU,MAAO,CAAA;AAAA,UACf,IAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA;AAAA,UACA,QAAU,EAAA,cAAA;AAAA,UACV,uBAAA;AAAA,UACA,kBAAA,EAAoB,IAAIC,qCAAmB;AAAA,SAC5C,EAAE,KAAM,EAAA;AAET,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,uBAAA;AAAA,YACA,oBAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n }).start();\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n catalog,\n httpAuth,\n permissions,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","CatalogMetricService","Scheduler","ThresholdEvaluator","createRouter"],"mappings":";;;;;;;;;;;;;;;AA0CO,MAAM,kBAAkBA,oCAAoB,CAAA;AAAA,EACjD,QAAU,EAAA,WAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,uBAAA,GAA0B,IAAIC,+CAAwB,EAAA;AAE5D,IAAA,GAAA,CAAI,uBAAuBC,2DAAgC,EAAA;AAAA,MACzD,qBAAqB,kBAAsC,EAAA;AACzD,QAAA,kBAAA,CAAmB,QAAQ,CAAkB,cAAA,KAAA;AAC3C,UAAA,uBAAA,CAAwB,SAAS,cAAc,CAAA;AAAA,SAChD,CAAA;AAAA;AACH,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,OAAS,EAAAC,mCAAA;AAAA,QACT,QAAQD,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA,mBAAA;AAAA,QAClC,WAAWA,6BAAa,CAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,mBAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAA,mBAAA,CAAoB,eAAgB,CAAA;AAAA,UAClC,WAAa,EAAAE,0CAAA;AAAA,UACb,YAAA,EAAc,OAAO,YAA2B,KAAA;AAC9C,YAAO,OAAA,uBAAA,CAAwB,YAAY,YAAY,CAAA;AAAA,WACzD;AAAA,UACA,WAAa,EAAAC,mDAAA;AAAA,UACb,KAAA,EAAO,MAAO,CAAA,MAAA,CAAOC,WAAc;AAAA,SACpC,CAAA;AAGD,QAAA,MAAMC,kBAAQ,QAAQ,CAAA;AAEtB,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAM,MAAA,cAAA,GAAiB,IAAIC,yCAAA,CAAqB,MAAM,CAAA;AAEtD,QAAM,MAAA,oBAAA,GAAuB,IAAIC,yCAAqB,CAAA;AAAA,UACpD,OAAA;AAAA,UACA,IAAA;AAAA,UACA,QAAU,EAAA,uBAAA;AAAA,UACV,QAAU,EAAA;AAAA,SACX,CAAA;AAED,QAAAC,eAAA,CAAU,MAAO,CAAA;AAAA,UACf,IAAA;AAAA,UACA,OAAA;AAAA,UACA,MAAA;AAAA,UACA,MAAA;AAAA,UACA,SAAA;AAAA,UACA,QAAU,EAAA,cAAA;AAAA,UACV,uBAAA;AAAA,UACA,kBAAA,EAAoB,IAAIC,qCAAmB;AAAA,SAC5C,EAAE,KAAM,EAAA;AAET,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,uBAAA;AAAA,YACA,oBAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metric = metricProvider.getMetric();\n const metricType = metricProvider.getMetricType();\n\n if (providerId !== metric.id) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n const expectedPrefix = `${providerDatasource}.`;\n if (\n !providerId.startsWith(expectedPrefix) ||\n providerId === expectedPrefix\n ) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(providerId)) {\n throw new ConflictError(\n `Metric provider with ID '${providerId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(providerId, metricProvider);\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(providerId);\n }\n\n getProvider(providerId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(providerId);\n if (!metricProvider) {\n throw new NotFoundError(\n `Metric provider with ID '${providerId}' is not registered.`,\n );\n }\n return metricProvider;\n }\n\n getMetric(providerId: string): Metric {\n return this.getProvider(providerId).getMetric();\n }\n\n async calculateMetric(\n providerId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(providerId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n providerIds: string[],\n entity: Entity,\n ): Promise<{ providerId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n providerIds.map(providerId => this.calculateMetric(providerId, entity)),\n );\n\n return results.map((result, index) => {\n const providerId = providerIds[index];\n if (result.status === 'fulfilled') {\n return { providerId, value: result.value };\n }\n return { providerId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n return Array.from(this.metricProviders.values());\n }\n\n listMetrics(providerIds?: string[]): Metric[] {\n if (providerIds && providerIds.length !== 0) {\n return providerIds\n .map(providerId => this.metricProviders.get(providerId)?.getMetric())\n .filter((m): m is Metric => m !== undefined);\n }\n return [...this.metricProviders.values()].map(provider =>\n provider.getMetric(),\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n return Array.from(providerIdsOfDatasource)\n .map(providerId => this.metricProviders.get(providerId))\n .filter((provider): provider is MetricProvider => provider !== undefined)\n .map(provider => provider.getMetric());\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":";;;;AA2BO,MAAM,uBAAwB,CAAA;AAAA,EAClB,eAAA,uBAAsB,GAA4B,EAAA;AAAA,EAClD,eAAA,uBAAsB,GAAyB,EAAA;AAAA,EAEhE,SAAS,cAAsC,EAAA;AAC7C,IAAM,MAAA,UAAA,GAAa,eAAe,aAAc,EAAA;AAChD,IAAM,MAAA,kBAAA,GAAqB,eAAe,uBAAwB,EAAA;AAClE,IAAM,MAAA,MAAA,GAAS,eAAe,SAAU,EAAA;AACxC,IAAM,MAAA,UAAA,GAAa,eAAe,aAAc,EAAA;AAEhD,IAAI,IAAA,UAAA,KAAe,OAAO,EAAI,EAAA;AAC5B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAmC,gCAAA,EAAA,UAAU,CAAuC,oCAAA,EAAA,MAAA,CAAO,EAAE,CAAA,CAAA;AAAA,OAC/F;AAAA;AAGF,IAAI,IAAA,UAAA,KAAe,OAAO,IAAM,EAAA;AAC9B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,UAAU,CAAA,yDAAA,EAA4D,UAAU,CAAA,YAAA,EAAe,OAAO,IAAI,CAAA,CAAA;AAAA,OAC/I;AAAA;AAGF,IAAM,MAAA,cAAA,GAAiB,GAAG,kBAAkB,CAAA,CAAA,CAAA;AAC5C,IAAA,IACE,CAAC,UAAW,CAAA,UAAA,CAAW,cAAc,CAAA,IACrC,eAAe,cACf,EAAA;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gCAAA,EAAmC,UAAU,CAAA,oBAAA,EAAuB,kBAAkB,CAAA,8CAAA;AAAA,OACxF;AAAA;AAGF,IAAA,IAAI,IAAK,CAAA,eAAA,CAAgB,GAAI,CAAA,UAAU,CAAG,EAAA;AACxC,MAAA,MAAM,IAAIA,oBAAA;AAAA,QACR,4BAA4B,UAAU,CAAA,6BAAA;AAAA,OACxC;AAAA;AAGF,IAAK,IAAA,CAAA,eAAA,CAAgB,GAAI,CAAA,UAAA,EAAY,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metric = metricProvider.getMetric();\n const metricType = metricProvider.getMetricType();\n\n if (providerId !== metric.id) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n const expectedPrefix = `${providerDatasource}.`;\n if (\n !providerId.startsWith(expectedPrefix) ||\n providerId === expectedPrefix\n ) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(providerId)) {\n throw new ConflictError(\n `Metric provider with ID '${providerId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(providerId, metricProvider);\n\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(providerId);\n }\n\n getProvider(providerId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(providerId);\n if (!metricProvider) {\n throw new NotFoundError(\n `Metric provider with ID '${providerId}' is not registered.`,\n );\n }\n return metricProvider;\n }\n\n getMetric(providerId: string): Metric {\n return this.getProvider(providerId).getMetric();\n }\n\n async calculateMetric(\n providerId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(providerId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n providerIds: string[],\n entity: Entity,\n ): Promise<{ providerId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n providerIds.map(providerId => this.calculateMetric(providerId, entity)),\n );\n\n return results.map((result, index) => {\n const providerId = providerIds[index];\n if (result.status === 'fulfilled') {\n return { providerId, value: result.value };\n }\n return { providerId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n return Array.from(this.metricProviders.values());\n }\n\n listMetrics(providerIds?: string[]): Metric[] {\n if (providerIds && providerIds.length !== 0) {\n return providerIds\n .map(providerId => this.metricProviders.get(providerId)?.getMetric())\n .filter((m): m is Metric => m !== undefined);\n }\n return [...this.metricProviders.values()].map(provider =>\n provider.getMetric(),\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n return Array.from(providerIdsOfDatasource)\n .map(providerId => this.metricProviders.get(providerId))\n .filter((provider): provider is MetricProvider => provider !== undefined)\n .map(provider => provider.getMetric());\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":";;;;AA2BO,MAAM,uBAAwB,CAAA;AAAA,EAClB,eAAA,uBAAsB,GAA4B,EAAA;AAAA,EAClD,eAAA,uBAAsB,GAAyB,EAAA;AAAA,EAEhE,SAAS,cAAsC,EAAA;AAC7C,IAAM,MAAA,UAAA,GAAa,eAAe,aAAc,EAAA;AAChD,IAAM,MAAA,kBAAA,GAAqB,eAAe,uBAAwB,EAAA;AAClE,IAAM,MAAA,MAAA,GAAS,eAAe,SAAU,EAAA;AACxC,IAAM,MAAA,UAAA,GAAa,eAAe,aAAc,EAAA;AAEhD,IAAI,IAAA,UAAA,KAAe,OAAO,EAAI,EAAA;AAC5B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAmC,gCAAA,EAAA,UAAU,CAAuC,oCAAA,EAAA,MAAA,CAAO,EAAE,CAAA,CAAA;AAAA,OAC/F;AAAA;AAGF,IAAI,IAAA,UAAA,KAAe,OAAO,IAAM,EAAA;AAC9B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,UAAU,CAAA,yDAAA,EAA4D,UAAU,CAAA,YAAA,EAAe,OAAO,IAAI,CAAA,CAAA;AAAA,OAC/I;AAAA;AAGF,IAAM,MAAA,cAAA,GAAiB,GAAG,kBAAkB,CAAA,CAAA,CAAA;AAC5C,IAAA,IACE,CAAC,UAAW,CAAA,UAAA,CAAW,cAAc,CAAA,IACrC,eAAe,cACf,EAAA;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gCAAA,EAAmC,UAAU,CAAA,oBAAA,EAAuB,kBAAkB,CAAA,8CAAA;AAAA,OACxF;AAAA;AAGF,IAAA,IAAI,IAAK,CAAA,eAAA,CAAgB,GAAI,CAAA,UAAU,CAAG,EAAA;AACxC,MAAA,MAAM,IAAIA,oBAAA;AAAA,QACR,4BAA4B,UAAU,CAAA,6BAAA;AAAA,OACxC;AAAA;AAGF,IAAK,IAAA,CAAA,eAAA,CAAgB,GAAI,CAAA,UAAA,EAAY,cAAc,CAAA;AAEnD,IAAA,IAAI,mBAAsB,GAAA,IAAA,CAAK,eAAgB,CAAA,GAAA,CAAI,kBAAkB,CAAA;AACrE,IAAA,IAAI,CAAC,mBAAqB,EAAA;AACxB,MAAA,mBAAA,uBAA0B,GAAI,EAAA;AAC9B,MAAK,IAAA,CAAA,eAAA,CAAgB,GAAI,CAAA,kBAAA,EAAoB,mBAAmB,CAAA;AAAA;AAElE,IAAA,mBAAA,CAAoB,IAAI,UAAU,CAAA;AAAA;AACpC,EAEA,YAAY,UAAoC,EAAA;AAC9C,IAAA,MAAM,cAAiB,GAAA,IAAA,CAAK,eAAgB,CAAA,GAAA,CAAI,UAAU,CAAA;AAC1D,IAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,MAAA,MAAM,IAAIC,oBAAA;AAAA,QACR,4BAA4B,UAAU,CAAA,oBAAA;AAAA,OACxC;AAAA;AAEF,IAAO,OAAA,cAAA;AAAA;AACT,EAEA,UAAU,UAA4B,EAAA;AACpC,IAAA,OAAO,IAAK,CAAA,WAAA,CAAY,UAAU,CAAA,CAAE,SAAU,EAAA;AAAA;AAChD,EAEA,MAAM,eACJ,CAAA,UAAA,EACA,MACsB,EAAA;AACtB,IAAA,OAAO,IAAK,CAAA,WAAA,CAAY,UAAU,CAAA,CAAE,gBAAgB,MAAM,CAAA;AAAA;AAC5D,EAEA,MAAM,gBACJ,CAAA,WAAA,EACA,MACuE,EAAA;AACvE,IAAM,MAAA,OAAA,GAAU,MAAM,OAAQ,CAAA,UAAA;AAAA,MAC5B,YAAY,GAAI,CAAA,CAAA,UAAA,KAAc,KAAK,eAAgB,CAAA,UAAA,EAAY,MAAM,CAAC;AAAA,KACxE;AAEA,IAAA,OAAO,OAAQ,CAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,KAAU,KAAA;AACpC,MAAM,MAAA,UAAA,GAAa,YAAY,KAAK,CAAA;AACpC,MAAI,IAAA,MAAA,CAAO,WAAW,WAAa,EAAA;AACjC,QAAA,OAAO,EAAE,UAAA,EAAY,KAAO,EAAA,MAAA,CAAO,KAAM,EAAA;AAAA;AAE3C,MAAA,OAAO,EAAE,UAAA,EAAY,KAAO,EAAA,MAAA,CAAO,MAAgB,EAAA;AAAA,KACpD,CAAA;AAAA;AACH,EAEA,aAAkC,GAAA;AAChC,IAAA,OAAO,KAAM,CAAA,IAAA,CAAK,IAAK,CAAA,eAAA,CAAgB,QAAQ,CAAA;AAAA;AACjD,EAEA,YAAY,WAAkC,EAAA;AAC5C,IAAI,IAAA,WAAA,IAAe,WAAY,CAAA,MAAA,KAAW,CAAG,EAAA;AAC3C,MAAA,OAAO,WACJ,CAAA,GAAA,CAAI,CAAc,UAAA,KAAA,IAAA,CAAK,gBAAgB,GAAI,CAAA,UAAU,CAAG,EAAA,SAAA,EAAW,CACnE,CAAA,MAAA,CAAO,CAAC,CAAA,KAAmB,MAAM,MAAS,CAAA;AAAA;AAE/C,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,eAAgB,CAAA,MAAA,EAAQ,CAAE,CAAA,GAAA;AAAA,MAAI,CAAA,QAAA,KAC5C,SAAS,SAAU;AAAA,KACrB;AAAA;AACF,EAEA,wBAAwB,YAAgC,EAAA;AACtD,IAAA,MAAM,uBAA0B,GAAA,IAAA,CAAK,eAAgB,CAAA,GAAA,CAAI,YAAY,CAAA;AAErE,IAAA,IAAI,CAAC,uBAAyB,EAAA;AAC5B,MAAA,OAAO,EAAC;AAAA;AAGV,IAAO,OAAA,KAAA,CAAM,KAAK,uBAAuB,CAAA,CACtC,IAAI,CAAc,UAAA,KAAA,IAAA,CAAK,eAAgB,CAAA,GAAA,CAAI,UAAU,CAAC,EACtD,MAAO,CAAA,CAAC,aAAyC,QAAa,KAAA,MAAS,EACvE,GAAI,CAAA,CAAA,QAAA,KAAY,QAAS,CAAA,SAAA,EAAW,CAAA;AAAA;AAE3C;;;;"}
|
|
@@ -78,12 +78,13 @@ class PullMetricsByProviderTask {
|
|
|
78
78
|
cursor = entitiesResponse.pageInfo.nextCursor;
|
|
79
79
|
const batchResults = await Promise.allSettled(
|
|
80
80
|
entitiesResponse.items.map(async (entity) => {
|
|
81
|
+
let value;
|
|
81
82
|
try {
|
|
83
|
+
value = await provider.calculateMetric(entity);
|
|
82
84
|
const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
|
|
83
85
|
entity,
|
|
84
86
|
provider
|
|
85
87
|
);
|
|
86
|
-
const value = await provider.calculateMetric(entity);
|
|
87
88
|
const status = this.thresholdEvaluator.getFirstMatchingThreshold(
|
|
88
89
|
value,
|
|
89
90
|
metricType,
|
|
@@ -100,6 +101,7 @@ class PullMetricsByProviderTask {
|
|
|
100
101
|
return {
|
|
101
102
|
catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
|
|
102
103
|
metric_id: this.providerId,
|
|
104
|
+
value,
|
|
103
105
|
timestamp: /* @__PURE__ */ new Date(),
|
|
104
106
|
error_message: error instanceof Error ? error.message : String(error)
|
|
105
107
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValue } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { seconds: 3 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n try {\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n const value = await provider.calculateMetric(entity);\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n } as Omit<DbMetricValue, 'id'>;\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n } as Omit<DbMetricValue, 'id'>;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as Omit<DbMetricValue, 'id'>[]),\n );\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","mergeEntityAndProviderThresholds","stringifyEntityRef"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { seconds: 3 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n let value: MetricValue | undefined;\n\n try {\n value = await provider.calculateMetric(entity);\n\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n } as DbMetricValueCreate;\n } catch (error) {\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled') {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","mergeEntityAndProviderThresholds","stringifyEntityRef"],"mappings":"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"}
|
|
@@ -31,7 +31,7 @@ class CatalogMetricService {
|
|
|
31
31
|
if (!entity) {
|
|
32
32
|
throw new errors.NotFoundError(`Entity not found: ${entityRef}`);
|
|
33
33
|
}
|
|
34
|
-
const metricsToFetch =
|
|
34
|
+
const metricsToFetch = this.registry.listMetrics(providerIds);
|
|
35
35
|
const authorizedMetricsToFetch = permissionUtils.filterAuthorizedMetrics(
|
|
36
36
|
metricsToFetch,
|
|
37
37
|
filter
|
|
@@ -48,13 +48,15 @@ class CatalogMetricService {
|
|
|
48
48
|
const metric = provider.getMetric();
|
|
49
49
|
try {
|
|
50
50
|
thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(entity, provider);
|
|
51
|
-
if (value ===
|
|
51
|
+
if (value === null) {
|
|
52
52
|
thresholdError = "Unable to evaluate thresholds, metric value is missing";
|
|
53
|
+
} else if (error_message) {
|
|
54
|
+
thresholdError = error_message;
|
|
53
55
|
}
|
|
54
56
|
} catch (error) {
|
|
55
57
|
thresholdError = errors.stringifyError(error);
|
|
56
58
|
}
|
|
57
|
-
const isMetricCalcError = error_message
|
|
59
|
+
const isMetricCalcError = error_message !== null && value === null;
|
|
58
60
|
return {
|
|
59
61
|
id: metric.id,
|
|
60
62
|
status: isMetricCalcError ? "error" : "success",
|
|
@@ -81,6 +83,54 @@ class CatalogMetricService {
|
|
|
81
83
|
}
|
|
82
84
|
);
|
|
83
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Get aggregated metrics for multiple entities and metrics
|
|
88
|
+
*
|
|
89
|
+
* @param entityRefs - Array of entity references in format "kind:namespace/name"
|
|
90
|
+
* @param metricIds - Optional array of metric IDs to get aggregated metrics of.
|
|
91
|
+
* If not provided, gets all available aggregated metrics.
|
|
92
|
+
* @returns Aggregated metric results
|
|
93
|
+
*/
|
|
94
|
+
async getAggregatedMetricsByEntityRefs(entityRefs, metricIds, filter) {
|
|
95
|
+
const metricsToFetch = this.registry.listMetrics(metricIds);
|
|
96
|
+
const authorizedMetricsToFetch = permissionUtils.filterAuthorizedMetrics(
|
|
97
|
+
metricsToFetch,
|
|
98
|
+
filter
|
|
99
|
+
);
|
|
100
|
+
const aggregatedMetrics = await this.database.readAggregatedMetricsByEntityRefs(
|
|
101
|
+
entityRefs,
|
|
102
|
+
authorizedMetricsToFetch.map((m) => m.id)
|
|
103
|
+
);
|
|
104
|
+
return aggregatedMetrics.map((row) => {
|
|
105
|
+
const metricId = row.metric_id;
|
|
106
|
+
const success = row.success || 0;
|
|
107
|
+
const warning = row.warning || 0;
|
|
108
|
+
const error = row.error || 0;
|
|
109
|
+
const total = row.total || 0;
|
|
110
|
+
const timestamp = row.max_timestamp ? new Date(row.max_timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
111
|
+
const provider = this.registry.getProvider(metricId);
|
|
112
|
+
const metric = provider.getMetric();
|
|
113
|
+
return {
|
|
114
|
+
id: metricId,
|
|
115
|
+
status: "success",
|
|
116
|
+
metadata: {
|
|
117
|
+
title: metric.title,
|
|
118
|
+
description: metric.description,
|
|
119
|
+
type: metric.type,
|
|
120
|
+
history: metric.history
|
|
121
|
+
},
|
|
122
|
+
result: {
|
|
123
|
+
values: [
|
|
124
|
+
{ count: success, name: "success" },
|
|
125
|
+
{ count: warning, name: "warning" },
|
|
126
|
+
{ count: error, name: "error" }
|
|
127
|
+
],
|
|
128
|
+
total,
|
|
129
|
+
timestamp
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
84
134
|
}
|
|
85
135
|
|
|
86
136
|
exports.CatalogMetricService = CatalogMetricService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MetricResult,\n ThresholdConfig,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\n\nexport type CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n};\n\nexport class CatalogMetricService {\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n }\n\n /**\n * Get latest metric results for a specific catalog entity and metric providers.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param providerIds - Optional array of provider IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n providerIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = providerIds\n ? this.registry.listMetrics().filter(m => providerIds.includes(m.id))\n : this.registry.listMetrics();\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = provider.getMetric();\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === undefined) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = error_message || value === undefined;\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n}\n"],"names":["NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AggregatedMetricResult,\n MetricResult,\n ThresholdConfig,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\n\ntype CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n};\n\nexport type AggregatedMetricsByStatus = Record<\n string,\n { values: { success: number; warning: number; error: number }; total: number }\n>;\n\nexport class CatalogMetricService {\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n }\n\n /**\n * Get latest metric results for a specific catalog entity and metric providers.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param providerIds - Optional array of provider IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n providerIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = this.registry.listMetrics(providerIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = provider.getMetric();\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === null) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else if (error_message) {\n thresholdError = error_message;\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = error_message !== null && value === null;\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n\n /**\n * Get aggregated metrics for multiple entities and metrics\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricIds - Optional array of metric IDs to get aggregated metrics of.\n * If not provided, gets all available aggregated metrics.\n * @returns Aggregated metric results\n */\n async getAggregatedMetricsByEntityRefs(\n entityRefs: string[],\n metricIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<AggregatedMetricResult[]> {\n const metricsToFetch = this.registry.listMetrics(metricIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n\n const aggregatedMetrics =\n await this.database.readAggregatedMetricsByEntityRefs(\n entityRefs,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return aggregatedMetrics.map(row => {\n const metricId = row.metric_id;\n const success = row.success || 0;\n const warning = row.warning || 0;\n const error = row.error || 0;\n const total = row.total || 0;\n const timestamp = row.max_timestamp\n ? new Date(row.max_timestamp).toISOString()\n : new Date().toISOString();\n\n const provider = this.registry.getProvider(metricId);\n const metric = provider.getMetric();\n\n return {\n id: metricId,\n status: 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n result: {\n values: [\n { count: success, name: 'success' },\n { count: warning, name: 'warning' },\n { count: error, name: 'error' },\n ],\n total,\n timestamp,\n },\n };\n });\n }\n}\n"],"names":["NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError"],"mappings":"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"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var errors = require('@backstage/errors');
|
|
4
|
-
var zod = require('zod');
|
|
5
4
|
var express = require('express');
|
|
6
5
|
var Router = require('express-promise-router');
|
|
7
6
|
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
8
7
|
var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
|
|
9
8
|
var permissionUtils = require('../permissions/permissionUtils.cjs.js');
|
|
10
9
|
var catalogModel = require('@backstage/catalog-model');
|
|
10
|
+
var validateCatalogMetricsSchema = require('../validation/validateCatalogMetricsSchema.cjs.js');
|
|
11
|
+
var getEntitiesOwnedByUser = require('../utils/getEntitiesOwnedByUser.cjs.js');
|
|
12
|
+
var parseCommaSeparatedString = require('../utils/parseCommaSeparatedString.cjs.js');
|
|
13
|
+
var validateMetricsSchema = require('../validation/validateMetricsSchema.cjs.js');
|
|
11
14
|
|
|
12
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
13
16
|
|
|
@@ -17,6 +20,7 @@ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
|
17
20
|
async function createRouter({
|
|
18
21
|
metricProvidersRegistry,
|
|
19
22
|
catalogMetricService,
|
|
23
|
+
catalog,
|
|
20
24
|
httpAuth,
|
|
21
25
|
permissions
|
|
22
26
|
}) {
|
|
@@ -43,27 +47,23 @@ async function createRouter({
|
|
|
43
47
|
};
|
|
44
48
|
};
|
|
45
49
|
router.get("/metrics", async (req, res) => {
|
|
46
|
-
const {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const { metricIds, datasource } = validateMetricsSchema.validateMetricsSchema(req.query);
|
|
51
|
+
if (metricIds && datasource) {
|
|
52
|
+
throw new errors.InputError("Cannot filter by both metricIds and datasource");
|
|
53
|
+
}
|
|
54
|
+
if (metricIds) {
|
|
55
|
+
return res.json({
|
|
56
|
+
metrics: metricProvidersRegistry.listMetrics(
|
|
57
|
+
parseCommaSeparatedString.parseCommaSeparatedString(metricIds)
|
|
58
|
+
)
|
|
59
|
+
});
|
|
56
60
|
}
|
|
57
|
-
const { datasource } = parsed.data;
|
|
58
|
-
let metrics;
|
|
59
61
|
if (datasource) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
return res.json({
|
|
63
|
+
metrics: metricProvidersRegistry.listMetricsByDatasource(datasource)
|
|
64
|
+
});
|
|
63
65
|
}
|
|
64
|
-
res.json({
|
|
65
|
-
metrics: permissionUtils.filterAuthorizedMetrics(metrics, conditions)
|
|
66
|
-
});
|
|
66
|
+
return res.json({ metrics: metricProvidersRegistry.listMetrics() });
|
|
67
67
|
});
|
|
68
68
|
router.get("/metrics/catalog/:kind/:namespace/:name", async (req, res) => {
|
|
69
69
|
const { conditions } = await authorizeConditional(
|
|
@@ -71,17 +71,10 @@ async function createRouter({
|
|
|
71
71
|
backstagePluginScorecardCommon.scorecardMetricReadPermission
|
|
72
72
|
);
|
|
73
73
|
const { kind, namespace, name } = req.params;
|
|
74
|
-
const { metricIds } = req.query;
|
|
75
|
-
const catalogMetricsSchema = zod.z.object({
|
|
76
|
-
metricIds: zod.z.string().min(1).optional()
|
|
77
|
-
});
|
|
78
|
-
const parsed = catalogMetricsSchema.safeParse(req.query);
|
|
79
|
-
if (!parsed.success) {
|
|
80
|
-
throw new errors.InputError(`Invalid query parameters: ${parsed.error.message}`);
|
|
81
|
-
}
|
|
74
|
+
const { metricIds } = validateCatalogMetricsSchema.validateCatalogMetricsSchema(req.query);
|
|
82
75
|
const entityRef = catalogModel.stringifyEntityRef({ kind, namespace, name });
|
|
83
76
|
await permissionUtils.checkEntityAccess(entityRef, req, permissions, httpAuth);
|
|
84
|
-
const metricIdArray = metricIds ?
|
|
77
|
+
const metricIdArray = metricIds ? parseCommaSeparatedString.parseCommaSeparatedString(metricIds) : void 0;
|
|
85
78
|
const results = await catalogMetricService.getLatestEntityMetrics(
|
|
86
79
|
entityRef,
|
|
87
80
|
metricIdArray,
|
|
@@ -89,6 +82,42 @@ async function createRouter({
|
|
|
89
82
|
);
|
|
90
83
|
res.json(results);
|
|
91
84
|
});
|
|
85
|
+
router.get("/metrics/:metricId/catalog/aggregations", async (req, res) => {
|
|
86
|
+
const { metricId } = req.params;
|
|
87
|
+
const { conditions } = await authorizeConditional(
|
|
88
|
+
req,
|
|
89
|
+
backstagePluginScorecardCommon.scorecardMetricReadPermission
|
|
90
|
+
);
|
|
91
|
+
const metric = metricProvidersRegistry.getMetric(metricId);
|
|
92
|
+
const authorizedMetrics = permissionUtils.filterAuthorizedMetrics([metric], conditions);
|
|
93
|
+
if (authorizedMetrics.length === 0) {
|
|
94
|
+
throw new errors.NotAllowedError(
|
|
95
|
+
`To view the scorecard metrics, your administrator must grant you the required permission.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const credentials = await httpAuth.credentials(req, { allow: ["user"] });
|
|
99
|
+
const userEntityRef = credentials?.principal?.userEntityRef;
|
|
100
|
+
if (!userEntityRef) {
|
|
101
|
+
throw new errors.NotFoundError("User entity reference not found");
|
|
102
|
+
}
|
|
103
|
+
const entitiesOwnedByAUser = await getEntitiesOwnedByUser.getEntitiesOwnedByUser(userEntityRef, {
|
|
104
|
+
catalog,
|
|
105
|
+
credentials
|
|
106
|
+
});
|
|
107
|
+
if (entitiesOwnedByAUser.length === 0) {
|
|
108
|
+
res.json([]);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const entityRef of entitiesOwnedByAUser) {
|
|
112
|
+
await permissionUtils.checkEntityAccess(entityRef, req, permissions, httpAuth);
|
|
113
|
+
}
|
|
114
|
+
const aggregatedMetrics = await catalogMetricService.getAggregatedMetricsByEntityRefs(
|
|
115
|
+
entitiesOwnedByAUser,
|
|
116
|
+
[metricId],
|
|
117
|
+
conditions
|
|
118
|
+
);
|
|
119
|
+
res.json(aggregatedMetrics);
|
|
120
|
+
});
|
|
92
121
|
return router;
|
|
93
122
|
}
|
|
94
123
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { InputError, NotAllowedError } from '@backstage/errors';\nimport
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport express, { Request } from 'express';\nimport Router from 'express-promise-router';\nimport type { CatalogMetricService } from './CatalogMetricService';\nimport type { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n type HttpAuthService,\n type PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n AuthorizeResult,\n BasicPermission,\n PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport { scorecardMetricReadPermission } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n filterAuthorizedMetrics,\n checkEntityAccess,\n} from '../permissions/permissionUtils';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { validateCatalogMetricsSchema } from '../validation/validateCatalogMetricsSchema';\nimport { getEntitiesOwnedByUser } from '../utils/getEntitiesOwnedByUser';\nimport { parseCommaSeparatedString } from '../utils/parseCommaSeparatedString';\nimport { validateMetricsSchema } from '../validation/validateMetricsSchema';\n\nexport type ScorecardRouterOptions = {\n metricProvidersRegistry: MetricProvidersRegistry;\n catalogMetricService: CatalogMetricService;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n permissions: PermissionsService;\n};\n\nexport async function createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n catalog,\n httpAuth,\n permissions,\n}: ScorecardRouterOptions): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n const authorizeConditional = async (\n request: Request,\n permission: ResourcePermission<'scorecard-metric'> | BasicPermission,\n ) => {\n const credentials = await httpAuth.credentials(request);\n let decision: PolicyDecision;\n\n if (permission.type === 'resource') {\n decision = (\n await permissions.authorizeConditional([{ permission }], {\n credentials,\n })\n )[0];\n } else {\n decision = (\n await permissions.authorize([{ permission }], {\n credentials,\n })\n )[0];\n }\n\n if (decision.result === AuthorizeResult.DENY) {\n throw new NotAllowedError(); // 403\n }\n\n return {\n decision,\n conditions:\n decision.result === AuthorizeResult.CONDITIONAL\n ? decision.conditions\n : undefined,\n };\n };\n\n router.get('/metrics', async (req, res) => {\n const { metricIds, datasource } = validateMetricsSchema(req.query);\n\n if (metricIds && datasource) {\n throw new InputError('Cannot filter by both metricIds and datasource');\n }\n\n if (metricIds) {\n return res.json({\n metrics: metricProvidersRegistry.listMetrics(\n parseCommaSeparatedString(metricIds),\n ),\n });\n }\n\n if (datasource) {\n return res.json({\n metrics: metricProvidersRegistry.listMetricsByDatasource(datasource),\n });\n }\n\n return res.json({ metrics: metricProvidersRegistry.listMetrics() });\n });\n\n router.get('/metrics/catalog/:kind/:namespace/:name', async (req, res) => {\n const { conditions } = await authorizeConditional(\n req,\n scorecardMetricReadPermission,\n );\n\n const { kind, namespace, name } = req.params;\n\n const { metricIds } = validateCatalogMetricsSchema(req.query);\n\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n\n // Check if user has permission to read this specific catalog entity\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n\n const metricIdArray = metricIds\n ? parseCommaSeparatedString(metricIds)\n : undefined;\n\n const results = await catalogMetricService.getLatestEntityMetrics(\n entityRef,\n metricIdArray,\n conditions,\n );\n res.json(results);\n });\n\n router.get('/metrics/:metricId/catalog/aggregations', async (req, res) => {\n const { metricId } = req.params;\n\n const { conditions } = await authorizeConditional(\n req,\n scorecardMetricReadPermission,\n );\n\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new NotFoundError('User entity reference not found');\n }\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n if (entitiesOwnedByAUser.length === 0) {\n res.json([]);\n return;\n }\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const aggregatedMetrics =\n await catalogMetricService.getAggregatedMetricsByEntityRefs(\n entitiesOwnedByAUser,\n [metricId],\n conditions,\n );\n\n res.json(aggregatedMetrics);\n });\n\n return router;\n}\n"],"names":["Router","express","AuthorizeResult","NotAllowedError","validateMetricsSchema","InputError","parseCommaSeparatedString","scorecardMetricReadPermission","validateCatalogMetricsSchema","stringifyEntityRef","checkEntityAccess","filterAuthorizedMetrics","NotFoundError","getEntitiesOwnedByUser"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
5
|
+
|
|
6
|
+
const QUERY_ENTITIES_BATCH_SIZE = 50;
|
|
7
|
+
async function getEntitiesOwnedByUser(userEntityRef, options) {
|
|
8
|
+
const userEntity = await options.catalog.getEntityByRef(userEntityRef, {
|
|
9
|
+
credentials: options.credentials
|
|
10
|
+
});
|
|
11
|
+
if (!userEntity) {
|
|
12
|
+
throw new errors.NotFoundError("User entity not found in catalog");
|
|
13
|
+
}
|
|
14
|
+
const ownerRefs = [userEntityRef];
|
|
15
|
+
const memberOfRelations = userEntity.relations?.filter(
|
|
16
|
+
(relation) => relation.type === catalogModel.RELATION_MEMBER_OF
|
|
17
|
+
) ?? [];
|
|
18
|
+
if (memberOfRelations.length > 0) {
|
|
19
|
+
for (const relation of memberOfRelations) {
|
|
20
|
+
ownerRefs.push(relation.targetRef);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const entitiesOwnedByUserAndGroups = [];
|
|
24
|
+
for (const ownerRef of ownerRefs) {
|
|
25
|
+
let cursor = void 0;
|
|
26
|
+
do {
|
|
27
|
+
const entities = await options.catalog.queryEntities(
|
|
28
|
+
{
|
|
29
|
+
filter: {
|
|
30
|
+
[`relations.${catalogModel.RELATION_OWNED_BY}`]: ownerRef
|
|
31
|
+
},
|
|
32
|
+
fields: ["kind", "metadata"],
|
|
33
|
+
limit: QUERY_ENTITIES_BATCH_SIZE,
|
|
34
|
+
...cursor ? { cursor } : {}
|
|
35
|
+
},
|
|
36
|
+
{ credentials: options.credentials }
|
|
37
|
+
);
|
|
38
|
+
cursor = entities.pageInfo.nextCursor;
|
|
39
|
+
const entityRefs = entities.items.map(
|
|
40
|
+
(entity) => catalogModel.stringifyEntityRef(entity)
|
|
41
|
+
);
|
|
42
|
+
entitiesOwnedByUserAndGroups.push(...entityRefs);
|
|
43
|
+
} while (cursor !== void 0);
|
|
44
|
+
}
|
|
45
|
+
return entitiesOwnedByUserAndGroups;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
exports.getEntitiesOwnedByUser = getEntitiesOwnedByUser;
|
|
49
|
+
//# sourceMappingURL=getEntitiesOwnedByUser.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEntitiesOwnedByUser.cjs.js","sources":["../../src/utils/getEntitiesOwnedByUser.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 { NotFoundError } from '@backstage/errors';\nimport { BackstageCredentials } from '@backstage/backend-plugin-api';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n RELATION_MEMBER_OF,\n RELATION_OWNED_BY,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\n\nconst QUERY_ENTITIES_BATCH_SIZE = 50;\n\nexport async function getEntitiesOwnedByUser(\n userEntityRef: string,\n options: {\n catalog: CatalogService;\n credentials: BackstageCredentials;\n },\n): Promise<string[]> {\n const userEntity = await options.catalog.getEntityByRef(userEntityRef, {\n credentials: options.credentials,\n });\n\n if (!userEntity) {\n throw new NotFoundError('User entity not found in catalog');\n }\n\n const ownerRefs: string[] = [userEntityRef];\n\n const memberOfRelations =\n userEntity.relations?.filter(\n relation => relation.type === RELATION_MEMBER_OF,\n ) ?? [];\n\n if (memberOfRelations.length > 0) {\n for (const relation of memberOfRelations) {\n ownerRefs.push(relation.targetRef);\n }\n }\n\n const entitiesOwnedByUserAndGroups: string[] = [];\n\n for (const ownerRef of ownerRefs) {\n let cursor: string | undefined = undefined;\n\n do {\n const entities = await options.catalog.queryEntities(\n {\n filter: {\n [`relations.${RELATION_OWNED_BY}`]: ownerRef,\n },\n fields: ['kind', 'metadata'],\n limit: QUERY_ENTITIES_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: options.credentials },\n );\n\n cursor = entities.pageInfo.nextCursor;\n\n const entityRefs = entities.items.map(entity =>\n stringifyEntityRef(entity),\n );\n entitiesOwnedByUserAndGroups.push(...entityRefs);\n } while (cursor !== undefined);\n }\n\n return entitiesOwnedByUserAndGroups;\n}\n"],"names":["NotFoundError","RELATION_MEMBER_OF","RELATION_OWNED_BY","stringifyEntityRef"],"mappings":";;;;;AAyBA,MAAM,yBAA4B,GAAA,EAAA;AAEZ,eAAA,sBAAA,CACpB,eACA,OAImB,EAAA;AACnB,EAAA,MAAM,UAAa,GAAA,MAAM,OAAQ,CAAA,OAAA,CAAQ,eAAe,aAAe,EAAA;AAAA,IACrE,aAAa,OAAQ,CAAA;AAAA,GACtB,CAAA;AAED,EAAA,IAAI,CAAC,UAAY,EAAA;AACf,IAAM,MAAA,IAAIA,qBAAc,kCAAkC,CAAA;AAAA;AAG5D,EAAM,MAAA,SAAA,GAAsB,CAAC,aAAa,CAAA;AAE1C,EAAM,MAAA,iBAAA,GACJ,WAAW,SAAW,EAAA,MAAA;AAAA,IACpB,CAAA,QAAA,KAAY,SAAS,IAAS,KAAAC;AAAA,OAC3B,EAAC;AAER,EAAI,IAAA,iBAAA,CAAkB,SAAS,CAAG,EAAA;AAChC,IAAA,KAAA,MAAW,YAAY,iBAAmB,EAAA;AACxC,MAAU,SAAA,CAAA,IAAA,CAAK,SAAS,SAAS,CAAA;AAAA;AACnC;AAGF,EAAA,MAAM,+BAAyC,EAAC;AAEhD,EAAA,KAAA,MAAW,YAAY,SAAW,EAAA;AAChC,IAAA,IAAI,MAA6B,GAAA,MAAA;AAEjC,IAAG,GAAA;AACD,MAAM,MAAA,QAAA,GAAW,MAAM,OAAA,CAAQ,OAAQ,CAAA,aAAA;AAAA,QACrC;AAAA,UACE,MAAQ,EAAA;AAAA,YACN,CAAC,CAAA,UAAA,EAAaC,8BAAiB,CAAA,CAAE,GAAG;AAAA,WACtC;AAAA,UACA,MAAA,EAAQ,CAAC,MAAA,EAAQ,UAAU,CAAA;AAAA,UAC3B,KAAO,EAAA,yBAAA;AAAA,UACP,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,SAC7B;AAAA,QACA,EAAE,WAAa,EAAA,OAAA,CAAQ,WAAY;AAAA,OACrC;AAEA,MAAA,MAAA,GAAS,SAAS,QAAS,CAAA,UAAA;AAE3B,MAAM,MAAA,UAAA,GAAa,SAAS,KAAM,CAAA,GAAA;AAAA,QAAI,CAAA,MAAA,KACpCC,gCAAmB,MAAM;AAAA,OAC3B;AACA,MAA6B,4BAAA,CAAA,IAAA,CAAK,GAAG,UAAU,CAAA;AAAA,aACxC,MAAW,KAAA,MAAA;AAAA;AAGtB,EAAO,OAAA,4BAAA;AACT;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseCommaSeparatedString.cjs.js","sources":["../../src/utils/parseCommaSeparatedString.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\n/**\n * Parse a comma separated string into an array of strings\n *\n * @param value - The comma separated string to parse\n * @returns The array of strings\n */\nexport function parseCommaSeparatedString(value: string): string[] {\n return value.split(',').map(id => id.trim());\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,0BAA0B,KAAyB,EAAA;AACjE,EAAO,OAAA,KAAA,CAAM,MAAM,GAAG,CAAA,CAAE,IAAI,CAAM,EAAA,KAAA,EAAA,CAAG,MAAM,CAAA;AAC7C;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
|
|
6
|
+
function validateCatalogMetricsSchema(query) {
|
|
7
|
+
const catalogMetricsSchema = zod.z.object({
|
|
8
|
+
metricIds: zod.z.string().min(1).optional()
|
|
9
|
+
});
|
|
10
|
+
const parsed = catalogMetricsSchema.safeParse(query);
|
|
11
|
+
if (!parsed.success) {
|
|
12
|
+
throw new errors.InputError(`Invalid query parameters: ${parsed.error.message}`);
|
|
13
|
+
}
|
|
14
|
+
return parsed.data;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.validateCatalogMetricsSchema = validateCatalogMetricsSchema;
|
|
18
|
+
//# sourceMappingURL=validateCatalogMetricsSchema.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateCatalogMetricsSchema.cjs.js","sources":["../../src/validation/validateCatalogMetricsSchema.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 { z } from 'zod';\nimport { InputError } from '@backstage/errors';\n\nexport function validateCatalogMetricsSchema(query: unknown): {\n metricIds?: string;\n} {\n const catalogMetricsSchema = z.object({\n metricIds: z.string().min(1).optional(),\n });\n\n const parsed = catalogMetricsSchema.safeParse(query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n return parsed.data;\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAmBO,SAAS,6BAA6B,KAE3C,EAAA;AACA,EAAM,MAAA,oBAAA,GAAuBA,MAAE,MAAO,CAAA;AAAA,IACpC,WAAWA,KAAE,CAAA,MAAA,GAAS,GAAI,CAAA,CAAC,EAAE,QAAS;AAAA,GACvC,CAAA;AAED,EAAM,MAAA,MAAA,GAAS,oBAAqB,CAAA,SAAA,CAAU,KAAK,CAAA;AAEnD,EAAI,IAAA,CAAC,OAAO,OAAS,EAAA;AACnB,IAAA,MAAM,IAAIC,iBAAW,CAAA,CAAA,0BAAA,EAA6B,MAAO,CAAA,KAAA,CAAM,OAAO,CAAE,CAAA,CAAA;AAAA;AAG1E,EAAA,OAAO,MAAO,CAAA,IAAA;AAChB;;;;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
|
|
6
|
+
function validateMetricsSchema(query) {
|
|
7
|
+
const catalogMetricsSchema = zod.z.object({
|
|
8
|
+
metricIds: zod.z.string().min(1).optional(),
|
|
9
|
+
datasource: zod.z.string().min(1).optional()
|
|
10
|
+
});
|
|
11
|
+
const parsed = catalogMetricsSchema.safeParse(query);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
throw new errors.InputError(`Invalid query parameters: ${parsed.error.message}`);
|
|
14
|
+
}
|
|
15
|
+
return parsed.data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.validateMetricsSchema = validateMetricsSchema;
|
|
19
|
+
//# sourceMappingURL=validateMetricsSchema.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateMetricsSchema.cjs.js","sources":["../../src/validation/validateMetricsSchema.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 { z } from 'zod';\nimport { InputError } from '@backstage/errors';\n\nexport function validateMetricsSchema(query: unknown): {\n metricIds?: string;\n datasource?: string;\n} {\n const catalogMetricsSchema = z.object({\n metricIds: z.string().min(1).optional(),\n datasource: z.string().min(1).optional(),\n });\n\n const parsed = catalogMetricsSchema.safeParse(query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n return parsed.data;\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAmBO,SAAS,sBAAsB,KAGpC,EAAA;AACA,EAAM,MAAA,oBAAA,GAAuBA,MAAE,MAAO,CAAA;AAAA,IACpC,WAAWA,KAAE,CAAA,MAAA,GAAS,GAAI,CAAA,CAAC,EAAE,QAAS,EAAA;AAAA,IACtC,YAAYA,KAAE,CAAA,MAAA,GAAS,GAAI,CAAA,CAAC,EAAE,QAAS;AAAA,GACxC,CAAA;AAED,EAAM,MAAA,MAAA,GAAS,oBAAqB,CAAA,SAAA,CAAU,KAAK,CAAA;AAEnD,EAAI,IAAA,CAAC,OAAO,OAAS,EAAA;AACnB,IAAA,MAAM,IAAIC,iBAAW,CAAA,CAAA,0BAAA,EAA6B,MAAO,CAAA,KAAA,CAAM,OAAO,CAAE,CAAA,CAAA;AAAA;AAG1E,EAAA,OAAO,MAAO,CAAA,IAAA;AAChB;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"@backstage/plugin-catalog-node": "^1.20.0",
|
|
48
48
|
"@backstage/plugin-permission-common": "^0.9.3",
|
|
49
49
|
"@backstage/plugin-permission-node": "^0.10.6",
|
|
50
|
-
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.
|
|
51
|
-
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.
|
|
50
|
+
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.3.0",
|
|
51
|
+
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.3.0",
|
|
52
52
|
"express": "^4.17.1",
|
|
53
53
|
"express-promise-router": "^4.1.0",
|
|
54
54
|
"knex": "^3.1.0",
|