Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3cbb66b
Remove abstract metric providers, left only openssf metric provider a…
alizard0 Feb 10, 2026
4c4db46
fixed calculateMetric signature to receive entity as expected by the …
alizard0 Feb 10, 2026
b621e10
Merge branch 'main' into RHIDP-12106
alizard0 Feb 10, 2026
8d0afbd
add component for testing the new baseUrl annotation for openssf scor…
alizard0 Feb 11, 2026
2f23269
add exclude metric for openssf
alizard0 Feb 17, 2026
d8eaa6f
Merge branch 'main' into RHIDP-12183
alizard0 Feb 25, 2026
0152c57
renamed baseUrl to scorecard-location
alizard0 Feb 25, 2026
aea0d64
rename annotation excludeChecks to exclude-checks
alizard0 Feb 25, 2026
7a7647a
Merge branch 'main' into RHIDP-12183
alizard0 Feb 25, 2026
b3b73b4
rollback component name to openssf-scorecard-only
alizard0 Feb 25, 2026
80a7a99
fix unit tests
alizard0 Feb 25, 2026
84b2172
fix the issue where the exclude-checks is compsoed by a string instea…
alizard0 Feb 25, 2026
dcf4d50
Merge branch 'main' into RHIDP-12183
alizard0 Feb 25, 2026
8da97ef
Merge branch 'main' into RHIDP-12183
alizard0 Feb 27, 2026
b2b7cb4
Merge branch 'main' into RHIDP-12183
alizard0 Mar 2, 2026
56c6abc
exclude metrics in PullMetricsByProviderTask instead
alizard0 Mar 2, 2026
de32169
Merge branch 'main' into RHIDP-12183
alizard0 Mar 2, 2026
5552d49
Merge branch 'main' into RHIDP-12183
alizard0 Mar 2, 2026
9ad2312
review work cicd
alizard0 Mar 2, 2026
69b6f05
Merge branch 'main' into RHIDP-12183
alizard0 Mar 3, 2026
9c2996a
Merge branch 'main' into RHIDP-12183
alizard0 Mar 4, 2026
c9afc8d
implemented exclude/include metrics using app-config and exclude usin…
alizard0 Mar 4, 2026
4dfeaa3
Merge branch 'main' into RHIDP-12183
alizard0 Mar 5, 2026
ad03413
Merge branch 'main' into RHIDP-12183
alizard0 Mar 6, 2026
2251333
Merge branch 'main' into RHIDP-12183
alizard0 Mar 6, 2026
05e836f
review work
alizard0 Mar 6, 2026
e72289a
review work
alizard0 Mar 6, 2026
c26593f
review work
alizard0 Mar 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ metadata:
name: openssf-scorecard-only
annotations:
openssf/scorecard-location: https://api.securityscorecards.dev/projects/github.com/alizard0/rhdh-plugins
scorecard.io/disabled-metrics: openssf.maintained
spec:
type: service
owner: group:development/guests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ const mockOpenSSFResponse: OpenSSFResponse = {
details: null,
documentation: { short: '', url: '' },
},
{
name: 'Code-Review',
score: 9,
reason: null,
details: null,
documentation: { short: '', url: '' },
},
],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ export class OpenSSFClient {
);
}

const data: OpenSSFResponse = await response.json();

return data;
return await response.json();
}
}
17 changes: 17 additions & 0 deletions workspaces/scorecard/plugins/scorecard-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ export interface Config {
scorecard?: {
/** Number of days to retain metric data in the database. Older data will be automatically cleaned up. Default: 365 days */
dataRetentionDays?: number;
/** List of metric IDs (e.g. openssf.packaging) that are disabled globally. Entity annotations cannot override this. */
disabledMetrics?: string[];
/**
* Control whether users can override behavior via entity annotations.
* This only affects entity annotations (e.g. scorecard.io/disabled-metrics); it does not affect scorecard.disabledMetrics or other app-config.
*/
entityOverrides?: {
/** Whether entity scorecard.io/disabled-metrics annotation can override. Only affects annotations; global disabledMetrics is unchanged. */
disabledMetrics?: {
/** If true (default), entity can disable metrics via annotation; except list can force some to run. If false, entity list is still applied (union with disabledMetrics) but entity cannot override to re-enable anything. */
enabled?: boolean;
/** When enabled is true: metric IDs that entity cannot disable (must run). When enabled is false: not used. */
except?: string[];
};
};
/** @deprecated Use entityOverrides.disabledMetrics.except (with enabled: true) instead. List of metric IDs that must run even when disabled via entity annotation. */
includeMetrics?: string[];
/** Configuration for scorecard metric providers */
plugins?: {
/** Configuration for datasource */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,267 @@
});
});

describe('isMetricIdExcluded', () => {
const providerId = 'openssf.maintained';

function createTaskWithScorecardConfig(

Check warning on line 162 in workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'createTaskWithScorecardConfig' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZy4W60nJp304WWPokX5&open=AZy4W60nJp304WWPokX5&pullRequest=2393
scorecardOverrides: {
includeMetrics?: string[];
disabledMetrics?: string[];
entityOverrides?: {
disabledMetrics?: { enabled?: boolean; except?: string[] };
};
} = {},
) {
const config = mockServices.rootConfig({
data: {
scorecard: {
schedule: scheduleConfig,
...scorecardOverrides,
},
},
});
return new PullMetricsByProviderTask(
{
scheduler: mockScheduler,
logger: mockLogger,
database: mockDatabaseMetricValues,
config,
catalog: mockCatalog,
auth: mockAuth,
thresholdEvaluator: mockThresholdEvaluator,
},
mockProvider,
);
}

function createEntity(annotationValue?: string) {

Check warning on line 193 in workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'createEntity' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZy4W60oJp304WWPokX6&open=AZy4W60oJp304WWPokX6&pullRequest=2393
return {
apiVersion: '1.0.0' as const,
kind: 'Component' as const,
metadata: {
name: 'test-entity',
...(annotationValue !== undefined && {
annotations: {
'scorecard.io/disabled-metrics': annotationValue,
},
}),
},
};
}

it('returns true when metric is in app-config disabledMetrics', () => {
const taskWithConfig = createTaskWithScorecardConfig({
disabledMetrics: [providerId],
});
const entity = createEntity();

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by app-config: ${providerId}`,
);
});

it('returns true when metric is in app-config disabledMetrics even if also in includeMetrics (disabled wins)', () => {
const taskWithConfig = createTaskWithScorecardConfig({
includeMetrics: [providerId],
disabledMetrics: [providerId],
});
const entity = createEntity();

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by app-config: ${providerId}`,
);
});

it('returns false when disabled by annotation but included by app-config (include overrides annotation)', () => {
const taskWithConfig = createTaskWithScorecardConfig({
includeMetrics: [providerId],
});
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(false);
expect(mockLogger.info).toHaveBeenCalledWith(
`Entity override: metric disabled by annotation but in entityOverrides.disabledMetrics.except (must run): ${providerId}`,
);
});

it('returns true when disabled by annotation and not in app-config includeMetrics', () => {
const taskWithConfig = createTaskWithScorecardConfig();
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by annotation: ${providerId}`,
);
});

it('returns true when disabled by annotation and app-config includeMetrics exists but does not contain this metric', () => {
const taskWithConfig = createTaskWithScorecardConfig({
includeMetrics: ['other_metric'],
});
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by annotation: ${providerId}`,
);
});

it('parses comma-separated annotation and excludes when providerId is in the list', () => {
const taskWithConfig = createTaskWithScorecardConfig();
const entity = createEntity('github.test_metric,openssf.maintained');

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by annotation: ${providerId}`,
);
});

it('returns false when not disabled by app-config or annotation', () => {
const taskWithConfig = createTaskWithScorecardConfig();
const entity = createEntity();

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(false);
});

it('returns true when entityOverrides.disabledMetrics.enabled is false and metric is in entity annotation (union of app-config and entity list)', () => {
const taskWithConfig = createTaskWithScorecardConfig({
entityOverrides: {
disabledMetrics: { enabled: false },
},
});
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by annotation (entity overrides disabled): ${providerId}`,
);
});

it('returns false when entityOverrides.disabledMetrics.enabled is false and metric is not in entity annotation', () => {
const taskWithConfig = createTaskWithScorecardConfig({
entityOverrides: {
disabledMetrics: { enabled: false },
},
});
const entity = createEntity(); // no annotation

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(false);
});

it('returns true when enabled is false with nested config and entity annotation (getOptionalConfig path)', () => {
const taskWithConfig = createTaskWithScorecardConfig({
entityOverrides: {
disabledMetrics: {
enabled: false,
except: [providerId],
},
},
});
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(true);
expect(mockLogger.info).toHaveBeenCalledWith(
`Disabled metric by annotation (entity overrides disabled): ${providerId}`,
);
});

it('returns false when disabled by annotation but metric is in entityOverrides.disabledMetrics.except', () => {
const taskWithConfig = createTaskWithScorecardConfig({
entityOverrides: {
disabledMetrics: { enabled: true, except: [providerId] },
},
});
const entity = createEntity(providerId);

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(false);
expect(mockLogger.info).toHaveBeenCalledWith(
`Entity override: metric disabled by annotation but in entityOverrides.disabledMetrics.except (must run): ${providerId}`,
);
});

it('returns false when no config and no annotation', () => {
const taskWithConfig = createTaskWithScorecardConfig();
const entity = createEntity();

const result = (taskWithConfig as any).isMetricIdExcluded(
providerId,
entity,
mockLogger,
);

expect(result).toBe(false);
});
});

describe('getScheduleFromConfig', () => {
it('should return the default schedule if not configured', () => {
const config = (task as any).getScheduleFromConfig(
Expand Down Expand Up @@ -304,6 +565,48 @@
);
});

it('should skip entities when scorecard.io/disabled-metrics annotation contains the provider id', async () => {
const entityExcluded = {
apiVersion: '1.0.0',
kind: 'Component',
metadata: {
name: 'excluded-entity',
annotations: {
'scorecard.io/disabled-metrics': 'github.test_metric',
},
},
};
const entityIncluded = {
apiVersion: '1.0.0',
kind: 'Component',
metadata: { name: 'included-entity' },
};
mockCatalog.queryEntities.mockReset().mockResolvedValueOnce({
items: [entityExcluded, entityIncluded],
pageInfo: { nextCursor: undefined },
totalItems: 2,
});

const calculateMetricSpy = jest.spyOn(mockProvider, 'calculateMetric');
const createMetricValuesSpy = jest.spyOn(
mockDatabaseMetricValues,
'createMetricValues',
);
await (task as any).pullProviderMetrics(mockProvider, mockLogger);

expect(calculateMetricSpy).toHaveBeenCalledTimes(1);
expect(calculateMetricSpy).toHaveBeenCalledWith(entityIncluded);
expect(createMetricValuesSpy).toHaveBeenCalledTimes(1);
expect(createMetricValuesSpy).toHaveBeenCalledWith([
expect.objectContaining({
catalog_entity_ref: 'component:default/included-entity',
metric_id: 'github.test_metric',
value: 42,
status: 'success',
}),
]);
});

it('should throw error if pullProviderMetrics fails', async () => {
(task as any).pullProviderMetrics = jest
.fn()
Expand Down
Loading