Skip to content

Commit 393b767

Browse files
committed
chore(7107): Include a Sentry Explore Link in the Announcement
1 parent 45043af commit 393b767

8 files changed

Lines changed: 350 additions & 37 deletions

File tree

development/lint-changed.mts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,36 @@ function getChangedFileNames(): string[] {
5353
return [...unique];
5454
}
5555

56+
/**
57+
* Paths can appear in git output but not exist on disk (e.g. staged add + working-tree
58+
* delete shows as `AD`). ESLint fails on missing paths; skip those.
59+
*/
60+
function filterToExistingFiles(files: string[]): string[] {
61+
const existing: string[] = [];
62+
const missing: string[] = [];
63+
64+
for (const file of files) {
65+
const absolute = path.join(process.cwd(), file);
66+
if (fs.existsSync(absolute)) {
67+
existing.push(file);
68+
} else {
69+
missing.push(file);
70+
}
71+
}
72+
73+
if (missing.length > 0) {
74+
process.stderr.write(
75+
`Skipping ${missing.length} changed path(s) not found on disk (fix git state, e.g. restore files or git rm --cached):\n`,
76+
);
77+
for (const file of missing) {
78+
process.stderr.write(` - ${file}\n`);
79+
}
80+
process.stderr.write('\n');
81+
}
82+
83+
return existing;
84+
}
85+
5686
function parseArgs(argv: string[]): LintChangedOptions {
5787
return {
5888
fix: argv.includes('--fix'),
@@ -100,8 +130,8 @@ function chunkByApproxCommandLength(
100130
function main(): void {
101131
const options = parseArgs(process.argv.slice(2));
102132

103-
const changedFiles = getChangedFileNames().filter((file) =>
104-
JS_TS_TSX_SNAP_FILE_REGEX.test(file),
133+
const changedFiles = filterToExistingFiles(
134+
getChangedFileNames().filter((file) => JS_TS_TSX_SNAP_FILE_REGEX.test(file)),
105135
);
106136

107137
if (changedFiles.length === 0) {

development/metamaskbot-build-announce/compare-benchmarks.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('compare-benchmarks', () => {
4747
it('passes when results are within thresholds', () => {
4848
const benchmarks = [
4949
{
50-
name: 'benchmark-chrome-browserify-userJourneyOnboardingImport',
50+
name: 'benchmark-chrome-webpack-userJourneyOnboardingImport',
5151
data: {
5252
onboardingImportWallet: makeBenchmarkResults({
5353
p75: { importWalletToSocialScreen: 1500 },
@@ -66,7 +66,7 @@ describe('compare-benchmarks', () => {
6666
it('fails when p75 exceeds fail threshold', () => {
6767
const benchmarks = [
6868
{
69-
name: 'benchmark-chrome-browserify-userJourneyOnboardingImport',
69+
name: 'benchmark-chrome-webpack-userJourneyOnboardingImport',
7070
data: {
7171
onboardingImportWallet: makeBenchmarkResults({
7272
p75: { importWalletToSocialScreen: 99999 },
@@ -84,7 +84,7 @@ describe('compare-benchmarks', () => {
8484
it('includes relative metrics when baseline is available', () => {
8585
const benchmarks = [
8686
{
87-
name: 'benchmark-chrome-browserify-userJourneyOnboardingImport',
87+
name: 'benchmark-chrome-webpack-userJourneyOnboardingImport',
8888
data: {
8989
onboardingImportWallet: makeBenchmarkResults({
9090
p75: { importWalletToSocialScreen: 1500 },
@@ -124,7 +124,7 @@ describe('compare-benchmarks', () => {
124124
it('resolves page-load baseline for startup benchmarks', () => {
125125
const benchmarks = [
126126
{
127-
name: 'benchmark-chrome-browserify-startupStandardHome',
127+
name: 'benchmark-chrome-webpack-startupStandardHome',
128128
data: {
129129
startupStandardHome: makeBenchmarkResults({
130130
p75: { uiStartup: 1800 },

development/metamaskbot-build-announce/comparison-utils.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,12 +366,10 @@ describe('THRESHOLD_REGISTRY', () => {
366366
expect(THRESHOLD_REGISTRY.loadNewAccount).toBeDefined();
367367
expect(THRESHOLD_REGISTRY.confirmTx).toBeDefined();
368368
expect(THRESHOLD_REGISTRY.bridgeUserActions).toBeDefined();
369-
expect(
370-
THRESHOLD_REGISTRY['chrome-browserify-loadNewAccount'],
371-
).toBeUndefined();
369+
expect(THRESHOLD_REGISTRY['chrome-webpack-loadNewAccount']).toBeUndefined();
372370
});
373371

374-
it('has platform-agnostic keys for user journey (chrome-browserify + chrome-webpack)', () => {
372+
it('has platform-agnostic keys for user journey (no per-platform threshold keys)', () => {
375373
expect(THRESHOLD_REGISTRY.onboardingImportWallet).toBeDefined();
376374
expect(THRESHOLD_REGISTRY.swap).toBeDefined();
377375
expect(THRESHOLD_REGISTRY['chrome-webpack-swap']).toBeUndefined();
@@ -381,7 +379,7 @@ describe('THRESHOLD_REGISTRY', () => {
381379
expect(THRESHOLD_REGISTRY.startupStandardHome).toBeDefined();
382380
expect(THRESHOLD_REGISTRY.startupPowerUserHome).toBeDefined();
383381
expect(
384-
THRESHOLD_REGISTRY['chrome-browserify-startupStandardHome'],
382+
THRESHOLD_REGISTRY['chrome-webpack-startupStandardHome'],
385383
).toBeUndefined();
386384
expect(
387385
THRESHOLD_REGISTRY['firefox-webpack-startupPowerUserHome'],

development/metamaskbot-build-announce/performance-benchmarks.ts

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
buildArtifactFilename,
4747
buildArtifactUrl,
4848
} from './utils';
49+
import { buildSentryLogsUrl } from './sentry-utils';
4950

5051
export type BenchmarkEntry = {
5152
benchmarkName: string;
@@ -476,6 +477,53 @@ function extractDisplayName(benchmarkName: string): string {
476477
return match ? match[1] : benchmarkName;
477478
}
478479

480+
/**
481+
* Builds HTML for an artifact log link and optional Sentry Logs Explorer link.
482+
*
483+
* @param logHref - Optional artifact log URL
484+
* @param entry - Benchmark entry (for Sentry filters)
485+
* @param logLinkInnerHtml - Inner HTML for the artifact log anchor (e.g. '[Show logs]')
486+
*/
487+
function buildArtifactAndSentryLinksHtml(
488+
logHref: string | undefined,
489+
entry: BenchmarkEntry | undefined,
490+
logLinkInnerHtml: string,
491+
): string {
492+
const branchName = process.env.BRANCH || process.env.GITHUB_REF_NAME;
493+
const sentryUrl =
494+
branchName && entry
495+
? buildSentryLogsUrl(branchName, undefined, {
496+
browser: entry.platform,
497+
buildType: entry.buildType,
498+
benchmarkName: entry.benchmarkName,
499+
})
500+
: null;
501+
502+
const logsLink = logHref
503+
? `<a href="${logHref}">${logLinkInnerHtml}</a>`
504+
: '';
505+
const sentryLinkHtml = sentryUrl
506+
? `<a href="${sentryUrl}">[Sentry log]</a>`
507+
: '';
508+
509+
return [logsLink, sentryLinkHtml].filter(Boolean).join(' ');
510+
}
511+
512+
/**
513+
* Builds table cell content with health icon and links (CI artifact + Sentry).
514+
* @param icon
515+
* @param logHref
516+
* @param entry
517+
*/
518+
function buildCellContent(
519+
icon: string,
520+
logHref: string | undefined,
521+
entry: BenchmarkEntry | undefined,
522+
): string {
523+
const links = buildArtifactAndSentryLinksHtml(logHref, entry, '[Show logs]');
524+
return links ? `${icon} ${links}` : icon;
525+
}
526+
479527
/**
480528
* Counts the number of failing and warning benchmark entries.
481529
*
@@ -561,9 +609,12 @@ function formatTimerDetails(
561609
return null;
562610
}
563611

564-
const logsLine = logHref
565-
? `<br><a href="${logHref}">[Show logs]</a>`
566-
: '';
612+
const linksHtml = buildArtifactAndSentryLinksHtml(
613+
logHref,
614+
entry,
615+
'[Show logs]',
616+
);
617+
const logsLine = linksHtml ? `<br>${linksHtml}` : '';
567618
return `<div>${icon} <code>${metricName}</code>${logsLine}</div>`;
568619
})
569620
.filter((item) => item !== null)
@@ -813,17 +864,13 @@ export function buildBenchmarkSection(
813864
logHref,
814865
);
815866

816-
const logsLink = logHref
817-
? `<a href="${logHref}">[Show logs]</a>`
818-
: '';
819-
820867
let cell: string;
821868
switch (true) {
822869
case Boolean(timerDetails):
823870
cell = timerDetails;
824871
break;
825872
case Boolean(logHref):
826-
cell = `${icon} ${logsLink}`;
873+
cell = buildCellContent(icon, logHref, entry);
827874
break;
828875
default:
829876
cell = icon;
@@ -943,9 +990,12 @@ function buildHealthMatrixHtml(
943990
);
944991
const logHref = entry?.artifactUrl;
945992
const label = data.label ? `${icon} ${data.label}` : icon;
946-
const cell = logHref
947-
? `${label} <a href="${logHref}">[logs]</a>`
948-
: label;
993+
const links = buildArtifactAndSentryLinksHtml(
994+
logHref,
995+
entry,
996+
'[logs]',
997+
);
998+
const cell = links ? `${label} ${links}` : label;
949999
return `<td align="center">${cell}</td>`;
9501000
})
9511001
.join('');
@@ -1030,7 +1080,12 @@ function buildFailingItemsHtml(
10301080
const worstLabel = getWorstViolationLabel(entry, baselineMetrics);
10311081
const labelPart = worstLabel ? ` — ${worstLabel}` : '';
10321082
const logHref = entry.artifactUrl ?? runUrl;
1033-
const logAnchor = logHref ? ` <a href="${logHref}">[Show logs]</a>` : '';
1083+
const links = buildArtifactAndSentryLinksHtml(
1084+
logHref,
1085+
entry,
1086+
'[Show logs]',
1087+
);
1088+
const logAnchor = links ? ` ${links}` : '';
10341089
return [
10351090
`<li><b>${entry.benchmarkName}</b> · ${entry.platform}-${entry.buildType}${labelPart}${logAnchor}</li>`,
10361091
];
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { parseSentryDSN, buildSentryLogsUrl } from './sentry-utils';
2+
3+
describe('parseSentryDSN', () => {
4+
it('parses standard Sentry DSN format', () => {
5+
const dsn = 'https://fake@metamask.sentry.io/4510302346608640';
6+
const result = parseSentryDSN(dsn);
7+
8+
expect(result).toStrictEqual({
9+
organization: 'metamask',
10+
projectId: '4510302346608640',
11+
});
12+
});
13+
14+
it('parses ingest subdomain format', () => {
15+
const dsn = 'https://key@org.ingest.sentry.io/123456';
16+
const result = parseSentryDSN(dsn);
17+
18+
expect(result).toStrictEqual({
19+
organization: 'org',
20+
projectId: '123456',
21+
});
22+
});
23+
24+
it('returns null for invalid URL', () => {
25+
const dsn = 'not-a-valid-url';
26+
const result = parseSentryDSN(dsn);
27+
28+
expect(result).toBeNull();
29+
});
30+
31+
it('returns null for non-sentry.io hostname', () => {
32+
const dsn = 'https://key@example.com/123';
33+
const result = parseSentryDSN(dsn);
34+
35+
expect(result).toBeNull();
36+
});
37+
38+
it('returns null when project ID is missing', () => {
39+
const dsn = 'https://key@metamask.sentry.io/';
40+
const result = parseSentryDSN(dsn);
41+
42+
expect(result).toBeNull();
43+
});
44+
});
45+
46+
describe('buildSentryLogsUrl', () => {
47+
const mockDsn = 'https://fake@metamask.sentry.io/4510302346608640';
48+
49+
beforeEach(() => {
50+
delete process.env.SENTRY_DSN_PERFORMANCE;
51+
});
52+
53+
afterEach(() => {
54+
delete process.env.SENTRY_DSN_PERFORMANCE;
55+
});
56+
57+
it('builds Sentry Logs Explorer URL with branch filter', () => {
58+
const url = buildSentryLogsUrl('feat/my-branch', mockDsn);
59+
60+
expect(url).toContain('https://metamask.sentry.io/explore/logs/');
61+
expect(url).toContain('logsQuery=ci.branch%3Afeat%2Fmy-branch');
62+
expect(url).toContain('project=4510302346608640');
63+
expect(url).toContain('statsPeriod=2w');
64+
expect(url).toContain('logsSortBys=-timestamp');
65+
expect(url).toContain('logsFields=timestamp');
66+
expect(url).toContain('logsFields=message');
67+
});
68+
69+
it('uses SENTRY_DSN_PERFORMANCE env var when DSN not provided', () => {
70+
process.env.SENTRY_DSN_PERFORMANCE = mockDsn;
71+
72+
const url = buildSentryLogsUrl('main');
73+
74+
expect(url).toContain('logsQuery=ci.branch%3Amain');
75+
});
76+
77+
it('returns null when DSN is not provided and env var not set', () => {
78+
const url = buildSentryLogsUrl('main');
79+
80+
expect(url).toBeNull();
81+
});
82+
83+
it('returns null when DSN is invalid', () => {
84+
const url = buildSentryLogsUrl('main', 'invalid-dsn');
85+
86+
expect(url).toBeNull();
87+
});
88+
89+
it('handles branch names with special characters', () => {
90+
const url = buildSentryLogsUrl('feature/add-new-thing', mockDsn);
91+
92+
expect(url).toContain('logsQuery=ci.branch%3Afeature%2Fadd-new-thing');
93+
});
94+
95+
it('includes browser filter when provided', () => {
96+
const url = buildSentryLogsUrl('main', mockDsn, { browser: 'chrome' });
97+
98+
expect(url).toContain('ci.branch%3Amain');
99+
expect(url).toContain('ci.browser%3Achrome');
100+
});
101+
102+
it('includes buildType filter when provided', () => {
103+
const url = buildSentryLogsUrl('main', mockDsn, {
104+
buildType: 'browserify',
105+
});
106+
107+
expect(url).toContain('ci.buildType%3Abrowserify');
108+
});
109+
110+
it('includes benchmark name filter with message:Contains format', () => {
111+
const url = buildSentryLogsUrl('main', mockDsn, {
112+
benchmarkName: 'firefox-browserify-startupStandardHome',
113+
});
114+
115+
expect(url).toContain('message');
116+
expect(url).toContain('Contains');
117+
expect(url).toContain('benchmark.firefox-browserify-startupStandardHome');
118+
});
119+
120+
it('combines all filters when all options provided', () => {
121+
const url = buildSentryLogsUrl('feat/test', mockDsn, {
122+
browser: 'firefox',
123+
buildType: 'webpack',
124+
benchmarkName: 'test-benchmark',
125+
});
126+
127+
expect(url).toContain('ci.branch%3Afeat%2Ftest');
128+
expect(url).toContain('ci.browser%3Afirefox');
129+
expect(url).toContain('ci.buildType%3Awebpack');
130+
expect(url).toContain('message');
131+
expect(url).toContain('benchmark.test-benchmark');
132+
});
133+
});

0 commit comments

Comments
 (0)