Skip to content

Commit d6cebb2

Browse files
BioPhotonTlacenka
andauthored
refactor: make categories optional in core config (#437)
Closes #407 I aimed for handling the optional categories only in the cli layer and maintained the existing data structure fully in reports. I just hide the sections for empty categories. Follow-up by @Tlacenka: - I added tests for scoring and sorting reports. - I allowed passing both `undefined` and `[]` by configuring categories schema to allow empty array. - I reduced minimal mocks. - I typed categories as a non-optional property as soon as in the only plugins middleware. --------- Co-authored-by: Katerina Pilatova <katerina.pilatova@flowup.cz>
1 parent 8360dbc commit d6cebb2

27 files changed

Lines changed: 702 additions & 151 deletions

packages/cli/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ _If you're looking for programmatic usage, then refer to the underlying [@code-p
4343
plugins: [
4444
// ...
4545
],
46-
categories: [
47-
// ...
48-
],
4946
};
5047
```
5148

@@ -67,7 +64,7 @@ _If you're looking for programmatic usage, then refer to the underlying [@code-p
6764
};
6865
```
6966

70-
4. Define your custom categories.
67+
4. Optionally define your custom categories. This section provides an overview of thematically related audits and groups.
7168

7269
```js
7370
export default {
@@ -77,6 +74,7 @@ _If you're looking for programmatic usage, then refer to the underlying [@code-p
7774
slug: 'performance',
7875
title: 'Performance',
7976
refs: [
77+
// reference to an existing audit or group from plugins
8078
{
8179
type: 'audit',
8280
plugin: 'eslint',

packages/cli/docs/custom-plugins.md

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,12 @@ Code PushUp Report - @code-pushup/core@x.y.z
9797
My plugin audits
9898
● My audit 0
9999

100-
Categories
101-
┌──────────┬───────┬────────┐
102-
│ Category │ Score │ Audits │
103-
└──────────┴───────┴────────┘
104-
105100
Made with ❤ by code-pushup.dev
106101
```
107102

108103
</details>
109104

110-
The categories are empty for now. But under the audit listing you can see your plugin title `My plugin`, its listed
111-
audit `My audit` and the resulting value `0`.
105+
Under the audit listing you can see your plugin title `My plugin`, its listed audit `My audit` and the resulting value `0`.
112106

113107
## Plugin output
114108

@@ -251,11 +245,6 @@ Code PushUp Report - @code-pushup/core@x.y.z
251245
File size plugin audits
252246
● File size audit 2 files
253247

254-
Categories
255-
┌──────────┬───────┬────────┐
256-
│ Category │ Score │ Audits │
257-
└──────────┴───────┴────────┘
258-
259248
Made with ❤ by code-pushup.dev
260249
```
261250

@@ -387,11 +376,6 @@ Code PushUp Report - @code-pushup/core@x.y.z
387376
Chrome Lighthosue audits
388377
● Largest Contentful Paint 0
389378

390-
Categories
391-
┌──────────┬───────┬────────┐
392-
│ Category │ Score │ Audits │
393-
└──────────┴───────┴────────┘
394-
395379
Made with ❤ by code-pushup.dev
396380
```
397381

@@ -498,37 +482,6 @@ async function runnerFunction(options: Options): Promise<AuditOutputs> {
498482
}
499483
```
500484

501-
### Audit groups
502-
503-
As an optional property a plugin can maintain `groups` as an array of [`Group`s](@TODO).
504-
While [categories](#plugins-and-categories) can score audits across plugins, groups are only targeting plugins within a plugin.
505-
For simple plugins this is not needed but it is beneficial in bigger plugins as audit groups also simplify the configuration.
506-
507-
An audit group maintains:
508-
509-
- metadata about the group [GroupMeta](@TODO)
510-
- a list of referenced audits under `refs` as [GroupRef](@TODO) array
511-
512-
The minimum information of an audit group looks like this:
513-
514-
```typescript
515-
import { Group } from '@code-pushup/models';
516-
517-
const myGroup: Group = {
518-
slug: 'performance',
519-
refs: [
520-
{
521-
slug: 'file-size-audit',
522-
weight: 1,
523-
},
524-
],
525-
// optional details
526-
};
527-
```
528-
529-
The weight property of a reference is used to calculate a score for all audits listed under refs.
530-
A weight of 0 can be used for informative audits and a value from 1 upward to give a weight in the score compared to other audits.
531-
532485
### Audit details
533486

534487
To have better attribution in your audits you can use the `details` section in `AuditOutputs`.
@@ -630,11 +583,42 @@ The `report.md` file should contain a similar content like the following:
630583

631584
</details>
632585

633-
## Plugins and categories
586+
## Groups
587+
588+
As an optional property a plugin can maintain `groups` as an array of [`Group`s](@TODO).
589+
While [categories](#plugins-and-categories) can score audits across plugins, groups are only targeting audits within a plugin.
590+
For simple plugins this is not needed but it is beneficial in bigger plugins as audit groups also simplify the configuration.
591+
592+
An audit group maintains:
593+
594+
- metadata about the group [GroupMeta](@TODO)
595+
- a list of referenced audits under `refs` as [GroupRef](@TODO) array
596+
597+
The minimum information of an audit group looks like this:
598+
599+
```typescript
600+
import { Group } from '@code-pushup/models';
601+
602+
const myGroup: Group = {
603+
slug: 'performance',
604+
refs: [
605+
{
606+
slug: 'file-size-audit',
607+
weight: 1,
608+
},
609+
],
610+
// optional details
611+
};
612+
```
613+
614+
The weight property of a reference is used to calculate a score for all audits listed under refs.
615+
A weight of 0 can be used for informative audits and a value from 1 upward to give a weight in the score compared to other audits.
616+
617+
## Categories
634618

635619
In this chapter we will see how plugin results contribute to the category scoring.
636620

637-
**basic categories setup**
621+
**basic category setup**
638622
In this example we create a Performance category which contains one audit and one group.
639623

640624
Assign weights based on what influence each audit and group should have on the overall category score (assign weight 0 to only include it for extra info, without influencing the category score).

packages/cli/src/lib/autorun/autorun-command.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ export function yargsAutorunCommandObject() {
3535

3636
await collectAndPersistReports(optionsWithFormat);
3737

38+
if (options.categories.length === 0) {
39+
console.info(
40+
chalk.gray(
41+
'💡 Configure categories to see the scores in an overview table. See: https://github.com/code-pushup/cli/blob/main/packages/cli/README.md',
42+
),
43+
);
44+
}
45+
3846
if (options.upload) {
3947
await upload(options);
4048
} else {

packages/cli/src/lib/collect/collect-command.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export function yargsCollectCommandObject(): CommandModule {
1818
console.info(chalk.bold(CLI_NAME));
1919
console.info(chalk.gray(`Run ${command}...`));
2020
await collectAndPersistReports(options);
21+
22+
if (options.categories.length === 0) {
23+
console.info(
24+
chalk.gray(
25+
'💡 Configure categories to see the scores in an overview table. See: https://github.com/code-pushup/cli/blob/main/packages/cli/README.md',
26+
),
27+
);
28+
}
29+
2130
const { upload = {} } = args as unknown as Record<
2231
'upload',
2332
object | undefined

packages/cli/src/lib/implementation/core-config.integration.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@ vi.mock('@code-pushup/core', async () => {
2323
outputDir: 'rc-outputDir',
2424
},
2525
};
26-
2726
const persistOnlyFilename = {
2827
...CORE_CONFIG_MOCK,
2928
persist: {
3029
filename: 'rc-filename',
3130
},
3231
};
33-
34-
const noPersistFilename = CORE_CONFIG_MOCK;
32+
const noPersist = CORE_CONFIG_MOCK;
33+
const noCategory = { plugins: CORE_CONFIG_MOCK.plugins };
3534

3635
return filepath.includes('all-persist-options')
3736
? allPersistOptions
38-
: filepath.includes('no-persist')
39-
? noPersistFilename
4037
: filepath.includes('persist-only-filename')
4138
? persistOnlyFilename
39+
: filepath.includes('no-persist')
40+
? noPersist
41+
: filepath.includes('no-category')
42+
? noCategory
4243
: CORE_CONFIG_MOCK;
4344
}),
4445
};
@@ -137,4 +138,16 @@ describe('parsing values from CLI and middleware', () => {
137138
outputDir: 'cli-outputdir',
138139
});
139140
});
141+
142+
it('should set empty array for categories if not given in config file', async () => {
143+
const { categories } = await yargsCli<CoreConfig>(
144+
['--config=./no-category.config.ts'],
145+
{
146+
options: { ...yargsCoreConfigOptionsDefinition() },
147+
middlewares: [{ middlewareFunction: coreConfigMiddleware }],
148+
},
149+
).parseAsync();
150+
151+
expect(categories).toEqual([]);
152+
});
140153
});

packages/cli/src/lib/implementation/core-config.middleware.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export async function coreConfigMiddleware<
2222
const {
2323
persist: rcPersist,
2424
upload: rcUpload,
25+
categories: rcCategories,
2526
...remainingRcConfig
2627
} = importedRc;
2728

@@ -39,6 +40,7 @@ export async function coreConfigMiddleware<
3940
format: cliPersist?.format ?? rcPersist?.format ?? PERSIST_FORMAT,
4041
filename: cliPersist?.filename ?? rcPersist?.filename ?? PERSIST_FILENAME,
4142
},
43+
categories: rcCategories ?? [],
4244
};
4345

4446
return parsedProcessArgs;

packages/cli/src/lib/implementation/only-plugins.middleware.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ export function onlyPluginsMiddleware<
1717

1818
validateOnlyPluginsOption(cliOptions.plugins, cliOptions);
1919

20-
const parsedProcessArgs: CoreConfig & GeneralCliOptions & OnlyPluginsOptions =
21-
{
22-
...cliOptions,
23-
plugins: filterPluginsBySlug(cliOptions.plugins, cliOptions),
24-
categories: filterCategoryByPluginSlug(cliOptions.categories, cliOptions),
25-
};
20+
const parsedProcessArgs: Required<CoreConfig> &
21+
GeneralCliOptions &
22+
OnlyPluginsOptions = {
23+
...cliOptions,
24+
plugins: filterPluginsBySlug(cliOptions.plugins, cliOptions),
25+
categories: filterCategoryByPluginSlug(cliOptions.categories, cliOptions),
26+
};
2627

2728
return parsedProcessArgs;
2829
}

packages/cli/src/lib/implementation/only-plugins.utils.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chalk from 'chalk';
2-
import { CoreConfig } from '@code-pushup/models';
2+
import { CategoryConfig, CoreConfig } from '@code-pushup/models';
33

44
export function filterPluginsBySlug(
55
plugins: CoreConfig['plugins'],
@@ -14,12 +14,15 @@ export function filterPluginsBySlug(
1414
// skip the whole category if it has at least one skipped plugin ref
1515
// see https://github.com/code-pushup/cli/pull/246#discussion_r1392274281
1616
export function filterCategoryByPluginSlug(
17-
categories: CoreConfig['categories'],
17+
categories: CategoryConfig[],
1818
{
1919
onlyPlugins,
2020
verbose = false,
2121
}: { onlyPlugins?: string[]; verbose?: boolean },
22-
): CoreConfig['categories'] {
22+
): CategoryConfig[] {
23+
if (categories.length === 0) {
24+
return categories;
25+
}
2326
if (!onlyPlugins?.length) {
2427
return categories;
2528
}

packages/cli/src/lib/implementation/only-plugins.utils.unit.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect } from 'vitest';
2-
import { CoreConfig } from '@code-pushup/models';
2+
import { CategoryConfig, CoreConfig } from '@code-pushup/models';
33
import {
44
filterCategoryByPluginSlug,
55
filterPluginsBySlug,
@@ -41,7 +41,7 @@ describe('filterCategoryByPluginSlug', () => {
4141
[
4242
{ refs: [{ slug: 'plugin1' }, { slug: 'plugin2' }] },
4343
{ refs: [{ slug: 'plugin3' }] },
44-
] as CoreConfig['categories'],
44+
] as CategoryConfig[],
4545
{},
4646
),
4747
).toEqual([
@@ -56,7 +56,7 @@ describe('filterCategoryByPluginSlug', () => {
5656
[
5757
{ refs: [{ slug: 'plugin1' }, { slug: 'plugin2' }] },
5858
{ refs: [{ slug: 'plugin3' }] },
59-
] as CoreConfig['categories'],
59+
] as CategoryConfig[],
6060
{ onlyPlugins: ['plugin1', 'plugin3'] },
6161
),
6262
).toEqual([{ refs: [{ slug: 'plugin3' }] }]);
@@ -70,7 +70,7 @@ describe('filterCategoryByPluginSlug', () => {
7070
refs: [{ slug: 'plugin1' }, { slug: 'plugin2' }, { slug: 'plugin4' }],
7171
},
7272
{ title: 'category2', refs: [{ slug: 'plugin3' }] },
73-
] as CoreConfig['categories'],
73+
] as CategoryConfig[],
7474
{
7575
onlyPlugins: ['plugin1', 'plugin3'],
7676
verbose: true,

packages/core/src/lib/collect-and-persist.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import { logPersistedResults, persistReport } from './implementation/persist';
55
import { normalizePersistConfig } from './normalize';
66
import { GlobalOptions } from './types';
77

8-
export type CollectAndPersistReportsOptions = Pick<
9-
CoreConfig,
10-
'persist' | 'plugins' | 'categories'
8+
export type CollectAndPersistReportsOptions = Required<
9+
Pick<CoreConfig, 'persist' | 'plugins' | 'categories'>
1110
> &
1211
GlobalOptions;
1312

0 commit comments

Comments
 (0)