Skip to content

Commit a08978e

Browse files
committed
feat(core): fetch portal comparison link if available when comparing reports
1 parent 76f18be commit a08978e

File tree

8 files changed

+182
-12
lines changed

8 files changed

+182
-12
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"node": ">=18.16"
4848
},
4949
"dependencies": {
50-
"@code-pushup/portal-client": "^0.8.0",
50+
"@code-pushup/portal-client": "^0.9.0",
5151
"@isaacs/cliui": "^8.0.2",
5252
"@nx/devkit": "17.3.2",
5353
"@poppinss/cliui": "^6.4.0",

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { bold, gray } from 'ansis';
22
import { CommandModule } from 'yargs';
33
import { compareReportFiles } from '@code-pushup/core';
4-
import { PersistConfig } from '@code-pushup/models';
4+
import { PersistConfig, UploadConfig } from '@code-pushup/models';
55
import { ui } from '@code-pushup/utils';
66
import { CLI_NAME } from '../constants';
7-
import type { CompareOptions } from '../implementation/compare.model';
7+
import { CompareOptions } from '../implementation/compare.model';
88
import { yargsCompareOptionsDefinition } from '../implementation/compare.options';
99

1010
export function yargsCompareCommandObject() {
@@ -19,11 +19,16 @@ export function yargsCompareCommandObject() {
1919

2020
const options = args as CompareOptions & {
2121
persist: Required<PersistConfig>;
22+
upload?: UploadConfig;
2223
};
2324

24-
const { before, after, persist } = options;
25+
const { before, after, persist, upload } = options;
2526

26-
const outputPaths = await compareReportFiles({ before, after }, persist);
27+
const outputPaths = await compareReportFiles(
28+
{ before, after },
29+
persist,
30+
upload,
31+
);
2732

2833
ui().logger.info(
2934
`Reports diff written to ${outputPaths

packages/cli/src/lib/compare/compare-command.unit.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('compare-command', () => {
4343
filename: DEFAULT_PERSIST_FILENAME,
4444
format: DEFAULT_PERSIST_FORMAT,
4545
},
46+
expect.any(Object),
4647
);
4748
});
4849

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"dependencies": {
66
"@code-pushup/models": "0.48.0",
77
"@code-pushup/utils": "0.48.0",
8-
"@code-pushup/portal-client": "^0.8.0",
8+
"@code-pushup/portal-client": "^0.9.0",
99
"ansis": "^3.3.0"
1010
},
1111
"type": "commonjs",

packages/core/src/lib/compare.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
3+
import {
4+
PortalOperationError,
5+
getPortalComparisonLink,
6+
} from '@code-pushup/portal-client';
37
import {
48
type Format,
59
type PersistConfig,
610
Report,
711
ReportsDiff,
12+
type UploadConfig,
813
reportSchema,
914
} from '@code-pushup/models';
1015
import {
@@ -14,6 +19,7 @@ import {
1419
generateMdReportsDiff,
1520
readJsonFile,
1621
scoreReport,
22+
ui,
1723
} from '@code-pushup/utils';
1824
import { name as packageName, version } from '../../package.json';
1925
import {
@@ -26,6 +32,7 @@ import {
2632
export async function compareReportFiles(
2733
inputPaths: Diff<string>,
2834
persistConfig: Required<PersistConfig>,
35+
uploadConfig: UploadConfig | undefined,
2936
): Promise<string[]> {
3037
const { outputDir, filename, format } = persistConfig;
3138

@@ -40,10 +47,15 @@ export async function compareReportFiles(
4047

4148
const reportsDiff = compareReports(reports);
4249

50+
const portalUrl =
51+
uploadConfig && reportsDiff.commits && format.includes('md')
52+
? await fetchPortalComparisonLink(uploadConfig, reportsDiff.commits)
53+
: undefined;
54+
4355
return Promise.all(
4456
format.map(async fmt => {
4557
const outputPath = join(outputDir, `${filename}-diff.${fmt}`);
46-
const content = reportsDiffToFileContent(reportsDiff, fmt);
58+
const content = reportsDiffToFileContent(reportsDiff, fmt, portalUrl);
4759
await ensureDirectoryExists(outputDir);
4860
await writeFile(outputPath, content);
4961
return outputPath;
@@ -86,11 +98,39 @@ export function compareReports(reports: Diff<Report>): ReportsDiff {
8698
function reportsDiffToFileContent(
8799
reportsDiff: ReportsDiff,
88100
format: Format,
101+
portalUrl: string | undefined,
89102
): string {
90103
switch (format) {
91104
case 'json':
92105
return JSON.stringify(reportsDiff, null, 2);
93106
case 'md':
94-
return generateMdReportsDiff(reportsDiff);
107+
return generateMdReportsDiff(reportsDiff, portalUrl ?? undefined);
108+
}
109+
}
110+
111+
async function fetchPortalComparisonLink(
112+
uploadConfig: UploadConfig,
113+
commits: NonNullable<ReportsDiff['commits']>,
114+
): Promise<string | undefined> {
115+
const { server, apiKey, organization, project } = uploadConfig;
116+
try {
117+
return await getPortalComparisonLink({
118+
server,
119+
apiKey,
120+
parameters: {
121+
organization,
122+
project,
123+
before: commits.before.hash,
124+
after: commits.after.hash,
125+
},
126+
});
127+
} catch (error) {
128+
if (error instanceof PortalOperationError) {
129+
ui().logger.warning(
130+
`Failed to fetch portal comparison link - ${error.message}`,
131+
);
132+
return undefined;
133+
}
134+
throw error;
95135
}
96136
}

packages/core/src/lib/compare.unit.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { vol } from 'memfs';
2+
import { readFile } from 'node:fs/promises';
23
import { join } from 'node:path';
4+
import { getPortalComparisonLink } from '@code-pushup/portal-client';
35
import { Commit, Report, reportsDiffSchema } from '@code-pushup/models';
46
import {
57
COMMIT_ALT_MOCK,
@@ -13,6 +15,11 @@ import { Diff, fileExists, readJsonFile } from '@code-pushup/utils';
1315
import { compareReportFiles, compareReports } from './compare';
1416

1517
describe('compareReportFiles', () => {
18+
const commitShas = {
19+
before: MINIMAL_REPORT_MOCK.commit!.hash,
20+
after: REPORT_MOCK.commit!.hash,
21+
};
22+
1623
beforeEach(() => {
1724
vol.fromJSON(
1825
{
@@ -30,6 +37,7 @@ describe('compareReportFiles', () => {
3037
after: join(MEMFS_VOLUME, 'target-report.json'),
3138
},
3239
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json'] },
40+
undefined,
3341
);
3442

3543
const reportsDiffPromise = readJsonFile(
@@ -48,6 +56,7 @@ describe('compareReportFiles', () => {
4856
after: join(MEMFS_VOLUME, 'target-report.json'),
4957
},
5058
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json', 'md'] },
59+
undefined,
5160
);
5261

5362
await expect(
@@ -57,6 +66,116 @@ describe('compareReportFiles', () => {
5766
fileExists(join(MEMFS_VOLUME, 'report-diff.md')),
5867
).resolves.toBeTruthy();
5968
});
69+
70+
it('should include portal link (fetched using upload config) in Markdown file', async () => {
71+
await compareReportFiles(
72+
{
73+
before: join(MEMFS_VOLUME, 'source-report.json'),
74+
after: join(MEMFS_VOLUME, 'target-report.json'),
75+
},
76+
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json', 'md'] },
77+
{
78+
server: 'https://api.code-pushup.dev/graphql',
79+
apiKey: 'cp_XXXXX',
80+
organization: 'dunder-mifflin',
81+
project: 'website',
82+
},
83+
);
84+
85+
await expect(
86+
readFile(join(MEMFS_VOLUME, 'report-diff.md'), 'utf8'),
87+
).resolves.toContain(
88+
`[🕵️ See full comparison in Code PushUp portal 🔍](https://code-pushup.example.com/portal/dunder-mifflin/website/comparison/${commitShas.before}/${commitShas.after})`,
89+
);
90+
91+
expect(getPortalComparisonLink).toHaveBeenCalledWith<
92+
Parameters<typeof getPortalComparisonLink>
93+
>({
94+
server: 'https://api.code-pushup.dev/graphql',
95+
apiKey: 'cp_XXXXX',
96+
parameters: {
97+
organization: 'dunder-mifflin',
98+
project: 'website',
99+
before: commitShas.before,
100+
after: commitShas.after,
101+
},
102+
});
103+
});
104+
105+
it('should not include portal link in Markdown if upload config is missing', async () => {
106+
await compareReportFiles(
107+
{
108+
before: join(MEMFS_VOLUME, 'source-report.json'),
109+
after: join(MEMFS_VOLUME, 'target-report.json'),
110+
},
111+
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json', 'md'] },
112+
undefined,
113+
);
114+
115+
await expect(
116+
readFile(join(MEMFS_VOLUME, 'report-diff.md'), 'utf8'),
117+
).resolves.not.toContain(
118+
'[🕵️ See full comparison in Code PushUp portal 🔍]',
119+
);
120+
121+
expect(getPortalComparisonLink).not.toHaveBeenCalled();
122+
});
123+
124+
it('should not include portal link in Markdown if report has no associated commits', async () => {
125+
vol.fromJSON(
126+
{
127+
'source-report.json': JSON.stringify({
128+
...MINIMAL_REPORT_MOCK,
129+
commit: null,
130+
} satisfies Report),
131+
'target-report.json': JSON.stringify(REPORT_MOCK),
132+
},
133+
MEMFS_VOLUME,
134+
);
135+
await compareReportFiles(
136+
{
137+
before: join(MEMFS_VOLUME, 'source-report.json'),
138+
after: join(MEMFS_VOLUME, 'target-report.json'),
139+
},
140+
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json', 'md'] },
141+
{
142+
server: 'https://api.code-pushup.dev/graphql',
143+
apiKey: 'cp_XXXXX',
144+
organization: 'dunder-mifflin',
145+
project: 'website',
146+
},
147+
);
148+
149+
await expect(
150+
readFile(join(MEMFS_VOLUME, 'report-diff.md'), 'utf8'),
151+
).resolves.not.toContain(
152+
'[🕵️ See full comparison in Code PushUp portal 🔍]',
153+
);
154+
155+
expect(getPortalComparisonLink).not.toHaveBeenCalled();
156+
});
157+
158+
it('should not fetch portal link if Markdown not included in formats', async () => {
159+
await compareReportFiles(
160+
{
161+
before: join(MEMFS_VOLUME, 'source-report.json'),
162+
after: join(MEMFS_VOLUME, 'target-report.json'),
163+
},
164+
{ outputDir: MEMFS_VOLUME, filename: 'report', format: ['json'] },
165+
{
166+
server: 'https://api.code-pushup.dev/graphql',
167+
apiKey: 'cp_XXXXX',
168+
organization: 'dunder-mifflin',
169+
project: 'website',
170+
},
171+
);
172+
173+
expect(getPortalComparisonLink).not.toHaveBeenCalled();
174+
175+
await expect(
176+
fileExists(join(MEMFS_VOLUME, 'report-diff.md')),
177+
).resolves.toBeFalsy();
178+
});
60179
});
61180

62181
describe('compareReports', () => {

testing/test-setup/src/lib/portal-client.mock.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { vi } from 'vitest';
22
import type {
3+
PortalComparisonLinkArgs,
34
PortalUploadArgs,
45
ReportFragment,
56
} from '@code-pushup/portal-client';
@@ -15,5 +16,9 @@ vi.mock('@code-pushup/portal-client', async () => {
1516
url: `https://code-pushup.example.com/portal/${data.organization}/${data.project}/commit/${data.commit}`,
1617
}),
1718
),
19+
getPortalComparisonLink: vi.fn(
20+
async ({ parameters }: PortalComparisonLinkArgs) =>
21+
`https://code-pushup.example.com/portal/${parameters.organization}/${parameters.project}/comparison/${parameters.before}/${parameters.after}`,
22+
),
1823
};
1924
});

0 commit comments

Comments
 (0)