@red-hat-developer-hub/backstage-plugin-scorecard-backend 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-scorecard-backend
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4ecaacd: Add support for batch metric providers, allowing a single provider to handle multiple metrics efficiently. Introduce a new backend module for configurable file existence checks (filecheck.\*) that verify whether required files (like README, LICENSE, or CODEOWNERS) are present in a repository.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [4ecaacd]
12
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@2.6.0
13
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@2.6.0
14
+
15
+ ## 2.5.2
16
+
17
+ ### Patch Changes
18
+
19
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@2.5.2
20
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@2.5.2
21
+
3
22
  ## 2.5.1
4
23
 
5
24
  ### Patch Changes
package/README.md CHANGED
@@ -88,12 +88,13 @@ For more information about schedule configuration options, see the [Metric Colle
88
88
 
89
89
  The following metric providers are available:
90
90
 
91
- | Provider | Metric ID | Title | Description | Type |
92
- | -------------- | ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------ |
93
- | **GitHub** | `github.open_prs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
94
- | **Jira** | `jira.open_issues` | Jira open issues | The number of opened issues in Jira | number |
95
- | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.code_review`, `openssf.maintained`). Each returns a score from 0-10. | number |
96
- | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
91
+ | Provider | Metric ID | Title | Description | Type |
92
+ | -------------- | ------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- |
93
+ | **GitHub** | `github.open_prs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
94
+ | **Filecheck** | `filecheck.*` | File Checks | Checks whether specific files (e.g., `README.md`, `LICENSE`, `CODEOWNERS`) exist in a repository. | boolean |
95
+ | **Jira** | `jira.open_issues` | Jira open issues | The number of opened issues in Jira | number |
96
+ | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.code_review`, `openssf.maintained`). Each returns a score from 0-10. | number |
97
+ | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
97
98
 
98
99
  To use these providers, install the corresponding backend modules:
99
100
 
@@ -101,6 +102,7 @@ To use these providers, install the corresponding backend modules:
101
102
  - Jira: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira`](../scorecard-backend-module-jira/README.md)
102
103
  - OpenSSF: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf`](../scorecard-backend-module-openssf/README.md)
103
104
  - Dependabot: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot`](../scorecard-backend-module-dependabot/README.md)
105
+ - Filecheck: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck`](../scorecard-backend-module-filecheck/README.md)
104
106
 
105
107
  ### Disabling Metrics
106
108
 
@@ -6,44 +6,51 @@ class MetricProvidersRegistry {
6
6
  metricProviders = /* @__PURE__ */ new Map();
7
7
  datasourceIndex = /* @__PURE__ */ new Map();
8
8
  register(metricProvider) {
9
- const providerId = metricProvider.getProviderId();
10
9
  const providerDatasource = metricProvider.getProviderDatasourceId();
11
- const metric = metricProvider.getMetric();
12
10
  const metricType = metricProvider.getMetricType();
13
- if (providerId !== metric.id) {
14
- throw new Error(
15
- `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`
16
- );
17
- }
18
- if (metricType !== metric.type) {
19
- throw new Error(
20
- `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`
21
- );
22
- }
23
- const expectedPrefix = `${providerDatasource}.`;
24
- if (!providerId.startsWith(expectedPrefix) || providerId === expectedPrefix) {
25
- throw new Error(
26
- `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`
27
- );
28
- }
29
- if (this.metricProviders.has(providerId)) {
30
- throw new errors.ConflictError(
31
- `Metric provider with ID '${providerId}' has already been registered`
32
- );
33
- }
34
- this.metricProviders.set(providerId, metricProvider);
35
- let datasourceProviders = this.datasourceIndex.get(providerDatasource);
36
- if (!datasourceProviders) {
37
- datasourceProviders = /* @__PURE__ */ new Set();
38
- this.datasourceIndex.set(providerDatasource, datasourceProviders);
11
+ const metricIds = metricProvider.getMetricIds?.() ?? [
12
+ metricProvider.getProviderId()
13
+ ];
14
+ const metrics = metricProvider.getMetrics?.() ?? [
15
+ metricProvider.getMetric()
16
+ ];
17
+ for (const metricId of metricIds) {
18
+ const metric = metrics.find((m) => m.id === metricId);
19
+ if (!metric) {
20
+ throw new Error(
21
+ `Invalid metric provider: metric ID '${metricId}' returned by getMetricIds() does not have a corresponding metric in getMetrics()`
22
+ );
23
+ }
24
+ if (metricType !== metric.type) {
25
+ throw new Error(
26
+ `Invalid metric provider with ID ${metricId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`
27
+ );
28
+ }
29
+ const expectedPrefix = `${providerDatasource}.`;
30
+ if (!metricId.startsWith(expectedPrefix) || metricId === expectedPrefix) {
31
+ throw new Error(
32
+ `Invalid metric provider with ID ${metricId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`
33
+ );
34
+ }
35
+ if (this.metricProviders.has(metricId)) {
36
+ throw new errors.ConflictError(
37
+ `Metric provider with ID '${metricId}' has already been registered`
38
+ );
39
+ }
40
+ this.metricProviders.set(metricId, metricProvider);
41
+ let datasourceProviders = this.datasourceIndex.get(providerDatasource);
42
+ if (!datasourceProviders) {
43
+ datasourceProviders = /* @__PURE__ */ new Set();
44
+ this.datasourceIndex.set(providerDatasource, datasourceProviders);
45
+ }
46
+ datasourceProviders.add(metricId);
39
47
  }
40
- datasourceProviders.add(providerId);
41
48
  }
42
- getProvider(providerId) {
43
- const metricProvider = this.metricProviders.get(providerId);
49
+ getProvider(metricId) {
50
+ const metricProvider = this.metricProviders.get(metricId);
44
51
  if (!metricProvider) {
45
52
  throw new errors.NotFoundError(
46
- `Metric provider with ID '${providerId}' is not registered.`
53
+ `No metric provider registered for metric ID '${metricId}'.`
47
54
  );
48
55
  }
49
56
  return metricProvider;
@@ -51,33 +58,49 @@ class MetricProvidersRegistry {
51
58
  hasProvider(providerId) {
52
59
  return this.metricProviders.has(providerId);
53
60
  }
54
- getMetric(providerId) {
55
- return this.getProvider(providerId).getMetric();
61
+ getMetric(metricId) {
62
+ const provider = this.getProvider(metricId);
63
+ if (provider.getMetrics) {
64
+ const metrics = provider.getMetrics();
65
+ const metric = metrics.find((m) => m.id === metricId);
66
+ if (metric) {
67
+ return metric;
68
+ }
69
+ }
70
+ return provider.getMetric();
56
71
  }
57
- async calculateMetric(providerId, entity) {
58
- return this.getProvider(providerId).calculateMetric(entity);
72
+ async calculateMetric(metricId, entity) {
73
+ return this.getProvider(metricId).calculateMetric(entity);
59
74
  }
60
- async calculateMetrics(providerIds, entity) {
75
+ async calculateMetrics(metricIds, entity) {
61
76
  const results = await Promise.allSettled(
62
- providerIds.map((providerId) => this.calculateMetric(providerId, entity))
77
+ metricIds.map((metricId) => this.calculateMetric(metricId, entity))
63
78
  );
64
79
  return results.map((result, index) => {
65
- const providerId = providerIds[index];
80
+ const metricId = metricIds[index];
66
81
  if (result.status === "fulfilled") {
67
- return { providerId, value: result.value };
82
+ return { metricId, value: result.value };
68
83
  }
69
- return { providerId, error: result.reason };
84
+ return { metricId, error: result.reason };
70
85
  });
71
86
  }
72
87
  listProviders() {
73
- return Array.from(this.metricProviders.values());
88
+ return [...new Set(this.metricProviders.values())];
74
89
  }
75
- listMetrics(providerIds) {
76
- if (providerIds && providerIds.length !== 0) {
77
- return providerIds.map((providerId) => this.metricProviders.get(providerId)?.getMetric()).filter((m) => m !== void 0);
90
+ listMetrics(metricIds) {
91
+ if (metricIds && metricIds.length !== 0) {
92
+ return metricIds.map((metricId) => {
93
+ const provider = this.metricProviders.get(metricId);
94
+ if (!provider) return void 0;
95
+ if (provider.getMetrics) {
96
+ const metrics = provider.getMetrics();
97
+ return metrics.find((m) => m.id === metricId);
98
+ }
99
+ return provider.getMetric();
100
+ }).filter((m) => m !== void 0);
78
101
  }
79
- return [...this.metricProviders.values()].map(
80
- (provider) => provider.getMetric()
102
+ return this.listProviders().flatMap(
103
+ (provider) => provider.getMetrics?.() ?? [provider.getMetric()]
81
104
  );
82
105
  }
83
106
  listMetricsByDatasource(datasourceId) {
@@ -85,7 +108,10 @@ class MetricProvidersRegistry {
85
108
  if (!providerIdsOfDatasource) {
86
109
  return [];
87
110
  }
88
- return Array.from(providerIdsOfDatasource).map((providerId) => this.metricProviders.get(providerId)).filter((provider) => provider !== void 0).map((provider) => provider.getMetric());
111
+ const providers = [...providerIdsOfDatasource].map((id) => this.metricProviders.get(id)).filter((p) => p !== void 0);
112
+ return [...new Set(providers)].flatMap(
113
+ (provider) => provider.getMetrics?.() ?? [provider.getMetric()]
114
+ );
89
115
  }
90
116
  }
91
117
 
@@ -1 +1 @@
1
- {"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerId = metricProvider.getProviderId();\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metric = metricProvider.getMetric();\n const metricType = metricProvider.getMetricType();\n\n if (providerId !== metric.id) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, provider ID must match metric ID '${metric.id}'`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, getMetricType() must match getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n const expectedPrefix = `${providerDatasource}.`;\n if (\n !providerId.startsWith(expectedPrefix) ||\n providerId === expectedPrefix\n ) {\n throw new Error(\n `Invalid metric provider with ID ${providerId}, must have format '${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(providerId)) {\n throw new ConflictError(\n `Metric provider with ID '${providerId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(providerId, metricProvider);\n\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(providerId);\n }\n\n getProvider(providerId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(providerId);\n if (!metricProvider) {\n throw new NotFoundError(\n `Metric provider with ID '${providerId}' is not registered.`,\n );\n }\n return metricProvider;\n }\n\n hasProvider(providerId: string): boolean {\n return this.metricProviders.has(providerId);\n }\n\n getMetric(providerId: string): Metric {\n return this.getProvider(providerId).getMetric();\n }\n\n async calculateMetric(\n providerId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(providerId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n providerIds: string[],\n entity: Entity,\n ): Promise<{ providerId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n providerIds.map(providerId => this.calculateMetric(providerId, entity)),\n );\n\n return results.map((result, index) => {\n const providerId = providerIds[index];\n if (result.status === 'fulfilled') {\n return { providerId, value: result.value };\n }\n return { providerId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n return Array.from(this.metricProviders.values());\n }\n\n listMetrics(providerIds?: string[]): Metric[] {\n if (providerIds && providerIds.length !== 0) {\n return providerIds\n .map(providerId => this.metricProviders.get(providerId)?.getMetric())\n .filter((m): m is Metric => m !== undefined);\n }\n return [...this.metricProviders.values()].map(provider =>\n provider.getMetric(),\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n return Array.from(providerIdsOfDatasource)\n .map(providerId => this.metricProviders.get(providerId))\n .filter((provider): provider is MetricProvider => provider !== undefined)\n .map(provider => provider.getMetric());\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":"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"}
1
+ {"version":3,"file":"MetricProvidersRegistry.cjs.js","sources":["../../src/providers/MetricProvidersRegistry.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n MetricValue,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport type { Entity } from '@backstage/catalog-model';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Registry of all registered metric providers.\n */\nexport class MetricProvidersRegistry {\n private readonly metricProviders = new Map<string, MetricProvider>();\n private readonly datasourceIndex = new Map<string, Set<string>>();\n\n register(metricProvider: MetricProvider): void {\n const providerDatasource = metricProvider.getProviderDatasourceId();\n const metricType = metricProvider.getMetricType();\n\n // Support both single and batch providers\n const metricIds = metricProvider.getMetricIds?.() ?? [\n metricProvider.getProviderId(),\n ];\n const metrics = metricProvider.getMetrics?.() ?? [\n metricProvider.getMetric(),\n ];\n\n // Validate: Each metric ID must have a corresponding metric definition\n for (const metricId of metricIds) {\n const metric = metrics.find(m => m.id === metricId);\n if (!metric) {\n throw new Error(\n `Invalid metric provider: metric ID '${metricId}' returned by getMetricIds() ` +\n `does not have a corresponding metric in getMetrics()`,\n );\n }\n\n if (metricType !== metric.type) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, getMetricType() must match ` +\n `getMetric().type. Expected '${metricType}', but got '${metric.type}'`,\n );\n }\n\n // Validate: Provider ID format (datasource.metric_name)\n const expectedPrefix = `${providerDatasource}.`;\n if (!metricId.startsWith(expectedPrefix) || metricId === expectedPrefix) {\n throw new Error(\n `Invalid metric provider with ID ${metricId}, must have format ` +\n `'${providerDatasource}.<metric_name>' where metric name is not empty`,\n );\n }\n\n if (this.metricProviders.has(metricId)) {\n throw new ConflictError(\n `Metric provider with ID '${metricId}' has already been registered`,\n );\n }\n\n this.metricProviders.set(metricId, metricProvider);\n\n // Index by datasource\n let datasourceProviders = this.datasourceIndex.get(providerDatasource);\n if (!datasourceProviders) {\n datasourceProviders = new Set();\n this.datasourceIndex.set(providerDatasource, datasourceProviders);\n }\n datasourceProviders.add(metricId);\n }\n }\n\n getProvider(metricId: string): MetricProvider {\n const metricProvider = this.metricProviders.get(metricId);\n if (!metricProvider) {\n throw new NotFoundError(\n `No metric provider registered for metric ID '${metricId}'.`,\n );\n }\n return metricProvider;\n }\n\n hasProvider(providerId: string): boolean {\n return this.metricProviders.has(providerId);\n }\n\n getMetric(metricId: string): Metric {\n const provider = this.getProvider(metricId);\n\n // For batch providers, find the specific metric by ID\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n const metric = metrics.find(m => m.id === metricId);\n if (metric) {\n return metric;\n }\n }\n\n return provider.getMetric();\n }\n\n async calculateMetric(\n metricId: string,\n entity: Entity,\n ): Promise<MetricValue> {\n return this.getProvider(metricId).calculateMetric(entity);\n }\n\n async calculateMetrics(\n metricIds: string[],\n entity: Entity,\n ): Promise<{ metricId: string; value?: MetricValue; error?: Error }[]> {\n const results = await Promise.allSettled(\n metricIds.map(metricId => this.calculateMetric(metricId, entity)),\n );\n\n return results.map((result, index) => {\n const metricId = metricIds[index];\n if (result.status === 'fulfilled') {\n return { metricId, value: result.value };\n }\n return { metricId, error: result.reason as Error };\n });\n }\n\n listProviders(): MetricProvider[] {\n // Deduplicate providers since batch providers are stored under multiple metric IDs\n return [...new Set(this.metricProviders.values())];\n }\n\n listMetrics(metricIds?: string[]): Metric[] {\n if (metricIds && metricIds.length !== 0) {\n return metricIds\n .map(metricId => {\n const provider = this.metricProviders.get(metricId);\n if (!provider) return undefined;\n\n if (provider.getMetrics) {\n const metrics = provider.getMetrics();\n return metrics.find(m => m.id === metricId);\n }\n\n return provider.getMetric();\n })\n .filter((m): m is Metric => m !== undefined);\n }\n\n // List all metrics from all providers (deduplicate batch providers)\n return this.listProviders().flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n\n listMetricsByDatasource(datasourceId: string): Metric[] {\n const providerIdsOfDatasource = this.datasourceIndex.get(datasourceId);\n\n if (!providerIdsOfDatasource) {\n return [];\n }\n\n // Get unique providers for this datasource, then get their metrics\n const providers = [...providerIdsOfDatasource]\n .map(id => this.metricProviders.get(id))\n .filter((p): p is MetricProvider => p !== undefined);\n\n return [...new Set(providers)].flatMap(\n provider => provider.getMetrics?.() ?? [provider.getMetric()],\n );\n }\n}\n"],"names":["ConflictError","NotFoundError"],"mappings":"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"}
@@ -67,6 +67,8 @@ class PullMetricsByProviderTask {
67
67
  let totalProcessed = 0;
68
68
  let cursor = void 0;
69
69
  const metricType = provider.getMetricType();
70
+ const isBatchProvider = typeof provider.calculateMetrics === "function";
71
+ const metricIds = provider.getMetricIds?.() ?? [provider.getProviderId()];
70
72
  try {
71
73
  do {
72
74
  const entitiesResponse = await this.catalog.queryEntities(
@@ -80,6 +82,81 @@ class PullMetricsByProviderTask {
80
82
  cursor = entitiesResponse.pageInfo.nextCursor;
81
83
  const batchResults = await Promise.allSettled(
82
84
  entitiesResponse.items.map(async (entity) => {
85
+ if (isBatchProvider && provider.calculateMetrics) {
86
+ const entityRef = catalogModel.stringifyEntityRef(entity);
87
+ const entityKind = normalizeField(entity.kind);
88
+ const entityNamespace = normalizeField(entity.metadata.namespace);
89
+ const entityOwner = normalizeOwnerRef.normalizeOwnerRef(entity?.spec?.owner);
90
+ const enabledMetricIds = metricIds.filter(
91
+ (metricId) => !metricUtils.isMetricIdDisabled(this.config, metricId, entity, logger)
92
+ );
93
+ if (enabledMetricIds.length === 0) {
94
+ return void 0;
95
+ }
96
+ try {
97
+ const resultsMap = await provider.calculateMetrics(entity);
98
+ return enabledMetricIds.map((metricId) => {
99
+ if (!resultsMap.has(metricId)) {
100
+ return {
101
+ catalog_entity_ref: entityRef,
102
+ metric_id: metricId,
103
+ value: void 0,
104
+ timestamp: /* @__PURE__ */ new Date(),
105
+ error_message: `calculateMetrics() did not return an entry for metric '${metricId}'`,
106
+ entity_kind: entityKind,
107
+ entity_namespace: entityNamespace,
108
+ entity_owner: entityOwner
109
+ };
110
+ }
111
+ const value2 = resultsMap.get(metricId);
112
+ try {
113
+ const thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(
114
+ entity,
115
+ provider
116
+ );
117
+ const status = this.thresholdEvaluator.getFirstMatchingThreshold(
118
+ value2,
119
+ metricType,
120
+ thresholds
121
+ );
122
+ return {
123
+ catalog_entity_ref: entityRef,
124
+ metric_id: metricId,
125
+ value: value2,
126
+ timestamp: /* @__PURE__ */ new Date(),
127
+ status,
128
+ entity_kind: entityKind,
129
+ entity_namespace: entityNamespace,
130
+ entity_owner: entityOwner
131
+ };
132
+ } catch (error) {
133
+ return {
134
+ catalog_entity_ref: entityRef,
135
+ metric_id: metricId,
136
+ value: value2,
137
+ timestamp: /* @__PURE__ */ new Date(),
138
+ error_message: error instanceof Error ? error.message : String(error),
139
+ entity_kind: entityKind,
140
+ entity_namespace: entityNamespace,
141
+ entity_owner: entityOwner
142
+ };
143
+ }
144
+ });
145
+ } catch (error) {
146
+ return enabledMetricIds.map(
147
+ (metricId) => ({
148
+ catalog_entity_ref: entityRef,
149
+ metric_id: metricId,
150
+ value: void 0,
151
+ timestamp: /* @__PURE__ */ new Date(),
152
+ error_message: error instanceof Error ? error.message : String(error),
153
+ entity_kind: entityKind,
154
+ entity_namespace: entityNamespace,
155
+ entity_owner: entityOwner
156
+ })
157
+ );
158
+ }
159
+ }
83
160
  let value;
84
161
  try {
85
162
  if (metricUtils.isMetricIdDisabled(
@@ -111,6 +188,12 @@ class PullMetricsByProviderTask {
111
188
  entity_owner: normalizeOwnerRef.normalizeOwnerRef(entity?.spec?.owner)
112
189
  };
113
190
  } catch (error) {
191
+ logger.warn(
192
+ `Failed to calculate metric for entity ${catalogModel.stringifyEntityRef(
193
+ entity
194
+ )}: ${error}`,
195
+ error instanceof Error ? error : void 0
196
+ );
114
197
  return {
115
198
  catalog_entity_ref: catalogModel.stringifyEntityRef(entity),
116
199
  metric_id: this.providerId,
@@ -126,11 +209,21 @@ class PullMetricsByProviderTask {
126
209
  ).then(
127
210
  (promises) => promises.reduce((acc, curr) => {
128
211
  if (curr.status === "fulfilled" && curr.value !== void 0) {
129
- return [...acc, curr.value];
212
+ const result = curr.value;
213
+ if (Array.isArray(result)) {
214
+ return [...acc, ...result];
215
+ }
216
+ return [...acc, result];
130
217
  }
131
218
  return acc;
132
219
  }, [])
133
220
  );
221
+ if (batchResults.length > 0) {
222
+ const errorCount = batchResults.filter((r) => r.error_message).length;
223
+ logger.debug(
224
+ `Storing ${batchResults.length} metric values (${errorCount} errors)`
225
+ );
226
+ }
134
227
  await this.database.createMetricValues(batchResults);
135
228
  totalProcessed += entitiesResponse.items.length;
136
229
  } while (cursor !== void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { isMetricIdDisabled } from '../../utils/metricUtils';\nimport { normalizeOwnerRef } from '../../utils/normalizeOwnerRef';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n let value: MetricValue | undefined;\n\n try {\n if (\n isMetricIdDisabled(\n this.config,\n provider.getProviderId(),\n entity,\n logger,\n )\n ) {\n return undefined;\n }\n\n value = await provider.calculateMetric(entity);\n\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n } catch (error) {\n // status is intentionally omitted — a calculation failure produces a NULL status\n // in the database, which sorts last when sortBy=status is used\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled' && curr.value !== undefined) {\n return [...acc, curr.value];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n\nfunction normalizeField(field: unknown): string | undefined {\n if (typeof field !== 'string') return undefined;\n const normalized = field.trim().toLowerCase();\n if (!normalized) return undefined;\n\n // Prevent DB insertion failures (limits column length to 255 characters)\n return normalized.length <= 255 ? normalized : normalized.slice(0, 255);\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","isMetricIdDisabled","mergeEntityAndProviderThresholds","stringifyEntityRef","normalizeOwnerRef"],"mappings":"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"}
1
+ {"version":3,"file":"PullMetricsByProviderTask.cjs.js","sources":["../../../src/scheduler/tasks/PullMetricsByProviderTask.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseMetricValues } from '../../database/DatabaseMetricValues';\nimport {\n AuthService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { mergeEntityAndProviderThresholds } from '../../utils/mergeEntityAndProviderThresholds';\nimport { isMetricIdDisabled } from '../../utils/metricUtils';\nimport { normalizeOwnerRef } from '../../utils/normalizeOwnerRef';\nimport { v4 as uuid } from 'uuid';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DbMetricValueCreate } from '../../database/types';\nimport { SchedulerOptions, SchedulerTask } from '../types';\nimport { ThresholdEvaluator } from '../../threshold/ThresholdEvaluator';\nimport { MetricValue } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\ntype Options = Pick<\n SchedulerOptions,\n | 'scheduler'\n | 'logger'\n | 'database'\n | 'config'\n | 'catalog'\n | 'auth'\n | 'thresholdEvaluator'\n>;\n\nexport class PullMetricsByProviderTask implements SchedulerTask {\n private readonly config: Config;\n private readonly auth: AuthService;\n private readonly providerId: string;\n private readonly logger: LoggerService;\n private readonly catalog: CatalogService;\n private readonly provider: MetricProvider;\n private readonly scheduler: SchedulerService;\n private readonly database: DatabaseMetricValues;\n private readonly thresholdEvaluator: ThresholdEvaluator;\n\n private static readonly CATALOG_BATCH_SIZE = 50;\n\n private static readonly DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: { hours: 1 },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n };\n\n constructor(options: Options, provider: MetricProvider) {\n this.config = options.config;\n this.auth = options.auth;\n this.providerId = provider.getProviderId();\n this.logger = options.logger;\n this.catalog = options.catalog;\n this.provider = provider;\n this.scheduler = options.scheduler;\n this.database = options.database;\n this.thresholdEvaluator = options.thresholdEvaluator;\n }\n\n async start(): Promise<void> {\n const scheduleConfigPath = `scorecard.plugins.${this.providerId}.schedule`;\n const schedule = this.getScheduleFromConfig(scheduleConfigPath);\n\n const taskRunner = this.scheduler.createScheduledTaskRunner(schedule);\n\n await taskRunner.run({\n id: this.providerId,\n fn: async () => {\n const logger = this.logger.child({\n class: this.constructor.name,\n taskId: this.providerId,\n taskInstanceId: uuid(),\n });\n\n try {\n await this.pullProviderMetrics(this.provider, logger);\n } catch (error) {\n logger.error(\n `${this.providerId} pulling metrics failed, ${error}`,\n error,\n );\n }\n },\n });\n }\n\n private getScheduleFromConfig(\n schedulePath: string,\n ): SchedulerServiceTaskScheduleDefinition {\n return this.config.has(schedulePath)\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n this.config.getConfig(schedulePath),\n )\n : PullMetricsByProviderTask.DEFAULT_SCHEDULE;\n }\n\n private async pullProviderMetrics(\n provider: MetricProvider,\n logger: LoggerService,\n ): Promise<void> {\n logger.info(`Pulling metrics for ${this.providerId}`);\n\n let totalProcessed = 0;\n let cursor: string | undefined = undefined;\n\n const metricType = provider.getMetricType();\n const isBatchProvider = typeof provider.calculateMetrics === 'function';\n const metricIds = provider.getMetricIds?.() ?? [provider.getProviderId()];\n\n try {\n do {\n const entitiesResponse = await this.catalog.queryEntities(\n {\n filter: provider.getCatalogFilter(),\n limit: PullMetricsByProviderTask.CATALOG_BATCH_SIZE,\n ...(cursor ? { cursor } : {}),\n },\n { credentials: await this.auth.getOwnServiceCredentials() },\n );\n\n cursor = entitiesResponse.pageInfo.nextCursor;\n\n const batchResults = await Promise.allSettled(\n entitiesResponse.items.map(async entity => {\n // Handle batch providers\n if (isBatchProvider && provider.calculateMetrics) {\n const entityRef = stringifyEntityRef(entity);\n const entityKind = normalizeField(entity.kind);\n const entityNamespace = normalizeField(entity.metadata.namespace);\n const entityOwner = normalizeOwnerRef(entity?.spec?.owner);\n\n const enabledMetricIds = metricIds.filter(\n metricId =>\n !isMetricIdDisabled(this.config, metricId, entity, logger),\n );\n\n if (enabledMetricIds.length === 0) {\n return undefined;\n }\n\n try {\n const resultsMap = await provider.calculateMetrics(entity);\n\n return enabledMetricIds.map(metricId => {\n if (!resultsMap.has(metricId)) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message: `calculateMetrics() did not return an entry for metric '${metricId}'`,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n\n const value = resultsMap.get(metricId) as MetricValue;\n\n try {\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status =\n this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n } catch (error) {\n return {\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate;\n }\n });\n } catch (error) {\n return enabledMetricIds.map(\n metricId =>\n ({\n catalog_entity_ref: entityRef,\n metric_id: metricId,\n value: undefined,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: entityKind,\n entity_namespace: entityNamespace,\n entity_owner: entityOwner,\n } as DbMetricValueCreate),\n );\n }\n }\n\n let value: MetricValue | undefined;\n\n try {\n if (\n isMetricIdDisabled(\n this.config,\n provider.getProviderId(),\n entity,\n logger,\n )\n ) {\n return undefined;\n }\n\n value = await provider.calculateMetric(entity);\n\n const thresholds = mergeEntityAndProviderThresholds(\n entity,\n provider,\n );\n\n const status = this.thresholdEvaluator.getFirstMatchingThreshold(\n value,\n metricType,\n thresholds,\n );\n\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n status,\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n } catch (error) {\n // status is intentionally omitted — a calculation failure produces a NULL status\n // in the database, which sorts last when sortBy=status is used\n logger.warn(\n `Failed to calculate metric for entity ${stringifyEntityRef(\n entity,\n )}: ${error}`,\n error instanceof Error ? error : undefined,\n );\n return {\n catalog_entity_ref: stringifyEntityRef(entity),\n metric_id: this.providerId,\n value,\n timestamp: new Date(),\n error_message:\n error instanceof Error ? error.message : String(error),\n entity_kind: normalizeField(entity.kind),\n entity_namespace: normalizeField(entity.metadata.namespace),\n entity_owner: normalizeOwnerRef(entity?.spec?.owner),\n } as DbMetricValueCreate;\n }\n }),\n ).then(promises =>\n promises.reduce((acc, curr) => {\n if (curr.status === 'fulfilled' && curr.value !== undefined) {\n // Batch providers return an array of results, single providers return one result\n const result = curr.value;\n if (Array.isArray(result)) {\n return [...acc, ...result];\n }\n return [...acc, result];\n }\n return acc;\n }, [] as DbMetricValueCreate[]),\n );\n\n if (batchResults.length > 0) {\n const errorCount = batchResults.filter(r => r.error_message).length;\n logger.debug(\n `Storing ${batchResults.length} metric values (${errorCount} errors)`,\n );\n }\n\n await this.database.createMetricValues(batchResults);\n totalProcessed += entitiesResponse.items.length;\n } while (cursor !== undefined);\n\n logger.info(\n `Completed metric pull for ${this.providerId}: processed ${totalProcessed} entities`,\n );\n } catch (error) {\n logger.error(`Failed to pull metrics for ${this.providerId}: ${error}`);\n\n throw error;\n }\n }\n}\n\nfunction normalizeField(field: unknown): string | undefined {\n if (typeof field !== 'string') return undefined;\n const normalized = field.trim().toLowerCase();\n if (!normalized) return undefined;\n\n // Prevent DB insertion failures (limits column length to 255 characters)\n return normalized.length <= 255 ? normalized : normalized.slice(0, 255);\n}\n"],"names":["uuid","readSchedulerServiceTaskScheduleDefinitionFromConfig","stringifyEntityRef","normalizeOwnerRef","isMetricIdDisabled","value","mergeEntityAndProviderThresholds"],"mappings":";;;;;;;;;AAgDO,MAAM,yBAAmD,CAAA;AAAA,EAC7C,MAAA;AAAA,EACA,IAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,kBAAA;AAAA,EAEjB,OAAwB,kBAAqB,GAAA,EAAA;AAAA,EAE7C,OAAwB,gBACtB,GAAA;AAAA,IACE,SAAA,EAAW,EAAE,KAAA,EAAO,CAAE,EAAA;AAAA,IACtB,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,IACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE;AAAA,GAC7B;AAAA,EAEF,WAAA,CAAY,SAAkB,QAA0B,EAAA;AACtD,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAK,IAAA,CAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AACzC,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,qBAAqB,OAAQ,CAAA,kBAAA;AAAA;AACpC,EAEA,MAAM,KAAuB,GAAA;AAC3B,IAAM,MAAA,kBAAA,GAAqB,CAAqB,kBAAA,EAAA,IAAA,CAAK,UAAU,CAAA,SAAA,CAAA;AAC/D,IAAM,MAAA,QAAA,GAAW,IAAK,CAAA,qBAAA,CAAsB,kBAAkB,CAAA;AAE9D,IAAA,MAAM,UAAa,GAAA,IAAA,CAAK,SAAU,CAAA,yBAAA,CAA0B,QAAQ,CAAA;AAEpE,IAAA,MAAM,WAAW,GAAI,CAAA;AAAA,MACnB,IAAI,IAAK,CAAA,UAAA;AAAA,MACT,IAAI,YAAY;AACd,QAAM,MAAA,MAAA,GAAS,IAAK,CAAA,MAAA,CAAO,KAAM,CAAA;AAAA,UAC/B,KAAA,EAAO,KAAK,WAAY,CAAA,IAAA;AAAA,UACxB,QAAQ,IAAK,CAAA,UAAA;AAAA,UACb,gBAAgBA,OAAK;AAAA,SACtB,CAAA;AAED,QAAI,IAAA;AACF,UAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,IAAK,CAAA,QAAA,EAAU,MAAM,CAAA;AAAA,iBAC7C,KAAO,EAAA;AACd,UAAO,MAAA,CAAA,KAAA;AAAA,YACL,CAAG,EAAA,IAAA,CAAK,UAAU,CAAA,yBAAA,EAA4B,KAAK,CAAA,CAAA;AAAA,YACnD;AAAA,WACF;AAAA;AACF;AACF,KACD,CAAA;AAAA;AACH,EAEQ,sBACN,YACwC,EAAA;AACxC,IAAA,OAAO,IAAK,CAAA,MAAA,CAAO,GAAI,CAAA,YAAY,CAC/B,GAAAC,qEAAA;AAAA,MACE,IAAA,CAAK,MAAO,CAAA,SAAA,CAAU,YAAY;AAAA,QAEpC,yBAA0B,CAAA,gBAAA;AAAA;AAChC,EAEA,MAAc,mBACZ,CAAA,QAAA,EACA,MACe,EAAA;AACf,IAAA,MAAA,CAAO,IAAK,CAAA,CAAA,oBAAA,EAAuB,IAAK,CAAA,UAAU,CAAE,CAAA,CAAA;AAEpD,IAAA,IAAI,cAAiB,GAAA,CAAA;AACrB,IAAA,IAAI,MAA6B,GAAA,MAAA;AAEjC,IAAM,MAAA,UAAA,GAAa,SAAS,aAAc,EAAA;AAC1C,IAAM,MAAA,eAAA,GAAkB,OAAO,QAAA,CAAS,gBAAqB,KAAA,UAAA;AAC7D,IAAA,MAAM,YAAY,QAAS,CAAA,YAAA,QAAoB,CAAC,QAAA,CAAS,eAAe,CAAA;AAExE,IAAI,IAAA;AACF,MAAG,GAAA;AACD,QAAM,MAAA,gBAAA,GAAmB,MAAM,IAAA,CAAK,OAAQ,CAAA,aAAA;AAAA,UAC1C;AAAA,YACE,MAAA,EAAQ,SAAS,gBAAiB,EAAA;AAAA,YAClC,OAAO,yBAA0B,CAAA,kBAAA;AAAA,YACjC,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,WAC7B;AAAA,UACA,EAAE,WAAa,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,0BAA2B;AAAA,SAC5D;AAEA,QAAA,MAAA,GAAS,iBAAiB,QAAS,CAAA,UAAA;AAEnC,QAAM,MAAA,YAAA,GAAe,MAAM,OAAQ,CAAA,UAAA;AAAA,UACjC,gBAAiB,CAAA,KAAA,CAAM,GAAI,CAAA,OAAM,MAAU,KAAA;AAEzC,YAAI,IAAA,eAAA,IAAmB,SAAS,gBAAkB,EAAA;AAChD,cAAM,MAAA,SAAA,GAAYC,gCAAmB,MAAM,CAAA;AAC3C,cAAM,MAAA,UAAA,GAAa,cAAe,CAAA,MAAA,CAAO,IAAI,CAAA;AAC7C,cAAA,MAAM,eAAkB,GAAA,cAAA,CAAe,MAAO,CAAA,QAAA,CAAS,SAAS,CAAA;AAChE,cAAA,MAAM,WAAc,GAAAC,mCAAA,CAAkB,MAAQ,EAAA,IAAA,EAAM,KAAK,CAAA;AAEzD,cAAA,MAAM,mBAAmB,SAAU,CAAA,MAAA;AAAA,gBACjC,cACE,CAACC,8BAAA,CAAmB,KAAK,MAAQ,EAAA,QAAA,EAAU,QAAQ,MAAM;AAAA,eAC7D;AAEA,cAAI,IAAA,gBAAA,CAAiB,WAAW,CAAG,EAAA;AACjC,gBAAO,OAAA,KAAA,CAAA;AAAA;AAGT,cAAI,IAAA;AACF,gBAAA,MAAM,UAAa,GAAA,MAAM,QAAS,CAAA,gBAAA,CAAiB,MAAM,CAAA;AAEzD,gBAAO,OAAA,gBAAA,CAAiB,IAAI,CAAY,QAAA,KAAA;AACtC,kBAAA,IAAI,CAAC,UAAA,CAAW,GAAI,CAAA,QAAQ,CAAG,EAAA;AAC7B,oBAAO,OAAA;AAAA,sBACL,kBAAoB,EAAA,SAAA;AAAA,sBACpB,SAAW,EAAA,QAAA;AAAA,sBACX,KAAO,EAAA,KAAA,CAAA;AAAA,sBACP,SAAA,sBAAe,IAAK,EAAA;AAAA,sBACpB,aAAA,EAAe,0DAA0D,QAAQ,CAAA,CAAA,CAAA;AAAA,sBACjF,WAAa,EAAA,UAAA;AAAA,sBACb,gBAAkB,EAAA,eAAA;AAAA,sBAClB,YAAc,EAAA;AAAA,qBAChB;AAAA;AAGF,kBAAMC,MAAAA,MAAAA,GAAQ,UAAW,CAAA,GAAA,CAAI,QAAQ,CAAA;AAErC,kBAAI,IAAA;AACF,oBAAA,MAAM,UAAa,GAAAC,iEAAA;AAAA,sBACjB,MAAA;AAAA,sBACA;AAAA,qBACF;AAEA,oBAAM,MAAA,MAAA,GACJ,KAAK,kBAAmB,CAAA,yBAAA;AAAA,sBACtBD,MAAAA;AAAA,sBACA,UAAA;AAAA,sBACA;AAAA,qBACF;AAEF,oBAAO,OAAA;AAAA,sBACL,kBAAoB,EAAA,SAAA;AAAA,sBACpB,SAAW,EAAA,QAAA;AAAA,sBACX,KAAAA,EAAAA,MAAAA;AAAA,sBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,sBACpB,MAAA;AAAA,sBACA,WAAa,EAAA,UAAA;AAAA,sBACb,gBAAkB,EAAA,eAAA;AAAA,sBAClB,YAAc,EAAA;AAAA,qBAChB;AAAA,2BACO,KAAO,EAAA;AACd,oBAAO,OAAA;AAAA,sBACL,kBAAoB,EAAA,SAAA;AAAA,sBACpB,SAAW,EAAA,QAAA;AAAA,sBACX,KAAAA,EAAAA,MAAAA;AAAA,sBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,sBACpB,eACE,KAAiB,YAAA,KAAA,GAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK,CAAA;AAAA,sBACvD,WAAa,EAAA,UAAA;AAAA,sBACb,gBAAkB,EAAA,eAAA;AAAA,sBAClB,YAAc,EAAA;AAAA,qBAChB;AAAA;AACF,iBACD,CAAA;AAAA,uBACM,KAAO,EAAA;AACd,gBAAA,OAAO,gBAAiB,CAAA,GAAA;AAAA,kBACtB,CACG,QAAA,MAAA;AAAA,oBACC,kBAAoB,EAAA,SAAA;AAAA,oBACpB,SAAW,EAAA,QAAA;AAAA,oBACX,KAAO,EAAA,KAAA,CAAA;AAAA,oBACP,SAAA,sBAAe,IAAK,EAAA;AAAA,oBACpB,eACE,KAAiB,YAAA,KAAA,GAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK,CAAA;AAAA,oBACvD,WAAa,EAAA,UAAA;AAAA,oBACb,gBAAkB,EAAA,eAAA;AAAA,oBAClB,YAAc,EAAA;AAAA,mBAChB;AAAA,iBACJ;AAAA;AACF;AAGF,YAAI,IAAA,KAAA;AAEJ,YAAI,IAAA;AACF,cACE,IAAAD,8BAAA;AAAA,gBACE,IAAK,CAAA,MAAA;AAAA,gBACL,SAAS,aAAc,EAAA;AAAA,gBACvB,MAAA;AAAA,gBACA;AAAA,eAEF,EAAA;AACA,gBAAO,OAAA,KAAA,CAAA;AAAA;AAGT,cAAQ,KAAA,GAAA,MAAM,QAAS,CAAA,eAAA,CAAgB,MAAM,CAAA;AAE7C,cAAA,MAAM,UAAa,GAAAE,iEAAA;AAAA,gBACjB,MAAA;AAAA,gBACA;AAAA,eACF;AAEA,cAAM,MAAA,MAAA,GAAS,KAAK,kBAAmB,CAAA,yBAAA;AAAA,gBACrC,KAAA;AAAA,gBACA,UAAA;AAAA,gBACA;AAAA,eACF;AAEA,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBJ,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB,MAAA;AAAA,gBACA,WAAA,EAAa,cAAe,CAAA,MAAA,CAAO,IAAI,CAAA;AAAA,gBACvC,gBAAkB,EAAA,cAAA,CAAe,MAAO,CAAA,QAAA,CAAS,SAAS,CAAA;AAAA,gBAC1D,YAAc,EAAAC,mCAAA,CAAkB,MAAQ,EAAA,IAAA,EAAM,KAAK;AAAA,eACrD;AAAA,qBACO,KAAO,EAAA;AAGd,cAAO,MAAA,CAAA,IAAA;AAAA,gBACL,CAAyC,sCAAA,EAAAD,+BAAA;AAAA,kBACvC;AAAA,iBACD,KAAK,KAAK,CAAA,CAAA;AAAA,gBACX,KAAA,YAAiB,QAAQ,KAAQ,GAAA,KAAA;AAAA,eACnC;AACA,cAAO,OAAA;AAAA,gBACL,kBAAA,EAAoBA,gCAAmB,MAAM,CAAA;AAAA,gBAC7C,WAAW,IAAK,CAAA,UAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,SAAA,sBAAe,IAAK,EAAA;AAAA,gBACpB,eACE,KAAiB,YAAA,KAAA,GAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK,CAAA;AAAA,gBACvD,WAAA,EAAa,cAAe,CAAA,MAAA,CAAO,IAAI,CAAA;AAAA,gBACvC,gBAAkB,EAAA,cAAA,CAAe,MAAO,CAAA,QAAA,CAAS,SAAS,CAAA;AAAA,gBAC1D,YAAc,EAAAC,mCAAA,CAAkB,MAAQ,EAAA,IAAA,EAAM,KAAK;AAAA,eACrD;AAAA;AACF,WACD;AAAA,SACD,CAAA,IAAA;AAAA,UAAK,CACL,QAAA,KAAA,QAAA,CAAS,MAAO,CAAA,CAAC,KAAK,IAAS,KAAA;AAC7B,YAAA,IAAI,IAAK,CAAA,MAAA,KAAW,WAAe,IAAA,IAAA,CAAK,UAAU,KAAW,CAAA,EAAA;AAE3D,cAAA,MAAM,SAAS,IAAK,CAAA,KAAA;AACpB,cAAI,IAAA,KAAA,CAAM,OAAQ,CAAA,MAAM,CAAG,EAAA;AACzB,gBAAA,OAAO,CAAC,GAAG,GAAK,EAAA,GAAG,MAAM,CAAA;AAAA;AAE3B,cAAO,OAAA,CAAC,GAAG,GAAA,EAAK,MAAM,CAAA;AAAA;AAExB,YAAO,OAAA,GAAA;AAAA,WACT,EAAG,EAA2B;AAAA,SAChC;AAEA,QAAI,IAAA,YAAA,CAAa,SAAS,CAAG,EAAA;AAC3B,UAAA,MAAM,aAAa,YAAa,CAAA,MAAA,CAAO,CAAK,CAAA,KAAA,CAAA,CAAE,aAAa,CAAE,CAAA,MAAA;AAC7D,UAAO,MAAA,CAAA,KAAA;AAAA,YACL,CAAW,QAAA,EAAA,YAAA,CAAa,MAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA,QAAA;AAAA,WAC7D;AAAA;AAGF,QAAM,MAAA,IAAA,CAAK,QAAS,CAAA,kBAAA,CAAmB,YAAY,CAAA;AACnD,QAAA,cAAA,IAAkB,iBAAiB,KAAM,CAAA,MAAA;AAAA,eAClC,MAAW,KAAA,KAAA,CAAA;AAEpB,MAAO,MAAA,CAAA,IAAA;AAAA,QACL,CAA6B,0BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,YAAA,EAAe,cAAc,CAAA,SAAA;AAAA,OAC3E;AAAA,aACO,KAAO,EAAA;AACd,MAAA,MAAA,CAAO,MAAM,CAA8B,2BAAA,EAAA,IAAA,CAAK,UAAU,CAAA,EAAA,EAAK,KAAK,CAAE,CAAA,CAAA;AAEtE,MAAM,MAAA,KAAA;AAAA;AACR;AAEJ;AAEA,SAAS,eAAe,KAAoC,EAAA;AAC1D,EAAI,IAAA,OAAO,KAAU,KAAA,QAAA,EAAiB,OAAA,MAAA;AACtC,EAAA,MAAM,UAAa,GAAA,KAAA,CAAM,IAAK,EAAA,CAAE,WAAY,EAAA;AAC5C,EAAI,IAAA,CAAC,YAAmB,OAAA,MAAA;AAGxB,EAAA,OAAO,WAAW,MAAU,IAAA,GAAA,GAAM,aAAa,UAAW,CAAA,KAAA,CAAM,GAAG,GAAG,CAAA;AACxE;;;;"}
@@ -27,22 +27,22 @@ class CatalogMetricService {
27
27
  this.config = options.config;
28
28
  }
29
29
  /**
30
- * Get latest metric results for a specific catalog entity and metric providers.
30
+ * Get latest metric results for a specific catalog entity.
31
31
  *
32
32
  * @param entityRef - Entity reference in format "kind:namespace/name"
33
- * @param providerIds - Optional array of provider IDs to get latest metrics of.
34
- * If not provided, gets all available latest metrics.
33
+ * @param metricIds - Optional array of metric IDs to get latest metrics of.
34
+ * If not provided, gets all available latest metrics.
35
35
  * @param filter - Permission filter
36
36
  * @returns Metric results with entity-specific thresholds applied
37
37
  */
38
- async getLatestEntityMetrics(entityRef, providerIds, filter) {
38
+ async getLatestEntityMetrics(entityRef, metricIds, filter) {
39
39
  const entity = await this.catalog.getEntityByRef(entityRef, {
40
40
  credentials: await this.auth.getOwnServiceCredentials()
41
41
  });
42
42
  if (!entity) {
43
43
  throw new errors.NotFoundError(`Entity not found: ${entityRef}`);
44
44
  }
45
- const metricsToFetch = this.registry.listMetrics(providerIds);
45
+ const metricsToFetch = this.registry.listMetrics(metricIds);
46
46
  const authorizedMetricsToFetch = permissionUtils.filterAuthorizedMetrics(
47
47
  metricsToFetch,
48
48
  filter
@@ -56,7 +56,7 @@ class CatalogMetricService {
56
56
  let thresholds;
57
57
  let thresholdError;
58
58
  const provider = this.registry.getProvider(metric_id);
59
- const metric = provider.getMetric();
59
+ const metric = this.registry.getMetric(metric_id);
60
60
  try {
61
61
  thresholds = mergeEntityAndProviderThresholds.mergeEntityAndProviderThresholds(entity, provider);
62
62
  if (value === null) {
@@ -1 +1 @@
1
- {"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MetricResult,\n ThresholdConfig,\n AggregatedMetric,\n EntityMetricDetailResponse,\n EntityMetricDetail,\n aggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { Entity } from '@backstage/catalog-model';\nimport { normalizeOwnerRef } from '../utils/normalizeOwnerRef';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\nimport { AggregatedMetricMapper } from './mappers';\nimport { DbMetricValue } from '../database/types';\nimport type { Config } from '@backstage/config';\nimport {\n buildAggregationConfig,\n type AggregationConfig,\n} from '../utils/buildAggregationConfig';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../constants';\n\ntype CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n logger: LoggerService;\n config: Config;\n};\n\nexport class CatalogMetricService {\n private readonly logger: LoggerService;\n\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n private readonly config: Config;\n\n private static readonly MAX_FETCHABLE_ROWS = 10_000;\n private static readonly BATCH_SIZE = 100;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n this.logger = options.logger;\n this.config = options.config;\n }\n\n /**\n * Get latest metric results for a specific catalog entity and metric providers.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param providerIds - Optional array of provider IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n providerIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = this.registry.listMetrics(providerIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = provider.getMetric();\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === null) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else if (error_message) {\n thresholdError = error_message;\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = error_message !== null && value === null;\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n\n /**\n * Get an aggregated metric by status grouped for multiple entities and a single metric ID.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @returns Aggregated metric by status grouped results\n */\n async getStatusGroupedAggregatedMetrics(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n /**\n * Get an aggregated metric by aggregation type.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @param aggregationType - Aggregation type to use.\n * @returns Aggregated metric by aggregation type results\n */\n async getAggregatedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationType: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length !== 0) {\n if (aggregationType === aggregationTypes.statusGrouped) {\n return this.getStatusGroupedAggregatedMetrics(entityRefs, metricId);\n }\n throw new Error(`Unsupported aggregation type: ${aggregationType}`);\n }\n\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n /**\n * Get detailed entity metrics for drill-down with filtering, sorting, and pagination.\n *\n * Fetches individual entity metric values and enriches them with catalog metadata.\n * Supports database-level filtering (status, owner, kind, entityName),\n * database-level sorting, and in-memory pagination over the permission-filtered result set.\n * Returns empty entities if the catalog is unavailable (fail-secure).\n *\n * @param metricId - Metric ID to fetch (e.g., \"github.open_prs\")\n * @param options - Query options for filtering, sorting, and pagination\n * @param options.status - Filter by threshold status (database-level)\n * @param options.owner - Filter by owner entity reference (database-level)\n * @param options.kind - Filter by entity kind (database-level)\n * @param options.entityName - Substring search against the entity ref `kind:namespace/name` (database-level)\n * @param options.namespace - Exact match against the entity namespace (database-level)\n * @param options.sortBy - Field to sort by (default: \"timestamp\")\n * @param options.sortOrder - Sort direction: \"asc\" or \"desc\" (default: \"desc\")\n * @param options.page - Page number (1-indexed)\n * @param options.limit - Entities per page (max: 100)\n * @returns Paginated entity metric details with metadata\n */\n async getEntityMetricDetails(\n metricId: string,\n credentials: BackstageCredentials,\n options: {\n status?: string;\n owner?: string[];\n kind?: string;\n entityName?: string;\n namespace?: string;\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n page: number;\n limit: number;\n },\n ): Promise<EntityMetricDetailResponse> {\n // Get metric metadata\n const metric = this.registry.getMetric(metricId);\n\n // High-page early-exit guard\n if (\n (options.page - 1) * options.limit >=\n CatalogMetricService.MAX_FETCHABLE_ROWS\n ) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n };\n }\n\n // Query database with all DB filters first\n // At the moment, this is going to be an O(MAX_FETCHABLE_ROWS) cost and is intentional to avoid leaking\n // pre-auth counts. MAX_FETCHABLE_ROWS and BATCH_SIZE are to be used as a method to of adjustment to\n // find the right amount of performance\n const rows = await this.database.readEntityMetricsWithFilters(metricId, {\n status: options.status,\n entityName: options.entityName,\n entityKind: options.kind,\n entityNamespace: options.namespace,\n entityOwner: options.owner,\n sortBy: options.sortBy,\n sortOrder: options.sortOrder,\n pagination: {\n limit: CatalogMetricService.MAX_FETCHABLE_ROWS,\n offset: 0,\n },\n });\n\n // Filter to authorized rows by batching through catalog.getEntitiesByRefs with user\n // credentials. The catalog enforces auth natively: null = unauthorized or deleted.\n // We also cache the returned Entity objects so we can enrich the page rows without\n // a second catalog round-trip. Sequential processing preserves DB sort order.\n const entityMap = new Map<string, Entity>();\n const accessibleRows: DbMetricValue[] = [];\n try {\n for (let i = 0; i < rows.length; i += CatalogMetricService.BATCH_SIZE) {\n const batch = rows.slice(i, i + CatalogMetricService.BATCH_SIZE);\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: batch.map(row => row.catalog_entity_ref),\n fields: [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'spec.owner',\n ],\n },\n { credentials },\n );\n\n // Filter out the unauthorized entities\n for (let j = 0; j < batch.length; j++) {\n const entity = response.items[j];\n if (!entity) continue; // null = unauthorized or not found, skip\n entityMap.set(batch[j].catalog_entity_ref, entity);\n accessibleRows.push(batch[j]);\n }\n }\n } catch (error) {\n // Fail secure: if the catalog is unavailable we cannot confirm authorization,\n // so return empty rather than potentially unauthorized data.\n this.logger.error('Failed to fetch entities from catalog', { error });\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n };\n }\n\n // True when DB results were capped; pagination.total may undercount the full dataset.\n const isCapped = rows.length === CatalogMetricService.MAX_FETCHABLE_ROWS;\n\n // Apply pagination to filtered entities\n const totalFiltered = accessibleRows.length;\n const pageRows = accessibleRows.slice(\n (options.page - 1) * options.limit,\n options.page * options.limit,\n );\n\n // No rows on this page — either no matching results or the requested page is beyond\n // the last page.\n if (pageRows.length === 0) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n };\n }\n\n // Enrich page rows from the cached entity map\n const enrichedEntities: EntityMetricDetail[] = [];\n for (const row of pageRows) {\n const entity = entityMap.get(row.catalog_entity_ref);\n if (!entity) continue;\n enrichedEntities.push({\n entityRef: row.catalog_entity_ref,\n entityNamespace: entity.metadata.namespace,\n entityName: entity.metadata.name,\n entityKind: entity.kind,\n owner: normalizeOwnerRef(entity.spec?.owner) ?? '',\n metricValue: row.value,\n timestamp: new Date(row.timestamp).toISOString(),\n status: row.status,\n });\n }\n\n // Format and return response\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: enrichedEntities,\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n };\n }\n\n /**\n * Get the aggregation configs for a given aggregation IDs filtered by metric IDs.\n * If no aggregation IDs are provided, all aggregation configs will be returned.\n * If no metric IDs are provided, all metrics will be included.\n *\n * @param aggregationIds - Optional array of aggregation IDs to fetch the configs for.\n * @param metricIds - Optional array of metric IDs, when provided, only aggregations whose config metricId is in this array will be returned.\n * @returns Aggregation configs\n */\n getAggregationConfigs(\n aggregationIds: string[] = [],\n metricIds: string[] = [],\n ): AggregationConfig[] {\n const aggregationKPIsConfig = this.config.getOptionalConfig(\n AGGREGATION_KPIS_CONFIG_PATH,\n );\n\n if (!aggregationKPIsConfig) {\n return [];\n }\n\n const aggregations: AggregationConfig[] = [];\n\n const aggregationConfigIds =\n aggregationIds.length > 0 ? aggregationIds : aggregationKPIsConfig.keys();\n\n for (const aggregationId of aggregationConfigIds) {\n const config = aggregationKPIsConfig.getOptionalConfig(aggregationId);\n\n if (config) {\n const aggregationConfig = buildAggregationConfig(aggregationId, {\n config,\n });\n\n if (\n metricIds.length === 0 ||\n metricIds.includes(aggregationConfig.metricId)\n ) {\n aggregations.push(aggregationConfig);\n }\n }\n }\n\n return aggregations;\n }\n}\n"],"names":["NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError","AggregatedMetricMapper","aggregationTypes","normalizeOwnerRef","AGGREGATION_KPIS_CONFIG_PATH","buildAggregationConfig"],"mappings":";;;;;;;;;;;AA4DO,MAAM,oBAAqB,CAAA;AAAA,EACf,MAAA;AAAA,EAEA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EAEjB,OAAwB,kBAAqB,GAAA,GAAA;AAAA,EAC7C,OAAwB,UAAa,GAAA,GAAA;AAAA,EAErC,YAAY,OAAsC,EAAA;AAChD,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AAAA;AACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,sBAAA,CACJ,SACA,EAAA,WAAA,EACA,MAGyB,EAAA;AACzB,IAAA,MAAM,MAAS,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,eAAe,SAAW,EAAA;AAAA,MAC1D,WAAa,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,wBAAyB;AAAA,KACvD,CAAA;AACD,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,IAAIA,oBAAA,CAAc,CAAqB,kBAAA,EAAA,SAAS,CAAE,CAAA,CAAA;AAAA;AAG1D,IAAA,MAAM,cAAiB,GAAA,IAAA,CAAK,QAAS,CAAA,WAAA,CAAY,WAAW,CAAA;AAE5D,IAAA,MAAM,wBAA2B,GAAAC,uCAAA;AAAA,MAC/B,cAAA;AAAA,MACA;AAAA,KACF;AACA,IAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,QAAS,CAAA,4BAAA;AAAA,MACrC,SAAA;AAAA,MACA,wBAAyB,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,EAAE;AAAA,KACxC;AAEA,IAAA,OAAO,UAAW,CAAA,GAAA;AAAA,MAChB,CAAC,EAAE,SAAA,EAAW,OAAO,aAAe,EAAA,SAAA,EAAW,QAAa,KAAA;AAC1D,QAAI,IAAA,UAAA;AACJ,QAAI,IAAA,cAAA;AAEJ,QAAA,MAAM,QAAW,GAAA,IAAA,CAAK,QAAS,CAAA,WAAA,CAAY,SAAS,CAAA;AACpD,QAAM,MAAA,MAAA,GAAS,SAAS,SAAU,EAAA;AAElC,QAAI,IAAA;AACF,UAAa,UAAA,GAAAC,iEAAA,CAAiC,QAAQ,QAAQ,CAAA;AAE9D,UAAA,IAAI,UAAU,IAAM,EAAA;AAClB,YACE,cAAA,GAAA,wDAAA;AAAA,qBACO,aAAe,EAAA;AACxB,YAAiB,cAAA,GAAA,aAAA;AAAA;AACnB,iBACO,KAAO,EAAA;AACd,UAAA,cAAA,GAAiBC,sBAAe,KAAK,CAAA;AAAA;AAGvC,QAAM,MAAA,iBAAA,GAAoB,aAAkB,KAAA,IAAA,IAAQ,KAAU,KAAA,IAAA;AAE9D,QAAO,OAAA;AAAA,UACL,IAAI,MAAO,CAAA,EAAA;AAAA,UACX,MAAA,EAAQ,oBAAoB,OAAU,GAAA,SAAA;AAAA,UACtC,QAAU,EAAA;AAAA,YACR,OAAO,MAAO,CAAA,KAAA;AAAA,YACd,aAAa,MAAO,CAAA,WAAA;AAAA,YACpB,MAAM,MAAO,CAAA,IAAA;AAAA,YACb,SAAS,MAAO,CAAA;AAAA,WAClB;AAAA,UACA,GAAI,iBAAqB,IAAA;AAAA,YACvB,OACE,aACA,IAAAA,qBAAA,CAAe,IAAI,KAAA,CAAM,6BAA6B,CAAC;AAAA,WAC3D;AAAA,UACA,MAAQ,EAAA;AAAA,YACN,KAAA;AAAA,YACA,SAAW,EAAA,IAAI,IAAK,CAAA,SAAS,EAAE,WAAY,EAAA;AAAA,YAC3C,eAAiB,EAAA;AAAA,cACf,UAAY,EAAA,UAAA;AAAA,cACZ,MAAA,EAAQ,iBAAiB,OAAU,GAAA,SAAA;AAAA,cACnC,UAAY,EAAA,MAAA;AAAA,cACZ,GAAI,cAAA,IAAkB,EAAE,KAAA,EAAO,cAAe;AAAA;AAChD;AACF,SACF;AAAA;AACF,KACF;AAAA;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iCACJ,CAAA,UAAA,EACA,QAC2B,EAAA;AAC3B,IAAM,MAAA,gBAAA,GACJ,MAAM,IAAA,CAAK,QAAS,CAAA,gCAAA;AAAA,MAClB,UAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAO,OAAAC,8BAAA,CAAuB,mBAAmB,gBAAgB,CAAA;AAAA;AACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,+BAAA,CACJ,UACA,EAAA,QAAA,EACA,eAC2B,EAAA;AAC3B,IAAI,IAAA,UAAA,CAAW,WAAW,CAAG,EAAA;AAC3B,MAAI,IAAA,eAAA,KAAoBC,gDAAiB,aAAe,EAAA;AACtD,QAAO,OAAA,IAAA,CAAK,iCAAkC,CAAA,UAAA,EAAY,QAAQ,CAAA;AAAA;AAEpE,MAAA,MAAM,IAAI,KAAA,CAAM,CAAiC,8BAAA,EAAA,eAAe,CAAE,CAAA,CAAA;AAAA;AAGpE,IAAA,OAAOD,+BAAuB,kBAAmB,EAAA;AAAA;AACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,sBAAA,CACJ,QACA,EAAA,WAAA,EACA,OAkBqC,EAAA;AAErC,IAAA,MAAM,MAAS,GAAA,IAAA,CAAK,QAAS,CAAA,SAAA,CAAU,QAAQ,CAAA;AAG/C,IAAA,IAAA,CACG,QAAQ,IAAO,GAAA,CAAA,IAAK,OAAQ,CAAA,KAAA,IAC7B,qBAAqB,kBACrB,EAAA;AACA,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,CAAA;AAAA,UACP,UAAY,EAAA,CAAA;AAAA,UACZ,QAAU,EAAA;AAAA;AACZ,OACF;AAAA;AAOF,IAAA,MAAM,IAAO,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,6BAA6B,QAAU,EAAA;AAAA,MACtE,QAAQ,OAAQ,CAAA,MAAA;AAAA,MAChB,YAAY,OAAQ,CAAA,UAAA;AAAA,MACpB,YAAY,OAAQ,CAAA,IAAA;AAAA,MACpB,iBAAiB,OAAQ,CAAA,SAAA;AAAA,MACzB,aAAa,OAAQ,CAAA,KAAA;AAAA,MACrB,QAAQ,OAAQ,CAAA,MAAA;AAAA,MAChB,WAAW,OAAQ,CAAA,SAAA;AAAA,MACnB,UAAY,EAAA;AAAA,QACV,OAAO,oBAAqB,CAAA,kBAAA;AAAA,QAC5B,MAAQ,EAAA;AAAA;AACV,KACD,CAAA;AAMD,IAAM,MAAA,SAAA,uBAAgB,GAAoB,EAAA;AAC1C,IAAA,MAAM,iBAAkC,EAAC;AACzC,IAAI,IAAA;AACF,MAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,KAAK,MAAQ,EAAA,CAAA,IAAK,qBAAqB,UAAY,EAAA;AACrE,QAAA,MAAM,QAAQ,IAAK,CAAA,KAAA,CAAM,CAAG,EAAA,CAAA,GAAI,qBAAqB,UAAU,CAAA;AAC/D,QAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,OAAQ,CAAA,iBAAA;AAAA,UAClC;AAAA,YACE,UAAY,EAAA,KAAA,CAAM,GAAI,CAAA,CAAA,GAAA,KAAO,IAAI,kBAAkB,CAAA;AAAA,YACnD,MAAQ,EAAA;AAAA,cACN,MAAA;AAAA,cACA,eAAA;AAAA,cACA,oBAAA;AAAA,cACA;AAAA;AACF,WACF;AAAA,UACA,EAAE,WAAY;AAAA,SAChB;AAGA,QAAA,KAAA,IAAS,CAAI,GAAA,CAAA,EAAG,CAAI,GAAA,KAAA,CAAM,QAAQ,CAAK,EAAA,EAAA;AACrC,UAAM,MAAA,MAAA,GAAS,QAAS,CAAA,KAAA,CAAM,CAAC,CAAA;AAC/B,UAAA,IAAI,CAAC,MAAQ,EAAA;AACb,UAAA,SAAA,CAAU,GAAI,CAAA,KAAA,CAAM,CAAC,CAAA,CAAE,oBAAoB,MAAM,CAAA;AACjD,UAAe,cAAA,CAAA,IAAA,CAAK,KAAM,CAAA,CAAC,CAAC,CAAA;AAAA;AAC9B;AACF,aACO,KAAO,EAAA;AAGd,MAAA,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,uCAAyC,EAAA,EAAE,OAAO,CAAA;AACpE,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,CAAA;AAAA,UACP,UAAY,EAAA,CAAA;AAAA,UACZ,QAAU,EAAA;AAAA;AACZ,OACF;AAAA;AAIF,IAAM,MAAA,QAAA,GAAW,IAAK,CAAA,MAAA,KAAW,oBAAqB,CAAA,kBAAA;AAGtD,IAAA,MAAM,gBAAgB,cAAe,CAAA,MAAA;AACrC,IAAA,MAAM,WAAW,cAAe,CAAA,KAAA;AAAA,MAC7B,CAAA,OAAA,CAAQ,IAAO,GAAA,CAAA,IAAK,OAAQ,CAAA,KAAA;AAAA,MAC7B,OAAA,CAAQ,OAAO,OAAQ,CAAA;AAAA,KACzB;AAIA,IAAI,IAAA,QAAA,CAAS,WAAW,CAAG,EAAA;AACzB,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,aAAA;AAAA,UACP,UAAY,EAAA,IAAA,CAAK,IAAK,CAAA,aAAA,GAAgB,QAAQ,KAAK,CAAA;AAAA,UACnD;AAAA;AACF,OACF;AAAA;AAIF,IAAA,MAAM,mBAAyC,EAAC;AAChD,IAAA,KAAA,MAAW,OAAO,QAAU,EAAA;AAC1B,MAAA,MAAM,MAAS,GAAA,SAAA,CAAU,GAAI,CAAA,GAAA,CAAI,kBAAkB,CAAA;AACnD,MAAA,IAAI,CAAC,MAAQ,EAAA;AACb,MAAA,gBAAA,CAAiB,IAAK,CAAA;AAAA,QACpB,WAAW,GAAI,CAAA,kBAAA;AAAA,QACf,eAAA,EAAiB,OAAO,QAAS,CAAA,SAAA;AAAA,QACjC,UAAA,EAAY,OAAO,QAAS,CAAA,IAAA;AAAA,QAC5B,YAAY,MAAO,CAAA,IAAA;AAAA,QACnB,KAAO,EAAAE,mCAAA,CAAkB,MAAO,CAAA,IAAA,EAAM,KAAK,CAAK,IAAA,EAAA;AAAA,QAChD,aAAa,GAAI,CAAA,KAAA;AAAA,QACjB,WAAW,IAAI,IAAA,CAAK,GAAI,CAAA,SAAS,EAAE,WAAY,EAAA;AAAA,QAC/C,QAAQ,GAAI,CAAA;AAAA,OACb,CAAA;AAAA;AAIH,IAAO,OAAA;AAAA,MACL,UAAU,MAAO,CAAA,EAAA;AAAA,MACjB,cAAgB,EAAA;AAAA,QACd,OAAO,MAAO,CAAA,KAAA;AAAA,QACd,aAAa,MAAO,CAAA,WAAA;AAAA,QACpB,MAAM,MAAO,CAAA;AAAA,OACf;AAAA,MACA,QAAU,EAAA,gBAAA;AAAA,MACV,UAAY,EAAA;AAAA,QACV,MAAM,OAAQ,CAAA,IAAA;AAAA,QACd,UAAU,OAAQ,CAAA,KAAA;AAAA,QAClB,KAAO,EAAA,aAAA;AAAA,QACP,UAAY,EAAA,IAAA,CAAK,IAAK,CAAA,aAAA,GAAgB,QAAQ,KAAK,CAAA;AAAA,QACnD;AAAA;AACF,KACF;AAAA;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,sBACE,cAA2B,GAAA,EAC3B,EAAA,SAAA,GAAsB,EACD,EAAA;AACrB,IAAM,MAAA,qBAAA,GAAwB,KAAK,MAAO,CAAA,iBAAA;AAAA,MACxCC;AAAA,KACF;AAEA,IAAA,IAAI,CAAC,qBAAuB,EAAA;AAC1B,MAAA,OAAO,EAAC;AAAA;AAGV,IAAA,MAAM,eAAoC,EAAC;AAE3C,IAAA,MAAM,uBACJ,cAAe,CAAA,MAAA,GAAS,CAAI,GAAA,cAAA,GAAiB,sBAAsB,IAAK,EAAA;AAE1E,IAAA,KAAA,MAAW,iBAAiB,oBAAsB,EAAA;AAChD,MAAM,MAAA,MAAA,GAAS,qBAAsB,CAAA,iBAAA,CAAkB,aAAa,CAAA;AAEpE,MAAA,IAAI,MAAQ,EAAA;AACV,QAAM,MAAA,iBAAA,GAAoBC,8CAAuB,aAAe,EAAA;AAAA,UAC9D;AAAA,SACD,CAAA;AAED,QAAA,IACE,UAAU,MAAW,KAAA,CAAA,IACrB,UAAU,QAAS,CAAA,iBAAA,CAAkB,QAAQ,CAC7C,EAAA;AACA,UAAA,YAAA,CAAa,KAAK,iBAAiB,CAAA;AAAA;AACrC;AACF;AAGF,IAAO,OAAA,YAAA;AAAA;AAEX;;;;"}
1
+ {"version":3,"file":"CatalogMetricService.cjs.js","sources":["../../src/service/CatalogMetricService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MetricResult,\n ThresholdConfig,\n AggregatedMetric,\n EntityMetricDetailResponse,\n EntityMetricDetail,\n aggregationTypes,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { Entity } from '@backstage/catalog-model';\nimport { normalizeOwnerRef } from '../utils/normalizeOwnerRef';\nimport { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport { NotFoundError, stringifyError } from '@backstage/errors';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { filterAuthorizedMetrics } from '../permissions/permissionUtils';\nimport {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { DatabaseMetricValues } from '../database/DatabaseMetricValues';\nimport { mergeEntityAndProviderThresholds } from '../utils/mergeEntityAndProviderThresholds';\nimport { AggregatedMetricMapper } from './mappers';\nimport { DbMetricValue } from '../database/types';\nimport type { Config } from '@backstage/config';\nimport {\n buildAggregationConfig,\n type AggregationConfig,\n} from '../utils/buildAggregationConfig';\nimport { AGGREGATION_KPIS_CONFIG_PATH } from '../constants';\n\ntype CatalogMetricServiceOptions = {\n catalog: CatalogService;\n auth: AuthService;\n registry: MetricProvidersRegistry;\n database: DatabaseMetricValues;\n logger: LoggerService;\n config: Config;\n};\n\nexport class CatalogMetricService {\n private readonly logger: LoggerService;\n\n private readonly catalog: CatalogService;\n private readonly auth: AuthService;\n private readonly registry: MetricProvidersRegistry;\n private readonly database: DatabaseMetricValues;\n private readonly config: Config;\n\n private static readonly MAX_FETCHABLE_ROWS = 10_000;\n private static readonly BATCH_SIZE = 100;\n\n constructor(options: CatalogMetricServiceOptions) {\n this.catalog = options.catalog;\n this.auth = options.auth;\n this.registry = options.registry;\n this.database = options.database;\n this.logger = options.logger;\n this.config = options.config;\n }\n\n /**\n * Get latest metric results for a specific catalog entity.\n *\n * @param entityRef - Entity reference in format \"kind:namespace/name\"\n * @param metricIds - Optional array of metric IDs to get latest metrics of.\n * If not provided, gets all available latest metrics.\n * @param filter - Permission filter\n * @returns Metric results with entity-specific thresholds applied\n */\n async getLatestEntityMetrics(\n entityRef: string,\n metricIds?: string[],\n filter?: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n ): Promise<MetricResult[]> {\n const entity = await this.catalog.getEntityByRef(entityRef, {\n credentials: await this.auth.getOwnServiceCredentials(),\n });\n if (!entity) {\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n const metricsToFetch = this.registry.listMetrics(metricIds);\n\n const authorizedMetricsToFetch = filterAuthorizedMetrics(\n metricsToFetch,\n filter,\n );\n const rawResults = await this.database.readLatestEntityMetricValues(\n entityRef,\n authorizedMetricsToFetch.map(m => m.id),\n );\n\n return rawResults.map(\n ({ metric_id, value, error_message, timestamp, status }) => {\n let thresholds: ThresholdConfig | undefined;\n let thresholdError: string | undefined;\n\n const provider = this.registry.getProvider(metric_id);\n const metric = this.registry.getMetric(metric_id);\n\n try {\n thresholds = mergeEntityAndProviderThresholds(entity, provider);\n\n if (value === null) {\n thresholdError =\n 'Unable to evaluate thresholds, metric value is missing';\n } else if (error_message) {\n thresholdError = error_message;\n }\n } catch (error) {\n thresholdError = stringifyError(error);\n }\n\n const isMetricCalcError = error_message !== null && value === null;\n\n return {\n id: metric.id,\n status: isMetricCalcError ? 'error' : 'success',\n metadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n history: metric.history,\n },\n ...(isMetricCalcError && {\n error:\n error_message ??\n stringifyError(new Error(`Metric value is 'undefined'`)),\n }),\n result: {\n value,\n timestamp: new Date(timestamp).toISOString(),\n thresholdResult: {\n definition: thresholds,\n status: thresholdError ? 'error' : 'success',\n evaluation: status,\n ...(thresholdError && { error: thresholdError }),\n },\n },\n };\n },\n );\n }\n\n /**\n * Get an aggregated metric by status grouped for multiple entities and a single metric ID.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @returns Aggregated metric by status grouped results\n */\n async getStatusGroupedAggregatedMetrics(\n entityRefs: string[],\n metricId: string,\n ): Promise<AggregatedMetric> {\n const aggregatedMetric =\n await this.database.readAggregatedMetricByEntityRefs(\n entityRefs,\n metricId,\n );\n\n return AggregatedMetricMapper.toAggregatedMetric(aggregatedMetric);\n }\n\n /**\n * Get an aggregated metric by aggregation type.\n *\n * @param entityRefs - Array of entity references in format \"kind:namespace/name\"\n * @param metricId - Metric ID to aggregate.\n * @param aggregationType - Aggregation type to use.\n * @returns Aggregated metric by aggregation type results\n */\n async getAggregatedMetricByEntityRefs(\n entityRefs: string[],\n metricId: string,\n aggregationType: string,\n ): Promise<AggregatedMetric> {\n if (entityRefs.length !== 0) {\n if (aggregationType === aggregationTypes.statusGrouped) {\n return this.getStatusGroupedAggregatedMetrics(entityRefs, metricId);\n }\n throw new Error(`Unsupported aggregation type: ${aggregationType}`);\n }\n\n return AggregatedMetricMapper.toAggregatedMetric();\n }\n\n /**\n * Get detailed entity metrics for drill-down with filtering, sorting, and pagination.\n *\n * Fetches individual entity metric values and enriches them with catalog metadata.\n * Supports database-level filtering (status, owner, kind, entityName),\n * database-level sorting, and in-memory pagination over the permission-filtered result set.\n * Returns empty entities if the catalog is unavailable (fail-secure).\n *\n * @param metricId - Metric ID to fetch (e.g., \"github.open_prs\")\n * @param options - Query options for filtering, sorting, and pagination\n * @param options.status - Filter by threshold status (database-level)\n * @param options.owner - Filter by owner entity reference (database-level)\n * @param options.kind - Filter by entity kind (database-level)\n * @param options.entityName - Substring search against the entity ref `kind:namespace/name` (database-level)\n * @param options.namespace - Exact match against the entity namespace (database-level)\n * @param options.sortBy - Field to sort by (default: \"timestamp\")\n * @param options.sortOrder - Sort direction: \"asc\" or \"desc\" (default: \"desc\")\n * @param options.page - Page number (1-indexed)\n * @param options.limit - Entities per page (max: 100)\n * @returns Paginated entity metric details with metadata\n */\n async getEntityMetricDetails(\n metricId: string,\n credentials: BackstageCredentials,\n options: {\n status?: string;\n owner?: string[];\n kind?: string;\n entityName?: string;\n namespace?: string;\n sortBy?:\n | 'entityName'\n | 'owner'\n | 'entityKind'\n | 'timestamp'\n | 'metricValue'\n | 'namespace'\n | 'status';\n sortOrder?: 'asc' | 'desc';\n page: number;\n limit: number;\n },\n ): Promise<EntityMetricDetailResponse> {\n // Get metric metadata\n const metric = this.registry.getMetric(metricId);\n\n // High-page early-exit guard\n if (\n (options.page - 1) * options.limit >=\n CatalogMetricService.MAX_FETCHABLE_ROWS\n ) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n };\n }\n\n // Query database with all DB filters first\n // At the moment, this is going to be an O(MAX_FETCHABLE_ROWS) cost and is intentional to avoid leaking\n // pre-auth counts. MAX_FETCHABLE_ROWS and BATCH_SIZE are to be used as a method to of adjustment to\n // find the right amount of performance\n const rows = await this.database.readEntityMetricsWithFilters(metricId, {\n status: options.status,\n entityName: options.entityName,\n entityKind: options.kind,\n entityNamespace: options.namespace,\n entityOwner: options.owner,\n sortBy: options.sortBy,\n sortOrder: options.sortOrder,\n pagination: {\n limit: CatalogMetricService.MAX_FETCHABLE_ROWS,\n offset: 0,\n },\n });\n\n // Filter to authorized rows by batching through catalog.getEntitiesByRefs with user\n // credentials. The catalog enforces auth natively: null = unauthorized or deleted.\n // We also cache the returned Entity objects so we can enrich the page rows without\n // a second catalog round-trip. Sequential processing preserves DB sort order.\n const entityMap = new Map<string, Entity>();\n const accessibleRows: DbMetricValue[] = [];\n try {\n for (let i = 0; i < rows.length; i += CatalogMetricService.BATCH_SIZE) {\n const batch = rows.slice(i, i + CatalogMetricService.BATCH_SIZE);\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: batch.map(row => row.catalog_entity_ref),\n fields: [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'spec.owner',\n ],\n },\n { credentials },\n );\n\n // Filter out the unauthorized entities\n for (let j = 0; j < batch.length; j++) {\n const entity = response.items[j];\n if (!entity) continue; // null = unauthorized or not found, skip\n entityMap.set(batch[j].catalog_entity_ref, entity);\n accessibleRows.push(batch[j]);\n }\n }\n } catch (error) {\n // Fail secure: if the catalog is unavailable we cannot confirm authorization,\n // so return empty rather than potentially unauthorized data.\n this.logger.error('Failed to fetch entities from catalog', { error });\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: 0,\n totalPages: 0,\n isCapped: false,\n },\n };\n }\n\n // True when DB results were capped; pagination.total may undercount the full dataset.\n const isCapped = rows.length === CatalogMetricService.MAX_FETCHABLE_ROWS;\n\n // Apply pagination to filtered entities\n const totalFiltered = accessibleRows.length;\n const pageRows = accessibleRows.slice(\n (options.page - 1) * options.limit,\n options.page * options.limit,\n );\n\n // No rows on this page — either no matching results or the requested page is beyond\n // the last page.\n if (pageRows.length === 0) {\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: [],\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n };\n }\n\n // Enrich page rows from the cached entity map\n const enrichedEntities: EntityMetricDetail[] = [];\n for (const row of pageRows) {\n const entity = entityMap.get(row.catalog_entity_ref);\n if (!entity) continue;\n enrichedEntities.push({\n entityRef: row.catalog_entity_ref,\n entityNamespace: entity.metadata.namespace,\n entityName: entity.metadata.name,\n entityKind: entity.kind,\n owner: normalizeOwnerRef(entity.spec?.owner) ?? '',\n metricValue: row.value,\n timestamp: new Date(row.timestamp).toISOString(),\n status: row.status,\n });\n }\n\n // Format and return response\n return {\n metricId: metric.id,\n metricMetadata: {\n title: metric.title,\n description: metric.description,\n type: metric.type,\n },\n entities: enrichedEntities,\n pagination: {\n page: options.page,\n pageSize: options.limit,\n total: totalFiltered,\n totalPages: Math.ceil(totalFiltered / options.limit),\n isCapped,\n },\n };\n }\n\n /**\n * Get the aggregation configs for a given aggregation IDs filtered by metric IDs.\n * If no aggregation IDs are provided, all aggregation configs will be returned.\n * If no metric IDs are provided, all metrics will be included.\n *\n * @param aggregationIds - Optional array of aggregation IDs to fetch the configs for.\n * @param metricIds - Optional array of metric IDs, when provided, only aggregations whose config metricId is in this array will be returned.\n * @returns Aggregation configs\n */\n getAggregationConfigs(\n aggregationIds: string[] = [],\n metricIds: string[] = [],\n ): AggregationConfig[] {\n const aggregationKPIsConfig = this.config.getOptionalConfig(\n AGGREGATION_KPIS_CONFIG_PATH,\n );\n\n if (!aggregationKPIsConfig) {\n return [];\n }\n\n const aggregations: AggregationConfig[] = [];\n\n const aggregationConfigIds =\n aggregationIds.length > 0 ? aggregationIds : aggregationKPIsConfig.keys();\n\n for (const aggregationId of aggregationConfigIds) {\n const config = aggregationKPIsConfig.getOptionalConfig(aggregationId);\n\n if (config) {\n const aggregationConfig = buildAggregationConfig(aggregationId, {\n config,\n });\n\n if (\n metricIds.length === 0 ||\n metricIds.includes(aggregationConfig.metricId)\n ) {\n aggregations.push(aggregationConfig);\n }\n }\n }\n\n return aggregations;\n }\n}\n"],"names":["NotFoundError","filterAuthorizedMetrics","mergeEntityAndProviderThresholds","stringifyError","AggregatedMetricMapper","aggregationTypes","normalizeOwnerRef","AGGREGATION_KPIS_CONFIG_PATH","buildAggregationConfig"],"mappings":";;;;;;;;;;;AA4DO,MAAM,oBAAqB,CAAA;AAAA,EACf,MAAA;AAAA,EAEA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EAEjB,OAAwB,kBAAqB,GAAA,GAAA;AAAA,EAC7C,OAAwB,UAAa,GAAA,GAAA;AAAA,EAErC,YAAY,OAAsC,EAAA;AAChD,IAAA,IAAA,CAAK,UAAU,OAAQ,CAAA,OAAA;AACvB,IAAA,IAAA,CAAK,OAAO,OAAQ,CAAA,IAAA;AACpB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AACxB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,MAAA;AAAA;AACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,sBAAA,CACJ,SACA,EAAA,SAAA,EACA,MAGyB,EAAA;AACzB,IAAA,MAAM,MAAS,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,eAAe,SAAW,EAAA;AAAA,MAC1D,WAAa,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,wBAAyB;AAAA,KACvD,CAAA;AACD,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,IAAIA,oBAAA,CAAc,CAAqB,kBAAA,EAAA,SAAS,CAAE,CAAA,CAAA;AAAA;AAG1D,IAAA,MAAM,cAAiB,GAAA,IAAA,CAAK,QAAS,CAAA,WAAA,CAAY,SAAS,CAAA;AAE1D,IAAA,MAAM,wBAA2B,GAAAC,uCAAA;AAAA,MAC/B,cAAA;AAAA,MACA;AAAA,KACF;AACA,IAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,QAAS,CAAA,4BAAA;AAAA,MACrC,SAAA;AAAA,MACA,wBAAyB,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,EAAE;AAAA,KACxC;AAEA,IAAA,OAAO,UAAW,CAAA,GAAA;AAAA,MAChB,CAAC,EAAE,SAAA,EAAW,OAAO,aAAe,EAAA,SAAA,EAAW,QAAa,KAAA;AAC1D,QAAI,IAAA,UAAA;AACJ,QAAI,IAAA,cAAA;AAEJ,QAAA,MAAM,QAAW,GAAA,IAAA,CAAK,QAAS,CAAA,WAAA,CAAY,SAAS,CAAA;AACpD,QAAA,MAAM,MAAS,GAAA,IAAA,CAAK,QAAS,CAAA,SAAA,CAAU,SAAS,CAAA;AAEhD,QAAI,IAAA;AACF,UAAa,UAAA,GAAAC,iEAAA,CAAiC,QAAQ,QAAQ,CAAA;AAE9D,UAAA,IAAI,UAAU,IAAM,EAAA;AAClB,YACE,cAAA,GAAA,wDAAA;AAAA,qBACO,aAAe,EAAA;AACxB,YAAiB,cAAA,GAAA,aAAA;AAAA;AACnB,iBACO,KAAO,EAAA;AACd,UAAA,cAAA,GAAiBC,sBAAe,KAAK,CAAA;AAAA;AAGvC,QAAM,MAAA,iBAAA,GAAoB,aAAkB,KAAA,IAAA,IAAQ,KAAU,KAAA,IAAA;AAE9D,QAAO,OAAA;AAAA,UACL,IAAI,MAAO,CAAA,EAAA;AAAA,UACX,MAAA,EAAQ,oBAAoB,OAAU,GAAA,SAAA;AAAA,UACtC,QAAU,EAAA;AAAA,YACR,OAAO,MAAO,CAAA,KAAA;AAAA,YACd,aAAa,MAAO,CAAA,WAAA;AAAA,YACpB,MAAM,MAAO,CAAA,IAAA;AAAA,YACb,SAAS,MAAO,CAAA;AAAA,WAClB;AAAA,UACA,GAAI,iBAAqB,IAAA;AAAA,YACvB,OACE,aACA,IAAAA,qBAAA,CAAe,IAAI,KAAA,CAAM,6BAA6B,CAAC;AAAA,WAC3D;AAAA,UACA,MAAQ,EAAA;AAAA,YACN,KAAA;AAAA,YACA,SAAW,EAAA,IAAI,IAAK,CAAA,SAAS,EAAE,WAAY,EAAA;AAAA,YAC3C,eAAiB,EAAA;AAAA,cACf,UAAY,EAAA,UAAA;AAAA,cACZ,MAAA,EAAQ,iBAAiB,OAAU,GAAA,SAAA;AAAA,cACnC,UAAY,EAAA,MAAA;AAAA,cACZ,GAAI,cAAA,IAAkB,EAAE,KAAA,EAAO,cAAe;AAAA;AAChD;AACF,SACF;AAAA;AACF,KACF;AAAA;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iCACJ,CAAA,UAAA,EACA,QAC2B,EAAA;AAC3B,IAAM,MAAA,gBAAA,GACJ,MAAM,IAAA,CAAK,QAAS,CAAA,gCAAA;AAAA,MAClB,UAAA;AAAA,MACA;AAAA,KACF;AAEF,IAAO,OAAAC,8BAAA,CAAuB,mBAAmB,gBAAgB,CAAA;AAAA;AACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,+BAAA,CACJ,UACA,EAAA,QAAA,EACA,eAC2B,EAAA;AAC3B,IAAI,IAAA,UAAA,CAAW,WAAW,CAAG,EAAA;AAC3B,MAAI,IAAA,eAAA,KAAoBC,gDAAiB,aAAe,EAAA;AACtD,QAAO,OAAA,IAAA,CAAK,iCAAkC,CAAA,UAAA,EAAY,QAAQ,CAAA;AAAA;AAEpE,MAAA,MAAM,IAAI,KAAA,CAAM,CAAiC,8BAAA,EAAA,eAAe,CAAE,CAAA,CAAA;AAAA;AAGpE,IAAA,OAAOD,+BAAuB,kBAAmB,EAAA;AAAA;AACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,sBAAA,CACJ,QACA,EAAA,WAAA,EACA,OAkBqC,EAAA;AAErC,IAAA,MAAM,MAAS,GAAA,IAAA,CAAK,QAAS,CAAA,SAAA,CAAU,QAAQ,CAAA;AAG/C,IAAA,IAAA,CACG,QAAQ,IAAO,GAAA,CAAA,IAAK,OAAQ,CAAA,KAAA,IAC7B,qBAAqB,kBACrB,EAAA;AACA,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,CAAA;AAAA,UACP,UAAY,EAAA,CAAA;AAAA,UACZ,QAAU,EAAA;AAAA;AACZ,OACF;AAAA;AAOF,IAAA,MAAM,IAAO,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,6BAA6B,QAAU,EAAA;AAAA,MACtE,QAAQ,OAAQ,CAAA,MAAA;AAAA,MAChB,YAAY,OAAQ,CAAA,UAAA;AAAA,MACpB,YAAY,OAAQ,CAAA,IAAA;AAAA,MACpB,iBAAiB,OAAQ,CAAA,SAAA;AAAA,MACzB,aAAa,OAAQ,CAAA,KAAA;AAAA,MACrB,QAAQ,OAAQ,CAAA,MAAA;AAAA,MAChB,WAAW,OAAQ,CAAA,SAAA;AAAA,MACnB,UAAY,EAAA;AAAA,QACV,OAAO,oBAAqB,CAAA,kBAAA;AAAA,QAC5B,MAAQ,EAAA;AAAA;AACV,KACD,CAAA;AAMD,IAAM,MAAA,SAAA,uBAAgB,GAAoB,EAAA;AAC1C,IAAA,MAAM,iBAAkC,EAAC;AACzC,IAAI,IAAA;AACF,MAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,KAAK,MAAQ,EAAA,CAAA,IAAK,qBAAqB,UAAY,EAAA;AACrE,QAAA,MAAM,QAAQ,IAAK,CAAA,KAAA,CAAM,CAAG,EAAA,CAAA,GAAI,qBAAqB,UAAU,CAAA;AAC/D,QAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,OAAQ,CAAA,iBAAA;AAAA,UAClC;AAAA,YACE,UAAY,EAAA,KAAA,CAAM,GAAI,CAAA,CAAA,GAAA,KAAO,IAAI,kBAAkB,CAAA;AAAA,YACnD,MAAQ,EAAA;AAAA,cACN,MAAA;AAAA,cACA,eAAA;AAAA,cACA,oBAAA;AAAA,cACA;AAAA;AACF,WACF;AAAA,UACA,EAAE,WAAY;AAAA,SAChB;AAGA,QAAA,KAAA,IAAS,CAAI,GAAA,CAAA,EAAG,CAAI,GAAA,KAAA,CAAM,QAAQ,CAAK,EAAA,EAAA;AACrC,UAAM,MAAA,MAAA,GAAS,QAAS,CAAA,KAAA,CAAM,CAAC,CAAA;AAC/B,UAAA,IAAI,CAAC,MAAQ,EAAA;AACb,UAAA,SAAA,CAAU,GAAI,CAAA,KAAA,CAAM,CAAC,CAAA,CAAE,oBAAoB,MAAM,CAAA;AACjD,UAAe,cAAA,CAAA,IAAA,CAAK,KAAM,CAAA,CAAC,CAAC,CAAA;AAAA;AAC9B;AACF,aACO,KAAO,EAAA;AAGd,MAAA,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,uCAAyC,EAAA,EAAE,OAAO,CAAA;AACpE,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,CAAA;AAAA,UACP,UAAY,EAAA,CAAA;AAAA,UACZ,QAAU,EAAA;AAAA;AACZ,OACF;AAAA;AAIF,IAAM,MAAA,QAAA,GAAW,IAAK,CAAA,MAAA,KAAW,oBAAqB,CAAA,kBAAA;AAGtD,IAAA,MAAM,gBAAgB,cAAe,CAAA,MAAA;AACrC,IAAA,MAAM,WAAW,cAAe,CAAA,KAAA;AAAA,MAC7B,CAAA,OAAA,CAAQ,IAAO,GAAA,CAAA,IAAK,OAAQ,CAAA,KAAA;AAAA,MAC7B,OAAA,CAAQ,OAAO,OAAQ,CAAA;AAAA,KACzB;AAIA,IAAI,IAAA,QAAA,CAAS,WAAW,CAAG,EAAA;AACzB,MAAO,OAAA;AAAA,QACL,UAAU,MAAO,CAAA,EAAA;AAAA,QACjB,cAAgB,EAAA;AAAA,UACd,OAAO,MAAO,CAAA,KAAA;AAAA,UACd,aAAa,MAAO,CAAA,WAAA;AAAA,UACpB,MAAM,MAAO,CAAA;AAAA,SACf;AAAA,QACA,UAAU,EAAC;AAAA,QACX,UAAY,EAAA;AAAA,UACV,MAAM,OAAQ,CAAA,IAAA;AAAA,UACd,UAAU,OAAQ,CAAA,KAAA;AAAA,UAClB,KAAO,EAAA,aAAA;AAAA,UACP,UAAY,EAAA,IAAA,CAAK,IAAK,CAAA,aAAA,GAAgB,QAAQ,KAAK,CAAA;AAAA,UACnD;AAAA;AACF,OACF;AAAA;AAIF,IAAA,MAAM,mBAAyC,EAAC;AAChD,IAAA,KAAA,MAAW,OAAO,QAAU,EAAA;AAC1B,MAAA,MAAM,MAAS,GAAA,SAAA,CAAU,GAAI,CAAA,GAAA,CAAI,kBAAkB,CAAA;AACnD,MAAA,IAAI,CAAC,MAAQ,EAAA;AACb,MAAA,gBAAA,CAAiB,IAAK,CAAA;AAAA,QACpB,WAAW,GAAI,CAAA,kBAAA;AAAA,QACf,eAAA,EAAiB,OAAO,QAAS,CAAA,SAAA;AAAA,QACjC,UAAA,EAAY,OAAO,QAAS,CAAA,IAAA;AAAA,QAC5B,YAAY,MAAO,CAAA,IAAA;AAAA,QACnB,KAAO,EAAAE,mCAAA,CAAkB,MAAO,CAAA,IAAA,EAAM,KAAK,CAAK,IAAA,EAAA;AAAA,QAChD,aAAa,GAAI,CAAA,KAAA;AAAA,QACjB,WAAW,IAAI,IAAA,CAAK,GAAI,CAAA,SAAS,EAAE,WAAY,EAAA;AAAA,QAC/C,QAAQ,GAAI,CAAA;AAAA,OACb,CAAA;AAAA;AAIH,IAAO,OAAA;AAAA,MACL,UAAU,MAAO,CAAA,EAAA;AAAA,MACjB,cAAgB,EAAA;AAAA,QACd,OAAO,MAAO,CAAA,KAAA;AAAA,QACd,aAAa,MAAO,CAAA,WAAA;AAAA,QACpB,MAAM,MAAO,CAAA;AAAA,OACf;AAAA,MACA,QAAU,EAAA,gBAAA;AAAA,MACV,UAAY,EAAA;AAAA,QACV,MAAM,OAAQ,CAAA,IAAA;AAAA,QACd,UAAU,OAAQ,CAAA,KAAA;AAAA,QAClB,KAAO,EAAA,aAAA;AAAA,QACP,UAAY,EAAA,IAAA,CAAK,IAAK,CAAA,aAAA,GAAgB,QAAQ,KAAK,CAAA;AAAA,QACnD;AAAA;AACF,KACF;AAAA;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,sBACE,cAA2B,GAAA,EAC3B,EAAA,SAAA,GAAsB,EACD,EAAA;AACrB,IAAM,MAAA,qBAAA,GAAwB,KAAK,MAAO,CAAA,iBAAA;AAAA,MACxCC;AAAA,KACF;AAEA,IAAA,IAAI,CAAC,qBAAuB,EAAA;AAC1B,MAAA,OAAO,EAAC;AAAA;AAGV,IAAA,MAAM,eAAoC,EAAC;AAE3C,IAAA,MAAM,uBACJ,cAAe,CAAA,MAAA,GAAS,CAAI,GAAA,cAAA,GAAiB,sBAAsB,IAAK,EAAA;AAE1E,IAAA,KAAA,MAAW,iBAAiB,oBAAsB,EAAA;AAChD,MAAM,MAAA,MAAA,GAAS,qBAAsB,CAAA,iBAAA,CAAkB,aAAa,CAAA;AAEpE,MAAA,IAAI,MAAQ,EAAA;AACV,QAAM,MAAA,iBAAA,GAAoBC,8CAAuB,aAAe,EAAA;AAAA,UAC9D;AAAA,SACD,CAAA;AAED,QAAA,IACE,UAAU,MAAW,KAAA,CAAA,IACrB,UAAU,QAAS,CAAA,iBAAA,CAAkB,QAAQ,CAC7C,EAAA;AACA,UAAA,YAAA,CAAa,KAAK,iBAAiB,CAAA;AAAA;AACrC;AACF;AAGF,IAAO,OAAA,YAAA;AAAA;AAEX;;;;"}
@@ -96,7 +96,7 @@ async function createRouter({
96
96
  backstagePluginScorecardCommon.scorecardMetricReadPermission
97
97
  );
98
98
  const provider = metricProvidersRegistry.getProvider(metricId);
99
- const metric = provider.getMetric();
99
+ const metric = metricProvidersRegistry.getMetric(metricId);
100
100
  const authorizedMetrics = permissionUtils.filterAuthorizedMetrics([metric], conditions);
101
101
  if (authorizedMetrics.length === 0) {
102
102
  throw new errors.NotAllowedError(
@@ -202,7 +202,9 @@ async function createRouter({
202
202
  const provider = metricProvidersRegistry.getProvider(
203
203
  aggregationConfig?.metricId ?? aggregationId
204
204
  );
205
- const metric = provider.getMetric();
205
+ const metric = metricProvidersRegistry.getMetric(
206
+ aggregationConfig?.metricId ?? aggregationId
207
+ );
206
208
  const entitiesOwnedByAUser = await getEntitiesOwnedByUser.getEntitiesOwnedByUser(userEntityRef, {
207
209
  catalog,
208
210
  credentials
@@ -240,10 +242,9 @@ async function createRouter({
240
242
  const [aggregationConfig] = catalogMetricService.getAggregationConfigs([
241
243
  aggregationId
242
244
  ]);
243
- const provider = metricProvidersRegistry.getProvider(
245
+ const metric = metricProvidersRegistry.getMetric(
244
246
  aggregationConfig?.metricId ?? aggregationId
245
247
  );
246
- const metric = provider.getMetric();
247
248
  res.json(
248
249
  mappers.AggregatedMetricMapper.toAggregationMetadata(metric, aggregationConfig)
249
250
  );
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthenticationError,\n InputError,\n NotAllowedError,\n} from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport type { CatalogMetricService } from './CatalogMetricService';\nimport type { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n LoggerService,\n type HttpAuthService,\n type PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n filterAuthorizedMetrics,\n checkEntityAccess,\n authorizeConditional,\n getUserEntityRef,\n} from '../permissions/permissionUtils';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { validateMetricIdsQueryParams } from '../middlewares/validateMetricIdsQueryParams';\nimport { getEntitiesOwnedByUser } from '../utils/getEntitiesOwnedByUser';\nimport { parseCommaSeparatedString } from '../utils/parseCommaSeparatedString';\nimport { AggregatedMetricMapper } from './mappers';\nimport { validateDrillDownMetricsSchema } from '../validation/validateDrillDownMetricsSchema';\nimport { validateAggregationIdParam } from '../middlewares/validateAggregationIdParam';\nimport {\n aggregationTypes,\n scorecardMetricReadPermission,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { validateDatasourceQueryParams } from '../middlewares/validateDatasourceQueryParams';\n\nexport type ScorecardRouterOptions = {\n metricProvidersRegistry: MetricProvidersRegistry;\n catalogMetricService: CatalogMetricService;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n permissions: PermissionsService;\n logger: LoggerService;\n};\n\nexport async function createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n catalog,\n httpAuth,\n permissions,\n logger,\n}: ScorecardRouterOptions): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.get(\n '/metrics',\n validateMetricIdsQueryParams,\n validateDatasourceQueryParams,\n async (req, res) => {\n const { metricIds, datasource } = req.query;\n\n if (metricIds && datasource) {\n throw new InputError('Cannot filter by both metricIds and datasource');\n }\n\n if (metricIds) {\n return res.json({\n metrics: metricProvidersRegistry.listMetrics(\n parseCommaSeparatedString(metricIds as string),\n ),\n });\n }\n\n if (datasource) {\n return res.json({\n metrics: metricProvidersRegistry.listMetricsByDatasource(\n datasource as string,\n ),\n });\n }\n\n return res.json({ metrics: metricProvidersRegistry.listMetrics() });\n },\n );\n\n router.get(\n '/metrics/catalog/:kind/:namespace/:name',\n validateMetricIdsQueryParams,\n async (req, res) => {\n const { metricIds } = req.query;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const { kind, namespace, name } = req.params;\n\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n\n // Check if user has permission to read this specific catalog entity\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n\n const metricIdArray = metricIds\n ? parseCommaSeparatedString(metricIds as string)\n : undefined;\n\n const results = await catalogMetricService.getLatestEntityMetrics(\n entityRef,\n metricIdArray,\n conditions,\n );\n res.json(results);\n },\n );\n\n // Deprecated (RFC 8594): use GET /aggregations/:aggregationId instead.\n router.get(\n '/metrics/:metricId/catalog/aggregations',\n (req, res, next) => {\n const { metricId } = req.params;\n const successorPath = `${req.baseUrl}/aggregations/${encodeURIComponent(\n metricId,\n )}`;\n res.setHeader('Deprecation', 'true');\n res.setHeader('Link', `<${successorPath}>; rel=\"alternate\"`);\n next();\n },\n async (req, res) => {\n const { metricId } = req.params;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const provider = metricProvidersRegistry.getProvider(metricId);\n const metric = provider.getMetric();\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const thresholds = provider.getMetricThresholds();\n const aggregatedMetric =\n await catalogMetricService.getAggregatedMetricByEntityRefs(\n entitiesOwnedByAUser,\n metricId,\n aggregationTypes.statusGrouped, // By default, used the status grouped aggregation type\n );\n\n logger.warn(\n `Deprecated Scorecard API: GET /metrics/${metricId}/catalog/aggregations is deprecated; use GET /aggregations/:aggregationId (e.g. when the aggregation id matches the metric id, GET /aggregations/${metricId}).`,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n thresholds,\n aggregatedMetric,\n ),\n );\n },\n );\n\n router.get(\n '/metrics/:metricId/catalog/aggregations/entities',\n async (req, res) => {\n const { metricId } = req.params;\n\n const {\n page,\n pageSize,\n status,\n owner,\n kind,\n namespace,\n entityName,\n sortBy,\n sortOrder,\n } = validateDrillDownMetricsSchema(req.query, logger);\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entityMetrics = await catalogMetricService.getEntityMetricDetails(\n metricId,\n credentials,\n {\n status,\n owner,\n kind,\n entityName,\n namespace,\n sortBy,\n sortOrder,\n page,\n limit: pageSize,\n },\n );\n\n res.json(entityMetrics);\n },\n );\n\n router.get(\n '/aggregations/:aggregationId',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const userEntityRef = await getUserEntityRef(credentials);\n\n const [aggregationConfig] = catalogMetricService.getAggregationConfigs([\n aggregationId,\n ]);\n\n const provider = metricProvidersRegistry.getProvider(\n aggregationConfig?.metricId ?? aggregationId,\n );\n const metric = provider.getMetric();\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the aggregation of a scorecard metric, your administrator must grant you the required permission.`,\n );\n }\n\n const thresholds = provider.getMetricThresholds();\n\n const aggregatedMetric =\n await catalogMetricService.getAggregatedMetricByEntityRefs(\n entitiesOwnedByAUser,\n metric.id,\n aggregationConfig?.type ?? aggregationTypes.statusGrouped,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n thresholds,\n aggregatedMetric,\n aggregationConfig,\n ),\n );\n },\n );\n\n router.get(\n '/aggregations/:aggregationId/metadata',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const [aggregationConfig] = catalogMetricService.getAggregationConfigs([\n aggregationId,\n ]);\n\n const provider = metricProvidersRegistry.getProvider(\n aggregationConfig?.metricId ?? aggregationId,\n );\n const metric = provider.getMetric();\n\n res.json(\n AggregatedMetricMapper.toAggregationMetadata(metric, aggregationConfig),\n );\n },\n );\n\n return router;\n}\n"],"names":["Router","express","validateMetricIdsQueryParams","validateDatasourceQueryParams","InputError","parseCommaSeparatedString","authorizeConditional","scorecardMetricReadPermission","stringifyEntityRef","checkEntityAccess","filterAuthorizedMetrics","NotAllowedError","AuthenticationError","getEntitiesOwnedByUser","aggregationTypes","AggregatedMetricMapper","validateDrillDownMetricsSchema","validateAggregationIdParam","getUserEntityRef"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n AuthenticationError,\n InputError,\n NotAllowedError,\n} from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport type { CatalogMetricService } from './CatalogMetricService';\nimport type { MetricProvidersRegistry } from '../providers/MetricProvidersRegistry';\nimport {\n LoggerService,\n type HttpAuthService,\n type PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n filterAuthorizedMetrics,\n checkEntityAccess,\n authorizeConditional,\n getUserEntityRef,\n} from '../permissions/permissionUtils';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { validateMetricIdsQueryParams } from '../middlewares/validateMetricIdsQueryParams';\nimport { getEntitiesOwnedByUser } from '../utils/getEntitiesOwnedByUser';\nimport { parseCommaSeparatedString } from '../utils/parseCommaSeparatedString';\nimport { AggregatedMetricMapper } from './mappers';\nimport { validateDrillDownMetricsSchema } from '../validation/validateDrillDownMetricsSchema';\nimport { validateAggregationIdParam } from '../middlewares/validateAggregationIdParam';\nimport {\n aggregationTypes,\n scorecardMetricReadPermission,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { validateDatasourceQueryParams } from '../middlewares/validateDatasourceQueryParams';\n\nexport type ScorecardRouterOptions = {\n metricProvidersRegistry: MetricProvidersRegistry;\n catalogMetricService: CatalogMetricService;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n permissions: PermissionsService;\n logger: LoggerService;\n};\n\nexport async function createRouter({\n metricProvidersRegistry,\n catalogMetricService,\n catalog,\n httpAuth,\n permissions,\n logger,\n}: ScorecardRouterOptions): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.get(\n '/metrics',\n validateMetricIdsQueryParams,\n validateDatasourceQueryParams,\n async (req, res) => {\n const { metricIds, datasource } = req.query;\n\n if (metricIds && datasource) {\n throw new InputError('Cannot filter by both metricIds and datasource');\n }\n\n if (metricIds) {\n return res.json({\n metrics: metricProvidersRegistry.listMetrics(\n parseCommaSeparatedString(metricIds as string),\n ),\n });\n }\n\n if (datasource) {\n return res.json({\n metrics: metricProvidersRegistry.listMetricsByDatasource(\n datasource as string,\n ),\n });\n }\n\n return res.json({ metrics: metricProvidersRegistry.listMetrics() });\n },\n );\n\n router.get(\n '/metrics/catalog/:kind/:namespace/:name',\n validateMetricIdsQueryParams,\n async (req, res) => {\n const { metricIds } = req.query;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const { kind, namespace, name } = req.params;\n\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n\n // Check if user has permission to read this specific catalog entity\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n\n const metricIdArray = metricIds\n ? parseCommaSeparatedString(metricIds as string)\n : undefined;\n\n const results = await catalogMetricService.getLatestEntityMetrics(\n entityRef,\n metricIdArray,\n conditions,\n );\n res.json(results);\n },\n );\n\n // Deprecated (RFC 8594): use GET /aggregations/:aggregationId instead.\n router.get(\n '/metrics/:metricId/catalog/aggregations',\n (req, res, next) => {\n const { metricId } = req.params;\n const successorPath = `${req.baseUrl}/aggregations/${encodeURIComponent(\n metricId,\n )}`;\n res.setHeader('Deprecation', 'true');\n res.setHeader('Link', `<${successorPath}>; rel=\"alternate\"`);\n next();\n },\n async (req, res) => {\n const { metricId } = req.params;\n\n const { conditions } = await authorizeConditional(\n await httpAuth.credentials(req),\n permissions,\n scorecardMetricReadPermission,\n );\n\n const provider = metricProvidersRegistry.getProvider(metricId);\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const thresholds = provider.getMetricThresholds();\n const aggregatedMetric =\n await catalogMetricService.getAggregatedMetricByEntityRefs(\n entitiesOwnedByAUser,\n metricId,\n aggregationTypes.statusGrouped, // By default, used the status grouped aggregation type\n );\n\n logger.warn(\n `Deprecated Scorecard API: GET /metrics/${metricId}/catalog/aggregations is deprecated; use GET /aggregations/:aggregationId (e.g. when the aggregation id matches the metric id, GET /aggregations/${metricId}).`,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n thresholds,\n aggregatedMetric,\n ),\n );\n },\n );\n\n router.get(\n '/metrics/:metricId/catalog/aggregations/entities',\n async (req, res) => {\n const { metricId } = req.params;\n\n const {\n page,\n pageSize,\n status,\n owner,\n kind,\n namespace,\n entityName,\n sortBy,\n sortOrder,\n } = validateDrillDownMetricsSchema(req.query, logger);\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const metric = metricProvidersRegistry.getMetric(metricId);\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the scorecard metrics, your administrator must grant you the required permission.`,\n );\n }\n\n const userEntityRef = credentials?.principal?.userEntityRef;\n\n if (!userEntityRef) {\n throw new AuthenticationError('User entity reference not found');\n }\n\n const entityMetrics = await catalogMetricService.getEntityMetricDetails(\n metricId,\n credentials,\n {\n status,\n owner,\n kind,\n entityName,\n namespace,\n sortBy,\n sortOrder,\n page,\n limit: pageSize,\n },\n );\n\n res.json(entityMetrics);\n },\n );\n\n router.get(\n '/aggregations/:aggregationId',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const credentials = await httpAuth.credentials(req, { allow: ['user'] });\n\n const { conditions } = await authorizeConditional(\n credentials,\n permissions,\n scorecardMetricReadPermission,\n );\n\n const userEntityRef = await getUserEntityRef(credentials);\n\n const [aggregationConfig] = catalogMetricService.getAggregationConfigs([\n aggregationId,\n ]);\n\n const provider = metricProvidersRegistry.getProvider(\n aggregationConfig?.metricId ?? aggregationId,\n );\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n const entitiesOwnedByAUser = await getEntitiesOwnedByUser(userEntityRef, {\n catalog,\n credentials,\n });\n for (const entityRef of entitiesOwnedByAUser) {\n await checkEntityAccess(entityRef, req, permissions, httpAuth);\n }\n\n const authorizedMetrics = filterAuthorizedMetrics([metric], conditions);\n if (authorizedMetrics.length === 0) {\n throw new NotAllowedError(\n `To view the aggregation of a scorecard metric, your administrator must grant you the required permission.`,\n );\n }\n\n const thresholds = provider.getMetricThresholds();\n\n const aggregatedMetric =\n await catalogMetricService.getAggregatedMetricByEntityRefs(\n entitiesOwnedByAUser,\n metric.id,\n aggregationConfig?.type ?? aggregationTypes.statusGrouped,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregatedMetricResult(\n metric,\n thresholds,\n aggregatedMetric,\n aggregationConfig,\n ),\n );\n },\n );\n\n router.get(\n '/aggregations/:aggregationId/metadata',\n validateAggregationIdParam,\n async (req, res) => {\n const { aggregationId } = req.params;\n\n const [aggregationConfig] = catalogMetricService.getAggregationConfigs([\n aggregationId,\n ]);\n\n const metric = metricProvidersRegistry.getMetric(\n aggregationConfig?.metricId ?? aggregationId,\n );\n\n res.json(\n AggregatedMetricMapper.toAggregationMetadata(metric, aggregationConfig),\n );\n },\n );\n\n return router;\n}\n"],"names":["Router","express","validateMetricIdsQueryParams","validateDatasourceQueryParams","InputError","parseCommaSeparatedString","authorizeConditional","scorecardMetricReadPermission","stringifyEntityRef","checkEntityAccess","filterAuthorizedMetrics","NotAllowedError","AuthenticationError","getEntitiesOwnedByUser","aggregationTypes","AggregatedMetricMapper","validateDrillDownMetricsSchema","validateAggregationIdParam","getUserEntityRef"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
3
- "version": "2.5.1",
3
+ "version": "2.6.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.1.0",
48
48
  "@backstage/plugin-permission-common": "^0.9.7",
49
49
  "@backstage/plugin-permission-node": "^0.10.11",
50
- "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.5.1",
51
- "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.5.1",
50
+ "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.6.0",
51
+ "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.6.0",
52
52
  "express": "^4.17.1",
53
53
  "express-promise-router": "^4.1.0",
54
54
  "knex": "^3.1.0",