Skip to content

Commit 5d447f1

Browse files
dzemanovimykhno
andauthored
feat(scorecard)!: add database and schedulers (#1544)
* Introduce metric_values db Signed-off-by: Dominika Zemanovicova <[email protected]> * Connect metric providers on schedule Signed-off-by: Dominika Zemanovicova <[email protected]> * Add catalog entities processing to providers Signed-off-by: Dominika Zemanovicova <[email protected]> * Extract to BaseMetricProvider Signed-off-by: Dominika Zemanovicova <[email protected]> * Rename metricValue row Signed-off-by: Dominika Zemanovicova <[email protected]> * Integrate db read to metrics endpoint Signed-off-by: Dominika Zemanovicova <[email protected]> * Use directly catalogService in CatalogMetricService Signed-off-by: Dominika Zemanovicova <[email protected]> * Remove not needed readMetricValues Signed-off-by: Dominika Zemanovicova <[email protected]> * Switch db value to json Signed-off-by: Dominika Zemanovicova <[email protected]> * Extract scheduling to scorecard backend Signed-off-by: Dominika Zemanovicova <[email protected]> * Simplify DatabaseMetricValuesStore Signed-off-by: Dominika Zemanovicova <[email protected]> * Add tests Signed-off-by: Dominika Zemanovicova <[email protected]> * Update docs Signed-off-by: Dominika Zemanovicova <[email protected]> * Increate timeout for db Signed-off-by: Dominika Zemanovicova <[email protected]> * feat(scorecard): implement cleanup expired metrics mechanism Signed-off-by: Ihor Mykhno <[email protected]> * refactor(scorecard): logic to load and cleanup metrics Signed-off-by: Ihor Mykhno <[email protected]> * refactor(scorecard): scorecard metric loading logic Signed-off-by: Ihor Mykhno <[email protected]> * Update changeset Signed-off-by: Dominika Zemanovicova <[email protected]> --------- Signed-off-by: Dominika Zemanovicova <[email protected]> Signed-off-by: Ihor Mykhno <[email protected]> Co-authored-by: Ihor Mykhno <[email protected]>
1 parent a79251c commit 5d447f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2003
-275
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github': major
3+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira': major
4+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': major
5+
'@red-hat-developer-hub/backstage-plugin-scorecard-node': major
6+
'@red-hat-developer-hub/backstage-plugin-scorecard': major
7+
---
8+
9+
**BREAKING**: The `supportsEntity` function has been replaced with `getCatalogFilter` for `MetricProvider`. The new function returns a catalog filter instead of taking an entity parameter and returning a boolean. This allows the plugin to query the catalog for entities that support the metric provider.
10+
11+
These changes are **required** to your `MyMetricProvider`:
12+
13+
```diff
14+
export class MyMetricProvider implements MetricProvider {
15+
16+
- supportsEntity(entity: Entity): boolean {
17+
- return entity.metadata.annotations?.['my/annotation'] !== undefined;
18+
- }
19+
+ getCatalogFilter(): Record<string, string | symbol | (string | symbol)[]> {
20+
+ return {
21+
+ 'metadata.annotations.my/annotation': CATALOG_FILTER_EXISTS,
22+
+ };
23+
+ }
24+
```
25+
26+
Implemented database support. Implemented scheduler to fetch metrics by provider and to cleanup outdated metrics from database.

workspaces/scorecard/plugins/scorecard-backend-module-github/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,23 @@ This metric counts all pull requests that are currently in an "open" state for t
9090
### Threshold Configuration
9191

9292
Thresholds define conditions that determine which category a metric value belongs to ( `error`, `warning`, or `success`). You can configure custom thresholds for the GitHub metrics. Check out detailed explanation of [threshold configuration](../scorecard-backend/docs/thresholds.md).
93+
94+
## Schedule Configuration
95+
96+
The Scorecard plugin uses Backstage's built-in scheduler service to automatically collect metrics from all registered providers every hour by default. However, this configuration can be changed in the `app-config.yaml` file. Here is an example of how to do that:
97+
98+
```yaml
99+
scorecard:
100+
plugins:
101+
github:
102+
open_prs:
103+
schedule:
104+
frequency:
105+
cron: '0 6 * * *'
106+
timeout:
107+
minutes: 5
108+
initialDelay:
109+
seconds: 5
110+
```
111+
112+
The schedule configuration follows Backstage's `SchedulerServiceTaskScheduleDefinitionConfig` [schema](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts#L157).

workspaces/scorecard/plugins/scorecard-backend-module-github/config.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
1617

1718
export interface Config {
1819
/** Configuration for scorecard plugin */
@@ -29,6 +30,7 @@ export interface Config {
2930
expression: string;
3031
}>;
3132
};
33+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
3234
};
3335
};
3436
};

workspaces/scorecard/plugins/scorecard-backend-module-github/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
},
4040
"dependencies": {
4141
"@backstage/backend-plugin-api": "^1.4.2",
42+
"@backstage/catalog-client": "^1.11.0",
4243
"@backstage/catalog-model": "^1.7.5",
4344
"@backstage/integration": "^1.17.1",
45+
"@backstage/plugin-catalog-node": "^1.18.0",
4446
"@octokit/graphql": "^9.0.1",
4547
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "workspace:^",
4648
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "workspace:^"

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubOpenPRsProvider.test.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,42 +30,6 @@ jest.mock('@backstage/catalog-model', () => ({
3030
jest.mock('../github/GithubClient');
3131

3232
describe('GithubOpenPRsProvider', () => {
33-
describe('supportsEntity', () => {
34-
let provider: GithubOpenPRsProvider;
35-
36-
beforeEach(() => {
37-
provider = GithubOpenPRsProvider.fromConfig(new ConfigReader({}));
38-
});
39-
40-
it.each([
41-
[
42-
'should return true for entity with github.com/project-slug annotation',
43-
{
44-
'github.com/project-slug': 'org/repo',
45-
},
46-
true,
47-
],
48-
[
49-
'should return false for entity without github.com/project-slug annotation',
50-
{
51-
'some.other/annotation': 'value',
52-
},
53-
false,
54-
],
55-
['should return false for entity with no annotations', undefined, false],
56-
])('%s', (_, annotations, expected) => {
57-
const entity: Entity = {
58-
apiVersion: 'backstage.io/v1alpha1',
59-
kind: 'Component',
60-
metadata: {
61-
name: 'test-component',
62-
annotations,
63-
},
64-
};
65-
expect(provider.supportsEntity(entity)).toBe(expected);
66-
});
67-
});
68-
6933
describe('fromConfig', () => {
7034
it('should create provider with default thresholds when no thresholds are configured', () => {
7135
const provider = GithubOpenPRsProvider.fromConfig(new ConfigReader({}));

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubOpenPRsProvider.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,22 @@
1616

1717
import type { Config } from '@backstage/config';
1818
import { getEntitySourceLocation, type Entity } from '@backstage/catalog-model';
19+
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
1920
import {
2021
DEFAULT_NUMBER_THRESHOLDS,
2122
Metric,
2223
ThresholdConfig,
2324
} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
2425
import {
26+
getThresholdsFromConfig,
2527
MetricProvider,
26-
validateThresholds,
2728
} from '@red-hat-developer-hub/backstage-plugin-scorecard-node';
2829
import { GithubClient } from '../github/GithubClient';
2930
import { getRepositoryInformationFromEntity } from '../github/utils';
30-
import { GITHUB_PROJECT_ANNOTATION } from '../github/constants';
3131

3232
export class GithubOpenPRsProvider implements MetricProvider<'number'> {
33-
private readonly thresholds: ThresholdConfig;
3433
private readonly githubClient: GithubClient;
34+
private readonly thresholds: ThresholdConfig;
3535

3636
private constructor(config: Config, thresholds?: ThresholdConfig) {
3737
this.githubClient = new GithubClient(config);
@@ -61,20 +61,20 @@ export class GithubOpenPRsProvider implements MetricProvider<'number'> {
6161
return this.thresholds;
6262
}
6363

64-
supportsEntity(entity: Entity): boolean {
65-
return (
66-
entity.metadata.annotations?.[GITHUB_PROJECT_ANNOTATION] !== undefined
67-
);
64+
getCatalogFilter(): Record<string, string | symbol | (string | symbol)[]> {
65+
return {
66+
'metadata.annotations.github.com/project-slug': CATALOG_FILTER_EXISTS,
67+
};
6868
}
6969

7070
static fromConfig(config: Config): GithubOpenPRsProvider {
71-
const configPath = 'scorecard.plugins.github.open_prs.thresholds';
72-
const configuredThresholds = config.getOptional(configPath);
73-
if (configuredThresholds !== undefined) {
74-
validateThresholds(configuredThresholds, 'number');
75-
}
71+
const thresholds = getThresholdsFromConfig(
72+
config,
73+
'scorecard.plugins.github.open_prs.thresholds',
74+
'number',
75+
);
7676

77-
return new GithubOpenPRsProvider(config, configuredThresholds);
77+
return new GithubOpenPRsProvider(config, thresholds);
7878
}
7979

8080
async calculateMetric(entity: Entity): Promise<number> {

workspaces/scorecard/plugins/scorecard-backend-module-jira/README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This module also requires a Jira integration to be configured in your `app-confi
1515

1616
## Configuration
1717

18-
### Authentification `token`
18+
### Authentication `token`
1919

2020
- For the `cloud` product:
2121

@@ -98,6 +98,26 @@ scorecard:
9898
customFilter: priority in ("Critical", "Blocker")
9999
```
100100
101+
## Schedule Configuration
102+
103+
The Scorecard plugin uses Backstage's built-in scheduler service to automatically collect metrics from all registered providers every hour by default. However, this configuration can be changed in the `app-config.yaml` file. Here is an example of how to do that:
104+
105+
```yaml
106+
scorecard:
107+
plugins:
108+
jira:
109+
open_issues:
110+
schedule:
111+
frequency:
112+
cron: '0 6 * * *'
113+
timeout:
114+
minutes: 5
115+
initialDelay:
116+
seconds: 5
117+
```
118+
119+
The schedule configuration follows Backstage's `SchedulerServiceTaskScheduleDefinitionConfig` [schema](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts#L157).
120+
101121
## Installation
102122

103123
To install this backend module:

workspaces/scorecard/plugins/scorecard-backend-module-jira/config.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
18+
1719
export interface Config {
1820
/** Configuration for jira plugin */
1921
jira: (
@@ -48,6 +50,7 @@ export interface Config {
4850
expression: string;
4951
}>;
5052
};
53+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
5154
};
5255
};
5356
};

workspaces/scorecard/plugins/scorecard-backend-module-jira/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@
3939
},
4040
"dependencies": {
4141
"@backstage/backend-plugin-api": "^1.4.2",
42+
"@backstage/catalog-client": "^1.11.0",
43+
"@backstage/catalog-model": "^1.7.5",
44+
"@backstage/plugin-catalog-node": "^1.18.0",
4245
"@red-hat-developer-hub/backstage-plugin-scorecard-common": "workspace:^",
4346
"@red-hat-developer-hub/backstage-plugin-scorecard-node": "workspace:^"
4447
},
4548
"devDependencies": {
4649
"@backstage/backend-test-utils": "^1.8.0",
47-
"@backstage/catalog-model": "^1.7.5",
4850
"@backstage/cli": "^0.34.1",
4951
"@backstage/config": "^1.3.3"
5052
},

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/clients/base.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import type { Config } from '@backstage/config';
1818
import type { Entity } from '@backstage/catalog-model';
1919
import { JiraEntityFilters, JiraOptions, RequestOptions } from './types';
20-
import { JIRA_OPTIONS_PATH, JIRA_MANDATORY_FILTER } from '../constants';
20+
import { JIRA_MANDATORY_FILTER, OPEN_ISSUES_CONFIG_PATH } from '../constants';
2121
import { ScorecardJiraAnnotations } from '../annotations';
2222
import { sanitizeValue, validateIdentifier, validateJQLValue } from './utils';
2323
import { ConnectionStrategy } from '../strategies/ConnectionStrategy';
@@ -32,7 +32,9 @@ export abstract class JiraClient {
3232
constructor(rootConfig: Config, connectionStrategy: ConnectionStrategy) {
3333
this.connectionStrategy = connectionStrategy;
3434

35-
const jiraOptions = rootConfig.getOptionalConfig(JIRA_OPTIONS_PATH);
35+
const jiraOptions = rootConfig.getOptionalConfig(
36+
`${OPEN_ISSUES_CONFIG_PATH}.options`,
37+
);
3638
if (jiraOptions) {
3739
this.options = {
3840
mandatoryFilter: jiraOptions.getOptionalString('mandatoryFilter'),

0 commit comments

Comments
 (0)