Skip to content

Commit 662b957

Browse files
ref: Optimize file fetching code (#96)
While looking around in here I noticed we were doing a whole extra fetch to get the metadata for the file when all of the info we need was already present on the page. This PR updates the code to remove that fetch. Additionally, adds support for file coverage when browsing at a specific commit, this was previously not supported.
1 parent 3ec8392 commit 662b957

File tree

4 files changed

+120
-109
lines changed

4 files changed

+120
-109
lines changed

src/content/github/common/fetchers.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,6 @@ import {
55
MessageType,
66
} from "src/types";
77

8-
export async function getMetadata(url: string): Promise<FileMetadata> {
9-
const response = await fetch(url, {
10-
headers: {
11-
"Accept": "application/json",
12-
},
13-
}).then((response) => response.json());
14-
let branch = undefined;
15-
if (response.payload.refInfo.refType === "branch") {
16-
branch = response.payload.refInfo.name;
17-
}
18-
return {
19-
owner: response.payload.repo.ownerLogin,
20-
repo: response.payload.repo.name,
21-
path: response.payload.path,
22-
commit: response.payload.refInfo.currentOid,
23-
branch: branch,
24-
};
25-
}
26-
278
export async function getFlags(metadata: FileMetadata): Promise<string[]> {
289
const payload = {
2910
service: "github",
@@ -65,6 +46,11 @@ export async function getCommitReport(
6546
flag: string | undefined,
6647
component_id: string | undefined
6748
): Promise<FileCoverageReportResponse> {
49+
// metadata.commit must be defined, check it before calling
50+
if (!metadata.commit) {
51+
throw new Error("getCommitReport called without commit sha");
52+
}
53+
6854
const payload = {
6955
service: "github",
7056
owner: metadata.owner,

src/content/github/file/main.tsx

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React from "dom-chef";
21
import browser from "webextension-polyfill";
32
import alpha from "color-alpha";
43
import Drop from "tether-drop";
@@ -27,15 +26,14 @@ import {
2726
import { colors } from "../common/constants";
2827
import { createDropdown } from "./utils/dropdown";
2928
import {
30-
getMetadata,
3129
getComponents,
3230
getCommitReport,
3331
getFlags,
3432
getBranchReport,
3533
} from "../common/fetchers";
3634
import { print } from "src/utils";
35+
import Sentry from "../../common/sentry";
3736
import { isFileUrl } from "../common/utils";
38-
import Sentry from '../../common/sentry';
3937

4038
const globals: {
4139
coverageReport?: FileCoverageReport;
@@ -47,7 +45,7 @@ const globals: {
4745
prompt?: HTMLElement;
4846
} = {};
4947

50-
init()
48+
init();
5149

5250
function init(): Promise<void> {
5351
// this event discovered by "reverse-engineering GitHub"
@@ -63,21 +61,60 @@ function init(): Promise<void> {
6361

6462
async function main(): Promise<void> {
6563
try {
66-
if (!isFileUrl(document.URL)) {
64+
const urlMetadata = getMetadataFromURL();
65+
if (!urlMetadata) {
6766
print("file not detected at current URL");
6867
return;
6968
}
69+
globals.coverageButton = createCoverageButton();
70+
process(urlMetadata);
71+
} catch (e) {
72+
Sentry.captureException(e);
73+
throw e;
74+
}
75+
}
7076

71-
let metadata: FileMetadata;
72-
metadata = await getMetadata(document.URL);
77+
function getMetadataFromURL(): FileMetadata | null {
78+
const regexp =
79+
/\/(?<owner>.+?)\/(?<repo>.+?)\/blob\/(?<branch>.+?)\/(?<path>.+?)$/;
80+
const matches = regexp.exec(window.location.pathname);
81+
const groups = matches?.groups;
82+
if (!groups) {
83+
return null;
84+
}
7385

74-
globals.coverageButton = createCoverageButton();
86+
const branch = groups.branch;
87+
const commitMatch = branch.match(/[\da-f]+/);
7588

76-
process(metadata)
77-
} catch (e) {
78-
Sentry.captureException(e)
79-
throw e
89+
// branch could be a commit sha
90+
if (
91+
commitMatch &&
92+
commitMatch[0].length == branch.length &&
93+
(groups.branch.length === 40 || branch.length === 7)
94+
) {
95+
// branch is actually a commit sha
96+
let commit = branch;
97+
98+
// if it's a short sha, we need to get the full sha
99+
if (commit.length === 7) {
100+
const commitLink = document.querySelector(
101+
`[href^="/${groups.owner}/${groups.repo}/tree/${commit}"]`
102+
);
103+
if (!commitLink)
104+
throw new Error("Could not find commit link from short sha");
105+
const longSha = commitLink
106+
.getAttribute("href")
107+
?.match(/[\da-f]{40}/)?.[0];
108+
if (!longSha) throw new Error("Could not get long sha from commit link");
109+
commit = longSha;
110+
}
111+
112+
return {
113+
...groups,
114+
commit,
115+
};
80116
}
117+
return groups;
81118
}
82119

83120
async function process(metadata: FileMetadata): Promise<void> {
@@ -111,17 +148,16 @@ async function process(metadata: FileMetadata): Promise<void> {
111148
previousElement: globals.coverageButton!,
112149
selectedOptions: selectedFlags,
113150
onClick: handleFlagClick,
114-
})
115-
.then(({ button, list }) => {
116-
globals.flagsButton = button;
117-
globals.flagsDrop = new Drop({
118-
target: button,
119-
content: list,
120-
classes: "drop-theme-arrows codecov-z1 codecov-bg-white",
121-
position: "bottom right",
122-
openOn: "click",
123-
});
124-
})
151+
}).then(({ button, list }) => {
152+
globals.flagsButton = button;
153+
globals.flagsDrop = new Drop({
154+
target: button,
155+
content: list,
156+
classes: "drop-theme-arrows codecov-z1 codecov-bg-white",
157+
position: "bottom right",
158+
openOn: "click",
159+
});
160+
});
125161
}
126162

127163
const components = await getComponents(metadata);
@@ -134,7 +170,7 @@ async function process(metadata: FileMetadata): Promise<void> {
134170
return [];
135171
});
136172

137-
// TODO: allow setting selected flags for different files at the same time
173+
// TODO: allow setting selected components for different files at the same time
138174
if (
139175
selectedComponents.length > 0 &&
140176
_.intersection(components, selectedComponents).length === 0
@@ -151,35 +187,45 @@ async function process(metadata: FileMetadata): Promise<void> {
151187
previousElement: globals.coverageButton!,
152188
onClick: handleComponentClick,
153189
selectedOptions: selectedComponents,
154-
})
155-
.then(({ button, list }) => {
156-
globals.componentsButton = button;
157-
globals.componentsDrop = new Drop({
158-
target: button,
159-
content: list,
160-
classes: "drop-theme-arrows codecov-z1 codecov-bg-white",
161-
position: "bottom right",
162-
openOn: "click",
163-
});
164-
})
190+
}).then(({ button, list }) => {
191+
globals.componentsButton = button;
192+
globals.componentsDrop = new Drop({
193+
target: button,
194+
content: list,
195+
classes: "drop-theme-arrows codecov-z1 codecov-bg-white",
196+
position: "bottom right",
197+
openOn: "click",
198+
});
199+
});
165200
}
166201

202+
// If commit sha is defined use that, otherwise just branch name
203+
const getReportFn = metadata.commit ? getCommitReport : getBranchReport;
204+
167205
let coverageReportResponses: Array<FileCoverageReportResponse>;
168206
try {
169-
if (selectedFlags?.length > 0) {
207+
if (selectedFlags?.length > 0 && selectedComponents?.length > 0) {
208+
coverageReportResponses = await Promise.all(
209+
selectedFlags.flatMap((flag) =>
210+
selectedComponents.map((component) =>
211+
getReportFn(metadata, flag, component)
212+
)
213+
)
214+
);
215+
} else if (selectedFlags?.length > 0) {
170216
coverageReportResponses = await Promise.all(
171-
selectedFlags.map((flag) => getCommitReport(metadata, flag, undefined))
217+
selectedFlags.map((flag) => getReportFn(metadata, flag, undefined))
172218
);
173219
} else if (selectedComponents?.length > 0) {
174220
coverageReportResponses = await Promise.all(
175221
selectedComponents.map((component) =>
176-
getCommitReport(metadata, undefined, component)
222+
getReportFn(metadata, undefined, component)
177223
)
178224
);
179225
} else {
180-
coverageReportResponses = await Promise.all([
181-
await getCommitReport(metadata, undefined, undefined),
182-
]);
226+
coverageReportResponses = [
227+
await getReportFn(metadata, undefined, undefined),
228+
];
183229
}
184230
} catch (e) {
185231
updateButton(`Coverage: ⚠`);
@@ -220,7 +266,6 @@ async function process(metadata: FileMetadata): Promise<void> {
220266
if (_.isEmpty(coverageReport)) {
221267
updateButton(`Coverage: N/A`);
222268
globals.coverageReport = {};
223-
await promptPastReport(metadata);
224269
return;
225270
}
226271

@@ -232,40 +277,6 @@ async function process(metadata: FileMetadata): Promise<void> {
232277
animateAndAnnotateLines(noVirtLineSelector, annotateLine);
233278
}
234279

235-
async function promptPastReport(metadata: FileMetadata): Promise<void> {
236-
if (!metadata.branch) {
237-
return;
238-
}
239-
const response = await getBranchReport(metadata);
240-
const regexp = /app.codecov.io\/github\/.*\/.*\/commit\/(?<commit>.*)\/blob/;
241-
const matches = regexp.exec(response.commit_file_url);
242-
const commit = matches?.groups?.commit;
243-
if (!commit) {
244-
throw new Error("Could not parse commit hash from response for past coverage report")
245-
}
246-
const link = document.URL.replace(
247-
`blob/${metadata.branch}`,
248-
`blob/${commit}`
249-
);
250-
globals.prompt = createPrompt(
251-
<span>
252-
Coverage report not available for branch HEAD (
253-
{metadata.commit.substr(0, 7)}), most recent coverage report for this
254-
branch available at commit <a href={link}>{commit.substr(0, 7)}</a>
255-
</span>
256-
);
257-
}
258-
259-
function createPrompt(child: any) {
260-
const ref = document.querySelector('[data-testid="latest-commit"]')
261-
?.parentElement?.parentElement;
262-
if (!ref) {
263-
throw new Error("Could not find reference element to render prompt")
264-
}
265-
const prompt = <div className="codecov-mb2 codecov-mx1">{child}</div>;
266-
return ref.insertAdjacentElement("afterend", prompt) as HTMLElement;
267-
}
268-
269280
function createCoverageButton() {
270281
const rawButton = document.querySelector('[data-testid="raw-button"]');
271282
if (!rawButton) {

src/content/github/file/utils/dropdown.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,34 @@ export async function createDropdown({
1717
previousElement: HTMLElement;
1818
selectedOptions: string[];
1919
}) {
20-
const editButton = document
21-
.querySelector('[data-testid="more-edit-button"]')!
22-
.closest("div")!;
23-
const dropdownButton = editButton.cloneNode(true) as HTMLElement;
24-
const textNode: HTMLElement = dropdownButton.querySelector('[data-component="IconButton"]')!;
20+
// Build the button out of the Raw/copy/download button group
21+
const rawButton = document
22+
.querySelector('[data-testid="download-raw-button"]')!
23+
.closest("div");
24+
if (!rawButton) throw new Error("Could not find raw button group");
25+
const dropdownButton = rawButton.cloneNode(true) as HTMLElement;
26+
// Remove copy button
27+
const copyButton = dropdownButton.querySelector(
28+
'[data-testid="copy-raw-button"]'
29+
);
30+
if (!copyButton) throw new Error("Could not find copy button");
31+
dropdownButton.removeChild(copyButton);
32+
// Replace download button with dropdown button
33+
const downloadButton = dropdownButton.querySelector(
34+
'[data-testid="download-raw-button"]'
35+
);
36+
if (!downloadButton || !downloadButton.firstChild)
37+
throw new Error("Could not find download button or it is missing children");
38+
const triangleDownSvg = document.querySelector(".octicon-triangle-down");
39+
if (!triangleDownSvg) throw new Error("Could not find triangle down svg");
40+
downloadButton.replaceChild(triangleDownSvg, downloadButton.firstChild);
41+
42+
const textNode = dropdownButton.querySelector('[data-testid="raw-button"]');
43+
if (!textNode || !textNode.parentElement)
44+
throw new Error("Could not find textNode");
2545
textNode.innerHTML = "";
2646
textNode.ariaDisabled = "false";
27-
textNode.parentElement!.ariaLabel = tooltip;
28-
textNode.style.padding = `0 ${title.length * 5}px`;
47+
textNode.parentElement.ariaLabel = tooltip;
2948
textNode.appendChild(<span>{title}</span>);
3049
previousElement.insertAdjacentElement("afterend", dropdownButton);
3150

src/types.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
export type FileMetadata = {
2-
owner: string;
3-
repo: string;
4-
path: string;
5-
commit: string;
6-
branch: string | undefined;
7-
};
1+
export type FileMetadata = { [key: string]: string };
2+
export type PRMetadata = { [key: string]: string };
83

94
export enum CoverageStatus {
105
COVERED,

0 commit comments

Comments
 (0)