@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.7.8 → 2.8.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 +22 -0
- package/README.md +64 -0
- package/dist/actions/getEntityMetrics.cjs.js +84 -0
- package/dist/actions/getEntityMetrics.cjs.js.map +1 -0
- package/dist/actions/index.cjs.js +14 -0
- package/dist/actions/index.cjs.js.map +1 -0
- package/dist/actions/listMetrics.cjs.js +46 -0
- package/dist/actions/listMetrics.cjs.js.map +1 -0
- package/dist/constants/aggregationKPIs.cjs.js.map +1 -1
- package/dist/database/DatabaseMetricValues.cjs.js.map +1 -1
- package/dist/database/migration.cjs.js.map +1 -1
- package/dist/middlewares/validateAggregationIdParam.cjs.js.map +1 -1
- package/dist/middlewares/validateDatasourceQueryParams.cjs.js.map +1 -1
- package/dist/middlewares/validateMetricIdsQueryParams.cjs.js.map +1 -1
- package/dist/permissions/permissionUtils.cjs.js.map +1 -1
- package/dist/permissions/rules.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +10 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/providers/MetricProvidersRegistry.cjs.js.map +1 -1
- package/dist/scheduler/constants.cjs.js.map +1 -1
- package/dist/scheduler/index.cjs.js.map +1 -1
- package/dist/scheduler/tasks/CleanupExpiredMetricsTask.cjs.js +2 -2
- package/dist/scheduler/tasks/CleanupExpiredMetricsTask.cjs.js.map +1 -1
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js +2 -2
- package/dist/scheduler/tasks/PullMetricsByProviderTask.cjs.js.map +1 -1
- package/dist/scheduler/tasks/utils.cjs.js.map +1 -1
- package/dist/service/CatalogMetricService.cjs.js.map +1 -1
- package/dist/service/aggregations/AggregatedMetricLoader.cjs.js.map +1 -1
- package/dist/service/aggregations/AggregationService.cjs.js.map +1 -1
- package/dist/service/aggregations/strategies/AverageAggregationStrategy.cjs.js.map +1 -1
- package/dist/service/aggregations/strategies/StatusGroupedAggregationStrategy.cjs.js.map +1 -1
- package/dist/service/aggregations/strategies/registerStrategies.cjs.js.map +1 -1
- package/dist/service/mappers.cjs.js.map +1 -1
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/threshold/ThresholdEvaluator.cjs.js.map +1 -1
- package/dist/threshold/ThresholdResolver.cjs.js.map +1 -1
- package/dist/utils/buildAggregationConfig.cjs.js.map +1 -1
- package/dist/utils/getEntitiesOwnedByUser.cjs.js.map +1 -1
- package/dist/utils/mergeEntityAndProviderThresholds.cjs.js.map +1 -1
- package/dist/utils/metricCalculationError.cjs.js.map +1 -1
- package/dist/utils/metricUtils.cjs.js.map +1 -1
- package/dist/utils/normalizeOwnerRef.cjs.js.map +1 -1
- package/dist/utils/parseCommaSeparatedString.cjs.js.map +1 -1
- package/dist/validation/schemas/aggregationConfigSchemas.cjs.js.map +1 -1
- package/dist/validation/validateAggregationConfig.cjs.js.map +1 -1
- package/dist/validation/validateDrillDownMetricsSchema.cjs.js.map +1 -1
- package/package.json +15 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-scorecard-backend
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0d1ea32: create actions to list metrics and to get entity metrics
|
|
8
|
+
- 8c85bd4: Backstage version bump to v1.51.1
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- efb4c4f: Replace deprecated uuid dependency with the Node.js built-in `crypto.randomUUID`.
|
|
13
|
+
- Updated dependencies [efb4c4f]
|
|
14
|
+
- Updated dependencies [8c85bd4]
|
|
15
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.8.0
|
|
16
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.8.0
|
|
17
|
+
|
|
18
|
+
## 2.7.9
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-common@2.7.9
|
|
23
|
+
- @red-hat-developer-hub/backstage-plugin-scorecard-node@2.7.9
|
|
24
|
+
|
|
3
25
|
## 2.7.8
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -327,6 +327,70 @@ scorecard:
|
|
|
327
327
|
dataRetentionDays: 12
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
+
## MCP Actions
|
|
331
|
+
|
|
332
|
+
The Scorecard backend plugin registers MCP (Model Context Protocol) actions that allow AI agents and MCP clients (e.g., Cursor, Claude Code) to query scorecard data programmatically.
|
|
333
|
+
|
|
334
|
+
### Available Actions
|
|
335
|
+
|
|
336
|
+
| Action | Description |
|
|
337
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
338
|
+
| `list-metrics` | Lists all available scorecard metrics and their datasources. |
|
|
339
|
+
| `get-entity-metrics` | Returns the latest metric values for a specific catalog entity. |
|
|
340
|
+
|
|
341
|
+
### Enabling MCP Actions
|
|
342
|
+
|
|
343
|
+
To enable MCP actions, install the `@backstage/plugin-mcp-actions-backend` package and configure authentication:
|
|
344
|
+
|
|
345
|
+
1. Install the MCP actions backend plugin:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# From your root directory
|
|
349
|
+
yarn --cwd packages/backend add @backstage/plugin-mcp-actions-backend
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
2. Add the plugin to your backend in `packages/backend/src/index.ts`:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
backend.add(import('@backstage/plugin-mcp-actions-backend'));
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
3. Add the scorecard plugin as an action source and configure a static token for MCP client authentication in your `app-config.yaml`:
|
|
359
|
+
|
|
360
|
+
```yaml
|
|
361
|
+
backend:
|
|
362
|
+
actions:
|
|
363
|
+
pluginSources:
|
|
364
|
+
- 'scorecard'
|
|
365
|
+
auth:
|
|
366
|
+
externalAccess:
|
|
367
|
+
- type: static
|
|
368
|
+
options:
|
|
369
|
+
token: ${MCP_TOKEN}
|
|
370
|
+
subject: mcp-clients
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
4. Set the `MCP_TOKEN` environment variable (8 characters or longer) before starting the backend.
|
|
374
|
+
|
|
375
|
+
### Interacting with MCP Actions
|
|
376
|
+
|
|
377
|
+
See the [Backstage MCP Actions Backend documentation](https://github.com/backstage/backstage/tree/master/plugins/mcp-actions-backend#configuring-mcp-clients) for more information on configuring MCP clients.
|
|
378
|
+
|
|
379
|
+
Sample `mcp.json` for Cursor:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"mcpServers": {
|
|
384
|
+
"backstage-actions": {
|
|
385
|
+
"url": "http://localhost:7007/api/mcp-actions/v1",
|
|
386
|
+
"headers": {
|
|
387
|
+
"Authorization": "Bearer ${MCP_TOKEN}"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
330
394
|
## Development
|
|
331
395
|
|
|
332
396
|
This plugin backend can be started in a standalone mode from directly in this
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
4
|
+
var alpha = require('@backstage/plugin-catalog-common/alpha');
|
|
5
|
+
var errors = require('@backstage/errors');
|
|
6
|
+
var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
|
|
7
|
+
var permissionUtils = require('../permissions/permissionUtils.cjs.js');
|
|
8
|
+
|
|
9
|
+
const createGetEntityMetricsAction = ({
|
|
10
|
+
actionsRegistry,
|
|
11
|
+
permissions,
|
|
12
|
+
catalogMetricService
|
|
13
|
+
}) => {
|
|
14
|
+
actionsRegistry.register({
|
|
15
|
+
name: "get-entity-metrics",
|
|
16
|
+
title: "Get Entity Metrics",
|
|
17
|
+
attributes: {
|
|
18
|
+
readOnly: true
|
|
19
|
+
},
|
|
20
|
+
description: 'Returns the latest scorecard metric values for a catalog entity. Provide the entity reference (e.g. "component:default/my-service") to retrieve all scored metrics with their current values, thresholds, and statuses.',
|
|
21
|
+
schema: {
|
|
22
|
+
input: (z) => z.object({
|
|
23
|
+
entityRef: z.string().describe(
|
|
24
|
+
'Entity reference in "kind:namespace/name" format, e.g. "component:default/my-service"'
|
|
25
|
+
)
|
|
26
|
+
}),
|
|
27
|
+
output: (z) => z.object({
|
|
28
|
+
metrics: z.array(
|
|
29
|
+
z.object({
|
|
30
|
+
id: z.string(),
|
|
31
|
+
status: z.enum(["success", "error"]),
|
|
32
|
+
metadata: z.object({
|
|
33
|
+
title: z.string(),
|
|
34
|
+
description: z.string(),
|
|
35
|
+
type: z.enum(["number", "boolean"]),
|
|
36
|
+
history: z.boolean().optional()
|
|
37
|
+
}),
|
|
38
|
+
result: z.object({
|
|
39
|
+
value: z.union([z.number(), z.boolean(), z.null()]),
|
|
40
|
+
timestamp: z.string(),
|
|
41
|
+
thresholdResult: z.object({
|
|
42
|
+
definition: z.unknown().optional(),
|
|
43
|
+
status: z.enum(["success", "error"]),
|
|
44
|
+
evaluation: z.string().nullable().optional(),
|
|
45
|
+
error: z.string().optional()
|
|
46
|
+
})
|
|
47
|
+
}),
|
|
48
|
+
error: z.string().optional()
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
action: async ({ input, credentials }) => {
|
|
54
|
+
const entityAccessDecision = await permissions.authorize(
|
|
55
|
+
[
|
|
56
|
+
{
|
|
57
|
+
permission: alpha.catalogEntityReadPermission,
|
|
58
|
+
resourceRef: input.entityRef
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
{ credentials }
|
|
62
|
+
);
|
|
63
|
+
if (entityAccessDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
64
|
+
throw new errors.NotAllowedError(
|
|
65
|
+
`Access to "${input.entityRef}" entity metrics denied`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const { conditions } = await permissionUtils.authorizeConditional(
|
|
69
|
+
credentials,
|
|
70
|
+
permissions,
|
|
71
|
+
backstagePluginScorecardCommon.scorecardMetricReadPermission
|
|
72
|
+
);
|
|
73
|
+
const metrics = await catalogMetricService.getLatestEntityMetrics(
|
|
74
|
+
input.entityRef,
|
|
75
|
+
void 0,
|
|
76
|
+
conditions
|
|
77
|
+
);
|
|
78
|
+
return { output: { metrics } };
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
exports.createGetEntityMetricsAction = createGetEntityMetricsAction;
|
|
84
|
+
//# sourceMappingURL=getEntityMetrics.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEntityMetrics.cjs.js","sources":["../../src/actions/getEntityMetrics.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 { PermissionsService } from '@backstage/backend-plugin-api';\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport { NotAllowedError } from '@backstage/errors';\nimport { scorecardMetricReadPermission } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { CatalogMetricService } from '../service/CatalogMetricService';\nimport { authorizeConditional } from '../permissions/permissionUtils';\n\nexport const createGetEntityMetricsAction = ({\n actionsRegistry,\n permissions,\n catalogMetricService,\n}: {\n actionsRegistry: ActionsRegistryService;\n permissions: PermissionsService;\n catalogMetricService: CatalogMetricService;\n}) => {\n actionsRegistry.register({\n name: 'get-entity-metrics',\n title: 'Get Entity Metrics',\n attributes: {\n readOnly: true,\n },\n description:\n 'Returns the latest scorecard metric values for a catalog entity. ' +\n 'Provide the entity reference (e.g. \"component:default/my-service\") to ' +\n 'retrieve all scored metrics with their current values, thresholds, and statuses.',\n schema: {\n input: z =>\n z.object({\n entityRef: z\n .string()\n .describe(\n 'Entity reference in \"kind:namespace/name\" format, e.g. \"component:default/my-service\"',\n ),\n }),\n output: z =>\n z.object({\n metrics: z.array(\n z.object({\n id: z.string(),\n status: z.enum(['success', 'error']),\n metadata: z.object({\n title: z.string(),\n description: z.string(),\n type: z.enum(['number', 'boolean']),\n history: z.boolean().optional(),\n }),\n result: z.object({\n value: z.union([z.number(), z.boolean(), z.null()]),\n timestamp: z.string(),\n thresholdResult: z.object({\n definition: z.unknown().optional(),\n status: z.enum(['success', 'error']),\n evaluation: z.string().nullable().optional(),\n error: z.string().optional(),\n }),\n }),\n error: z.string().optional(),\n }),\n ),\n }),\n },\n action: async ({ input, credentials }) => {\n const entityAccessDecision = await permissions.authorize(\n [\n {\n permission: catalogEntityReadPermission,\n resourceRef: input.entityRef,\n },\n ],\n { credentials },\n );\n if (entityAccessDecision[0].result !== AuthorizeResult.ALLOW) {\n throw new NotAllowedError(\n `Access to \"${input.entityRef}\" entity metrics denied`,\n );\n }\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const metrics = await catalogMetricService.getLatestEntityMetrics(\n input.entityRef,\n undefined,\n conditions,\n );\n\n return { output: { metrics } };\n },\n });\n};\n"],"names":["catalogEntityReadPermission","AuthorizeResult","NotAllowedError","authorizeConditional","scorecardMetricReadPermission"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var getEntityMetrics = require('./getEntityMetrics.cjs.js');
|
|
4
|
+
var listMetrics = require('./listMetrics.cjs.js');
|
|
5
|
+
|
|
6
|
+
const createScorecardActions = (options) => {
|
|
7
|
+
getEntityMetrics.createGetEntityMetricsAction(options);
|
|
8
|
+
listMetrics.createListMetricsAction(options);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
exports.createGetEntityMetricsAction = getEntityMetrics.createGetEntityMetricsAction;
|
|
12
|
+
exports.createListMetricsAction = listMetrics.createListMetricsAction;
|
|
13
|
+
exports.createScorecardActions = createScorecardActions;
|
|
14
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../src/actions/index.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { AuthService, PermissionsService } from '@backstage/backend-plugin-api';\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from '../service/CatalogMetricService';\nimport { createGetEntityMetricsAction } from './getEntityMetrics';\nimport { createListMetricsAction } from './listMetrics';\n\nexport { createGetEntityMetricsAction } from './getEntityMetrics';\nexport { createListMetricsAction } from './listMetrics';\n\nexport const createScorecardActions = (options: {\n actionsRegistry: ActionsRegistryService;\n auth: AuthService;\n permissions: PermissionsService;\n catalog: CatalogService;\n metricProvidersRegistry: MetricProvidersRegistry;\n catalogMetricService: CatalogMetricService;\n}) => {\n createGetEntityMetricsAction(options);\n createListMetricsAction(options);\n};\n"],"names":["createGetEntityMetricsAction","createListMetricsAction"],"mappings":";;;;;AA0BO,MAAM,sBAAA,GAAyB,CAAC,OAAA,KAOjC;AACJ,EAAAA,6CAAA,CAA6B,OAAO,CAAA;AACpC,EAAAC,mCAAA,CAAwB,OAAO,CAAA;AACjC;;;;;;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
|
|
4
|
+
var permissionUtils = require('../permissions/permissionUtils.cjs.js');
|
|
5
|
+
|
|
6
|
+
const createListMetricsAction = ({
|
|
7
|
+
actionsRegistry,
|
|
8
|
+
permissions,
|
|
9
|
+
metricProvidersRegistry
|
|
10
|
+
}) => {
|
|
11
|
+
actionsRegistry.register({
|
|
12
|
+
name: "list-metrics",
|
|
13
|
+
title: "List Metrics",
|
|
14
|
+
attributes: {
|
|
15
|
+
readOnly: true
|
|
16
|
+
},
|
|
17
|
+
description: "Lists all available scorecard metrics and their datasources. Use this to discover which metrics are configured before querying entity-specific values with get-entity-metrics.",
|
|
18
|
+
schema: {
|
|
19
|
+
input: (z) => z.object({}),
|
|
20
|
+
output: (z) => z.object({
|
|
21
|
+
metrics: z.array(
|
|
22
|
+
z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
description: z.string(),
|
|
26
|
+
type: z.enum(["number", "boolean"]),
|
|
27
|
+
history: z.boolean().optional()
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
},
|
|
32
|
+
action: async ({ credentials }) => {
|
|
33
|
+
const { conditions } = await permissionUtils.authorizeConditional(
|
|
34
|
+
credentials,
|
|
35
|
+
permissions,
|
|
36
|
+
backstagePluginScorecardCommon.scorecardMetricReadPermission
|
|
37
|
+
);
|
|
38
|
+
const allMetrics = metricProvidersRegistry.listMetrics();
|
|
39
|
+
const metrics = permissionUtils.filterAuthorizedMetrics(allMetrics, conditions);
|
|
40
|
+
return { output: { metrics } };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
exports.createListMetricsAction = createListMetricsAction;
|
|
46
|
+
//# sourceMappingURL=listMetrics.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listMetrics.cjs.js","sources":["../../src/actions/listMetrics.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 { PermissionsService } from '@backstage/backend-plugin-api';\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport { scorecardMetricReadPermission } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n authorizeConditional,\n filterAuthorizedMetrics,\n} from '../permissions/permissionUtils';\n\nexport const createListMetricsAction = ({\n actionsRegistry,\n permissions,\n metricProvidersRegistry,\n}: {\n actionsRegistry: ActionsRegistryService;\n permissions: PermissionsService;\n metricProvidersRegistry: MetricProvidersRegistry;\n}) => {\n actionsRegistry.register({\n name: 'list-metrics',\n title: 'List Metrics',\n attributes: {\n readOnly: true,\n },\n description:\n 'Lists all available scorecard metrics and their datasources. ' +\n 'Use this to discover which metrics are configured before querying ' +\n 'entity-specific values with get-entity-metrics.',\n schema: {\n input: z => z.object({}),\n output: z =>\n z.object({\n metrics: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n description: z.string(),\n type: z.enum(['number', 'boolean']),\n history: z.boolean().optional(),\n }),\n ),\n }),\n },\n action: async ({ credentials }) => {\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const allMetrics = metricProvidersRegistry.listMetrics();\n const metrics = filterAuthorizedMetrics(allMetrics, conditions);\n\n return { output: { metrics } };\n },\n });\n};\n"],"names":["authorizeConditional","scorecardMetricReadPermission","filterAuthorizedMetrics"],"mappings":";;;;;AAwBO,MAAM,0BAA0B,CAAC;AAAA,EACtC,eAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,KAIM;AACJ,EAAA,eAAA,CAAgB,QAAA,CAAS;AAAA,IACvB,IAAA,EAAM,cAAA;AAAA,IACN,KAAA,EAAO,cAAA;AAAA,IACP,UAAA,EAAY;AAAA,MACV,QAAA,EAAU;AAAA,KACZ;AAAA,IACA,WAAA,EACE,gLAAA;AAAA,IAGF,MAAA,EAAQ;AAAA,MACN,KAAA,EAAO,CAAA,CAAA,KAAK,CAAA,CAAE,MAAA,CAAO,EAAE,CAAA;AAAA,MACvB,MAAA,EAAQ,CAAA,CAAA,KACN,CAAA,CAAE,MAAA,CAAO;AAAA,QACP,SAAS,CAAA,CAAE,KAAA;AAAA,UACT,EAAE,MAAA,CAAO;AAAA,YACP,EAAA,EAAI,EAAE,MAAA,EAAO;AAAA,YACb,KAAA,EAAO,EAAE,MAAA,EAAO;AAAA,YAChB,WAAA,EAAa,EAAE,MAAA,EAAO;AAAA,YACtB,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,QAAA,EAAU,SAAS,CAAC,CAAA;AAAA,YAClC,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA;AAAS,WAC/B;AAAA;AACH,OACD;AAAA,KACL;AAAA,IACA,MAAA,EAAQ,OAAO,EAAE,WAAA,EAAY,KAAM;AACjC,MAAA,MAAM,EAAE,UAAA,EAAW,GAAI,MAAMA,oCAAA;AAAA,QAC3B,WAAA;AAAA,QACA,WAAA;AAAA,QACAC;AAAA,OACF;AAEA,MAAA,MAAM,UAAA,GAAa,wBAAwB,WAAA,EAAY;AACvD,MAAA,MAAM,OAAA,GAAUC,uCAAA,CAAwB,UAAA,EAAY,UAAU,CAAA;AAE9D,MAAA,OAAO,EAAE,MAAA,EAAQ,EAAE,OAAA,EAAQ,EAAE;AAAA,IAC/B;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregationKPIs.cjs.js","sources":["../../src/constants/aggregationKPIs.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ThresholdConfig,\n ScorecardThresholdRuleColors,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport const AGGREGATION_KPIS_CONFIG_PATH =\n 'scorecard.aggregationKPIs' as const;\n\n/**\n * Default applied by `AverageAggregationStrategy` when `options.thresholds` is omitted\n * from app-config. Higher headline percentage (0–100) = better. Evaluated in order; first match wins.\n */\nexport const DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS: ThresholdConfig = {\n rules: [\n {\n key: 'success',\n expression: '>=80',\n color: ScorecardThresholdRuleColors.SUCCESS,\n },\n {\n key: 'warning',\n expression: '30-80',\n color: ScorecardThresholdRuleColors.WARNING,\n },\n {\n key: 'error',\n expression: '<30',\n color: ScorecardThresholdRuleColors.ERROR,\n },\n ],\n};\n"],"names":["ScorecardThresholdRuleColors"],"mappings":";;;;AAqBO,MAAM,
|
|
1
|
+
{"version":3,"file":"aggregationKPIs.cjs.js","sources":["../../src/constants/aggregationKPIs.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ThresholdConfig,\n ScorecardThresholdRuleColors,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport const AGGREGATION_KPIS_CONFIG_PATH =\n 'scorecard.aggregationKPIs' as const;\n\n/**\n * Default applied by `AverageAggregationStrategy` when `options.thresholds` is omitted\n * from app-config. Higher headline percentage (0–100) = better. Evaluated in order; first match wins.\n */\nexport const DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS: ThresholdConfig = {\n rules: [\n {\n key: 'success',\n expression: '>=80',\n color: ScorecardThresholdRuleColors.SUCCESS,\n },\n {\n key: 'warning',\n expression: '30-80',\n color: ScorecardThresholdRuleColors.WARNING,\n },\n {\n key: 'error',\n expression: '<30',\n color: ScorecardThresholdRuleColors.ERROR,\n },\n ],\n};\n"],"names":["ScorecardThresholdRuleColors"],"mappings":";;;;AAqBO,MAAM,4BAAA,GACX;AAMK,MAAM,qCAAA,GAAyD;AAAA,EACpE,KAAA,EAAO;AAAA,IACL;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,MAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,SAAA;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA,KACtC;AAAA,IACA;AAAA,MACE,GAAA,EAAK,OAAA;AAAA,MACL,UAAA,EAAY,KAAA;AAAA,MACZ,OAAOA,2DAAA,CAA6B;AAAA;AACtC;AAEJ;;;;;"}
|
|
@@ -1 +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 {\n DbMetricValueCreate,\n DbMetricValue,\n DbAggregatedMetric,\n} from './types';\n\ntype ReadEntityMetricsWithFiltersOptions = {\n status?: string;\n entityName?: string;\n entityKind?: string;\n entityNamespace?: string;\n entityOwner?: string[];\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n pagination?: { limit: number; offset: number };\n};\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 if (metricValues.length === 0) {\n return;\n }\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 readAggregatedMetricByEntityRefs(\n catalog_entity_refs: string[],\n metric_id: string,\n ): Promise<DbAggregatedMetric | undefined> {\n if (catalog_entity_refs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metric_id)\n .whereIn('catalog_entity_ref', catalog_entity_refs)\n .groupBy('catalog_entity_ref');\n\n // `value` is a JSON column. Depending on database/driver, a \"missing\" metric value can\n // arrive either as SQL NULL or as JSON literal null (`CAST(value AS TEXT) = 'null'`).\n const metricValueIsMissingExpr =\n \"(value IS NULL OR CAST(value AS TEXT) = 'null')\";\n\n // One round-trip for latest-row count, calculation-error count, and max timestamp\n // (same latest-id set as the status breakdown query below).\n const statsRow = await this.dbClient(this.tableName)\n .whereIn('id', latestIdsSubquery)\n .select(\n this.dbClient.raw('COUNT(*) as latest_row_count'),\n this.dbClient.raw(\n `SUM(CASE WHEN error_message IS NOT NULL AND ${metricValueIsMissingExpr} THEN 1 ELSE 0 END) as calculation_error_count`,\n ),\n this.dbClient.raw('MAX(timestamp) as max_timestamp'),\n )\n .first();\n\n const latestRowCount = Number(\n (statsRow as { latest_row_count?: string | number } | undefined)\n ?.latest_row_count ?? 0,\n );\n if (latestRowCount === 0) {\n return undefined;\n }\n\n const calculation_error_count = Number(\n (statsRow as { calculation_error_count?: string | number } | undefined)\n ?.calculation_error_count ?? 0,\n );\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 const normalizeTimestamp = (timestamp: unknown): Date => {\n if (timestamp instanceof Date) {\n return timestamp;\n }\n if (typeof timestamp === 'number' || typeof timestamp === 'string') {\n return new Date(timestamp);\n }\n return new Date();\n };\n\n const maxTimestampAllLatest = normalizeTimestamp(\n (statsRow as { max_timestamp?: unknown })?.max_timestamp,\n );\n\n const statusRows = await this.dbClient(this.tableName)\n .select('status')\n .count('* as count')\n .max('timestamp as max_timestamp')\n .whereIn('id', latestIdsSubquery)\n .whereNotNull('status')\n .whereRaw(`NOT ${metricValueIsMissingExpr}`)\n .groupBy('status');\n\n if (!statusRows || statusRows.length === 0) {\n return {\n metric_id,\n total: 0,\n max_timestamp: maxTimestampAllLatest,\n statusCounts: {},\n calculation_error_count,\n latest_entity_count: latestRowCount,\n };\n }\n\n let maxTimestamp = new Date(0);\n let total = 0;\n const statusCounts: Record<string, number> = {};\n for (const row of statusRows) {\n const rowTimestamp = normalizeTimestamp(row.max_timestamp);\n if (rowTimestamp > maxTimestamp) {\n maxTimestamp = rowTimestamp;\n }\n const name = row.status as string;\n const count = Number(row.count);\n statusCounts[name] = count;\n total += count;\n }\n\n const mergedMax =\n maxTimestampAllLatest.getTime() >= maxTimestamp.getTime()\n ? maxTimestampAllLatest\n : maxTimestamp;\n\n return {\n metric_id,\n total,\n max_timestamp: mergedMax,\n statusCounts,\n calculation_error_count,\n latest_entity_count: latestRowCount,\n };\n }\n\n /**\n * Fetch the latest entity metric values for a given metric, with optional filtering\n * by status, name, kind, namespace, or owner, plus sorting and pagination.\n */\n async readEntityMetricsWithFilters(\n metric_id: string,\n options: ReadEntityMetricsWithFiltersOptions,\n ): Promise<DbMetricValue[]> {\n const clientName: string =\n (this.dbClient as any).client?.config?.client ?? '';\n const isPostgres = clientName === 'pg' || clientName.includes('postgres');\n\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metric_id)\n .groupBy('catalog_entity_ref');\n\n const query = this.dbClient(this.tableName)\n .select('*')\n .whereIn('id', latestIdsSubquery);\n\n const sortColumnMap: Record<string, string> = {\n entityName: 'catalog_entity_ref',\n owner: 'entity_owner',\n entityKind: 'entity_kind',\n timestamp: 'timestamp',\n metricValue: 'value',\n namespace: 'entity_namespace',\n status: 'status',\n };\n\n const column =\n (options.sortBy && sortColumnMap[options.sortBy]) ?? 'timestamp';\n const direction = options.sortOrder === 'asc' ? 'asc' : 'desc';\n\n this.applySort(query, options.sortBy, column, direction, isPostgres);\n\n if (options.status) {\n query.where('status', options.status);\n }\n\n if (options.entityName) {\n const escaped = options.entityName.replace(/[%_\\\\]/g, '\\\\$&');\n query.whereRaw(\"catalog_entity_ref LIKE ? ESCAPE '\\\\'\", [`%${escaped}%`]);\n }\n\n if (options.entityKind) {\n query.where('entity_kind', options.entityKind);\n }\n\n if (options.entityNamespace) {\n query.where('entity_namespace', options.entityNamespace);\n }\n\n if (options.entityOwner && options.entityOwner.length > 0) {\n query.whereIn('entity_owner', options.entityOwner);\n }\n\n if (options.pagination) {\n query.limit(options.pagination.limit).offset(options.pagination.offset);\n }\n\n return await query;\n }\n\n private applySort(\n query: any,\n sortBy: string | undefined,\n column: string,\n direction: string,\n isPostgres: boolean,\n ): void {\n if (sortBy === 'metricValue') {\n // value is JSON and nullable; cast for numeric sort with NULLs last\n if (isPostgres) {\n query.orderByRaw(\n `CAST(value::text AS DOUBLE PRECISION) ${direction} NULLS LAST, id ASC`,\n );\n } else {\n // SQLite: \"value IS NULL\" puts nulls last; double-cast handles JSON-stored values\n query.orderByRaw(\n `value IS NULL, CAST(CAST(value AS TEXT) AS REAL) ${direction}, id ASC`,\n );\n }\n } else if (sortBy === 'status') {\n // status is nullable; NULLs always sort last regardless of direction\n if (isPostgres) {\n query.orderByRaw(`status ${direction} NULLS LAST, id ASC`);\n } else {\n // SQLite: \"status IS NULL\" evaluates to 1 for NULLs, pushing them to the end\n query.orderByRaw(`status IS NULL, status ${direction}, id ASC`);\n }\n } else {\n query.orderBy(column, direction);\n // Ensure a stable sort when two metrics share the same primary sort value\n query.orderBy('id', 'asc');\n }\n }\n}\n"],"names":[],"mappings":";;AAyCO,MAAM,oBAAqB,CAAA;AAAA,EAGhC,YAA6B,QAA4B,EAAA;AAA5B,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA;AAA6B,EAA7B,QAAA;AAAA,EAFZ,SAAY,GAAA,eAAA;AAAA;AAAA;AAAA;AAAA,EAO7B,MAAM,mBAAmB,YAAoD,EAAA;AAC3E,IAAI,IAAA,YAAA,CAAa,WAAW,CAAG,EAAA;AAC7B,MAAA;AAAA;AAEF,IAAA,MAAM,KAAK,QAAS,CAAA,IAAA,CAAK,SAAS,CAAA,CAAE,OAAO,YAAY,CAAA;AAAA;AACzD;AAAA;AAAA;AAAA,EAKA,MAAM,4BACJ,CAAA,kBAAA,EACA,UAC0B,EAAA;AAC1B,IAAO,OAAA,MAAM,KAAK,QAAS,CAAA,IAAA,CAAK,SAAS,CACtC,CAAA,MAAA,CAAO,GAAG,CACV,CAAA,OAAA;AAAA,MACC,IAAA;AAAA,MACA,KAAK,QAAS,CAAA,IAAA,CAAK,SAAS,CAAA,CACzB,IAAI,IAAI,CAAA,CACR,OAAQ,CAAA,WAAA,EAAa,UAAU,CAC/B,CAAA,KAAA,CAAM,sBAAsB,kBAAkB,CAAA,CAC9C,QAAQ,WAAW;AAAA,KACxB;AAAA;AACJ;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAsB,SAAkC,EAAA;AAC5D,IAAO,OAAA,MAAM,IAAK,CAAA,QAAA,CAAS,IAAK,CAAA,SAAS,CACtC,CAAA,KAAA,CAAM,WAAa,EAAA,GAAA,EAAK,SAAS,CAAA,CACjC,GAAI,EAAA;AAAA;AACT;AAAA;AAAA;AAAA,EAKA,MAAM,gCACJ,CAAA,mBAAA,EACA,SACyC,EAAA;AACzC,IAAI,IAAA,mBAAA,CAAoB,WAAW,CAAG,EAAA;AACpC,MAAO,OAAA,MAAA;AAAA;AAGT,IAAA,MAAM,oBAAoB,IAAK,CAAA,QAAA,CAAS,KAAK,SAAS,CAAA,CACnD,IAAI,IAAI,CAAA,CACR,KAAM,CAAA,WAAA,EAAa,SAAS,CAC5B,CAAA,OAAA,CAAQ,sBAAsB,mBAAmB,CAAA,CACjD,QAAQ,oBAAoB,CAAA;AAI/B,IAAA,MAAM,wBACJ,GAAA,iDAAA;AAIF,IAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,QAAS,CAAA,IAAA,CAAK,SAAS,CAChD,CAAA,OAAA,CAAQ,IAAM,EAAA,iBAAiB,CAC/B,CAAA,MAAA;AAAA,MACC,IAAA,CAAK,QAAS,CAAA,GAAA,CAAI,8BAA8B,CAAA;AAAA,MAChD,KAAK,QAAS,CAAA,GAAA;AAAA,QACZ,+CAA+C,wBAAwB,CAAA,8CAAA;AAAA,OACzE;AAAA,MACA,IAAA,CAAK,QAAS,CAAA,GAAA,CAAI,iCAAiC;AAAA,MAEpD,KAAM,EAAA;AAET,IAAA,MAAM,cAAiB,GAAA,MAAA;AAAA,MACpB,UACG,gBAAoB,IAAA;AAAA,KAC1B;AACA,IAAA,IAAI,mBAAmB,CAAG,EAAA;AACxB,MAAO,OAAA,MAAA;AAAA;AAGT,IAAA,MAAM,uBAA0B,GAAA,MAAA;AAAA,MAC7B,UACG,uBAA2B,IAAA;AAAA,KACjC;AAKA,IAAM,MAAA,kBAAA,GAAqB,CAAC,SAA6B,KAAA;AACvD,MAAA,IAAI,qBAAqB,IAAM,EAAA;AAC7B,QAAO,OAAA,SAAA;AAAA;AAET,MAAA,IAAI,OAAO,SAAA,KAAc,QAAY,IAAA,OAAO,cAAc,QAAU,EAAA;AAClE,QAAO,OAAA,IAAI,KAAK,SAAS,CAAA;AAAA;AAE3B,MAAA,2BAAW,IAAK,EAAA;AAAA,KAClB;AAEA,IAAA,MAAM,qBAAwB,GAAA,kBAAA;AAAA,MAC3B,QAA0C,EAAA;AAAA,KAC7C;AAEA,IAAA,MAAM,UAAa,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,IAAK,CAAA,SAAS,CAClD,CAAA,MAAA,CAAO,QAAQ,CAAA,CACf,KAAM,CAAA,YAAY,CAClB,CAAA,GAAA,CAAI,4BAA4B,CAAA,CAChC,OAAQ,CAAA,IAAA,EAAM,iBAAiB,CAAA,CAC/B,YAAa,CAAA,QAAQ,CACrB,CAAA,QAAA,CAAS,CAAO,IAAA,EAAA,wBAAwB,CAAE,CAAA,CAAA,CAC1C,QAAQ,QAAQ,CAAA;AAEnB,IAAA,IAAI,CAAC,UAAA,IAAc,UAAW,CAAA,MAAA,KAAW,CAAG,EAAA;AAC1C,MAAO,OAAA;AAAA,QACL,SAAA;AAAA,QACA,KAAO,EAAA,CAAA;AAAA,QACP,aAAe,EAAA,qBAAA;AAAA,QACf,cAAc,EAAC;AAAA,QACf,uBAAA;AAAA,QACA,mBAAqB,EAAA;AAAA,OACvB;AAAA;AAGF,IAAI,IAAA,YAAA,mBAAmB,IAAA,IAAA,CAAK,CAAC,CAAA;AAC7B,IAAA,IAAI,KAAQ,GAAA,CAAA;AACZ,IAAA,MAAM,eAAuC,EAAC;AAC9C,IAAA,KAAA,MAAW,OAAO,UAAY,EAAA;AAC5B,MAAM,MAAA,YAAA,GAAe,kBAAmB,CAAA,GAAA,CAAI,aAAa,CAAA;AACzD,MAAA,IAAI,eAAe,YAAc,EAAA;AAC/B,QAAe,YAAA,GAAA,YAAA;AAAA;AAEjB,MAAA,MAAM,OAAO,GAAI,CAAA,MAAA;AACjB,MAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,GAAA,CAAI,KAAK,CAAA;AAC9B,MAAA,YAAA,CAAa,IAAI,CAAI,GAAA,KAAA;AACrB,MAAS,KAAA,IAAA,KAAA;AAAA;AAGX,IAAA,MAAM,YACJ,qBAAsB,CAAA,OAAA,MAAa,YAAa,CAAA,OAAA,KAC5C,qBACA,GAAA,YAAA;AAEN,IAAO,OAAA;AAAA,MACL,SAAA;AAAA,MACA,KAAA;AAAA,MACA,aAAe,EAAA,SAAA;AAAA,MACf,YAAA;AAAA,MACA,uBAAA;AAAA,MACA,mBAAqB,EAAA;AAAA,KACvB;AAAA;AACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,4BACJ,CAAA,SAAA,EACA,OAC0B,EAAA;AAC1B,IAAA,MAAM,UACH,GAAA,IAAA,CAAK,QAAiB,CAAA,MAAA,EAAQ,QAAQ,MAAU,IAAA,EAAA;AACnD,IAAA,MAAM,UAAa,GAAA,UAAA,KAAe,IAAQ,IAAA,UAAA,CAAW,SAAS,UAAU,CAAA;AAExE,IAAA,MAAM,iBAAoB,GAAA,IAAA,CAAK,QAAS,CAAA,IAAA,CAAK,SAAS,CACnD,CAAA,GAAA,CAAI,IAAI,CAAA,CACR,KAAM,CAAA,WAAA,EAAa,SAAS,CAAA,CAC5B,QAAQ,oBAAoB,CAAA;AAE/B,IAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,QAAA,CAAS,IAAK,CAAA,SAAS,CACvC,CAAA,MAAA,CAAO,GAAG,CAAA,CACV,OAAQ,CAAA,IAAA,EAAM,iBAAiB,CAAA;AAElC,IAAA,MAAM,aAAwC,GAAA;AAAA,MAC5C,UAAY,EAAA,oBAAA;AAAA,MACZ,KAAO,EAAA,cAAA;AAAA,MACP,UAAY,EAAA,aAAA;AAAA,MACZ,SAAW,EAAA,WAAA;AAAA,MACX,WAAa,EAAA,OAAA;AAAA,MACb,SAAW,EAAA,kBAAA;AAAA,MACX,MAAQ,EAAA;AAAA,KACV;AAEA,IAAA,MAAM,UACH,OAAQ,CAAA,MAAA,IAAU,aAAc,CAAA,OAAA,CAAQ,MAAM,CAAM,KAAA,WAAA;AACvD,IAAA,MAAM,SAAY,GAAA,OAAA,CAAQ,SAAc,KAAA,KAAA,GAAQ,KAAQ,GAAA,MAAA;AAExD,IAAA,IAAA,CAAK,UAAU,KAAO,EAAA,OAAA,CAAQ,MAAQ,EAAA,MAAA,EAAQ,WAAW,UAAU,CAAA;AAEnE,IAAA,IAAI,QAAQ,MAAQ,EAAA;AAClB,MAAM,KAAA,CAAA,KAAA,CAAM,QAAU,EAAA,OAAA,CAAQ,MAAM,CAAA;AAAA;AAGtC,IAAA,IAAI,QAAQ,UAAY,EAAA;AACtB,MAAA,MAAM,OAAU,GAAA,OAAA,CAAQ,UAAW,CAAA,OAAA,CAAQ,WAAW,MAAM,CAAA;AAC5D,MAAA,KAAA,CAAM,SAAS,uCAAyC,EAAA,CAAC,CAAI,CAAA,EAAA,OAAO,GAAG,CAAC,CAAA;AAAA;AAG1E,IAAA,IAAI,QAAQ,UAAY,EAAA;AACtB,MAAM,KAAA,CAAA,KAAA,CAAM,aAAe,EAAA,OAAA,CAAQ,UAAU,CAAA;AAAA;AAG/C,IAAA,IAAI,QAAQ,eAAiB,EAAA;AAC3B,MAAM,KAAA,CAAA,KAAA,CAAM,kBAAoB,EAAA,OAAA,CAAQ,eAAe,CAAA;AAAA;AAGzD,IAAA,IAAI,OAAQ,CAAA,WAAA,IAAe,OAAQ,CAAA,WAAA,CAAY,SAAS,CAAG,EAAA;AACzD,MAAM,KAAA,CAAA,OAAA,CAAQ,cAAgB,EAAA,OAAA,CAAQ,WAAW,CAAA;AAAA;AAGnD,IAAA,IAAI,QAAQ,UAAY,EAAA;AACtB,MAAM,KAAA,CAAA,KAAA,CAAM,QAAQ,UAAW,CAAA,KAAK,EAAE,MAAO,CAAA,OAAA,CAAQ,WAAW,MAAM,CAAA;AAAA;AAGxE,IAAA,OAAO,MAAM,KAAA;AAAA;AACf,EAEQ,SACN,CAAA,KAAA,EACA,MACA,EAAA,MAAA,EACA,WACA,UACM,EAAA;AACN,IAAA,IAAI,WAAW,aAAe,EAAA;AAE5B,MAAA,IAAI,UAAY,EAAA;AACd,QAAM,KAAA,CAAA,UAAA;AAAA,UACJ,yCAAyC,SAAS,CAAA,mBAAA;AAAA,SACpD;AAAA,OACK,MAAA;AAEL,QAAM,KAAA,CAAA,UAAA;AAAA,UACJ,oDAAoD,SAAS,CAAA,QAAA;AAAA,SAC/D;AAAA;AACF,KACF,MAAA,IAAW,WAAW,QAAU,EAAA;AAE9B,MAAA,IAAI,UAAY,EAAA;AACd,QAAM,KAAA,CAAA,UAAA,CAAW,CAAU,OAAA,EAAA,SAAS,CAAqB,mBAAA,CAAA,CAAA;AAAA,OACpD,MAAA;AAEL,QAAM,KAAA,CAAA,UAAA,CAAW,CAA0B,uBAAA,EAAA,SAAS,CAAU,QAAA,CAAA,CAAA;AAAA;AAChE,KACK,MAAA;AACL,MAAM,KAAA,CAAA,OAAA,CAAQ,QAAQ,SAAS,CAAA;AAE/B,MAAM,KAAA,CAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA;AAC3B;AAEJ;;;;"}
|
|
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\ntype ReadEntityMetricsWithFiltersOptions = {\n status?: string;\n entityName?: string;\n entityKind?: string;\n entityNamespace?: string;\n entityOwner?: string[];\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n pagination?: { limit: number; offset: number };\n};\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 if (metricValues.length === 0) {\n return;\n }\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 readAggregatedMetricByEntityRefs(\n catalog_entity_refs: string[],\n metric_id: string,\n ): Promise<DbAggregatedMetric | undefined> {\n if (catalog_entity_refs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metric_id)\n .whereIn('catalog_entity_ref', catalog_entity_refs)\n .groupBy('catalog_entity_ref');\n\n // `value` is a JSON column. Depending on database/driver, a \"missing\" metric value can\n // arrive either as SQL NULL or as JSON literal null (`CAST(value AS TEXT) = 'null'`).\n const metricValueIsMissingExpr =\n \"(value IS NULL OR CAST(value AS TEXT) = 'null')\";\n\n // One round-trip for latest-row count, calculation-error count, and max timestamp\n // (same latest-id set as the status breakdown query below).\n const statsRow = await this.dbClient(this.tableName)\n .whereIn('id', latestIdsSubquery)\n .select(\n this.dbClient.raw('COUNT(*) as latest_row_count'),\n this.dbClient.raw(\n `SUM(CASE WHEN error_message IS NOT NULL AND ${metricValueIsMissingExpr} THEN 1 ELSE 0 END) as calculation_error_count`,\n ),\n this.dbClient.raw('MAX(timestamp) as max_timestamp'),\n )\n .first();\n\n const latestRowCount = Number(\n (statsRow as { latest_row_count?: string | number } | undefined)\n ?.latest_row_count ?? 0,\n );\n if (latestRowCount === 0) {\n return undefined;\n }\n\n const calculation_error_count = Number(\n (statsRow as { calculation_error_count?: string | number } | undefined)\n ?.calculation_error_count ?? 0,\n );\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 const normalizeTimestamp = (timestamp: unknown): Date => {\n if (timestamp instanceof Date) {\n return timestamp;\n }\n if (typeof timestamp === 'number' || typeof timestamp === 'string') {\n return new Date(timestamp);\n }\n return new Date();\n };\n\n const maxTimestampAllLatest = normalizeTimestamp(\n (statsRow as { max_timestamp?: unknown })?.max_timestamp,\n );\n\n const statusRows = await this.dbClient(this.tableName)\n .select('status')\n .count('* as count')\n .max('timestamp as max_timestamp')\n .whereIn('id', latestIdsSubquery)\n .whereNotNull('status')\n .whereRaw(`NOT ${metricValueIsMissingExpr}`)\n .groupBy('status');\n\n if (!statusRows || statusRows.length === 0) {\n return {\n metric_id,\n total: 0,\n max_timestamp: maxTimestampAllLatest,\n statusCounts: {},\n calculation_error_count,\n latest_entity_count: latestRowCount,\n };\n }\n\n let maxTimestamp = new Date(0);\n let total = 0;\n const statusCounts: Record<string, number> = {};\n for (const row of statusRows) {\n const rowTimestamp = normalizeTimestamp(row.max_timestamp);\n if (rowTimestamp > maxTimestamp) {\n maxTimestamp = rowTimestamp;\n }\n const name = row.status as string;\n const count = Number(row.count);\n statusCounts[name] = count;\n total += count;\n }\n\n const mergedMax =\n maxTimestampAllLatest.getTime() >= maxTimestamp.getTime()\n ? maxTimestampAllLatest\n : maxTimestamp;\n\n return {\n metric_id,\n total,\n max_timestamp: mergedMax,\n statusCounts,\n calculation_error_count,\n latest_entity_count: latestRowCount,\n };\n }\n\n /**\n * Fetch the latest entity metric values for a given metric, with optional filtering\n * by status, name, kind, namespace, or owner, plus sorting and pagination.\n */\n async readEntityMetricsWithFilters(\n metric_id: string,\n options: ReadEntityMetricsWithFiltersOptions,\n ): Promise<DbMetricValue[]> {\n const clientName: string =\n (this.dbClient as any).client?.config?.client ?? '';\n const isPostgres = clientName === 'pg' || clientName.includes('postgres');\n\n const latestIdsSubquery = this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metric_id)\n .groupBy('catalog_entity_ref');\n\n const query = this.dbClient(this.tableName)\n .select('*')\n .whereIn('id', latestIdsSubquery);\n\n const sortColumnMap: Record<string, string> = {\n entityName: 'catalog_entity_ref',\n owner: 'entity_owner',\n entityKind: 'entity_kind',\n timestamp: 'timestamp',\n metricValue: 'value',\n namespace: 'entity_namespace',\n status: 'status',\n };\n\n const column =\n (options.sortBy && sortColumnMap[options.sortBy]) ?? 'timestamp';\n const direction = options.sortOrder === 'asc' ? 'asc' : 'desc';\n\n this.applySort(query, options.sortBy, column, direction, isPostgres);\n\n if (options.status) {\n query.where('status', options.status);\n }\n\n if (options.entityName) {\n const escaped = options.entityName.replace(/[%_\\\\]/g, '\\\\$&');\n query.whereRaw(\"catalog_entity_ref LIKE ? ESCAPE '\\\\'\", [`%${escaped}%`]);\n }\n\n if (options.entityKind) {\n query.where('entity_kind', options.entityKind);\n }\n\n if (options.entityNamespace) {\n query.where('entity_namespace', options.entityNamespace);\n }\n\n if (options.entityOwner && options.entityOwner.length > 0) {\n query.whereIn('entity_owner', options.entityOwner);\n }\n\n if (options.pagination) {\n query.limit(options.pagination.limit).offset(options.pagination.offset);\n }\n\n return await query;\n }\n\n private applySort(\n query: any,\n sortBy: string | undefined,\n column: string,\n direction: string,\n isPostgres: boolean,\n ): void {\n if (sortBy === 'metricValue') {\n // value is JSON and nullable; cast for numeric sort with NULLs last\n if (isPostgres) {\n query.orderByRaw(\n `CAST(value::text AS DOUBLE PRECISION) ${direction} NULLS LAST, id ASC`,\n );\n } else {\n // SQLite: \"value IS NULL\" puts nulls last; double-cast handles JSON-stored values\n query.orderByRaw(\n `value IS NULL, CAST(CAST(value AS TEXT) AS REAL) ${direction}, id ASC`,\n );\n }\n } else if (sortBy === 'status') {\n // status is nullable; NULLs always sort last regardless of direction\n if (isPostgres) {\n query.orderByRaw(`status ${direction} NULLS LAST, id ASC`);\n } else {\n // SQLite: \"status IS NULL\" evaluates to 1 for NULLs, pushing them to the end\n query.orderByRaw(`status IS NULL, status ${direction}, id ASC`);\n }\n } else {\n query.orderBy(column, direction);\n // Ensure a stable sort when two metrics share the same primary sort value\n query.orderBy('id', 'asc');\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.cjs.js","sources":["../../src/database/migration.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 DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@red-hat-developer-hub/backstage-plugin-scorecard-backend',\n 'migrations',\n);\n\nexport async function migrate(databaseManager: DatabaseService) {\n const knex = await databaseManager.getClient();\n\n if (!databaseManager.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAoBA,MAAM,
|
|
1
|
+
{"version":3,"file":"migration.cjs.js","sources":["../../src/database/migration.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 DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@red-hat-developer-hub/backstage-plugin-scorecard-backend',\n 'migrations',\n);\n\nexport async function migrate(databaseManager: DatabaseService) {\n const knex = await databaseManager.getClient();\n\n if (!databaseManager.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAoBA,MAAM,aAAA,GAAgBA,mCAAA;AAAA,EACpB,2DAAA;AAAA,EACA;AACF,CAAA;AAEA,eAAsB,QAAQ,eAAA,EAAkC;AAC9D,EAAA,MAAM,IAAA,GAAO,MAAM,eAAA,CAAgB,SAAA,EAAU;AAE7C,EAAA,IAAI,CAAC,eAAA,CAAgB,UAAA,EAAY,IAAA,EAAM;AACrC,IAAA,MAAM,IAAA,CAAK,QAAQ,MAAA,CAAO;AAAA,MACxB,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateAggregationIdParam.cjs.js","sources":["../../src/middlewares/validateAggregationIdParam.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateAggregationIdParam(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n aggregationId: z.string().min(1).max(255),\n });\n\n const parsed = schema.safeParse(req.params);\n\n if (!parsed.success) {\n throw new InputError(\n `Invalid aggregationId parameter: ${parsed.error.message}`,\n );\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"validateAggregationIdParam.cjs.js","sources":["../../src/middlewares/validateAggregationIdParam.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateAggregationIdParam(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n aggregationId: z.string().min(1).max(255),\n });\n\n const parsed = schema.safeParse(req.params);\n\n if (!parsed.success) {\n throw new InputError(\n `Invalid aggregationId parameter: ${parsed.error.message}`,\n );\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAoBO,SAAS,0BAAA,CACd,GAAA,EACA,IAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,MAAA,GAASA,MAAE,MAAA,CAAO;AAAA,IACtB,aAAA,EAAeA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG;AAAA,GACzC,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,MAAM,CAAA;AAE1C,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,IAAIC,iBAAA;AAAA,MACR,CAAA,iCAAA,EAAoC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA;AAAA,KAC1D;AAAA,EACF;AAEA,EAAA,IAAA,EAAK;AACP;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateDatasourceQueryParams.cjs.js","sources":["../../src/middlewares/validateDatasourceQueryParams.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateDatasourceQueryParams(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n datasource: z.string().min(1).max(255).optional(),\n });\n\n const parsed = schema.safeParse(req.query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"validateDatasourceQueryParams.cjs.js","sources":["../../src/middlewares/validateDatasourceQueryParams.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateDatasourceQueryParams(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n datasource: z.string().min(1).max(255).optional(),\n });\n\n const parsed = schema.safeParse(req.query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAoBO,SAAS,6BAAA,CACd,GAAA,EACA,IAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,MAAA,GAASA,MAAE,MAAA,CAAO;AAAA,IACtB,UAAA,EAAYA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAAS,GACjD,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAEzC,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,IAAIC,iBAAA,CAAW,CAAA,0BAAA,EAA6B,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,EAC1E;AAEA,EAAA,IAAA,EAAK;AACP;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateMetricIdsQueryParams.cjs.js","sources":["../../src/middlewares/validateMetricIdsQueryParams.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateMetricIdsQueryParams(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n metricIds: z.string().min(1).max(255).optional(),\n });\n\n const parsed = schema.safeParse(req.query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"validateMetricIdsQueryParams.cjs.js","sources":["../../src/middlewares/validateMetricIdsQueryParams.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';\nimport type { Request, Response, NextFunction } from 'express';\n\nexport function validateMetricIdsQueryParams(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const schema = z.object({\n metricIds: z.string().min(1).max(255).optional(),\n });\n\n const parsed = schema.safeParse(req.query);\n\n if (!parsed.success) {\n throw new InputError(`Invalid query parameters: ${parsed.error.message}`);\n }\n\n next();\n}\n"],"names":["z","InputError"],"mappings":";;;;;AAoBO,SAAS,4BAAA,CACd,GAAA,EACA,IAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,MAAA,GAASA,MAAE,MAAA,CAAO;AAAA,IACtB,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAAS,GAChD,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAEzC,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,IAAIC,iBAAA,CAAW,CAAA,0BAAA,EAA6B,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,EAC1E;AAEA,EAAA,IAAA,EAAK;AACP;;;;"}
|
|
@@ -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 AuthorizeResult,\n BasicPermission,\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n BackstageCredentials,\n BackstageUserPrincipal,\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 getUserEntityRef = async (\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n) => {\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n return userEntityRef;\n};\n\ntype ScorecardPermission = {\n decision: PolicyDecision;\n conditions?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >;\n};\n\nexport const authorizeConditional = async (\n credentials: BackstageCredentials,\n permissions: PermissionsService,\n permission: ResourcePermission<'scorecard-metric'> | BasicPermission,\n) => {\n const scorecardPermission = {} as ScorecardPermission;\n\n if (permission.type === 'resource') {\n scorecardPermission.decision = (\n await permissions.authorizeConditional([{ permission }], {\n credentials,\n })\n )[0];\n } else {\n scorecardPermission.decision = (\n await permissions.authorize([{ permission }], {\n credentials,\n })\n )[0];\n }\n\n if (scorecardPermission.decision.result === AuthorizeResult.DENY) {\n throw new NotAllowedError();\n }\n\n scorecardPermission.conditions =\n scorecardPermission.decision.result === AuthorizeResult.CONDITIONAL\n ? scorecardPermission.decision.conditions\n : undefined;\n\n return scorecardPermission;\n};\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":["AuthenticationError","AuthorizeResult","NotAllowedError","catalogEntityReadPermission","scorecardRules"],"mappings":";;;;;;;
|
|
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 AuthorizeResult,\n BasicPermission,\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport { Request } from 'express';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';\nimport type {\n BackstageCredentials,\n BackstageUserPrincipal,\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 getUserEntityRef = async (\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n) => {\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n return userEntityRef;\n};\n\ntype ScorecardPermission = {\n decision: PolicyDecision;\n conditions?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >;\n};\n\nexport const authorizeConditional = async (\n credentials: BackstageCredentials,\n permissions: PermissionsService,\n permission: ResourcePermission<'scorecard-metric'> | BasicPermission,\n) => {\n const scorecardPermission = {} as ScorecardPermission;\n\n if (permission.type === 'resource') {\n scorecardPermission.decision = (\n await permissions.authorizeConditional([{ permission }], {\n credentials,\n })\n )[0];\n } else {\n scorecardPermission.decision = (\n await permissions.authorize([{ permission }], {\n credentials,\n })\n )[0];\n }\n\n if (scorecardPermission.decision.result === AuthorizeResult.DENY) {\n throw new NotAllowedError();\n }\n\n scorecardPermission.conditions =\n scorecardPermission.decision.result === AuthorizeResult.CONDITIONAL\n ? scorecardPermission.decision.conditions\n : undefined;\n\n return scorecardPermission;\n};\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":["AuthenticationError","AuthorizeResult","NotAllowedError","catalogEntityReadPermission","scorecardRules"],"mappings":";;;;;;;AAuCO,MAAM,gBAAA,GAAmB,OAC9B,WAAA,KACG;AACH,EAAA,MAAM,aAAA,GAAgB,aAAa,SAAA,EAAW,aAAA;AAE9C,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,MAAM,IAAIA,2BAAoB,iCAAiC,CAAA;AAAA,EACjE;AAEA,EAAA,OAAO,aAAA;AACT;AASO,MAAM,oBAAA,GAAuB,OAClC,WAAA,EACA,WAAA,EACA,UAAA,KACG;AACH,EAAA,MAAM,sBAAsB,EAAC;AAE7B,EAAA,IAAI,UAAA,CAAW,SAAS,UAAA,EAAY;AAClC,IAAA,mBAAA,CAAoB,QAAA,GAAA,CAClB,MAAM,WAAA,CAAY,oBAAA,CAAqB,CAAC,EAAE,UAAA,EAAY,CAAA,EAAG;AAAA,MACvD;AAAA,KACD,GACD,CAAC,CAAA;AAAA,EACL,CAAA,MAAO;AACL,IAAA,mBAAA,CAAoB,QAAA,GAAA,CAClB,MAAM,WAAA,CAAY,SAAA,CAAU,CAAC,EAAE,UAAA,EAAY,CAAA,EAAG;AAAA,MAC5C;AAAA,KACD,GACD,CAAC,CAAA;AAAA,EACL;AAEA,EAAA,IAAI,mBAAA,CAAoB,QAAA,CAAS,MAAA,KAAWC,sCAAA,CAAgB,IAAA,EAAM;AAChE,IAAA,MAAM,IAAIC,sBAAA,EAAgB;AAAA,EAC5B;AAEA,EAAA,mBAAA,CAAoB,UAAA,GAClB,oBAAoB,QAAA,CAAS,MAAA,KAAWD,uCAAgB,WAAA,GACpD,mBAAA,CAAoB,SAAS,UAAA,GAC7B,MAAA;AAEN,EAAA,OAAO,mBAAA;AACT;AAEO,MAAM,iBAAA,GAAoB,OAC/B,SAAA,EACA,GAAA,EACA,aACA,QAAA,KACkB;AAClB,EAAA,MAAM,oBAAA,GAAuB,MAAM,WAAA,CAAY,SAAA;AAAA,IAC7C,CAAC,EAAE,UAAA,EAAYE,iCAAA,EAA6B,WAAA,EAAa,WAAW,CAAA;AAAA,IACpE,EAAE,WAAA,EAAa,MAAM,QAAA,CAAS,WAAA,CAAY,GAAG,CAAA;AAAE,GACjD;AAEA,EAAA,IAAI,oBAAA,CAAqB,CAAC,CAAA,CAAE,MAAA,KAAWF,uCAAgB,KAAA,EAAO;AAC5D,IAAA,MAAM,IAAIC,sBAAA,CAAgB,CAAA,WAAA,EAAc,SAAS,CAAA,uBAAA,CAAyB,CAAA;AAAA,EAC5E;AACF;AAEO,MAAM,OAAA,GAAU,CACrB,MAAA,EACA,OAAA,KAGY;AACZ,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,WAAW,OAAA,EAAS;AACtB,IAAA,OAAO,QAAQ,KAAA,CAAM,KAAA,CAAM,YAAU,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,EAC9D;AAEA,EAAA,IAAI,WAAW,OAAA,EAAS;AACtB,IAAA,OAAO,QAAQ,KAAA,CAAM,IAAA,CAAK,YAAU,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,OAAO,CAAC,OAAA,CAAQ,MAAA,EAAQ,OAAA,CAAQ,GAAG,CAAA;AAAA,EACrC;AACA,EAAA,OACE,OAAO,MAAA,CAAOE,WAAc,CAAA,CACzB,IAAA,CAAK,OAAK,CAAA,CAAE,IAAA,KAAS,OAAA,CAAQ,IAAI,GAChC,KAAA,CAAM,MAAA,EAAQ,QAAQ,MAAA,IAAU,EAAE,CAAA,IAAK,KAAA;AAE/C;AAEO,MAAM,uBAAA,GAA0B,CACrC,OAAA,EACA,MAAA,KAGG;AACH,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,OAAO,QAAQ,MAAA,CAAO,CAAA,MAAA,KAAU,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAC,CAAA;AACzD;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.cjs.js","sources":["../../src/permissions/rules.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 createPermissionResourceRef,\n createPermissionRule,\n type PermissionRule,\n} from '@backstage/plugin-permission-node';\nimport { z } from 'zod';\nimport {\n Metric,\n RESOURCE_TYPE_SCORECARD_METRIC,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport type ScorecardFilter = {\n key: string;\n values: Array<string> | undefined;\n};\n\nexport type ScorecardFilters =\n | { anyOf: ScorecardFilters[] }\n | { allOf: ScorecardFilters[] }\n | { not: ScorecardFilters }\n | ScorecardFilter;\n\nexport const scorecardMetricPermissionResourceRef = createPermissionResourceRef<\n Metric,\n ScorecardFilter\n>().with({\n pluginId: 'scorecard',\n resourceType: RESOURCE_TYPE_SCORECARD_METRIC,\n});\n\ntype HasMetricIdParams = { metricIds?: string[] | undefined };\n\nconst hasMetricId = createPermissionRule({\n name: 'HAS_METRIC_ID',\n description: 'Should allow users to access metrics with specified metric IDs',\n resourceRef: scorecardMetricPermissionResourceRef,\n\n paramsSchema: z.object({\n metricIds: z\n .string()\n .array()\n .optional()\n .describe('List of metric IDs to match on'),\n }),\n apply: (metric: Metric, { metricIds }: HasMetricIdParams) => {\n return metricIds && metricIds.length > 0\n ? metricIds.includes(metric.id)\n : true;\n },\n toQuery: ({ metricIds }: HasMetricIdParams) => ({\n key: 'metricId',\n values: metricIds,\n }),\n} as any) as unknown as PermissionRule<\n Metric,\n ScorecardFilter,\n typeof RESOURCE_TYPE_SCORECARD_METRIC,\n HasMetricIdParams\n>;\n\nexport const rules = { hasMetricId };\n"],"names":["createPermissionResourceRef","RESOURCE_TYPE_SCORECARD_METRIC","createPermissionRule","z"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"rules.cjs.js","sources":["../../src/permissions/rules.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 createPermissionResourceRef,\n createPermissionRule,\n type PermissionRule,\n} from '@backstage/plugin-permission-node';\nimport { z } from 'zod';\nimport {\n Metric,\n RESOURCE_TYPE_SCORECARD_METRIC,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport type ScorecardFilter = {\n key: string;\n values: Array<string> | undefined;\n};\n\nexport type ScorecardFilters =\n | { anyOf: ScorecardFilters[] }\n | { allOf: ScorecardFilters[] }\n | { not: ScorecardFilters }\n | ScorecardFilter;\n\nexport const scorecardMetricPermissionResourceRef = createPermissionResourceRef<\n Metric,\n ScorecardFilter\n>().with({\n pluginId: 'scorecard',\n resourceType: RESOURCE_TYPE_SCORECARD_METRIC,\n});\n\ntype HasMetricIdParams = { metricIds?: string[] | undefined };\n\nconst hasMetricId = createPermissionRule({\n name: 'HAS_METRIC_ID',\n description: 'Should allow users to access metrics with specified metric IDs',\n resourceRef: scorecardMetricPermissionResourceRef,\n\n paramsSchema: z.object({\n metricIds: z\n .string()\n .array()\n .optional()\n .describe('List of metric IDs to match on'),\n }),\n apply: (metric: Metric, { metricIds }: HasMetricIdParams) => {\n return metricIds && metricIds.length > 0\n ? metricIds.includes(metric.id)\n : true;\n },\n toQuery: ({ metricIds }: HasMetricIdParams) => ({\n key: 'metricId',\n values: metricIds,\n }),\n} as any) as unknown as PermissionRule<\n Metric,\n ScorecardFilter,\n typeof RESOURCE_TYPE_SCORECARD_METRIC,\n HasMetricIdParams\n>;\n\nexport const rules = { hasMetricId };\n"],"names":["createPermissionResourceRef","RESOURCE_TYPE_SCORECARD_METRIC","createPermissionRule","z"],"mappings":";;;;;;AAsCO,MAAM,oCAAA,GAAuCA,gDAAA,EAGlD,CAAE,IAAA,CAAK;AAAA,EACP,QAAA,EAAU,WAAA;AAAA,EACV,YAAA,EAAcC;AAChB,CAAC;AAID,MAAM,cAAcC,yCAAA,CAAqB;AAAA,EACvC,IAAA,EAAM,eAAA;AAAA,EACN,WAAA,EAAa,gEAAA;AAAA,EACb,WAAA,EAAa,oCAAA;AAAA,EAEb,YAAA,EAAcC,MAAE,MAAA,CAAO;AAAA,IACrB,SAAA,EAAWA,MACR,MAAA,EAAO,CACP,OAAM,CACN,QAAA,EAAS,CACT,QAAA,CAAS,gCAAgC;AAAA,GAC7C,CAAA;AAAA,EACD,KAAA,EAAO,CAAC,MAAA,EAAgB,EAAE,WAAU,KAAyB;AAC3D,IAAA,OAAO,SAAA,IAAa,UAAU,MAAA,GAAS,CAAA,GACnC,UAAU,QAAA,CAAS,MAAA,CAAO,EAAE,CAAA,GAC5B,IAAA;AAAA,EACN,CAAA;AAAA,EACA,OAAA,EAAS,CAAC,EAAE,SAAA,EAAU,MAA0B;AAAA,IAC9C,GAAA,EAAK,UAAA;AAAA,IACL,MAAA,EAAQ;AAAA,GACV;AACF,CAAQ,CAAA;AAOD,MAAM,KAAA,GAAQ,EAAE,WAAA;;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var alpha = require('@backstage/backend-plugin-api/alpha');
|
|
4
5
|
var router = require('./service/router.cjs.js');
|
|
5
6
|
var pluginCatalogNode = require('@backstage/plugin-catalog-node');
|
|
7
|
+
var index$1 = require('./actions/index.cjs.js');
|
|
6
8
|
var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
|
|
7
9
|
var MetricProvidersRegistry = require('./providers/MetricProvidersRegistry.cjs.js');
|
|
8
10
|
var CatalogMetricService = require('./service/CatalogMetricService.cjs.js');
|
|
@@ -29,6 +31,7 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
29
31
|
});
|
|
30
32
|
env.registerInit({
|
|
31
33
|
deps: {
|
|
34
|
+
actionsRegistry: alpha.actionsRegistryServiceRef,
|
|
32
35
|
auth: backendPluginApi.coreServices.auth,
|
|
33
36
|
catalog: pluginCatalogNode.catalogServiceRef,
|
|
34
37
|
config: backendPluginApi.coreServices.rootConfig,
|
|
@@ -41,6 +44,7 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
41
44
|
scheduler: backendPluginApi.coreServices.scheduler
|
|
42
45
|
},
|
|
43
46
|
async init({
|
|
47
|
+
actionsRegistry,
|
|
44
48
|
auth,
|
|
45
49
|
catalog,
|
|
46
50
|
config,
|
|
@@ -99,6 +103,12 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
|
|
|
99
103
|
aggregationsService,
|
|
100
104
|
catalogMetricService
|
|
101
105
|
};
|
|
106
|
+
index$1.createScorecardActions({
|
|
107
|
+
actionsRegistry,
|
|
108
|
+
permissions,
|
|
109
|
+
metricProvidersRegistry,
|
|
110
|
+
catalogMetricService
|
|
111
|
+
});
|
|
102
112
|
httpRouter.use(
|
|
103
113
|
await router.createRouter({
|
|
104
114
|
metricProvidersRegistry,
|
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';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationService';\nimport { ThresholdResolver } from './threshold/ThresholdResolver';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n const thresholdResolver = new ThresholdResolver(\n config,\n metricProvidersRegistry.listProviders(),\n );\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n thresholdResolver,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n thresholdResolver,\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createRouter"],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createScorecardActions } from './actions';\nimport {\n MetricProvider,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationService';\nimport { ThresholdResolver } from './threshold/ThresholdResolver';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n actionsRegistry: actionsRegistryServiceRef,\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n actionsRegistry,\n auth,\n catalog,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n const thresholdResolver = new ThresholdResolver(\n config,\n metricProvidersRegistry.listProviders(),\n );\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n thresholdResolver,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n thresholdResolver,\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n createScorecardActions({\n actionsRegistry,\n auth,\n permissions,\n catalog,\n metricProvidersRegistry,\n catalogMetricService,\n });\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardMetricsExtensionPoint","actionsRegistryServiceRef","coreServices","catalogServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createScorecardActions","createRouter"],"mappings":"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"}
|