Skip to content

Commit 9b8c960

Browse files
committed
feat: add isSkipped to audits and groups for filtering
1 parent 4777a83 commit 9b8c960

File tree

7 files changed

+168
-96
lines changed

7 files changed

+168
-96
lines changed

packages/models/src/lib/audit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const auditSchema = z
1212
descriptionDescription: 'Description (markdown)',
1313
docsUrlDescription: 'Link to documentation (rationale)',
1414
description: 'List of scorable metrics for the given plugin',
15+
isSkippedDescription: 'Indicates whether the audit is skipped',
1516
}),
1617
);
1718

packages/models/src/lib/group.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const groupMetaSchema = metaSchema({
2222
descriptionDescription: 'Description of the group (markdown)',
2323
docsUrlDescription: 'Group documentation site',
2424
description: 'Group metadata',
25+
isSkippedDescription: 'Indicates whether the group is skipped',
2526
});
2627
export type GroupMeta = z.infer<typeof groupMetaSchema>;
2728

packages/models/src/lib/implementation/schemas.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export const scoreSchema = z
6969
.min(0)
7070
.max(1);
7171

72+
/** Schema for a property indicating whether an entity is filtered out */
73+
export const isSkippedSchema = z.boolean().optional();
74+
7275
/**
7376
* Used for categories, plugins and audits
7477
* @param options
@@ -78,12 +81,14 @@ export function metaSchema(options?: {
7881
descriptionDescription?: string;
7982
docsUrlDescription?: string;
8083
description?: string;
84+
isSkippedDescription?: string;
8185
}) {
8286
const {
8387
descriptionDescription,
8488
titleDescription,
8589
docsUrlDescription,
8690
description,
91+
isSkippedDescription,
8792
} = options ?? {};
8893
return z.object(
8994
{
@@ -96,6 +101,9 @@ export function metaSchema(options?: {
96101
docsUrl: docsUrlDescription
97102
? docsUrlSchema.describe(docsUrlDescription)
98103
: docsUrlSchema,
104+
isSkipped: isSkippedDescription
105+
? isSkippedSchema.describe(isSkippedDescription)
106+
: isSkippedSchema,
99107
},
100108
{ description },
101109
);

packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from './runner/constants.js';
99
import { createRunnerFunction } from './runner/runner.js';
1010
import type { LighthouseOptions } from './types.js';
11-
import { filterAuditsAndGroupsByOnlyOptions } from './utils.js';
11+
import { markSkippedAuditsAndGroups } from './utils.js';
1212

1313
export function lighthousePlugin(
1414
url: string,
@@ -17,7 +17,7 @@ export function lighthousePlugin(
1717
const { skipAudits, onlyAudits, onlyCategories, ...unparsedFlags } =
1818
normalizeFlags(flags ?? {});
1919

20-
const { audits, groups } = filterAuditsAndGroupsByOnlyOptions(
20+
const { audits, groups } = markSkippedAuditsAndGroups(
2121
LIGHTHOUSE_NAVIGATION_AUDITS,
2222
LIGHTHOUSE_GROUPS,
2323
{ skipAudits, onlyAudits, onlyCategories },

packages/plugin-lighthouse/src/lib/lighthouse-plugin.unit.test.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,36 @@ describe('lighthousePlugin-config-object', () => {
1717
]);
1818
});
1919

20-
it('should filter audits by onlyAudits string "first-contentful-paint"', () => {
20+
it('should mark audits in onlyAudits as not skipped', () => {
2121
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com', {
2222
onlyAudits: ['first-contentful-paint'],
2323
});
2424

2525
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
26-
27-
expect(pluginConfig.audits[0]).toEqual(
26+
expect(
27+
pluginConfig.audits.find(({ slug }) => slug === 'first-contentful-paint'),
28+
).toEqual(
2829
expect.objectContaining({
29-
slug: 'first-contentful-paint',
30+
isSkipped: false,
3031
}),
3132
);
3233
});
3334

34-
it('should filter groups by onlyAudits string "first-contentful-paint"', () => {
35+
it('should mark groups referencing audits in onlyAudits as not skipped', () => {
3536
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com', {
3637
onlyAudits: ['first-contentful-paint'],
3738
});
3839

3940
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
40-
expect(pluginConfig.groups).toHaveLength(1);
41-
42-
const refs = pluginConfig.groups?.[0]?.refs;
43-
expect(refs).toHaveLength(1);
4441

45-
expect(refs).toEqual(
46-
expect.arrayContaining([
47-
expect.objectContaining({
48-
slug: 'first-contentful-paint',
49-
}),
50-
]),
42+
const group = pluginConfig.groups?.find(({ refs }) =>
43+
refs.some(ref => ref.slug === 'first-contentful-paint'),
5144
);
52-
});
53-
54-
it('should throw when filtering groups by zero-weight onlyAudits', () => {
55-
const pluginConfig = lighthousePlugin('https://code-pushup-portal.com', {
56-
onlyAudits: ['csp-xss'],
57-
});
5845

59-
expect(() => pluginConfigSchema.parse(pluginConfig)).toThrow(
60-
'In a category, there has to be at least one ref with weight > 0. Affected refs: csp-xss',
46+
expect(group).toEqual(
47+
expect.objectContaining({
48+
isSkipped: false,
49+
}),
6150
);
6251
});
6352
});

packages/plugin-lighthouse/src/lib/utils.ts

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Audit, CategoryRef, Group } from '@code-pushup/models';
2-
import { filterItemRefsBy, toArray } from '@code-pushup/utils';
2+
import { toArray } from '@code-pushup/utils';
33
import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js';
44
import type { LighthouseCliFlags } from './runner/types.js';
55

@@ -70,7 +70,8 @@ export type FilterOptions = Partial<
7070
Pick<LighthouseCliFlags, 'onlyAudits' | 'onlyCategories' | 'skipAudits'>
7171
>;
7272

73-
export function filterAuditsAndGroupsByOnlyOptions(
73+
// eslint-disable-next-line max-lines-per-function
74+
export function markSkippedAuditsAndGroups(
7475
audits: Audit[],
7576
groups: Group[],
7677
options?: FilterOptions,
@@ -84,45 +85,57 @@ export function filterAuditsAndGroupsByOnlyOptions(
8485
onlyCategories = [],
8586
} = options ?? {};
8687

87-
// category wins over audits
88+
if (
89+
onlyCategories.length === 0 &&
90+
onlyAudits.length === 0 &&
91+
skipAudits.length === 0
92+
) {
93+
return { audits, groups };
94+
}
95+
8896
if (onlyCategories.length > 0) {
8997
validateOnlyCategories(groups, onlyCategories);
98+
}
9099

91-
const categorySlugs = new Set(onlyCategories);
92-
const filteredGroups: Group[] = groups.filter(({ slug }) =>
93-
categorySlugs.has(slug),
94-
);
95-
const auditSlugsFromRemainingGroups = new Set(
96-
filteredGroups.flatMap(({ refs }) => refs.map(({ slug }) => slug)),
97-
);
98-
return {
99-
audits: audits.filter(({ slug }) =>
100-
auditSlugsFromRemainingGroups.has(slug),
101-
),
102-
groups: filteredGroups,
103-
};
104-
} else if (onlyAudits.length > 0 || skipAudits.length > 0) {
100+
if (onlyAudits.length > 0 || skipAudits.length > 0) {
105101
validateAudits(audits, onlyAudits);
106102
validateAudits(audits, skipAudits);
107-
const onlyAuditSlugs = new Set(onlyAudits);
108-
const skipAuditSlugs = new Set(skipAudits);
109-
const filterAudits = ({ slug }: Pick<Audit, 'slug'>) =>
110-
!(
111-
// audit is NOT in given onlyAuditSlugs
112-
(
113-
(onlyAudits.length > 0 && !onlyAuditSlugs.has(slug)) ||
114-
// audit IS in given skipAuditSlugs
115-
(skipAudits.length > 0 && skipAuditSlugs.has(slug))
116-
)
117-
);
118-
return {
119-
audits: audits.filter(filterAudits),
120-
groups: filterItemRefsBy(groups, filterAudits),
121-
};
122103
}
123-
// return unchanged
104+
105+
const onlyGroupSlugs = new Set(onlyCategories);
106+
const onlyAuditSlugs = new Set(onlyAudits);
107+
const skipAuditSlugs = new Set(skipAudits);
108+
109+
const markedGroups: Group[] = groups.map(group => ({
110+
...group,
111+
isSkipped: onlyCategories.length > 0 && !onlyGroupSlugs.has(group.slug),
112+
}));
113+
114+
const validGroupAuditSlugs = new Set(
115+
markedGroups
116+
.filter(group => !group.isSkipped)
117+
.flatMap(group => group.refs.map(ref => ref.slug)),
118+
);
119+
120+
const markedAudits = audits.map(audit => ({
121+
...audit,
122+
isSkipped:
123+
(onlyAudits.length > 0 && !onlyAuditSlugs.has(audit.slug)) ||
124+
(skipAudits.length > 0 && skipAuditSlugs.has(audit.slug)) ||
125+
(validGroupAuditSlugs.size > 0 && !validGroupAuditSlugs.has(audit.slug)),
126+
}));
127+
128+
const fullyMarkedGroups = markedGroups.map(group => ({
129+
...group,
130+
isSkipped:
131+
group.isSkipped ||
132+
group.refs.every(ref =>
133+
markedAudits.some(audit => audit.slug === ref.slug && audit.isSkipped),
134+
),
135+
}));
136+
124137
return {
125-
audits,
126-
groups,
138+
audits: markedAudits,
139+
groups: fullyMarkedGroups,
127140
};
128141
}

0 commit comments

Comments
 (0)