@red-hat-developer-hub/backstage-plugin-scorecard-backend 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +50 -10
  3. package/config.d.ts +5 -0
  4. package/dist/database/DatabaseMetricValues.cjs.js +16 -8
  5. package/dist/database/DatabaseMetricValues.cjs.js.map +1 -1
  6. package/dist/database/utils/mapMetricValueRow.cjs.js.map +1 -1
  7. package/dist/plugin.cjs.js +2 -1
  8. package/dist/plugin.cjs.js.map +1 -1
  9. package/dist/service/aggregations/AggregatedMetricLoader.cjs.js +3 -2
  10. package/dist/service/aggregations/AggregatedMetricLoader.cjs.js.map +1 -1
  11. package/dist/service/aggregations/AggregationsService.cjs.js +3 -1
  12. package/dist/service/aggregations/AggregationsService.cjs.js.map +1 -1
  13. package/dist/service/aggregations/strategies/ScalarAggregationStrategy.cjs.js +2 -1
  14. package/dist/service/aggregations/strategies/ScalarAggregationStrategy.cjs.js.map +1 -1
  15. package/dist/service/mappers.cjs.js +12 -4
  16. package/dist/service/mappers.cjs.js.map +1 -1
  17. package/dist/utils/aggregation/buildAggregationConfig.cjs.js +5 -0
  18. package/dist/utils/aggregation/buildAggregationConfig.cjs.js.map +1 -1
  19. package/dist/utils/aggregation/buildAggregationConfigFilter.cjs.js +16 -0
  20. package/dist/utils/aggregation/buildAggregationConfigFilter.cjs.js.map +1 -0
  21. package/dist/utils/normalizeTimestamp.cjs.js +4 -1
  22. package/dist/utils/normalizeTimestamp.cjs.js.map +1 -1
  23. package/dist/validation/schemas/aggregationConfigSchemas.cjs.js +5 -11
  24. package/dist/validation/schemas/aggregationConfigSchemas.cjs.js.map +1 -1
  25. package/dist/validation/schemas/aggregationFilterSchema.cjs.js +10 -0
  26. package/dist/validation/schemas/aggregationFilterSchema.cjs.js.map +1 -0
  27. package/dist/validation/schemas/aggregationThresholdsConfigSchema.cjs.js +16 -0
  28. package/dist/validation/schemas/aggregationThresholdsConfigSchema.cjs.js.map +1 -0
  29. package/dist/validation/validateAggregationConfig.cjs.js +13 -9
  30. package/dist/validation/validateAggregationConfig.cjs.js.map +1 -1
  31. package/dist/validation/validateScalarAggregationConfig.cjs.js +24 -0
  32. package/dist/validation/validateScalarAggregationConfig.cjs.js.map +1 -0
  33. package/dist/validation/validateScalarFilterStatus.cjs.js +22 -0
  34. package/dist/validation/validateScalarFilterStatus.cjs.js.map +1 -0
  35. package/package.json +3 -3
  36. package/dist/utils/toIsoTimestamp.cjs.js +0 -8
  37. package/dist/utils/toIsoTimestamp.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-scorecard-backend
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e486f80: Implemented filter by `status` for scalar aggregation types (`sum`, `average`, `count`, `min`, `max`).
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [e486f80]
12
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@4.2.0
13
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@4.2.0
14
+
3
15
  ## 4.1.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -177,19 +177,59 @@ scorecard:
177
177
  - key: error
178
178
  expression: '<10'
179
179
  color: error.main
180
+ totalCriticalPRs:
181
+ title: Total Critical PRs
182
+ description: Sum of open PRs for entities in error status
183
+ type: sum
184
+ metricId: github.openPRs
185
+ filter:
186
+ status: error
187
+ options:
188
+ thresholds:
189
+ rules:
190
+ - key: success
191
+ expression: '<30'
192
+ color: '#6bb300'
193
+ - key: warning
194
+ expression: '30-80'
195
+ color: 'rgb(224, 189, 108)'
196
+ - key: error
197
+ expression: '>80'
198
+ color: '#be1ec7'
199
+ avgOpenIssues:
200
+ title: Average Open Issues
201
+ description: Mean open issues count per entity
202
+ type: average
203
+ metricId: jira.openIssues
204
+ entitiesWithOpenPrs:
205
+ title: Entities with Open PRs
206
+ description: Count of entities with a stored open-prs value
207
+ type: count
208
+ metricId: github.openPRs
209
+ maxOpenPrs:
210
+ title: Maximum Open PRs
211
+ description: Maximum open PR count per entity
212
+ type: max
213
+ metricId: github.openPRs
214
+ minOpenIssues:
215
+ title: Minimum Open Issues
216
+ description: Minimum open issues count per entity
217
+ type: min
218
+ metricId: jira.openIssues
180
219
  ```
181
220
 
182
- | Field | Description |
183
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
184
- | `title` | Display title for this aggregation (returned in API metadata). |
185
- | `description` | Display description for this aggregation. |
186
- | `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status), `weightedStatusScore` (normalized weighted score), or a **scalar** type — `sum`, `average`, `max`, `min`, `count` (rollup of latest numeric metric values; see [aggregation.md](./docs/aggregation.md#aggregation-types)). Scalar types require a **number** metric. |
187
- | `metricId` | Metric provider id used to load thresholds and compute counts or scalar values. |
188
- | `options` | **Optional:** extra configuration attributes required to further configure the aggregated card for a specific type |
221
+ | Field | Description |
222
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
223
+ | `title` | Display title for this aggregation (returned in API metadata). |
224
+ | `description` | Display description for this aggregation. |
225
+ | `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status), `weightedStatusScore` (normalized weighted score), or a **scalar** type — `sum`, `average`, `max`, `min`, `count` (rollup of latest numeric metric values; see [aggregation.md](./docs/aggregation.md#aggregation-types)). Scalar types require a **number** metric. |
226
+ | `metricId` | Metric provider id used to load thresholds and compute counts or values. |
227
+ | `filter` | Optional for **scalar types** only (silently ignored on **`statusGrouped`** / **`weightedStatusScore`**). **`filter.status`**: threshold status key (case-sensitive). Only latest rows with that status contribute to **`value`** and **`total`**. Must match a threshold rule **`key`** for **`metricId`** from provider defaults or app-config overrides at **`scorecard.metricProviders.<datasource>.<providerName>.metrics.<metricName>.thresholds`** (or provider-level **`scorecard.metricProviders.<datasource>.<providerName>.thresholds`**) (validated at startup for scalar types). **`entitiesConsidered`** and **`calculationErrorCount`** stay full-portfolio. Returned in API **`metadata.filter`** when set. |
228
+ | `options` | **Optional:** extra configuration attributes required to further configure the aggregated card for a specific type |
189
229
 
190
230
  - **Path**: `scorecard.aggregationKPIs.<aggregationId>`.
191
231
  - If **`aggregationKPIs` is omitted** or a given id is not listed, **`GET /aggregations/:aggregationId`** still works when **`aggregationId` equals the metric id** (e.g. `github.openPRs`): the backend uses that metric with the default `statusGrouped` aggregation and metric-defined title/description.
192
- - **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`weightedStatusScore`** KPIs without **`options.statusScores`**, bad expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).
232
+ - **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`weightedStatusScore`** KPIs without **`options.statusScores`**, non-count scalar types on boolean metrics, invalid **`filter.status`** keys on scalar types, bad threshold expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).
193
233
 
194
234
  **Homepage cards** are configured in the app (for example Dynamic Home Page mount points). They should pass **`aggregationId`** matching a key in `aggregationKPIs` or the metric id for the default case. See the [Scorecard frontend plugin README](../scorecard/README.md#homepage-scorecard-cards).
195
235
 
@@ -262,7 +302,7 @@ curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?
262
302
 
263
303
  Returns aggregated metrics for the authenticated user across all catalog entities they own (same ownership rules as the legacy route; see [aggregation.md](./docs/aggregation.md)).
264
304
 
265
- Response **`result`** shape depends on **`metadata.aggregationType`**: status counts for **`statusGrouped`**, weighted score fields for **`weightedStatusScore`**, or scalar fields for **`sum`** / **`average`** / **`max`** / **`min`** / **`count`** — see [Scalar result fields](./docs/aggregation.md#scalar-result-fields).
305
+ Response **`result`** shape depends on **`metadata.aggregationType`**: status counts for **`statusGrouped`**, weighted score fields for **`weightedStatusScore`**, or scalar fields for **`sum`** / **`average`** / **`max`** / **`min`** / **`count`** — see [Scalar result fields](./docs/aggregation.md#scalar-result-fields). Scalar KPIs may also return **`metadata.filter`** when **`filter.status`** is configured.
266
306
 
267
307
  The **`aggregationId`** is either:
268
308
 
@@ -292,7 +332,7 @@ curl -X GET "{{url}}/api/scorecard/aggregations/github.openPRs" \
292
332
 
293
333
  ### `GET /aggregations/:aggregationId/metadata`
294
334
 
295
- Returns **title**, **description**, **type**, **history**, and **aggregationType** for the aggregation without computing full aggregate counts. Uses the same resolution rules as `GET /aggregations/:aggregationId` (KPI config vs metric id fallback).
335
+ Returns **title**, **description**, **type**, **history**, and **aggregationType** for the aggregation without computing full aggregate counts. Includes **`filter`** when the KPI is a scalar type with **`filter.status`** configured. Uses the same resolution rules as `GET /aggregations/:aggregationId` (KPI config vs metric id fallback).
296
336
 
297
337
  ```bash
298
338
  curl -X GET "{{url}}/api/scorecard/aggregations/openIssuesKpi/metadata" \
package/config.d.ts CHANGED
@@ -76,6 +76,11 @@ export interface Config {
76
76
  | typeof aggregationTypes.max
77
77
  | typeof aggregationTypes.min
78
78
  | typeof aggregationTypes.count;
79
+ /** Optional: filter applied when aggregating scalar KPI values */
80
+ filter?: {
81
+ /** Threshold status key to include (e.g. success, warning, error) */
82
+ status?: string;
83
+ };
79
84
  /** Options specific to the scalar aggregation type */
80
85
  options?: {
81
86
  /** Optional: threshold rules for coloring the KPI headline value from the aggregation result */
@@ -49,7 +49,7 @@ class DatabaseMetricValues {
49
49
  maxTimestampAllLatest
50
50
  };
51
51
  }
52
- async readScalarAggregationByLatestIdsSubquery(latestIdsSubquery, aggregationFn) {
52
+ async readScalarAggregationByLatestIdsSubquery(latestIdsSubquery, aggregationFn, filter) {
53
53
  const clientName = this.dbClient.client?.config?.client ?? "";
54
54
  const isPostgres = clientName === "pg" || clientName.includes("postgres");
55
55
  const numericValueExpr = isPostgres ? "CAST(value::text AS DOUBLE PRECISION)" : "CAST(CAST(value AS TEXT) AS REAL)";
@@ -58,6 +58,9 @@ class DatabaseMetricValues {
58
58
  numericValueExpr
59
59
  );
60
60
  const aggregateQuery = this.dbClient(this.tableName).whereIn("id", latestIdsSubquery).whereRaw(`NOT ${DatabaseMetricValues.metricValueIsMissingExpr}`);
61
+ if (filter?.status && filter.status !== "") {
62
+ aggregateQuery.where("status", filter.status);
63
+ }
61
64
  const aggregateRow = await aggregateQuery.select(
62
65
  this.dbClient.raw(`${aggregateExpression} as value`),
63
66
  this.dbClient.raw("COUNT(*) as total"),
@@ -69,11 +72,15 @@ class DatabaseMetricValues {
69
72
  maxTimestamp: /* @__PURE__ */ new Date(0)
70
73
  };
71
74
  if (aggregateRow) {
72
- aggregateResult.value = Number(aggregateRow.value);
73
- aggregateResult.total = Number(aggregateRow.total);
74
- aggregateResult.maxTimestamp = normalizeTimestamp.normalizeTimestamp(
75
- aggregateRow.max_timestamp
76
- );
75
+ const rawValue = Number(aggregateRow.value);
76
+ const rawTotal = Number(aggregateRow.total);
77
+ aggregateResult.value = Number.isFinite(rawValue) ? rawValue : 0;
78
+ aggregateResult.total = Number.isFinite(rawTotal) ? rawTotal : 0;
79
+ if (aggregateResult.total > 0 && aggregateRow.max_timestamp !== null && aggregateRow.max_timestamp !== "") {
80
+ aggregateResult.maxTimestamp = normalizeTimestamp.normalizeTimestamp(
81
+ aggregateRow.max_timestamp
82
+ );
83
+ }
77
84
  }
78
85
  return {
79
86
  value: aggregateResult.value,
@@ -160,7 +167,7 @@ class DatabaseMetricValues {
160
167
  /**
161
168
  * Aggregate raw metric values across latest rows for multiple entities.
162
169
  */
163
- async readScalarAggregatedMetricByEntityRefs(catalogEntityRefs, metricId, aggregationFn) {
170
+ async readScalarAggregatedMetricByEntityRefs(catalogEntityRefs, metricId, aggregationFn, filter) {
164
171
  if (catalogEntityRefs.length === 0) {
165
172
  return void 0;
166
173
  }
@@ -178,7 +185,8 @@ class DatabaseMetricValues {
178
185
  maxTimestamp: aggregateMaxTimestamp
179
186
  } = await this.readScalarAggregationByLatestIdsSubquery(
180
187
  latestIdsSubquery,
181
- aggregationFn
188
+ aggregationFn,
189
+ filter
182
190
  );
183
191
  const mergedMax = mergeMaxTimestamp.mergeMaxTimestamp(
184
192
  maxTimestampAllLatest,
@@ -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 DbScalarAggregatedMetric,\n ScalarAggregationFn,\n} from './types';\nimport { normalizeTimestamp } from '../utils/normalizeTimestamp';\nimport { mergeMaxTimestamp } from '../utils/mergeMaxTimestamp';\nimport { getAggregateExpression } from './utils/getAggregateExpression';\nimport {\n fromMetricValueRow,\n toMetricValueRow,\n type MetricValueRowWithId,\n} from './utils/mapMetricValueRow';\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\ntype StatsRowResult = {\n latestIdsSubquery: Knex.QueryBuilder;\n latestRowCount: number;\n calculationErrorCount: number;\n maxTimestampAllLatest: Date;\n};\n\ntype ScalarAggregationRowResult = {\n value: number;\n total: number;\n maxTimestamp: Date;\n};\n\nexport class DatabaseMetricValues {\n private readonly tableName = 'metric_values';\n\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 */\n private static readonly metricValueIsMissingExpr =\n \"(value IS NULL OR CAST(value AS TEXT) = 'null')\";\n\n constructor(private readonly dbClient: Knex<any, any[]>) {}\n\n /**\n * Get the latest ids subquery for a given metric and catalog entity refs\n */\n private getLatestIdsSubquery(\n metricId: string,\n catalogEntityRefs: string[],\n ): Knex.QueryBuilder {\n return this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metricId)\n .whereIn('catalog_entity_ref', catalogEntityRefs)\n .groupBy('catalog_entity_ref');\n }\n\n /**\n * Get the stats row for a given latest ids subquery\n */\n private async readStatsRowByLatestIdsSubquery(\n latestIdsSubquery: Knex.QueryBuilder,\n ): Promise<StatsRowResult> {\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 ${DatabaseMetricValues.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\n const calculationErrorCount = Number(\n (statsRow as { calculation_error_count?: string | number } | undefined)\n ?.calculation_error_count ?? 0,\n );\n\n const maxTimestampAllLatest = normalizeTimestamp(\n (statsRow as { max_timestamp?: unknown })?.max_timestamp,\n );\n\n return {\n latestIdsSubquery,\n latestRowCount,\n calculationErrorCount,\n maxTimestampAllLatest,\n };\n }\n\n private async readScalarAggregationByLatestIdsSubquery(\n latestIdsSubquery: Knex.QueryBuilder,\n aggregationFn: ScalarAggregationFn,\n ): Promise<ScalarAggregationRowResult> {\n const clientName: string =\n (this.dbClient as any).client?.config?.client ?? '';\n const isPostgres = clientName === 'pg' || clientName.includes('postgres');\n\n const numericValueExpr = isPostgres\n ? 'CAST(value::text AS DOUBLE PRECISION)'\n : 'CAST(CAST(value AS TEXT) AS REAL)';\n\n const aggregateExpression = getAggregateExpression(\n aggregationFn,\n numericValueExpr,\n );\n\n const aggregateQuery = this.dbClient(this.tableName)\n .whereIn('id', latestIdsSubquery)\n .whereRaw(`NOT ${DatabaseMetricValues.metricValueIsMissingExpr}`);\n\n const aggregateRow = await aggregateQuery\n .select(\n this.dbClient.raw(`${aggregateExpression} as value`),\n this.dbClient.raw('COUNT(*) as total'),\n this.dbClient.raw('MAX(timestamp) as max_timestamp'),\n )\n .first();\n\n const aggregateResult = {\n value: 0,\n total: 0,\n maxTimestamp: new Date(0),\n };\n\n if (aggregateRow) {\n aggregateResult.value = Number(aggregateRow.value);\n aggregateResult.total = Number(aggregateRow.total);\n aggregateResult.maxTimestamp = normalizeTimestamp(\n aggregateRow.max_timestamp,\n );\n }\n\n return {\n value: aggregateResult.value,\n total: aggregateResult.total,\n maxTimestamp: aggregateResult.maxTimestamp,\n };\n }\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(\n metricValues.map(toMetricValueRow),\n );\n }\n\n /**\n * Get the latest metric values for a specific entity and metrics\n */\n async readLatestEntityMetricValues(\n catalogEntityRef: string,\n metricIds: string[],\n ): Promise<DbMetricValue[]> {\n const rows = await this.dbClient(this.tableName)\n .select('*')\n .whereIn(\n 'id',\n this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metricIds)\n .where('catalog_entity_ref', catalogEntityRef)\n .groupBy('metric_id'),\n );\n\n return (rows as MetricValueRowWithId[]).map(fromMetricValueRow);\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 catalogEntityRefs: string[],\n metricId: string,\n ): Promise<DbAggregatedMetric | undefined> {\n if (catalogEntityRefs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.getLatestIdsSubquery(\n metricId,\n catalogEntityRefs,\n );\n const { latestRowCount, calculationErrorCount, maxTimestampAllLatest } =\n await this.readStatsRowByLatestIdsSubquery(latestIdsSubquery);\n\n if (latestRowCount === 0) {\n return undefined;\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 ${DatabaseMetricValues.metricValueIsMissingExpr}`)\n .groupBy('status');\n\n if (!statusRows || statusRows.length === 0) {\n return {\n metricId,\n total: 0,\n maxTimestamp: maxTimestampAllLatest,\n statusCounts: {},\n calculationErrorCount,\n latestEntityCount: 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 = mergeMaxTimestamp(maxTimestampAllLatest, maxTimestamp);\n\n return {\n metricId,\n total,\n maxTimestamp: mergedMax,\n statusCounts,\n calculationErrorCount,\n latestEntityCount: latestRowCount,\n };\n }\n\n /**\n * Aggregate raw metric values across latest rows for multiple entities.\n */\n async readScalarAggregatedMetricByEntityRefs(\n catalogEntityRefs: string[],\n metricId: string,\n aggregationFn: ScalarAggregationFn,\n ): Promise<DbScalarAggregatedMetric | undefined> {\n if (catalogEntityRefs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.getLatestIdsSubquery(\n metricId,\n catalogEntityRefs,\n );\n const { latestRowCount, calculationErrorCount, maxTimestampAllLatest } =\n await this.readStatsRowByLatestIdsSubquery(latestIdsSubquery);\n\n if (latestRowCount === 0) {\n return undefined;\n }\n\n const {\n value,\n total,\n maxTimestamp: aggregateMaxTimestamp,\n } = await this.readScalarAggregationByLatestIdsSubquery(\n latestIdsSubquery,\n aggregationFn,\n );\n\n const mergedMax = mergeMaxTimestamp(\n maxTimestampAllLatest,\n aggregateMaxTimestamp,\n );\n\n return {\n metricId,\n total,\n maxTimestamp: mergedMax,\n value,\n calculationErrorCount,\n latestEntityCount: 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 metricId: 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', metricId)\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 const rows = await query;\n return (rows as MetricValueRowWithId[]).map(fromMetricValueRow);\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":["normalizeTimestamp","getAggregateExpression","toMetricValueRow","fromMetricValueRow","mergeMaxTimestamp"],"mappings":"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"}
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 type { AggregationConfigFilter } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n DbMetricValueCreate,\n DbMetricValue,\n DbAggregatedMetric,\n DbScalarAggregatedMetric,\n ScalarAggregationFn,\n} from './types';\nimport { normalizeTimestamp } from '../utils/normalizeTimestamp';\nimport { mergeMaxTimestamp } from '../utils/mergeMaxTimestamp';\nimport { getAggregateExpression } from './utils/getAggregateExpression';\nimport {\n fromMetricValueRow,\n toMetricValueRow,\n type MetricValueRowWithId,\n} from './utils/mapMetricValueRow';\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\ntype StatsRowResult = {\n latestIdsSubquery: Knex.QueryBuilder;\n latestRowCount: number;\n calculationErrorCount: number;\n maxTimestampAllLatest: Date;\n};\n\ntype ScalarAggregationRowResult = {\n value: number;\n total: number;\n maxTimestamp: Date;\n};\n\nexport class DatabaseMetricValues {\n private readonly tableName = 'metric_values';\n\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 */\n private static readonly metricValueIsMissingExpr =\n \"(value IS NULL OR CAST(value AS TEXT) = 'null')\";\n\n constructor(private readonly dbClient: Knex<any, any[]>) {}\n\n /**\n * Get the latest ids subquery for a given metric and catalog entity refs\n */\n private getLatestIdsSubquery(\n metricId: string,\n catalogEntityRefs: string[],\n ): Knex.QueryBuilder {\n return this.dbClient(this.tableName)\n .max('id')\n .where('metric_id', metricId)\n .whereIn('catalog_entity_ref', catalogEntityRefs)\n .groupBy('catalog_entity_ref');\n }\n\n /**\n * Get the stats row for a given latest ids subquery\n */\n private async readStatsRowByLatestIdsSubquery(\n latestIdsSubquery: Knex.QueryBuilder,\n ): Promise<StatsRowResult> {\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 ${DatabaseMetricValues.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\n const calculationErrorCount = Number(\n (statsRow as { calculation_error_count?: string | number } | undefined)\n ?.calculation_error_count ?? 0,\n );\n\n const maxTimestampAllLatest = normalizeTimestamp(\n (statsRow as { max_timestamp?: unknown })?.max_timestamp,\n );\n\n return {\n latestIdsSubquery,\n latestRowCount,\n calculationErrorCount,\n maxTimestampAllLatest,\n };\n }\n\n private async readScalarAggregationByLatestIdsSubquery(\n latestIdsSubquery: Knex.QueryBuilder,\n aggregationFn: ScalarAggregationFn,\n filter?: AggregationConfigFilter,\n ): Promise<ScalarAggregationRowResult> {\n const clientName: string =\n (this.dbClient as any).client?.config?.client ?? '';\n const isPostgres = clientName === 'pg' || clientName.includes('postgres');\n\n const numericValueExpr = isPostgres\n ? 'CAST(value::text AS DOUBLE PRECISION)'\n : 'CAST(CAST(value AS TEXT) AS REAL)';\n\n const aggregateExpression = getAggregateExpression(\n aggregationFn,\n numericValueExpr,\n );\n\n const aggregateQuery = this.dbClient(this.tableName)\n .whereIn('id', latestIdsSubquery)\n .whereRaw(`NOT ${DatabaseMetricValues.metricValueIsMissingExpr}`);\n\n if (filter?.status && filter.status !== '') {\n aggregateQuery.where('status', filter.status);\n }\n\n const aggregateRow = await aggregateQuery\n .select(\n this.dbClient.raw(`${aggregateExpression} as value`),\n this.dbClient.raw('COUNT(*) as total'),\n this.dbClient.raw('MAX(timestamp) as max_timestamp'),\n )\n .first();\n\n const aggregateResult = {\n value: 0,\n total: 0,\n maxTimestamp: new Date(0),\n };\n\n if (aggregateRow) {\n const rawValue = Number(aggregateRow.value);\n const rawTotal = Number(aggregateRow.total);\n aggregateResult.value = Number.isFinite(rawValue) ? rawValue : 0;\n aggregateResult.total = Number.isFinite(rawTotal) ? rawTotal : 0;\n // MAX(timestamp) is null when no rows contribute (e.g. filter matches nothing).\n // Keep epoch so mergeMaxTimestamp prefers the portfolio latest-row timestamp.\n if (\n aggregateResult.total > 0 &&\n aggregateRow.max_timestamp !== null &&\n aggregateRow.max_timestamp !== ''\n ) {\n aggregateResult.maxTimestamp = normalizeTimestamp(\n aggregateRow.max_timestamp,\n );\n }\n }\n\n return {\n value: aggregateResult.value,\n total: aggregateResult.total,\n maxTimestamp: aggregateResult.maxTimestamp,\n };\n }\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(\n metricValues.map(toMetricValueRow),\n );\n }\n\n /**\n * Get the latest metric values for a specific entity and metrics\n */\n async readLatestEntityMetricValues(\n catalogEntityRef: string,\n metricIds: string[],\n ): Promise<DbMetricValue[]> {\n const rows = await this.dbClient(this.tableName)\n .select('*')\n .whereIn(\n 'id',\n this.dbClient(this.tableName)\n .max('id')\n .whereIn('metric_id', metricIds)\n .where('catalog_entity_ref', catalogEntityRef)\n .groupBy('metric_id'),\n );\n\n return (rows as MetricValueRowWithId[]).map(fromMetricValueRow);\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 catalogEntityRefs: string[],\n metricId: string,\n ): Promise<DbAggregatedMetric | undefined> {\n if (catalogEntityRefs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.getLatestIdsSubquery(\n metricId,\n catalogEntityRefs,\n );\n const { latestRowCount, calculationErrorCount, maxTimestampAllLatest } =\n await this.readStatsRowByLatestIdsSubquery(latestIdsSubquery);\n\n if (latestRowCount === 0) {\n return undefined;\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 ${DatabaseMetricValues.metricValueIsMissingExpr}`)\n .groupBy('status');\n\n if (!statusRows || statusRows.length === 0) {\n return {\n metricId,\n total: 0,\n maxTimestamp: maxTimestampAllLatest,\n statusCounts: {},\n calculationErrorCount,\n latestEntityCount: 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 = mergeMaxTimestamp(maxTimestampAllLatest, maxTimestamp);\n\n return {\n metricId,\n total,\n maxTimestamp: mergedMax,\n statusCounts,\n calculationErrorCount,\n latestEntityCount: latestRowCount,\n };\n }\n\n /**\n * Aggregate raw metric values across latest rows for multiple entities.\n */\n async readScalarAggregatedMetricByEntityRefs(\n catalogEntityRefs: string[],\n metricId: string,\n aggregationFn: ScalarAggregationFn,\n filter?: AggregationConfigFilter,\n ): Promise<DbScalarAggregatedMetric | undefined> {\n if (catalogEntityRefs.length === 0) {\n return undefined;\n }\n\n const latestIdsSubquery = this.getLatestIdsSubquery(\n metricId,\n catalogEntityRefs,\n );\n const { latestRowCount, calculationErrorCount, maxTimestampAllLatest } =\n await this.readStatsRowByLatestIdsSubquery(latestIdsSubquery);\n\n if (latestRowCount === 0) {\n return undefined;\n }\n\n const {\n value,\n total,\n maxTimestamp: aggregateMaxTimestamp,\n } = await this.readScalarAggregationByLatestIdsSubquery(\n latestIdsSubquery,\n aggregationFn,\n filter,\n );\n\n const mergedMax = mergeMaxTimestamp(\n maxTimestampAllLatest,\n aggregateMaxTimestamp,\n );\n\n return {\n metricId,\n total,\n maxTimestamp: mergedMax,\n value,\n calculationErrorCount,\n latestEntityCount: 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 metricId: 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', metricId)\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 const rows = await query;\n return (rows as MetricValueRowWithId[]).map(fromMetricValueRow);\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":["normalizeTimestamp","getAggregateExpression","toMetricValueRow","fromMetricValueRow","mergeMaxTimestamp"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mapMetricValueRow.cjs.js","sources":["../../../src/database/utils/mapMetricValueRow.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { DbMetricValue, DbMetricValueCreate } from '../types';\n\nexport type MetricValueRow = {\n catalog_entity_ref: string;\n metric_id: string;\n value?: MetricValue | null;\n timestamp: Date;\n error_message?: string | null;\n status?: string | null;\n entity_kind?: string | null;\n entity_owner?: string | null;\n entity_namespace?: string | null;\n};\n\nexport type MetricValueRowWithId = MetricValueRow & {\n id: number;\n value: DbMetricValue['value'];\n error_message: string | null;\n status: string | null;\n entity_kind: string | null;\n entity_owner: string | null;\n entity_namespace: string | null;\n};\n\nexport type MetricValueCreateInput = Omit<\n DbMetricValueCreate,\n 'value' | 'errorMessage' | 'entityKind' | 'entityOwner' | 'entityNamespace'\n> & {\n value?: MetricValue | null;\n errorMessage?: string | null;\n entityKind?: string | null;\n entityOwner?: string | null;\n entityNamespace?: string | null;\n};\n\nexport function toMetricValueRow(\n value: MetricValueCreateInput,\n): MetricValueRow {\n return {\n catalog_entity_ref: value.catalogEntityRef,\n metric_id: value.metricId,\n value: value.value,\n timestamp: value.timestamp,\n error_message: value.errorMessage,\n status: value.status,\n entity_kind: value.entityKind,\n entity_owner: value.entityOwner,\n entity_namespace: value.entityNamespace,\n };\n}\n\nexport function fromMetricValueRow(row: MetricValueRowWithId): DbMetricValue {\n return {\n id: row.id,\n catalogEntityRef: row.catalog_entity_ref,\n metricId: row.metric_id,\n value: row.value,\n timestamp: row.timestamp,\n errorMessage: row.error_message,\n status: row.status,\n entityKind: row.entity_kind,\n entityOwner: row.entity_owner,\n entityNamespace: row.entity_namespace,\n };\n}\n"],"names":[],"mappings":";;AAoDO,SAAS,iBACd,KAAA,EACgB;AAChB,EAAA,OAAO;AAAA,IACL,oBAAoB,KAAA,CAAM,gBAAA;AAAA,IAC1B,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,WAAW,KAAA,CAAM,SAAA;AAAA,IACjB,eAAe,KAAA,CAAM,YAAA;AAAA,IACrB,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,aAAa,KAAA,CAAM,UAAA;AAAA,IACnB,cAAc,KAAA,CAAM,WAAA;AAAA,IACpB,kBAAkB,KAAA,CAAM;AAAA,GAC1B;AACF;AAEO,SAAS,mBAAmB,GAAA,EAA0C;AAC3E,EAAA,OAAO;AAAA,IACL,IAAI,GAAA,CAAI,EAAA;AAAA,IACR,kBAAkB,GAAA,CAAI,kBAAA;AAAA,IACtB,UAAU,GAAA,CAAI,SAAA;AAAA,IACd,OAAO,GAAA,CAAI,KAAA;AAAA,IACX,WAAW,GAAA,CAAI,SAAA;AAAA,IACf,cAAc,GAAA,CAAI,aAAA;AAAA,IAClB,QAAQ,GAAA,CAAI,MAAA;AAAA,IACZ,YAAY,GAAA,CAAI,WAAA;AAAA,IAChB,aAAa,GAAA,CAAI,YAAA;AAAA,IACjB,iBAAiB,GAAA,CAAI;AAAA,GACvB;AACF;;;;;"}
1
+ {"version":3,"file":"mapMetricValueRow.cjs.js","sources":["../../../src/database/utils/mapMetricValueRow.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { DbMetricValue, DbMetricValueCreate } from '../types';\n\nexport type MetricValueRow = {\n catalog_entity_ref: string;\n metric_id: string;\n value?: MetricValue | null;\n timestamp: Date;\n error_message?: string | null;\n status?: string | null;\n entity_kind?: string | null;\n entity_owner?: string | null;\n entity_namespace?: string | null;\n};\n\nexport type MetricValueRowWithId = MetricValueRow & {\n id: number;\n value: DbMetricValue['value'];\n error_message: string | null;\n status: string | null;\n entity_kind: string | null;\n entity_owner: string | null;\n entity_namespace: string | null;\n};\n\nexport type DbMetricValueCreateInput = Omit<\n DbMetricValueCreate,\n 'value' | 'errorMessage' | 'entityKind' | 'entityOwner' | 'entityNamespace'\n> & {\n value?: MetricValue | null;\n errorMessage?: string | null;\n entityKind?: string | null;\n entityOwner?: string | null;\n entityNamespace?: string | null;\n};\n\nexport function toMetricValueRow(\n value: DbMetricValueCreateInput,\n): MetricValueRow {\n return {\n catalog_entity_ref: value.catalogEntityRef,\n metric_id: value.metricId,\n value: value.value,\n timestamp: value.timestamp,\n error_message: value.errorMessage,\n status: value.status,\n entity_kind: value.entityKind,\n entity_owner: value.entityOwner,\n entity_namespace: value.entityNamespace,\n };\n}\n\nexport function fromMetricValueRow(row: MetricValueRowWithId): DbMetricValue {\n return {\n id: row.id,\n catalogEntityRef: row.catalog_entity_ref,\n metricId: row.metric_id,\n value: row.value,\n timestamp: row.timestamp,\n errorMessage: row.error_message,\n status: row.status,\n entityKind: row.entity_kind,\n entityOwner: row.entity_owner,\n entityNamespace: row.entity_namespace,\n };\n}\n"],"names":[],"mappings":";;AAoDO,SAAS,iBACd,KAAA,EACgB;AAChB,EAAA,OAAO;AAAA,IACL,oBAAoB,KAAA,CAAM,gBAAA;AAAA,IAC1B,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,WAAW,KAAA,CAAM,SAAA;AAAA,IACjB,eAAe,KAAA,CAAM,YAAA;AAAA,IACrB,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,aAAa,KAAA,CAAM,UAAA;AAAA,IACnB,cAAc,KAAA,CAAM,WAAA;AAAA,IACpB,kBAAkB,KAAA,CAAM;AAAA,GAC1B;AACF;AAEO,SAAS,mBAAmB,GAAA,EAA0C;AAC3E,EAAA,OAAO;AAAA,IACL,IAAI,GAAA,CAAI,EAAA;AAAA,IACR,kBAAkB,GAAA,CAAI,kBAAA;AAAA,IACtB,UAAU,GAAA,CAAI,SAAA;AAAA,IACd,OAAO,GAAA,CAAI,KAAA;AAAA,IACX,WAAW,GAAA,CAAI,SAAA;AAAA,IACf,cAAc,GAAA,CAAI,aAAA;AAAA,IAClB,QAAQ,GAAA,CAAI,MAAA;AAAA,IACZ,YAAY,GAAA,CAAI,WAAA;AAAA,IAChB,aAAa,GAAA,CAAI,YAAA;AAAA,IACjB,iBAAiB,GAAA,CAAI;AAAA,GACvB;AACF;;;;;"}
@@ -95,7 +95,8 @@ const scorecardPlugin = backendPluginApi.createBackendPlugin({
95
95
  });
96
96
  validateAggregationConfig.validateAggregationConfig({
97
97
  rootConfig: config,
98
- registry: metricProvidersRegistry
98
+ registry: metricProvidersRegistry,
99
+ thresholdResolver
99
100
  });
100
101
  index.Scheduler.create({
101
102
  auth,
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createScorecardActions } from './actions';\nimport {\n type Collector,\n type MetricProvider,\n scorecardCollectorsExtensionPoint,\n scorecardCollectorsServiceRef,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationsService';\nimport { ThresholdResolver } from './threshold/ThresholdResolver';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const collectors: Collector[] = [];\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardCollectorsExtensionPoint, {\n addCollector(...newCollectors: Collector[]) {\n collectors.push(...newCollectors);\n },\n });\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n actionsRegistry: actionsRegistryServiceRef,\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n collectorsService: scorecardCollectorsServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n actionsRegistry,\n auth,\n catalog,\n collectorsService,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n collectorsService.init({ collectors });\n\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n const thresholdResolver = new ThresholdResolver(\n config,\n metricProvidersRegistry.listProviders(),\n );\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n thresholdResolver,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n thresholdResolver,\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n createScorecardActions({\n actionsRegistry,\n auth,\n permissions,\n catalog,\n metricProvidersRegistry,\n catalogMetricService,\n });\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardCollectorsExtensionPoint","scorecardMetricsExtensionPoint","actionsRegistryServiceRef","coreServices","catalogServiceRef","scorecardCollectorsServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createScorecardActions","createRouter"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport { createRouter } from './service/router';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createScorecardActions } from './actions';\nimport {\n type Collector,\n type MetricProvider,\n scorecardCollectorsExtensionPoint,\n scorecardCollectorsServiceRef,\n scorecardMetricsExtensionPoint,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { MetricProvidersRegistry } from './providers/MetricProvidersRegistry';\nimport { CatalogMetricService } from './service/CatalogMetricService';\nimport { ThresholdEvaluator } from './threshold/ThresholdEvaluator';\nimport { scorecardPermissions } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport {\n scorecardMetricPermissionResourceRef,\n rules as scorecardRules,\n} from './permissions/rules';\nimport { migrate } from './database/migration';\nimport { DatabaseMetricValues } from './database/DatabaseMetricValues';\nimport { Scheduler } from './scheduler';\nimport { validateAggregationConfig } from './validation/validateAggregationConfig';\nimport { AggregationsService } from './service/aggregations/AggregationsService';\nimport { ThresholdResolver } from './threshold/ThresholdResolver';\n\n/**\n * scorecardPlugin backend plugin\n *\n * @public\n */\nexport const scorecardPlugin = createBackendPlugin({\n pluginId: 'scorecard',\n register(env) {\n const collectors: Collector[] = [];\n const metricProvidersRegistry = new MetricProvidersRegistry();\n\n env.registerExtensionPoint(scorecardCollectorsExtensionPoint, {\n addCollector(...newCollectors: Collector[]) {\n collectors.push(...newCollectors);\n },\n });\n\n env.registerExtensionPoint(scorecardMetricsExtensionPoint, {\n addMetricProvider(...newMetricProviders: MetricProvider[]) {\n newMetricProviders.forEach(metricProvider => {\n metricProvidersRegistry.register(metricProvider);\n });\n },\n });\n\n env.registerInit({\n deps: {\n actionsRegistry: actionsRegistryServiceRef,\n auth: coreServices.auth,\n catalog: catalogServiceRef,\n collectorsService: scorecardCollectorsServiceRef,\n config: coreServices.rootConfig,\n database: coreServices.database,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n scheduler: coreServices.scheduler,\n },\n async init({\n actionsRegistry,\n auth,\n catalog,\n collectorsService,\n config,\n database,\n httpRouter,\n httpAuth,\n logger,\n permissions,\n permissionsRegistry,\n scheduler,\n }) {\n collectorsService.init({ collectors });\n\n permissionsRegistry.addResourceType({\n resourceRef: scorecardMetricPermissionResourceRef,\n getResources: async (resourceRefs: string[]) => {\n return metricProvidersRegistry.listMetrics(resourceRefs);\n },\n permissions: scorecardPermissions,\n rules: Object.values(scorecardRules),\n });\n\n // Run database migrations\n await migrate(database);\n\n const client = await database.getClient();\n const dbMetricValues = new DatabaseMetricValues(client);\n const thresholdResolver = new ThresholdResolver(\n config,\n metricProvidersRegistry.listProviders(),\n );\n\n const catalogMetricService = new CatalogMetricService({\n catalog,\n auth,\n registry: metricProvidersRegistry,\n database: dbMetricValues,\n logger: logger,\n thresholdResolver,\n });\n\n const aggregationsService = new AggregationsService({\n config,\n database: dbMetricValues,\n logger: logger,\n });\n\n validateAggregationConfig({\n rootConfig: config,\n registry: metricProvidersRegistry,\n thresholdResolver,\n });\n\n Scheduler.create({\n auth,\n catalog,\n config,\n logger,\n scheduler,\n database: dbMetricValues,\n metricProvidersRegistry,\n thresholdEvaluator: new ThresholdEvaluator(),\n thresholdResolver,\n }).start();\n\n const service = {\n aggregationsService: aggregationsService,\n catalogMetricService: catalogMetricService,\n };\n\n createScorecardActions({\n actionsRegistry,\n auth,\n permissions,\n catalog,\n metricProvidersRegistry,\n catalogMetricService,\n });\n\n httpRouter.use(\n await createRouter({\n metricProvidersRegistry,\n service,\n catalog,\n httpAuth,\n permissions,\n logger,\n thresholdResolver,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","MetricProvidersRegistry","scorecardCollectorsExtensionPoint","scorecardMetricsExtensionPoint","actionsRegistryServiceRef","coreServices","catalogServiceRef","scorecardCollectorsServiceRef","scorecardMetricPermissionResourceRef","scorecardPermissions","scorecardRules","migrate","DatabaseMetricValues","ThresholdResolver","CatalogMetricService","AggregationsService","validateAggregationConfig","Scheduler","ThresholdEvaluator","createScorecardActions","createRouter"],"mappings":"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"}
@@ -17,14 +17,15 @@ class AggregatedMetricLoader {
17
17
  );
18
18
  return mappers.AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);
19
19
  }
20
- async loadScalarMetricByEntityRefs(entityRefs, metricId, aggregationFn) {
20
+ async loadScalarMetricByEntityRefs(entityRefs, metricId, aggregationFn, filter) {
21
21
  if (entityRefs.length === 0) {
22
22
  return mappers.AggregatedMetricMapper.toScalarAggregatedMetric();
23
23
  }
24
24
  const scalarMetric = await this.database.readScalarAggregatedMetricByEntityRefs(
25
25
  entityRefs,
26
26
  metricId,
27
- aggregationFn
27
+ aggregationFn,
28
+ filter
28
29
  );
29
30
  return mappers.AggregatedMetricMapper.toScalarAggregatedMetric(scalarMetric);
30
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AggregatedMetricLoader.cjs.js","sources":["../../../src/service/aggregations/AggregatedMetricLoader.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AggregatedMetric,\n ScalarAggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport type { ScalarAggregationFn } from '../../database/types';\nimport { AggregatedMetricMapper } from '../mappers';\n\nexport class AggregatedMetricLoader {\n constructor(private readonly database: DatabaseMetricValues) {}\n\n async loadStatusGroupedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length === 0) {\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n async loadScalarMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationFn: ScalarAggregationFn,\n ): Promise<ScalarAggregatedMetric> {\n if (entityRefs.length === 0) {\n return AggregatedMetricMapper.toScalarAggregatedMetric();\n }\n\n const scalarMetric =\n await this.database.readScalarAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n aggregationFn,\n );\n\n return AggregatedMetricMapper.toScalarAggregatedMetric(scalarMetric);\n }\n}\n"],"names":["AggregatedMetricMapper"],"mappings":";;;;AAwBO,MAAM,sBAAA,CAAuB;AAAA,EAClC,YAA6B,QAAA,EAAgC;AAAhC,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA,EAAiC;AAAA,EAAjC,QAAA;AAAA,EAE7B,MAAM,mCAAA,CACJ,UAAA,EACA,QAAA,EAC2B;AAC3B,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOA,+BAAuB,kBAAA,EAAmB;AAAA,IACnD;AAEA,IAAA,MAAM,gBAAA,GACJ,MAAM,IAAA,CAAK,QAAA,CAAS,gCAAA;AAAA,MAClB,UAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAA,OAAOA,8BAAA,CAAuB,mBAAmB,gBAAgB,CAAA;AAAA,EACnE;AAAA,EAEA,MAAM,4BAAA,CACJ,UAAA,EACA,QAAA,EACA,aAAA,EACiC;AACjC,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOA,+BAAuB,wBAAA,EAAyB;AAAA,IACzD;AAEA,IAAA,MAAM,YAAA,GACJ,MAAM,IAAA,CAAK,QAAA,CAAS,sCAAA;AAAA,MAClB,UAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAA,OAAOA,8BAAA,CAAuB,yBAAyB,YAAY,CAAA;AAAA,EACrE;AACF;;;;"}
1
+ {"version":3,"file":"AggregatedMetricLoader.cjs.js","sources":["../../../src/service/aggregations/AggregatedMetricLoader.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AggregatedMetric,\n AggregationConfigFilter,\n ScalarAggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport type { ScalarAggregationFn } from '../../database/types';\nimport { AggregatedMetricMapper } from '../mappers';\n\nexport class AggregatedMetricLoader {\n constructor(private readonly database: DatabaseMetricValues) {}\n\n async loadStatusGroupedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length === 0) {\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n async loadScalarMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationFn: ScalarAggregationFn,\n filter?: AggregationConfigFilter,\n ): Promise<ScalarAggregatedMetric> {\n if (entityRefs.length === 0) {\n return AggregatedMetricMapper.toScalarAggregatedMetric();\n }\n\n const scalarMetric =\n await this.database.readScalarAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n aggregationFn,\n filter,\n );\n\n return AggregatedMetricMapper.toScalarAggregatedMetric(scalarMetric);\n }\n}\n"],"names":["AggregatedMetricMapper"],"mappings":";;;;AAyBO,MAAM,sBAAA,CAAuB;AAAA,EAClC,YAA6B,QAAA,EAAgC;AAAhC,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA,EAAiC;AAAA,EAAjC,QAAA;AAAA,EAE7B,MAAM,mCAAA,CACJ,UAAA,EACA,QAAA,EAC2B;AAC3B,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOA,+BAAuB,kBAAA,EAAmB;AAAA,IACnD;AAEA,IAAA,MAAM,gBAAA,GACJ,MAAM,IAAA,CAAK,QAAA,CAAS,gCAAA;AAAA,MAClB,UAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAA,OAAOA,8BAAA,CAAuB,mBAAmB,gBAAgB,CAAA;AAAA,EACnE;AAAA,EAEA,MAAM,4BAAA,CACJ,UAAA,EACA,QAAA,EACA,eACA,MAAA,EACiC;AACjC,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOA,+BAAuB,wBAAA,EAAyB;AAAA,IACzD;AAEA,IAAA,MAAM,YAAA,GACJ,MAAM,IAAA,CAAK,QAAA,CAAS,sCAAA;AAAA,MAClB,UAAA;AAAA,MACA,QAAA;AAAA,MACA,aAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAA,OAAOA,8BAAA,CAAuB,yBAAyB,YAAY,CAAA;AAAA,EACrE;AACF;;;;"}
@@ -36,13 +36,15 @@ class AggregationsService {
36
36
  `No "${aggregationKPIs.AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}" block in app-config; using default type "${backstagePluginScorecardCommon.aggregationTypes.statusGrouped}" with metricId="${aggregationId}" (same as aggregation id). Add a KPI entry if you meant a custom title, description, or type.`
37
37
  );
38
38
  const metric = metricProviderRegistry.getMetric(aggregationId);
39
- return {
39
+ const fallbackConfig = {
40
40
  id: aggregationId,
41
41
  metricId: aggregationId,
42
42
  title: metric.title,
43
43
  description: metric.description,
44
44
  type: backstagePluginScorecardCommon.aggregationTypes.statusGrouped
45
45
  };
46
+ this.aggregationKpisConfigCache.set(aggregationId, fallbackConfig);
47
+ return fallbackConfig;
46
48
  }
47
49
  const validatedConfig = parseValidatedAggregationConfig.parseValidatedAggregationConfig(
48
50
  buildAggregationConfig.buildAggregationConfig(aggregationId, {
@@ -1 +1 @@
1
- {"version":3,"file":"AggregationsService.cjs.js","sources":["../../../src/service/aggregations/AggregationsService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AggregationOptions } from './types';\nimport { parseValidatedAggregationConfig } from '../../utils/aggregation/parseValidatedAggregationConfig';\nimport {\n type AggregatedMetricResult,\n type AggregationType,\n aggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Config } from '@backstage/config';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../../constants';\nimport { buildAggregationConfig } from '../../utils/aggregation/buildAggregationConfig';\nimport type { AggregationStrategy } from './strategies/types';\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport { AggregatedMetricLoader } from './AggregatedMetricLoader';\nimport { createAggregationStrategyRegistry } from './strategies/registerStrategies';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport type { ValidatedAggregationConfig } from '../../validation/schemas/aggregationConfigSchemas';\nimport type { MetricProvidersRegistry } from '../../providers/MetricProvidersRegistry';\n\nexport type AggregationsServiceOptions = {\n config: Config;\n logger: LoggerService;\n database: DatabaseMetricValues;\n};\n\nexport class AggregationsService {\n private readonly config: Config;\n private readonly database: DatabaseMetricValues;\n private readonly strategyRegistry: Map<AggregationType, AggregationStrategy>;\n private readonly logger: LoggerService;\n private readonly aggregationKpisConfigCache: Map<\n string,\n ValidatedAggregationConfig\n >;\n\n constructor(options: AggregationsServiceOptions) {\n this.config = options.config;\n this.logger = options.logger;\n this.database = options.database;\n this.aggregationKpisConfigCache = new Map();\n this.strategyRegistry = createAggregationStrategyRegistry(\n new AggregatedMetricLoader(this.database),\n this.logger,\n );\n }\n\n getAggregationConfig(\n aggregationId: string,\n metricProviderRegistry: MetricProvidersRegistry,\n ): ValidatedAggregationConfig {\n const cachedConfig = this.aggregationKpisConfigCache.get(aggregationId);\n\n if (cachedConfig) {\n return cachedConfig;\n }\n\n const config = this.config.getOptionalConfig(\n `${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}`,\n );\n\n if (!config) {\n this.logger.warn(\n `No \"${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}\" block in app-config; ` +\n `using default type \"${aggregationTypes.statusGrouped}\" with metricId=\"${aggregationId}\" ` +\n '(same as aggregation id). Add a KPI entry if you meant a custom title, description, or type.',\n );\n\n const metric = metricProviderRegistry.getMetric(aggregationId);\n\n return {\n id: aggregationId,\n metricId: aggregationId,\n title: metric.title,\n description: metric.description,\n type: aggregationTypes.statusGrouped,\n };\n }\n\n const validatedConfig = parseValidatedAggregationConfig(\n buildAggregationConfig(aggregationId, {\n config,\n }),\n );\n this.aggregationKpisConfigCache.set(aggregationId, validatedConfig);\n\n return validatedConfig;\n }\n\n async getAggregatedMetricByEntityRefs(\n options: AggregationOptions,\n ): Promise<AggregatedMetricResult> {\n const { aggregationConfig } = options;\n\n const strategy = this.strategyRegistry.get(aggregationConfig.type);\n\n if (!strategy) {\n throw new Error(\n `Unsupported aggregation type: ${aggregationConfig.type}`,\n );\n }\n\n return strategy.aggregate(options);\n }\n}\n"],"names":["createAggregationStrategyRegistry","AggregatedMetricLoader","AGGREGATION_KPIS_CONFIG_PATH","aggregationTypes","parseValidatedAggregationConfig","buildAggregationConfig"],"mappings":";;;;;;;;;AAwCO,MAAM,mBAAA,CAAoB;AAAA,EACd,MAAA;AAAA,EACA,QAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,0BAAA;AAAA,EAKjB,YAAY,OAAA,EAAqC;AAC/C,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AACxB,IAAA,IAAA,CAAK,0BAAA,uBAAiC,GAAA,EAAI;AAC1C,IAAA,IAAA,CAAK,gBAAA,GAAmBA,oDAAA;AAAA,MACtB,IAAIC,6CAAA,CAAuB,IAAA,CAAK,QAAQ,CAAA;AAAA,MACxC,IAAA,CAAK;AAAA,KACP;AAAA,EACF;AAAA,EAEA,oBAAA,CACE,eACA,sBAAA,EAC4B;AAC5B,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,0BAAA,CAA2B,GAAA,CAAI,aAAa,CAAA;AAEtE,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,OAAO,YAAA;AAAA,IACT;AAEA,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,CAAO,iBAAA;AAAA,MACzB,CAAA,EAAGC,4CAA4B,CAAA,CAAA,EAAI,aAAa,CAAA;AAAA,KAClD;AAEA,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,CAAA,IAAA,EAAOA,4CAA4B,CAAA,CAAA,EAAI,aAAa,8CAC3BC,+CAAA,CAAiB,aAAa,oBAAoB,aAAa,CAAA,8FAAA;AAAA,OAE1F;AAEA,MAAA,MAAM,MAAA,GAAS,sBAAA,CAAuB,SAAA,CAAU,aAAa,CAAA;AAE7D,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,aAAA;AAAA,QACJ,QAAA,EAAU,aAAA;AAAA,QACV,OAAO,MAAA,CAAO,KAAA;AAAA,QACd,aAAa,MAAA,CAAO,WAAA;AAAA,QACpB,MAAMA,+CAAA,CAAiB;AAAA,OACzB;AAAA,IACF;AAEA,IAAA,MAAM,eAAA,GAAkBC,+DAAA;AAAA,MACtBC,8CAAuB,aAAA,EAAe;AAAA,QACpC;AAAA,OACD;AAAA,KACH;AACA,IAAA,IAAA,CAAK,0BAAA,CAA2B,GAAA,CAAI,aAAA,EAAe,eAAe,CAAA;AAElE,IAAA,OAAO,eAAA;AAAA,EACT;AAAA,EAEA,MAAM,gCACJ,OAAA,EACiC;AACjC,IAAA,MAAM,EAAE,mBAAkB,GAAI,OAAA;AAE9B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,kBAAkB,IAAI,CAAA;AAEjE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,8BAAA,EAAiC,kBAAkB,IAAI,CAAA;AAAA,OACzD;AAAA,IACF;AAEA,IAAA,OAAO,QAAA,CAAS,UAAU,OAAO,CAAA;AAAA,EACnC;AACF;;;;"}
1
+ {"version":3,"file":"AggregationsService.cjs.js","sources":["../../../src/service/aggregations/AggregationsService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AggregationOptions } from './types';\nimport { parseValidatedAggregationConfig } from '../../utils/aggregation/parseValidatedAggregationConfig';\nimport {\n type AggregatedMetricResult,\n type AggregationType,\n aggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Config } from '@backstage/config';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../../constants';\nimport { buildAggregationConfig } from '../../utils/aggregation/buildAggregationConfig';\nimport type { AggregationStrategy } from './strategies/types';\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport { AggregatedMetricLoader } from './AggregatedMetricLoader';\nimport { createAggregationStrategyRegistry } from './strategies/registerStrategies';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport type { ValidatedAggregationConfig } from '../../validation/schemas/aggregationConfigSchemas';\nimport type { MetricProvidersRegistry } from '../../providers/MetricProvidersRegistry';\n\nexport type AggregationsServiceOptions = {\n config: Config;\n logger: LoggerService;\n database: DatabaseMetricValues;\n};\n\nexport class AggregationsService {\n private readonly config: Config;\n private readonly database: DatabaseMetricValues;\n private readonly strategyRegistry: Map<AggregationType, AggregationStrategy>;\n private readonly logger: LoggerService;\n private readonly aggregationKpisConfigCache: Map<\n string,\n ValidatedAggregationConfig\n >;\n\n constructor(options: AggregationsServiceOptions) {\n this.config = options.config;\n this.logger = options.logger;\n this.database = options.database;\n this.aggregationKpisConfigCache = new Map();\n this.strategyRegistry = createAggregationStrategyRegistry(\n new AggregatedMetricLoader(this.database),\n this.logger,\n );\n }\n\n getAggregationConfig(\n aggregationId: string,\n metricProviderRegistry: MetricProvidersRegistry,\n ): ValidatedAggregationConfig {\n const cachedConfig = this.aggregationKpisConfigCache.get(aggregationId);\n\n if (cachedConfig) {\n return cachedConfig;\n }\n\n const config = this.config.getOptionalConfig(\n `${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}`,\n );\n\n if (!config) {\n this.logger.warn(\n `No \"${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}\" block in app-config; ` +\n `using default type \"${aggregationTypes.statusGrouped}\" with metricId=\"${aggregationId}\" ` +\n '(same as aggregation id). Add a KPI entry if you meant a custom title, description, or type.',\n );\n\n const metric = metricProviderRegistry.getMetric(aggregationId);\n\n const fallbackConfig: ValidatedAggregationConfig = {\n id: aggregationId,\n metricId: aggregationId,\n title: metric.title,\n description: metric.description,\n type: aggregationTypes.statusGrouped,\n };\n this.aggregationKpisConfigCache.set(aggregationId, fallbackConfig);\n\n return fallbackConfig;\n }\n\n const validatedConfig = parseValidatedAggregationConfig(\n buildAggregationConfig(aggregationId, {\n config,\n }),\n );\n this.aggregationKpisConfigCache.set(aggregationId, validatedConfig);\n\n return validatedConfig;\n }\n\n async getAggregatedMetricByEntityRefs(\n options: AggregationOptions,\n ): Promise<AggregatedMetricResult> {\n const { aggregationConfig } = options;\n\n const strategy = this.strategyRegistry.get(aggregationConfig.type);\n\n if (!strategy) {\n throw new Error(\n `Unsupported aggregation type: ${aggregationConfig.type}`,\n );\n }\n\n return strategy.aggregate(options);\n }\n}\n"],"names":["createAggregationStrategyRegistry","AggregatedMetricLoader","AGGREGATION_KPIS_CONFIG_PATH","aggregationTypes","parseValidatedAggregationConfig","buildAggregationConfig"],"mappings":";;;;;;;;;AAwCO,MAAM,mBAAA,CAAoB;AAAA,EACd,MAAA;AAAA,EACA,QAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,0BAAA;AAAA,EAKjB,YAAY,OAAA,EAAqC;AAC/C,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AACxB,IAAA,IAAA,CAAK,0BAAA,uBAAiC,GAAA,EAAI;AAC1C,IAAA,IAAA,CAAK,gBAAA,GAAmBA,oDAAA;AAAA,MACtB,IAAIC,6CAAA,CAAuB,IAAA,CAAK,QAAQ,CAAA;AAAA,MACxC,IAAA,CAAK;AAAA,KACP;AAAA,EACF;AAAA,EAEA,oBAAA,CACE,eACA,sBAAA,EAC4B;AAC5B,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,0BAAA,CAA2B,GAAA,CAAI,aAAa,CAAA;AAEtE,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,OAAO,YAAA;AAAA,IACT;AAEA,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,CAAO,iBAAA;AAAA,MACzB,CAAA,EAAGC,4CAA4B,CAAA,CAAA,EAAI,aAAa,CAAA;AAAA,KAClD;AAEA,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,CAAA,IAAA,EAAOA,4CAA4B,CAAA,CAAA,EAAI,aAAa,8CAC3BC,+CAAA,CAAiB,aAAa,oBAAoB,aAAa,CAAA,8FAAA;AAAA,OAE1F;AAEA,MAAA,MAAM,MAAA,GAAS,sBAAA,CAAuB,SAAA,CAAU,aAAa,CAAA;AAE7D,MAAA,MAAM,cAAA,GAA6C;AAAA,QACjD,EAAA,EAAI,aAAA;AAAA,QACJ,QAAA,EAAU,aAAA;AAAA,QACV,OAAO,MAAA,CAAO,KAAA;AAAA,QACd,aAAa,MAAA,CAAO,WAAA;AAAA,QACpB,MAAMA,+CAAA,CAAiB;AAAA,OACzB;AACA,MAAA,IAAA,CAAK,0BAAA,CAA2B,GAAA,CAAI,aAAA,EAAe,cAAc,CAAA;AAEjE,MAAA,OAAO,cAAA;AAAA,IACT;AAEA,IAAA,MAAM,eAAA,GAAkBC,+DAAA;AAAA,MACtBC,8CAAuB,aAAA,EAAe;AAAA,QACpC;AAAA,OACD;AAAA,KACH;AACA,IAAA,IAAA,CAAK,0BAAA,CAA2B,GAAA,CAAI,aAAA,EAAe,eAAe,CAAA;AAElE,IAAA,OAAO,eAAA;AAAA,EACT;AAAA,EAEA,MAAM,gCACJ,OAAA,EACiC;AACjC,IAAA,MAAM,EAAE,mBAAkB,GAAI,OAAA;AAE9B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,kBAAkB,IAAI,CAAA;AAEjE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,8BAAA,EAAiC,kBAAkB,IAAI,CAAA;AAAA,OACzD;AAAA,IACF;AAEA,IAAA,OAAO,QAAA,CAAS,UAAU,OAAO,CAAA;AAAA,EACnC;AACF;;;;"}
@@ -28,7 +28,8 @@ class ScalarAggregationStrategy {
28
28
  } = await this.loader.loadScalarMetricByEntityRefs(
29
29
  entityRefs,
30
30
  metric.id,
31
- this.aggregationFn
31
+ this.aggregationFn,
32
+ aggregationConfig.filter
32
33
  );
33
34
  const result = {
34
35
  value,
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarAggregationStrategy.cjs.js","sources":["../../../../src/service/aggregations/strategies/ScalarAggregationStrategy.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 DEFAULT_NUMBER_THRESHOLDS,\n type AggregatedMetricResult,\n type ScalarAggregationResult,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { ScalarAggregationFn } from '../../../database/types';\nimport { AggregatedMetricMapper } from '../../mappers';\nimport type { AggregatedMetricLoader } from '../AggregatedMetricLoader';\nimport type { AggregationOptions } from '../types';\nimport type { AggregationStrategy } from './types';\nimport { isScalarAggregationConfig } from '../../../utils/aggregation/isScalarAggregationConfig';\n\nexport class ScalarAggregationStrategy implements AggregationStrategy {\n constructor(\n private readonly loader: AggregatedMetricLoader,\n private readonly aggregationFn: ScalarAggregationFn,\n ) {}\n\n async aggregate(\n options: AggregationOptions,\n ): Promise<AggregatedMetricResult> {\n const { entityRefs, metric, aggregationConfig } = options;\n\n if (!isScalarAggregationConfig(aggregationConfig)) {\n throw new Error(\n `Expected a scalar aggregation config but received type \"${aggregationConfig.type}\"`,\n );\n }\n\n const { thresholds: headlineThresholds = DEFAULT_NUMBER_THRESHOLDS } =\n aggregationConfig.options ?? {};\n\n const {\n value,\n total,\n entitiesConsidered,\n calculationErrorCount,\n timestamp,\n } = await this.loader.loadScalarMetricByEntityRefs(\n entityRefs,\n metric.id,\n this.aggregationFn,\n );\n\n const result = {\n value,\n total,\n entitiesConsidered,\n calculationErrorCount,\n timestamp,\n thresholds: headlineThresholds,\n } satisfies ScalarAggregationResult;\n\n return AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n result,\n aggregationConfig,\n );\n }\n}\n"],"names":["isScalarAggregationConfig","DEFAULT_NUMBER_THRESHOLDS","AggregatedMetricMapper"],"mappings":";;;;;;AA4BO,MAAM,yBAAA,CAAyD;AAAA,EACpE,WAAA,CACmB,QACA,aAAA,EACjB;AAFiB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAAA,EAChB;AAAA,EAFgB,MAAA;AAAA,EACA,aAAA;AAAA,EAGnB,MAAM,UACJ,OAAA,EACiC;AACjC,IAAA,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,iBAAA,EAAkB,GAAI,OAAA;AAElD,IAAA,IAAI,CAACA,mDAAA,CAA0B,iBAAiB,CAAA,EAAG;AACjD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,wDAAA,EAA2D,kBAAkB,IAAI,CAAA,CAAA;AAAA,OACnF;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,UAAA,EAAY,kBAAA,GAAqBC,0DAA0B,GACjE,iBAAA,CAAkB,WAAW,EAAC;AAEhC,IAAA,MAAM;AAAA,MACJ,KAAA;AAAA,MACA,KAAA;AAAA,MACA,kBAAA;AAAA,MACA,qBAAA;AAAA,MACA;AAAA,KACF,GAAI,MAAM,IAAA,CAAK,MAAA,CAAO,4BAAA;AAAA,MACpB,UAAA;AAAA,MACA,MAAA,CAAO,EAAA;AAAA,MACP,IAAA,CAAK;AAAA,KACP;AAEA,IAAA,MAAM,MAAA,GAAS;AAAA,MACb,KAAA;AAAA,MACA,KAAA;AAAA,MACA,kBAAA;AAAA,MACA,qBAAA;AAAA,MACA,SAAA;AAAA,MACA,UAAA,EAAY;AAAA,KACd;AAEA,IAAA,OAAOC,8BAAA,CAAuB,wBAAA;AAAA,MAC5B,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;;;;"}
1
+ {"version":3,"file":"ScalarAggregationStrategy.cjs.js","sources":["../../../../src/service/aggregations/strategies/ScalarAggregationStrategy.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 DEFAULT_NUMBER_THRESHOLDS,\n type AggregatedMetricResult,\n type ScalarAggregationResult,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { ScalarAggregationFn } from '../../../database/types';\nimport { AggregatedMetricMapper } from '../../mappers';\nimport type { AggregatedMetricLoader } from '../AggregatedMetricLoader';\nimport type { AggregationOptions } from '../types';\nimport type { AggregationStrategy } from './types';\nimport { isScalarAggregationConfig } from '../../../utils/aggregation/isScalarAggregationConfig';\n\nexport class ScalarAggregationStrategy implements AggregationStrategy {\n constructor(\n private readonly loader: AggregatedMetricLoader,\n private readonly aggregationFn: ScalarAggregationFn,\n ) {}\n\n async aggregate(\n options: AggregationOptions,\n ): Promise<AggregatedMetricResult> {\n const { entityRefs, metric, aggregationConfig } = options;\n\n if (!isScalarAggregationConfig(aggregationConfig)) {\n throw new Error(\n `Expected a scalar aggregation config but received type \"${aggregationConfig.type}\"`,\n );\n }\n\n const { thresholds: headlineThresholds = DEFAULT_NUMBER_THRESHOLDS } =\n aggregationConfig.options ?? {};\n\n const {\n value,\n total,\n entitiesConsidered,\n calculationErrorCount,\n timestamp,\n } = await this.loader.loadScalarMetricByEntityRefs(\n entityRefs,\n metric.id,\n this.aggregationFn,\n aggregationConfig.filter,\n );\n\n const result = {\n value,\n total,\n entitiesConsidered,\n calculationErrorCount,\n timestamp,\n thresholds: headlineThresholds,\n } satisfies ScalarAggregationResult;\n\n return AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n result,\n aggregationConfig,\n );\n }\n}\n"],"names":["isScalarAggregationConfig","DEFAULT_NUMBER_THRESHOLDS","AggregatedMetricMapper"],"mappings":";;;;;;AA4BO,MAAM,yBAAA,CAAyD;AAAA,EACpE,WAAA,CACmB,QACA,aAAA,EACjB;AAFiB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAAA,EAChB;AAAA,EAFgB,MAAA;AAAA,EACA,aAAA;AAAA,EAGnB,MAAM,UACJ,OAAA,EACiC;AACjC,IAAA,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,iBAAA,EAAkB,GAAI,OAAA;AAElD,IAAA,IAAI,CAACA,mDAAA,CAA0B,iBAAiB,CAAA,EAAG;AACjD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,wDAAA,EAA2D,kBAAkB,IAAI,CAAA,CAAA;AAAA,OACnF;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,UAAA,EAAY,kBAAA,GAAqBC,0DAA0B,GACjE,iBAAA,CAAkB,WAAW,EAAC;AAEhC,IAAA,MAAM;AAAA,MACJ,KAAA;AAAA,MACA,KAAA;AAAA,MACA,kBAAA;AAAA,MACA,qBAAA;AAAA,MACA;AAAA,KACF,GAAI,MAAM,IAAA,CAAK,MAAA,CAAO,4BAAA;AAAA,MACpB,UAAA;AAAA,MACA,MAAA,CAAO,EAAA;AAAA,MACP,IAAA,CAAK,aAAA;AAAA,MACL,iBAAA,CAAkB;AAAA,KACpB;AAEA,IAAA,MAAM,MAAA,GAAS;AAAA,MACb,KAAA;AAAA,MACA,KAAA;AAAA,MACA,kBAAA;AAAA,MACA,qBAAA;AAAA,MACA,SAAA;AAAA,MACA,UAAA,EAAY;AAAA,KACd;AAEA,IAAA,OAAOC,8BAAA,CAAuB,wBAAA;AAAA,MAC5B,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;;;;"}
@@ -1,11 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var toIsoTimestamp = require('../utils/toIsoTimestamp.cjs.js');
3
+ var normalizeTimestamp = require('../utils/normalizeTimestamp.cjs.js');
4
4
 
5
5
  class AggregatedMetricMapper {
6
6
  static toAggregatedMetric(aggregatedMetric) {
7
7
  const total = aggregatedMetric?.total ?? 0;
8
- const timestamp = toIsoTimestamp.toIsoTimestamp(aggregatedMetric?.maxTimestamp);
8
+ const timestamp = normalizeTimestamp.normalizeTimestamp(
9
+ aggregatedMetric?.maxTimestamp
10
+ ).toISOString();
9
11
  return {
10
12
  values: aggregatedMetric?.statusCounts ?? {},
11
13
  total,
@@ -15,7 +17,9 @@ class AggregatedMetricMapper {
15
17
  };
16
18
  }
17
19
  static toScalarAggregatedMetric(scalarMetric) {
18
- const timestamp = toIsoTimestamp.toIsoTimestamp(scalarMetric?.maxTimestamp);
20
+ const timestamp = normalizeTimestamp.normalizeTimestamp(
21
+ scalarMetric?.maxTimestamp
22
+ ).toISOString();
19
23
  return {
20
24
  value: scalarMetric?.value ?? 0,
21
25
  total: scalarMetric?.total ?? 0,
@@ -25,13 +29,17 @@ class AggregatedMetricMapper {
25
29
  };
26
30
  }
27
31
  static toAggregationMetadata(metric, aggregationConfig) {
28
- return {
32
+ const metadata = {
29
33
  type: metric.type,
30
34
  history: metric.history,
31
35
  title: aggregationConfig.title,
32
36
  description: aggregationConfig.description,
33
37
  aggregationType: aggregationConfig.type
34
38
  };
39
+ if ("filter" in aggregationConfig && aggregationConfig.filter) {
40
+ metadata.filter = aggregationConfig.filter;
41
+ }
42
+ return metadata;
35
43
  }
36
44
  static toAggregatedMetricResult(metric, result, aggregationConfig) {
37
45
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.cjs.js","sources":["../../src/service/mappers.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 AggregatedMetric,\n AggregatedMetricResult,\n AggregationMetadata,\n Metric,\n AggregationResultByType,\n ScalarAggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { DbAggregatedMetric } from '../database/types';\nimport type { DbScalarAggregatedMetric } from '../database/types';\nimport { toIsoTimestamp } from '../utils/toIsoTimestamp';\nimport { ValidatedAggregationConfig } from '../validation/schemas/aggregationConfigSchemas';\n\nexport class AggregatedMetricMapper {\n static toAggregatedMetric(\n aggregatedMetric?: DbAggregatedMetric,\n ): AggregatedMetric {\n const total = aggregatedMetric?.total ?? 0;\n const timestamp = toIsoTimestamp(aggregatedMetric?.maxTimestamp);\n\n return {\n values: aggregatedMetric?.statusCounts ?? {},\n total,\n timestamp,\n entitiesConsidered: aggregatedMetric?.latestEntityCount ?? 0,\n calculationErrorCount: aggregatedMetric?.calculationErrorCount ?? 0,\n };\n }\n\n static toScalarAggregatedMetric(\n scalarMetric?: DbScalarAggregatedMetric,\n ): ScalarAggregatedMetric {\n const timestamp = toIsoTimestamp(scalarMetric?.maxTimestamp);\n\n return {\n value: scalarMetric?.value ?? 0,\n total: scalarMetric?.total ?? 0,\n entitiesConsidered: scalarMetric?.latestEntityCount ?? 0,\n calculationErrorCount: scalarMetric?.calculationErrorCount ?? 0,\n timestamp,\n };\n }\n\n static toAggregationMetadata(\n metric: Metric,\n aggregationConfig: ValidatedAggregationConfig,\n ): AggregationMetadata {\n return {\n type: metric.type,\n history: metric.history,\n title: aggregationConfig.title,\n description: aggregationConfig.description,\n aggregationType: aggregationConfig.type,\n };\n }\n\n static toAggregatedMetricResult(\n metric: Metric,\n result: AggregationResultByType,\n aggregationConfig: ValidatedAggregationConfig,\n ): AggregatedMetricResult {\n return {\n id: metric.id,\n status: 'success',\n metadata: this.toAggregationMetadata(metric, aggregationConfig),\n result,\n };\n }\n}\n"],"names":["toIsoTimestamp"],"mappings":";;;;AA6BO,MAAM,sBAAA,CAAuB;AAAA,EAClC,OAAO,mBACL,gBAAA,EACkB;AAClB,IAAA,MAAM,KAAA,GAAQ,kBAAkB,KAAA,IAAS,CAAA;AACzC,IAAA,MAAM,SAAA,GAAYA,6BAAA,CAAe,gBAAA,EAAkB,YAAY,CAAA;AAE/D,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,gBAAA,EAAkB,YAAA,IAAgB,EAAC;AAAA,MAC3C,KAAA;AAAA,MACA,SAAA;AAAA,MACA,kBAAA,EAAoB,kBAAkB,iBAAA,IAAqB,CAAA;AAAA,MAC3D,qBAAA,EAAuB,kBAAkB,qBAAA,IAAyB;AAAA,KACpE;AAAA,EACF;AAAA,EAEA,OAAO,yBACL,YAAA,EACwB;AACxB,IAAA,MAAM,SAAA,GAAYA,6BAAA,CAAe,YAAA,EAAc,YAAY,CAAA;AAE3D,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,cAAc,KAAA,IAAS,CAAA;AAAA,MAC9B,KAAA,EAAO,cAAc,KAAA,IAAS,CAAA;AAAA,MAC9B,kBAAA,EAAoB,cAAc,iBAAA,IAAqB,CAAA;AAAA,MACvD,qBAAA,EAAuB,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAC9D;AAAA,KACF;AAAA,EACF;AAAA,EAEA,OAAO,qBAAA,CACL,MAAA,EACA,iBAAA,EACqB;AACrB,IAAA,OAAO;AAAA,MACL,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,OAAO,iBAAA,CAAkB,KAAA;AAAA,MACzB,aAAa,iBAAA,CAAkB,WAAA;AAAA,MAC/B,iBAAiB,iBAAA,CAAkB;AAAA,KACrC;AAAA,EACF;AAAA,EAEA,OAAO,wBAAA,CACL,MAAA,EACA,MAAA,EACA,iBAAA,EACwB;AACxB,IAAA,OAAO;AAAA,MACL,IAAI,MAAA,CAAO,EAAA;AAAA,MACX,MAAA,EAAQ,SAAA;AAAA,MACR,QAAA,EAAU,IAAA,CAAK,qBAAA,CAAsB,MAAA,EAAQ,iBAAiB,CAAA;AAAA,MAC9D;AAAA,KACF;AAAA,EACF;AACF;;;;"}
1
+ {"version":3,"file":"mappers.cjs.js","sources":["../../src/service/mappers.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 AggregatedMetric,\n AggregatedMetricResult,\n AggregationMetadata,\n Metric,\n AggregationResultByType,\n ScalarAggregatedMetric,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { DbAggregatedMetric } from '../database/types';\nimport type { DbScalarAggregatedMetric } from '../database/types';\nimport { ValidatedAggregationConfig } from '../validation/schemas/aggregationConfigSchemas';\nimport { normalizeTimestamp } from '../utils/normalizeTimestamp';\n\nexport class AggregatedMetricMapper {\n static toAggregatedMetric(\n aggregatedMetric?: DbAggregatedMetric,\n ): AggregatedMetric {\n const total = aggregatedMetric?.total ?? 0;\n const timestamp = normalizeTimestamp(\n aggregatedMetric?.maxTimestamp,\n ).toISOString();\n\n return {\n values: aggregatedMetric?.statusCounts ?? {},\n total,\n timestamp,\n entitiesConsidered: aggregatedMetric?.latestEntityCount ?? 0,\n calculationErrorCount: aggregatedMetric?.calculationErrorCount ?? 0,\n };\n }\n\n static toScalarAggregatedMetric(\n scalarMetric?: DbScalarAggregatedMetric,\n ): ScalarAggregatedMetric {\n const timestamp = normalizeTimestamp(\n scalarMetric?.maxTimestamp,\n ).toISOString();\n\n return {\n value: scalarMetric?.value ?? 0,\n total: scalarMetric?.total ?? 0,\n entitiesConsidered: scalarMetric?.latestEntityCount ?? 0,\n calculationErrorCount: scalarMetric?.calculationErrorCount ?? 0,\n timestamp,\n };\n }\n\n static toAggregationMetadata(\n metric: Metric,\n aggregationConfig: ValidatedAggregationConfig,\n ): AggregationMetadata {\n const metadata: AggregationMetadata = {\n type: metric.type,\n history: metric.history,\n title: aggregationConfig.title,\n description: aggregationConfig.description,\n aggregationType: aggregationConfig.type,\n };\n\n if ('filter' in aggregationConfig && aggregationConfig.filter) {\n metadata.filter = aggregationConfig.filter;\n }\n\n return metadata;\n }\n\n static toAggregatedMetricResult(\n metric: Metric,\n result: AggregationResultByType,\n aggregationConfig: ValidatedAggregationConfig,\n ): AggregatedMetricResult {\n return {\n id: metric.id,\n status: 'success',\n metadata: this.toAggregationMetadata(metric, aggregationConfig),\n result,\n };\n }\n}\n"],"names":["normalizeTimestamp"],"mappings":";;;;AA6BO,MAAM,sBAAA,CAAuB;AAAA,EAClC,OAAO,mBACL,gBAAA,EACkB;AAClB,IAAA,MAAM,KAAA,GAAQ,kBAAkB,KAAA,IAAS,CAAA;AACzC,IAAA,MAAM,SAAA,GAAYA,qCAAA;AAAA,MAChB,gBAAA,EAAkB;AAAA,MAClB,WAAA,EAAY;AAEd,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,gBAAA,EAAkB,YAAA,IAAgB,EAAC;AAAA,MAC3C,KAAA;AAAA,MACA,SAAA;AAAA,MACA,kBAAA,EAAoB,kBAAkB,iBAAA,IAAqB,CAAA;AAAA,MAC3D,qBAAA,EAAuB,kBAAkB,qBAAA,IAAyB;AAAA,KACpE;AAAA,EACF;AAAA,EAEA,OAAO,yBACL,YAAA,EACwB;AACxB,IAAA,MAAM,SAAA,GAAYA,qCAAA;AAAA,MAChB,YAAA,EAAc;AAAA,MACd,WAAA,EAAY;AAEd,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,cAAc,KAAA,IAAS,CAAA;AAAA,MAC9B,KAAA,EAAO,cAAc,KAAA,IAAS,CAAA;AAAA,MAC9B,kBAAA,EAAoB,cAAc,iBAAA,IAAqB,CAAA;AAAA,MACvD,qBAAA,EAAuB,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAC9D;AAAA,KACF;AAAA,EACF;AAAA,EAEA,OAAO,qBAAA,CACL,MAAA,EACA,iBAAA,EACqB;AACrB,IAAA,MAAM,QAAA,GAAgC;AAAA,MACpC,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,OAAO,iBAAA,CAAkB,KAAA;AAAA,MACzB,aAAa,iBAAA,CAAkB,WAAA;AAAA,MAC/B,iBAAiB,iBAAA,CAAkB;AAAA,KACrC;AAEA,IAAA,IAAI,QAAA,IAAY,iBAAA,IAAqB,iBAAA,CAAkB,MAAA,EAAQ;AAC7D,MAAA,QAAA,CAAS,SAAS,iBAAA,CAAkB,MAAA;AAAA,IACtC;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEA,OAAO,wBAAA,CACL,MAAA,EACA,MAAA,EACA,iBAAA,EACwB;AACxB,IAAA,OAAO;AAAA,MACL,IAAI,MAAA,CAAO,EAAA;AAAA,MACX,MAAA,EAAQ,SAAA;AAAA,MACR,QAAA,EAAU,IAAA,CAAK,qBAAA,CAAsB,MAAA,EAAQ,iBAAiB,CAAA;AAAA,MAC9D;AAAA,KACF;AAAA,EACF;AACF;;;;"}
@@ -4,6 +4,7 @@ var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-p
4
4
  var isScalarAggregationType = require('./isScalarAggregationType.cjs.js');
5
5
  var buildAggregationConfigThresholds = require('./buildAggregationConfigThresholds.cjs.js');
6
6
  var buildAggregationStatusScores = require('./buildAggregationStatusScores.cjs.js');
7
+ var buildAggregationConfigFilter = require('./buildAggregationConfigFilter.cjs.js');
7
8
 
8
9
  function buildAggregationConfig(aggregationId, options) {
9
10
  const { config } = options;
@@ -20,6 +21,10 @@ function buildAggregationConfig(aggregationId, options) {
20
21
  aggregationConfig.options.thresholds = buildAggregationConfigThresholds.buildAggregationConfigThresholds(config);
21
22
  } else if (isScalarAggregationType.isScalarAggregationType(aggregationConfig.type)) {
22
23
  aggregationConfig.options.thresholds = buildAggregationConfigThresholds.buildAggregationConfigThresholds(config);
24
+ const filter = buildAggregationConfigFilter.buildAggregationConfigFilter(config);
25
+ if (filter) {
26
+ aggregationConfig.filter = filter;
27
+ }
23
28
  }
24
29
  return aggregationConfig;
25
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"buildAggregationConfig.cjs.js","sources":["../../../src/utils/aggregation/buildAggregationConfig.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport {\n aggregationTypes,\n type AggregationConfig,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { isScalarAggregationType } from './isScalarAggregationType';\nimport { buildAggregationConfigThresholds } from './buildAggregationConfigThresholds';\nimport { buildAggregationStatusScores } from './buildAggregationStatusScores';\n\ntype Options = {\n config: Config;\n};\n\nexport function buildAggregationConfig(\n aggregationId: string,\n options: Options,\n): AggregationConfig {\n const { config } = options;\n\n const aggregationConfig: AggregationConfig = {\n id: aggregationId,\n type: config.getString('type'),\n title: config.getString('title'),\n metricId: config.getString('metricId'),\n description: config.getString('description'),\n } as AggregationConfig;\n\n aggregationConfig.options = {};\n\n if (aggregationConfig.type === aggregationTypes.weightedStatusScore) {\n aggregationConfig.options.statusScores =\n buildAggregationStatusScores(config);\n aggregationConfig.options.thresholds =\n buildAggregationConfigThresholds(config);\n } else if (isScalarAggregationType(aggregationConfig.type)) {\n aggregationConfig.options.thresholds =\n buildAggregationConfigThresholds(config);\n }\n\n return aggregationConfig;\n}\n"],"names":["aggregationTypes","buildAggregationStatusScores","buildAggregationConfigThresholds","isScalarAggregationType"],"mappings":";;;;;;;AA6BO,SAAS,sBAAA,CACd,eACA,OAAA,EACmB;AACnB,EAAA,MAAM,EAAE,QAAO,GAAI,OAAA;AAEnB,EAAA,MAAM,iBAAA,GAAuC;AAAA,IAC3C,EAAA,EAAI,aAAA;AAAA,IACJ,IAAA,EAAM,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA;AAAA,IAC7B,KAAA,EAAO,MAAA,CAAO,SAAA,CAAU,OAAO,CAAA;AAAA,IAC/B,QAAA,EAAU,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA;AAAA,IACrC,WAAA,EAAa,MAAA,CAAO,SAAA,CAAU,aAAa;AAAA,GAC7C;AAEA,EAAA,iBAAA,CAAkB,UAAU,EAAC;AAE7B,EAAA,IAAI,iBAAA,CAAkB,IAAA,KAASA,+CAAA,CAAiB,mBAAA,EAAqB;AACnE,IAAA,iBAAA,CAAkB,OAAA,CAAQ,YAAA,GACxBC,yDAAA,CAA6B,MAAM,CAAA;AACrC,IAAA,iBAAA,CAAkB,OAAA,CAAQ,UAAA,GACxBC,iEAAA,CAAiC,MAAM,CAAA;AAAA,EAC3C,CAAA,MAAA,IAAWC,+CAAA,CAAwB,iBAAA,CAAkB,IAAI,CAAA,EAAG;AAC1D,IAAA,iBAAA,CAAkB,OAAA,CAAQ,UAAA,GACxBD,iEAAA,CAAiC,MAAM,CAAA;AAAA,EAC3C;AAEA,EAAA,OAAO,iBAAA;AACT;;;;"}
1
+ {"version":3,"file":"buildAggregationConfig.cjs.js","sources":["../../../src/utils/aggregation/buildAggregationConfig.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport {\n aggregationTypes,\n type AggregationConfig,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { isScalarAggregationType } from './isScalarAggregationType';\nimport { buildAggregationConfigThresholds } from './buildAggregationConfigThresholds';\nimport { buildAggregationStatusScores } from './buildAggregationStatusScores';\nimport { buildAggregationConfigFilter } from './buildAggregationConfigFilter';\n\ntype Options = {\n config: Config;\n};\n\nexport function buildAggregationConfig(\n aggregationId: string,\n options: Options,\n): AggregationConfig {\n const { config } = options;\n\n // Represents base attributes for all aggregation types.\n const aggregationConfig: AggregationConfig = {\n id: aggregationId,\n type: config.getString('type'),\n title: config.getString('title'),\n metricId: config.getString('metricId'),\n description: config.getString('description'),\n } as AggregationConfig;\n\n aggregationConfig.options = {};\n\n if (aggregationConfig.type === aggregationTypes.weightedStatusScore) {\n aggregationConfig.options.statusScores =\n buildAggregationStatusScores(config);\n aggregationConfig.options.thresholds =\n buildAggregationConfigThresholds(config);\n } else if (isScalarAggregationType(aggregationConfig.type)) {\n aggregationConfig.options.thresholds =\n buildAggregationConfigThresholds(config);\n const filter = buildAggregationConfigFilter(config);\n if (filter) {\n aggregationConfig.filter = filter;\n }\n }\n\n return aggregationConfig;\n}\n"],"names":["aggregationTypes","buildAggregationStatusScores","buildAggregationConfigThresholds","isScalarAggregationType","buildAggregationConfigFilter"],"mappings":";;;;;;;;AA8BO,SAAS,sBAAA,CACd,eACA,OAAA,EACmB;AACnB,EAAA,MAAM,EAAE,QAAO,GAAI,OAAA;AAGnB,EAAA,MAAM,iBAAA,GAAuC;AAAA,IAC3C,EAAA,EAAI,aAAA;AAAA,IACJ,IAAA,EAAM,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA;AAAA,IAC7B,KAAA,EAAO,MAAA,CAAO,SAAA,CAAU,OAAO,CAAA;AAAA,IAC/B,QAAA,EAAU,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA;AAAA,IACrC,WAAA,EAAa,MAAA,CAAO,SAAA,CAAU,aAAa;AAAA,GAC7C;AAEA,EAAA,iBAAA,CAAkB,UAAU,EAAC;AAE7B,EAAA,IAAI,iBAAA,CAAkB,IAAA,KAASA,+CAAA,CAAiB,mBAAA,EAAqB;AACnE,IAAA,iBAAA,CAAkB,OAAA,CAAQ,YAAA,GACxBC,yDAAA,CAA6B,MAAM,CAAA;AACrC,IAAA,iBAAA,CAAkB,OAAA,CAAQ,UAAA,GACxBC,iEAAA,CAAiC,MAAM,CAAA;AAAA,EAC3C,CAAA,MAAA,IAAWC,+CAAA,CAAwB,iBAAA,CAAkB,IAAI,CAAA,EAAG;AAC1D,IAAA,iBAAA,CAAkB,OAAA,CAAQ,UAAA,GACxBD,iEAAA,CAAiC,MAAM,CAAA;AACzC,IAAA,MAAM,MAAA,GAASE,0DAA6B,MAAM,CAAA;AAClD,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,iBAAA,CAAkB,MAAA,GAAS,MAAA;AAAA,IAC7B;AAAA,EACF;AAEA,EAAA,OAAO,iBAAA;AACT;;;;"}
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ function buildAggregationConfigFilter(config) {
4
+ const filter = config.getOptionalConfig("filter");
5
+ if (!filter) {
6
+ return void 0;
7
+ }
8
+ const status = filter.getOptionalString("status");
9
+ if (!status) {
10
+ return void 0;
11
+ }
12
+ return { status };
13
+ }
14
+
15
+ exports.buildAggregationConfigFilter = buildAggregationConfigFilter;
16
+ //# sourceMappingURL=buildAggregationConfigFilter.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildAggregationConfigFilter.cjs.js","sources":["../../../src/utils/aggregation/buildAggregationConfigFilter.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport type { AggregationConfigFilter } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport function buildAggregationConfigFilter(\n config: Config,\n): AggregationConfigFilter | undefined {\n const filter = config.getOptionalConfig('filter');\n if (!filter) {\n return undefined;\n }\n\n const status = filter.getOptionalString('status');\n if (!status) {\n return undefined;\n }\n\n return { status };\n}\n"],"names":[],"mappings":";;AAmBO,SAAS,6BACd,MAAA,EACqC;AACrC,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,iBAAA,CAAkB,QAAQ,CAAA;AAChD,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,iBAAA,CAAkB,QAAQ,CAAA;AAChD,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,EAAE,MAAA,EAAO;AAClB;;;;"}
@@ -5,9 +5,12 @@ function normalizeTimestamp(timestamp) {
5
5
  return timestamp;
6
6
  }
7
7
  if (typeof timestamp === "number" || typeof timestamp === "string") {
8
+ if (timestamp === "") {
9
+ return /* @__PURE__ */ new Date(0);
10
+ }
8
11
  return new Date(timestamp);
9
12
  }
10
- return /* @__PURE__ */ new Date();
13
+ return /* @__PURE__ */ new Date(0);
11
14
  }
12
15
 
13
16
  exports.normalizeTimestamp = normalizeTimestamp;
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeTimestamp.cjs.js","sources":["../../src/utils/normalizeTimestamp.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\nexport function normalizeTimestamp(timestamp?: unknown): Date {\n if (timestamp instanceof Date) {\n return timestamp;\n }\n\n if (typeof timestamp === 'number' || typeof timestamp === 'string') {\n return new Date(timestamp);\n }\n\n return new Date();\n}\n"],"names":[],"mappings":";;AAgBO,SAAS,mBAAmB,SAAA,EAA2B;AAC5D,EAAA,IAAI,qBAAqB,IAAA,EAAM;AAC7B,IAAA,OAAO,SAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,OAAO,cAAc,QAAA,EAAU;AAClE,IAAA,OAAO,IAAI,KAAK,SAAS,CAAA;AAAA,EAC3B;AAEA,EAAA,2BAAW,IAAA,EAAK;AAClB;;;;"}
1
+ {"version":3,"file":"normalizeTimestamp.cjs.js","sources":["../../src/utils/normalizeTimestamp.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\nexport function normalizeTimestamp(timestamp?: unknown): Date {\n if (timestamp instanceof Date) {\n return timestamp;\n }\n\n if (typeof timestamp === 'number' || typeof timestamp === 'string') {\n if (timestamp === '') {\n return new Date(0);\n }\n return new Date(timestamp);\n }\n\n return new Date(0);\n}\n"],"names":[],"mappings":";;AAgBO,SAAS,mBAAmB,SAAA,EAA2B;AAC5D,EAAA,IAAI,qBAAqB,IAAA,EAAM;AAC7B,IAAA,OAAO,SAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,OAAO,cAAc,QAAA,EAAU;AAClE,IAAA,IAAI,cAAc,EAAA,EAAI;AACpB,MAAA,uBAAO,IAAI,KAAK,CAAC,CAAA;AAAA,IACnB;AACA,IAAA,OAAO,IAAI,KAAK,SAAS,CAAA;AAAA,EAC3B;AAEA,EAAA,uBAAO,IAAI,KAAK,CAAC,CAAA;AACnB;;;;"}
@@ -2,16 +2,9 @@
2
2
 
3
3
  var zod = require('zod');
4
4
  var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
5
+ var aggregationThresholdsConfigSchema = require('./aggregationThresholdsConfigSchema.cjs.js');
6
+ var aggregationFilterSchema = require('./aggregationFilterSchema.cjs.js');
5
7
 
6
- const thresholdsConfigSchema = zod.z.object({
7
- rules: zod.z.array(
8
- zod.z.object({
9
- key: zod.z.string(),
10
- expression: zod.z.string(),
11
- color: zod.z.string().optional()
12
- })
13
- )
14
- });
15
8
  const baseAggregationConfigSchema = zod.z.object({
16
9
  id: zod.z.string().min(1).max(128),
17
10
  title: zod.z.string().min(1).max(80),
@@ -29,15 +22,16 @@ const weightedStatusScoreAggregationConfigSchema = zod.z.object({
29
22
  statusScores: zod.z.record(zod.z.string(), zod.z.number().finite()).refine((scores) => Object.keys(scores).length > 0, {
30
23
  message: "options.statusScores must contain at least one weight value"
31
24
  }),
32
- thresholds: thresholdsConfigSchema.optional()
25
+ thresholds: aggregationThresholdsConfigSchema.aggregationThresholdsConfigSchema.optional()
33
26
  })
34
27
  });
35
28
  function scalarAggregationConfigSchema(type) {
36
29
  return zod.z.object({
37
30
  ...baseAggregationConfigSchema.shape,
38
31
  type: zod.z.literal(type),
32
+ filter: aggregationFilterSchema.aggregationFilterSchema.optional(),
39
33
  options: zod.z.strictObject({
40
- thresholds: thresholdsConfigSchema.optional()
34
+ thresholds: aggregationThresholdsConfigSchema.aggregationThresholdsConfigSchema.optional()
41
35
  }).optional()
42
36
  });
43
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"aggregationConfigSchemas.cjs.js","sources":["../../../src/validation/schemas/aggregationConfigSchemas.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 {\n aggregationTypes,\n scalarAggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nconst thresholdsConfigSchema = z.object({\n rules: z.array(\n z.object({\n key: z.string(),\n expression: z.string(),\n color: z.string().optional(),\n }),\n ),\n});\n\nconst baseAggregationConfigSchema = z.object({\n id: z.string().min(1).max(128),\n title: z.string().min(1).max(80),\n metricId: z.string().min(1).max(255),\n description: z.string().min(1).max(200),\n});\n\nconst statusGroupedAggregationConfigSchema = z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(aggregationTypes.statusGrouped),\n});\n\nconst weightedStatusScoreAggregationConfigSchema = z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(aggregationTypes.weightedStatusScore),\n options: z.strictObject({\n statusScores: z\n .record(z.string(), z.number().finite())\n .refine(scores => Object.keys(scores).length > 0, {\n message: 'options.statusScores must contain at least one weight value',\n }),\n thresholds: thresholdsConfigSchema.optional(),\n }),\n});\n\nfunction scalarAggregationConfigSchema(\n type: (typeof scalarAggregationTypes)[number],\n) {\n return z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(type),\n options: z\n .strictObject({\n thresholds: thresholdsConfigSchema.optional(),\n })\n .optional(),\n });\n}\n\nexport const aggregationConfigSchema = z.discriminatedUnion('type', [\n statusGroupedAggregationConfigSchema,\n weightedStatusScoreAggregationConfigSchema,\n scalarAggregationConfigSchema(aggregationTypes.sum),\n scalarAggregationConfigSchema(aggregationTypes.average),\n scalarAggregationConfigSchema(aggregationTypes.max),\n scalarAggregationConfigSchema(aggregationTypes.min),\n scalarAggregationConfigSchema(aggregationTypes.count),\n]);\n\n/** Post-validation aggregation KPI config (Zod discriminated union). */\nexport type ValidatedAggregationConfig = z.infer<\n typeof aggregationConfigSchema\n>;\n\nexport type WeightedStatusScoreAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: typeof aggregationTypes.weightedStatusScore }\n>;\n\nexport type ScalarAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: (typeof scalarAggregationTypes)[number] }\n>;\n\nexport type StatusGroupedAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: typeof aggregationTypes.statusGrouped }\n>;\n"],"names":["z","aggregationTypes"],"mappings":";;;;;AAsBA,MAAM,sBAAA,GAAyBA,MAAE,MAAA,CAAO;AAAA,EACtC,OAAOA,KAAA,CAAE,KAAA;AAAA,IACPA,MAAE,MAAA,CAAO;AAAA,MACP,GAAA,EAAKA,MAAE,MAAA,EAAO;AAAA,MACd,UAAA,EAAYA,MAAE,MAAA,EAAO;AAAA,MACrB,KAAA,EAAOA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAAS,KAC5B;AAAA;AAEL,CAAC,CAAA;AAED,MAAM,2BAAA,GAA8BA,MAAE,MAAA,CAAO;AAAA,EAC3C,EAAA,EAAIA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,EAC7B,KAAA,EAAOA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,EAAE,CAAA;AAAA,EAC/B,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,EACnC,WAAA,EAAaA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG;AACxC,CAAC,CAAA;AAED,MAAM,oCAAA,GAAuCA,MAAE,MAAA,CAAO;AAAA,EACpD,GAAG,2BAAA,CAA4B,KAAA;AAAA,EAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQC,+CAAA,CAAiB,aAAa;AAChD,CAAC,CAAA;AAED,MAAM,0CAAA,GAA6CD,MAAE,MAAA,CAAO;AAAA,EAC1D,GAAG,2BAAA,CAA4B,KAAA;AAAA,EAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQC,+CAAA,CAAiB,mBAAmB,CAAA;AAAA,EACpD,OAAA,EAASD,MAAE,YAAA,CAAa;AAAA,IACtB,cAAcA,KAAA,CACX,MAAA,CAAOA,MAAE,MAAA,EAAO,EAAGA,MAAE,MAAA,EAAO,CAAE,QAAQ,CAAA,CACtC,OAAO,CAAA,MAAA,KAAU,MAAA,CAAO,KAAK,MAAM,CAAA,CAAE,SAAS,CAAA,EAAG;AAAA,MAChD,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,IACH,UAAA,EAAY,uBAAuB,QAAA;AAAS,GAC7C;AACH,CAAC,CAAA;AAED,SAAS,8BACP,IAAA,EACA;AACA,EAAA,OAAOA,MAAE,MAAA,CAAO;AAAA,IACd,GAAG,2BAAA,CAA4B,KAAA;AAAA,IAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQ,IAAI,CAAA;AAAA,IACpB,OAAA,EAASA,MACN,YAAA,CAAa;AAAA,MACZ,UAAA,EAAY,uBAAuB,QAAA;AAAS,KAC7C,EACA,QAAA;AAAS,GACb,CAAA;AACH;AAEO,MAAM,uBAAA,GAA0BA,KAAA,CAAE,kBAAA,CAAmB,MAAA,EAAQ;AAAA,EAClE,oCAAA;AAAA,EACA,0CAAA;AAAA,EACA,6BAAA,CAA8BC,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,OAAO,CAAA;AAAA,EACtD,6BAAA,CAA8BA,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,KAAK;AACtD,CAAC;;;;"}
1
+ {"version":3,"file":"aggregationConfigSchemas.cjs.js","sources":["../../../src/validation/schemas/aggregationConfigSchemas.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 {\n aggregationTypes,\n scalarAggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { aggregationThresholdsConfigSchema } from './aggregationThresholdsConfigSchema';\nimport { aggregationFilterSchema } from './aggregationFilterSchema';\n\nconst baseAggregationConfigSchema = z.object({\n id: z.string().min(1).max(128),\n title: z.string().min(1).max(80),\n metricId: z.string().min(1).max(255),\n description: z.string().min(1).max(200),\n});\n\nconst statusGroupedAggregationConfigSchema = z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(aggregationTypes.statusGrouped),\n});\n\nconst weightedStatusScoreAggregationConfigSchema = z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(aggregationTypes.weightedStatusScore),\n options: z.strictObject({\n statusScores: z\n .record(z.string(), z.number().finite())\n .refine(scores => Object.keys(scores).length > 0, {\n message: 'options.statusScores must contain at least one weight value',\n }),\n thresholds: aggregationThresholdsConfigSchema.optional(),\n }),\n});\n\nfunction scalarAggregationConfigSchema(\n type: (typeof scalarAggregationTypes)[number],\n) {\n return z.object({\n ...baseAggregationConfigSchema.shape,\n type: z.literal(type),\n filter: aggregationFilterSchema.optional(),\n options: z\n .strictObject({\n thresholds: aggregationThresholdsConfigSchema.optional(),\n })\n .optional(),\n });\n}\n\nexport const aggregationConfigSchema = z.discriminatedUnion('type', [\n statusGroupedAggregationConfigSchema,\n weightedStatusScoreAggregationConfigSchema,\n scalarAggregationConfigSchema(aggregationTypes.sum),\n scalarAggregationConfigSchema(aggregationTypes.average),\n scalarAggregationConfigSchema(aggregationTypes.max),\n scalarAggregationConfigSchema(aggregationTypes.min),\n scalarAggregationConfigSchema(aggregationTypes.count),\n]);\n\n/** Post-validation aggregation KPI config (Zod discriminated union). */\nexport type ValidatedAggregationConfig = z.infer<\n typeof aggregationConfigSchema\n>;\n\nexport type WeightedStatusScoreAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: typeof aggregationTypes.weightedStatusScore }\n>;\n\nexport type ScalarAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: (typeof scalarAggregationTypes)[number] }\n>;\n\nexport type StatusGroupedAggregationConfig = Extract<\n ValidatedAggregationConfig,\n { type: typeof aggregationTypes.statusGrouped }\n>;\n"],"names":["z","aggregationTypes","aggregationThresholdsConfigSchema","aggregationFilterSchema"],"mappings":";;;;;;;AAwBA,MAAM,2BAAA,GAA8BA,MAAE,MAAA,CAAO;AAAA,EAC3C,EAAA,EAAIA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,EAC7B,KAAA,EAAOA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,EAAE,CAAA;AAAA,EAC/B,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,EACnC,WAAA,EAAaA,MAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA,CAAE,IAAI,GAAG;AACxC,CAAC,CAAA;AAED,MAAM,oCAAA,GAAuCA,MAAE,MAAA,CAAO;AAAA,EACpD,GAAG,2BAAA,CAA4B,KAAA;AAAA,EAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQC,+CAAA,CAAiB,aAAa;AAChD,CAAC,CAAA;AAED,MAAM,0CAAA,GAA6CD,MAAE,MAAA,CAAO;AAAA,EAC1D,GAAG,2BAAA,CAA4B,KAAA;AAAA,EAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQC,+CAAA,CAAiB,mBAAmB,CAAA;AAAA,EACpD,OAAA,EAASD,MAAE,YAAA,CAAa;AAAA,IACtB,cAAcA,KAAA,CACX,MAAA,CAAOA,MAAE,MAAA,EAAO,EAAGA,MAAE,MAAA,EAAO,CAAE,QAAQ,CAAA,CACtC,OAAO,CAAA,MAAA,KAAU,MAAA,CAAO,KAAK,MAAM,CAAA,CAAE,SAAS,CAAA,EAAG;AAAA,MAChD,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,IACH,UAAA,EAAYE,oEAAkC,QAAA;AAAS,GACxD;AACH,CAAC,CAAA;AAED,SAAS,8BACP,IAAA,EACA;AACA,EAAA,OAAOF,MAAE,MAAA,CAAO;AAAA,IACd,GAAG,2BAAA,CAA4B,KAAA;AAAA,IAC/B,IAAA,EAAMA,KAAA,CAAE,OAAA,CAAQ,IAAI,CAAA;AAAA,IACpB,MAAA,EAAQG,gDAAwB,QAAA,EAAS;AAAA,IACzC,OAAA,EAASH,MACN,YAAA,CAAa;AAAA,MACZ,UAAA,EAAYE,oEAAkC,QAAA;AAAS,KACxD,EACA,QAAA;AAAS,GACb,CAAA;AACH;AAEO,MAAM,uBAAA,GAA0BF,KAAA,CAAE,kBAAA,CAAmB,MAAA,EAAQ;AAAA,EAClE,oCAAA;AAAA,EACA,0CAAA;AAAA,EACA,6BAAA,CAA8BC,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,OAAO,CAAA;AAAA,EACtD,6BAAA,CAA8BA,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,GAAG,CAAA;AAAA,EAClD,6BAAA,CAA8BA,gDAAiB,KAAK;AACtD,CAAC;;;;"}
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ const aggregationFilterSchema = zod.z.object({
6
+ status: zod.z.string().min(1).max(64).optional()
7
+ });
8
+
9
+ exports.aggregationFilterSchema = aggregationFilterSchema;
10
+ //# sourceMappingURL=aggregationFilterSchema.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregationFilterSchema.cjs.js","sources":["../../../src/validation/schemas/aggregationFilterSchema.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';\n\nexport const aggregationFilterSchema = z.object({\n status: z.string().min(1).max(64).optional(),\n});\n"],"names":["z"],"mappings":";;;;AAkBO,MAAM,uBAAA,GAA0BA,MAAE,MAAA,CAAO;AAAA,EAC9C,MAAA,EAAQA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,CAAC,CAAA,CAAE,GAAA,CAAI,EAAE,CAAA,CAAE,QAAA;AACpC,CAAC;;;;"}
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ const aggregationThresholdsConfigSchema = zod.z.object({
6
+ rules: zod.z.array(
7
+ zod.z.object({
8
+ key: zod.z.string(),
9
+ expression: zod.z.string(),
10
+ color: zod.z.string().optional()
11
+ })
12
+ )
13
+ });
14
+
15
+ exports.aggregationThresholdsConfigSchema = aggregationThresholdsConfigSchema;
16
+ //# sourceMappingURL=aggregationThresholdsConfigSchema.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregationThresholdsConfigSchema.cjs.js","sources":["../../../src/validation/schemas/aggregationThresholdsConfigSchema.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';\n\nexport const aggregationThresholdsConfigSchema = z.object({\n rules: z.array(\n z.object({\n key: z.string(),\n expression: z.string(),\n color: z.string().optional(),\n }),\n ),\n});\n"],"names":["z"],"mappings":";;;;AAkBO,MAAM,iCAAA,GAAoCA,MAAE,MAAA,CAAO;AAAA,EACxD,OAAOA,KAAA,CAAE,KAAA;AAAA,IACPA,MAAE,MAAA,CAAO;AAAA,MACP,GAAA,EAAKA,MAAE,MAAA,EAAO;AAAA,MACd,UAAA,EAAYA,MAAE,MAAA,EAAO;AAAA,MACrB,KAAA,EAAOA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAAS,KAC5B;AAAA;AAEL,CAAC;;;;"}
@@ -1,12 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ var errors = require('@backstage/errors');
3
4
  var aggregationKPIs = require('../constants/aggregationKPIs.cjs.js');
4
- var buildAggregationConfig = require('../utils/aggregation/buildAggregationConfig.cjs.js');
5
+ var validateScalarAggregationConfig = require('./validateScalarAggregationConfig.cjs.js');
5
6
  var parseValidatedAggregationConfig = require('../utils/aggregation/parseValidatedAggregationConfig.cjs.js');
6
- var isScalarAggregationType = require('../utils/aggregation/isScalarAggregationType.cjs.js');
7
+ var buildAggregationConfig = require('../utils/aggregation/buildAggregationConfig.cjs.js');
8
+ var isScalarAggregationConfig = require('../utils/aggregation/isScalarAggregationConfig.cjs.js');
7
9
 
8
10
  function validateAggregationConfig(options) {
9
- const { rootConfig, registry } = options;
11
+ const { rootConfig, registry, thresholdResolver } = options;
10
12
  const aggregationKPIsConfig = rootConfig.getOptionalConfig(
11
13
  aggregationKPIs.AGGREGATION_KPIS_CONFIG_PATH
12
14
  );
@@ -21,15 +23,17 @@ function validateAggregationConfig(options) {
21
23
  })
22
24
  );
23
25
  if (!registry.hasProvider(aggregationConfig.metricId)) {
24
- throw new Error(
26
+ throw new errors.InputError(
25
27
  `Metric provider with ID '${aggregationConfig.metricId}' is not registered (${aggregationKPIs.AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}).`
26
28
  );
27
29
  }
28
- const metric = registry.getMetric(aggregationConfig.metricId);
29
- if (isScalarAggregationType.isScalarAggregationType(aggregationConfig.type) && metric.type === "boolean") {
30
- throw new Error(
31
- `Aggregation KPI "${aggregationId}" uses type "${aggregationConfig.type}" which requires a number metric, but "${aggregationConfig.metricId}" is boolean.`
32
- );
30
+ if (isScalarAggregationConfig.isScalarAggregationConfig(aggregationConfig)) {
31
+ validateScalarAggregationConfig.validateScalarAggregationConfig({
32
+ aggregationConfig,
33
+ aggregationId,
34
+ registry,
35
+ thresholdResolver
36
+ });
33
37
  }
34
38
  }
35
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validateAggregationConfig.cjs.js","sources":["../../src/validation/validateAggregationConfig.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../constants';\nimport { buildAggregationConfig } from '../utils/aggregation/buildAggregationConfig';\nimport { parseValidatedAggregationConfig } from '../utils/aggregation/parseValidatedAggregationConfig';\nimport { isScalarAggregationType } from '../utils/aggregation/isScalarAggregationType';\n\nexport function validateAggregationConfig(options: {\n rootConfig: Config;\n registry: MetricProvidersRegistry;\n}): void {\n const { rootConfig, registry } = options;\n\n const aggregationKPIsConfig = rootConfig.getOptionalConfig(\n AGGREGATION_KPIS_CONFIG_PATH,\n );\n\n if (!aggregationKPIsConfig) {\n return;\n }\n\n for (const aggregationId of aggregationKPIsConfig.keys()) {\n const config = aggregationKPIsConfig.getConfig(aggregationId);\n\n const aggregationConfig = parseValidatedAggregationConfig(\n buildAggregationConfig(aggregationId, {\n config,\n }),\n );\n\n if (!registry.hasProvider(aggregationConfig.metricId)) {\n throw new Error(\n `Metric provider with ID '${aggregationConfig.metricId}' is not registered (${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}).`,\n );\n }\n\n const metric = registry.getMetric(aggregationConfig.metricId);\n\n if (\n isScalarAggregationType(aggregationConfig.type) &&\n metric.type === 'boolean'\n ) {\n throw new Error(\n `Aggregation KPI \"${aggregationId}\" uses type \"${aggregationConfig.type}\" which requires a number metric, but \"${aggregationConfig.metricId}\" is boolean.`,\n );\n }\n }\n}\n"],"names":["AGGREGATION_KPIS_CONFIG_PATH","parseValidatedAggregationConfig","buildAggregationConfig","isScalarAggregationType"],"mappings":";;;;;;;AAuBO,SAAS,0BAA0B,OAAA,EAGjC;AACP,EAAA,MAAM,EAAE,UAAA,EAAY,QAAA,EAAS,GAAI,OAAA;AAEjC,EAAA,MAAM,wBAAwB,UAAA,CAAW,iBAAA;AAAA,IACvCA;AAAA,GACF;AAEA,EAAA,IAAI,CAAC,qBAAA,EAAuB;AAC1B,IAAA;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,aAAA,IAAiB,qBAAA,CAAsB,IAAA,EAAK,EAAG;AACxD,IAAA,MAAM,MAAA,GAAS,qBAAA,CAAsB,SAAA,CAAU,aAAa,CAAA;AAE5D,IAAA,MAAM,iBAAA,GAAoBC,+DAAA;AAAA,MACxBC,8CAAuB,aAAA,EAAe;AAAA,QACpC;AAAA,OACD;AAAA,KACH;AAEA,IAAA,IAAI,CAAC,QAAA,CAAS,WAAA,CAAY,iBAAA,CAAkB,QAAQ,CAAA,EAAG;AACrD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,4BAA4B,iBAAA,CAAkB,QAAQ,CAAA,qBAAA,EAAwBF,4CAA4B,IAAI,aAAa,CAAA,EAAA;AAAA,OAC7H;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,SAAA,CAAU,iBAAA,CAAkB,QAAQ,CAAA;AAE5D,IAAA,IACEG,gDAAwB,iBAAA,CAAkB,IAAI,CAAA,IAC9C,MAAA,CAAO,SAAS,SAAA,EAChB;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,oBAAoB,aAAa,CAAA,aAAA,EAAgB,kBAAkB,IAAI,CAAA,uCAAA,EAA0C,kBAAkB,QAAQ,CAAA,aAAA;AAAA,OAC7I;AAAA,IACF;AAAA,EACF;AACF;;;;"}
1
+ {"version":3,"file":"validateAggregationConfig.cjs.js","sources":["../../src/validation/validateAggregationConfig.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 { InputError } from '@backstage/errors';\nimport type { Config } from '@backstage/config';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../constants';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\nimport { validateScalarAggregationConfig } from './validateScalarAggregationConfig';\nimport { parseValidatedAggregationConfig } from '../utils/aggregation/parseValidatedAggregationConfig';\nimport { buildAggregationConfig } from '../utils/aggregation/buildAggregationConfig';\nimport { isScalarAggregationConfig } from '../utils/aggregation/isScalarAggregationConfig';\n\nexport function validateAggregationConfig(options: {\n rootConfig: Config;\n registry: MetricProvidersRegistry;\n thresholdResolver: ThresholdResolver;\n}): void {\n const { rootConfig, registry, thresholdResolver } = options;\n\n const aggregationKPIsConfig = rootConfig.getOptionalConfig(\n AGGREGATION_KPIS_CONFIG_PATH,\n );\n\n if (!aggregationKPIsConfig) {\n return;\n }\n\n for (const aggregationId of aggregationKPIsConfig.keys()) {\n const config = aggregationKPIsConfig.getConfig(aggregationId);\n\n const aggregationConfig = parseValidatedAggregationConfig(\n buildAggregationConfig(aggregationId, {\n config,\n }),\n );\n\n if (!registry.hasProvider(aggregationConfig.metricId)) {\n throw new InputError(\n `Metric provider with ID '${aggregationConfig.metricId}' is not registered (${AGGREGATION_KPIS_CONFIG_PATH}.${aggregationId}).`,\n );\n }\n\n if (isScalarAggregationConfig(aggregationConfig)) {\n validateScalarAggregationConfig({\n aggregationConfig,\n aggregationId,\n registry,\n thresholdResolver,\n });\n }\n }\n}\n"],"names":["AGGREGATION_KPIS_CONFIG_PATH","parseValidatedAggregationConfig","buildAggregationConfig","InputError","isScalarAggregationConfig","validateScalarAggregationConfig"],"mappings":";;;;;;;;;AA0BO,SAAS,0BAA0B,OAAA,EAIjC;AACP,EAAA,MAAM,EAAE,UAAA,EAAY,QAAA,EAAU,iBAAA,EAAkB,GAAI,OAAA;AAEpD,EAAA,MAAM,wBAAwB,UAAA,CAAW,iBAAA;AAAA,IACvCA;AAAA,GACF;AAEA,EAAA,IAAI,CAAC,qBAAA,EAAuB;AAC1B,IAAA;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,aAAA,IAAiB,qBAAA,CAAsB,IAAA,EAAK,EAAG;AACxD,IAAA,MAAM,MAAA,GAAS,qBAAA,CAAsB,SAAA,CAAU,aAAa,CAAA;AAE5D,IAAA,MAAM,iBAAA,GAAoBC,+DAAA;AAAA,MACxBC,8CAAuB,aAAA,EAAe;AAAA,QACpC;AAAA,OACD;AAAA,KACH;AAEA,IAAA,IAAI,CAAC,QAAA,CAAS,WAAA,CAAY,iBAAA,CAAkB,QAAQ,CAAA,EAAG;AACrD,MAAA,MAAM,IAAIC,iBAAA;AAAA,QACR,4BAA4B,iBAAA,CAAkB,QAAQ,CAAA,qBAAA,EAAwBH,4CAA4B,IAAI,aAAa,CAAA,EAAA;AAAA,OAC7H;AAAA,IACF;AAEA,IAAA,IAAII,mDAAA,CAA0B,iBAAiB,CAAA,EAAG;AAChD,MAAAC,+DAAA,CAAgC;AAAA,QAC9B,iBAAA;AAAA,QACA,aAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF;AACF;;;;"}
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var backstagePluginScorecardCommon = require('@red-hat-developer-hub/backstage-plugin-scorecard-common');
5
+ var validateScalarFilterStatus = require('./validateScalarFilterStatus.cjs.js');
6
+
7
+ function validateScalarAggregationConfig(options) {
8
+ const { aggregationConfig, aggregationId, registry, thresholdResolver } = options;
9
+ const metric = registry.getMetric(aggregationConfig.metricId);
10
+ if (metric.type === "boolean" && aggregationConfig.type !== backstagePluginScorecardCommon.aggregationTypes.count) {
11
+ throw new errors.InputError(
12
+ `Aggregation KPI "${aggregationId}" uses type "${aggregationConfig.type}" which requires a number metric, but "${aggregationConfig.metricId}" is boolean.`
13
+ );
14
+ }
15
+ validateScalarFilterStatus.validateScalarFilterStatus({
16
+ aggregationConfig,
17
+ aggregationId,
18
+ registry,
19
+ thresholdResolver
20
+ });
21
+ }
22
+
23
+ exports.validateScalarAggregationConfig = validateScalarAggregationConfig;
24
+ //# sourceMappingURL=validateScalarAggregationConfig.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateScalarAggregationConfig.cjs.js","sources":["../../src/validation/validateScalarAggregationConfig.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 { InputError } from '@backstage/errors';\nimport { aggregationTypes } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { ScalarAggregationConfig } from './schemas/aggregationConfigSchemas';\nimport { validateScalarFilterStatus } from './validateScalarFilterStatus';\n\ntype ValidateScalarAggregationConfigOptions = {\n aggregationConfig: ScalarAggregationConfig;\n aggregationId: string;\n registry: MetricProvidersRegistry;\n thresholdResolver: ThresholdResolver;\n};\n\nexport function validateScalarAggregationConfig(\n options: ValidateScalarAggregationConfigOptions,\n): void {\n const { aggregationConfig, aggregationId, registry, thresholdResolver } =\n options;\n const metric = registry.getMetric(aggregationConfig.metricId);\n\n if (\n metric.type === 'boolean' &&\n aggregationConfig.type !== aggregationTypes.count\n ) {\n throw new InputError(\n `Aggregation KPI \"${aggregationId}\" uses type \"${aggregationConfig.type}\" which requires a number metric, but \"${aggregationConfig.metricId}\" is boolean.`,\n );\n }\n\n validateScalarFilterStatus({\n aggregationConfig,\n aggregationId,\n registry,\n thresholdResolver,\n });\n}\n"],"names":["aggregationTypes","InputError","validateScalarFilterStatus"],"mappings":";;;;;;AA8BO,SAAS,gCACd,OAAA,EACM;AACN,EAAA,MAAM,EAAE,iBAAA,EAAmB,aAAA,EAAe,QAAA,EAAU,mBAAkB,GACpE,OAAA;AACF,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,SAAA,CAAU,iBAAA,CAAkB,QAAQ,CAAA;AAE5D,EAAA,IACE,OAAO,IAAA,KAAS,SAAA,IAChB,iBAAA,CAAkB,IAAA,KAASA,gDAAiB,KAAA,EAC5C;AACA,IAAA,MAAM,IAAIC,iBAAA;AAAA,MACR,oBAAoB,aAAa,CAAA,aAAA,EAAgB,kBAAkB,IAAI,CAAA,uCAAA,EAA0C,kBAAkB,QAAQ,CAAA,aAAA;AAAA,KAC7I;AAAA,EACF;AAEA,EAAAC,qDAAA,CAA2B;AAAA,IACzB,iBAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;;"}
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ function validateScalarFilterStatus(options) {
6
+ const { aggregationConfig, aggregationId, registry, thresholdResolver } = options;
7
+ const status = aggregationConfig.filter?.status;
8
+ if (!status) {
9
+ return;
10
+ }
11
+ const metric = registry.getMetric(aggregationConfig.metricId);
12
+ const thresholds = thresholdResolver.resolveMetricThresholds(metric);
13
+ const validKeys = thresholds.rules.map((rule) => rule.key);
14
+ if (!validKeys.includes(status)) {
15
+ throw new errors.InputError(
16
+ `Aggregation KPI "${aggregationId}" filter.status "${status}" is not a threshold rule key for metric "${aggregationConfig.metricId}". Valid keys: ${validKeys.join(", ")}.`
17
+ );
18
+ }
19
+ }
20
+
21
+ exports.validateScalarFilterStatus = validateScalarFilterStatus;
22
+ //# sourceMappingURL=validateScalarFilterStatus.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateScalarFilterStatus.cjs.js","sources":["../../src/validation/validateScalarFilterStatus.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 { InputError } from '@backstage/errors';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { ThresholdResolver } from '../threshold/ThresholdResolver';\nimport { ScalarAggregationConfig } from './schemas/aggregationConfigSchemas';\n\nexport function validateScalarFilterStatus(options: {\n aggregationConfig: ScalarAggregationConfig;\n aggregationId: string;\n registry: MetricProvidersRegistry;\n thresholdResolver: ThresholdResolver;\n}): void {\n const { aggregationConfig, aggregationId, registry, thresholdResolver } =\n options;\n const status = aggregationConfig.filter?.status;\n\n if (!status) {\n return;\n }\n\n const metric = registry.getMetric(aggregationConfig.metricId);\n const thresholds = thresholdResolver.resolveMetricThresholds(metric);\n const validKeys = thresholds.rules.map(rule => rule.key);\n\n if (!validKeys.includes(status)) {\n throw new InputError(\n `Aggregation KPI \"${aggregationId}\" filter.status \"${status}\" is not a threshold rule key ` +\n `for metric \"${\n aggregationConfig.metricId\n }\". Valid keys: ${validKeys.join(', ')}.`,\n );\n }\n}\n"],"names":["InputError"],"mappings":";;;;AAqBO,SAAS,2BAA2B,OAAA,EAKlC;AACP,EAAA,MAAM,EAAE,iBAAA,EAAmB,aAAA,EAAe,QAAA,EAAU,mBAAkB,GACpE,OAAA;AACF,EAAA,MAAM,MAAA,GAAS,kBAAkB,MAAA,EAAQ,MAAA;AAEzC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,SAAA,CAAU,iBAAA,CAAkB,QAAQ,CAAA;AAC5D,EAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,uBAAA,CAAwB,MAAM,CAAA;AACnE,EAAA,MAAM,YAAY,UAAA,CAAW,KAAA,CAAM,GAAA,CAAI,CAAA,IAAA,KAAQ,KAAK,GAAG,CAAA;AAEvD,EAAA,IAAI,CAAC,SAAA,CAAU,QAAA,CAAS,MAAM,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,iBAAA,EAAoB,aAAa,CAAA,iBAAA,EAAoB,MAAM,CAAA,0CAAA,EAEvD,iBAAA,CAAkB,QACpB,CAAA,eAAA,EAAkB,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,KAC1C;AAAA,EACF;AACF;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
3
- "version": "4.1.0",
3
+ "version": "4.2.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": "^2.2.2",
48
48
  "@backstage/plugin-permission-common": "^0.9.9",
49
49
  "@backstage/plugin-permission-node": "^0.11.1",
50
- "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^4.1.0",
51
- "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^4.1.0",
50
+ "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^4.2.0",
51
+ "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^4.2.0",
52
52
  "express": "^4.17.1",
53
53
  "express-promise-router": "^4.1.0",
54
54
  "knex": "^3.1.0",
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- function toIsoTimestamp(timestamp) {
4
- return timestamp ? new Date(timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
5
- }
6
-
7
- exports.toIsoTimestamp = toIsoTimestamp;
8
- //# sourceMappingURL=toIsoTimestamp.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"toIsoTimestamp.cjs.js","sources":["../../src/utils/toIsoTimestamp.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\nexport function toIsoTimestamp(timestamp?: Date): string {\n return timestamp\n ? new Date(timestamp).toISOString()\n : new Date().toISOString();\n}\n"],"names":[],"mappings":";;AAgBO,SAAS,eAAe,SAAA,EAA0B;AACvD,EAAA,OAAO,SAAA,GACH,IAAI,IAAA,CAAK,SAAS,CAAA,CAAE,aAAY,GAAA,iBAChC,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAC7B;;;;"}