Skip to content

Commit 3627b7e

Browse files
committed
Fix excessive bugzilla api requests leading to rate limit.
We now only do one request for all bugs.
1 parent 74b6b2b commit 3627b7e

File tree

4 files changed

+103
-69
lines changed

4 files changed

+103
-69
lines changed

src/app.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { LitElement, html, css } from "lit";
66
import { customElement, state } from "lit/decorators.js";
7-
import { ExceptionListEntry } from "./types";
7+
import { ExceptionListEntry, BugMetaMap, BugMeta } from "./types";
88
import "./exceptions-table";
99
import "./github-corner";
1010

@@ -64,6 +64,46 @@ async function fetchRecords(rsOrigin: string): Promise<ExceptionListEntry[]> {
6464
return json.data;
6565
}
6666

67+
/**
68+
* Fetch the metadata for a set of bug IDs from Bugzilla.
69+
* @param bugIds The set of bug IDs to fetch metadata for.
70+
* @returns A map of bug IDs to their metadata.
71+
*/
72+
async function fetchBugMetadata(bugIds: Set<string>): Promise<BugMetaMap> {
73+
if (bugIds.size === 0) {
74+
return {};
75+
}
76+
77+
let url = new URL("https://bugzilla.mozilla.org/rest/bug");
78+
url.searchParams.set("id", Array.from(bugIds).join(","));
79+
url.searchParams.set("include_fields", "id,is_open,summary");
80+
81+
const response = await fetch(url);
82+
if (!response.ok) {
83+
throw new Error(`Failed to fetch bug metadata: ${response.statusText}`);
84+
}
85+
const json = await response.json();
86+
87+
// Validate the response object.
88+
if (!json.bugs?.length) {
89+
throw new Error("Unexpected or outdated format.");
90+
}
91+
92+
// Convert API response which is an array of bugs into a map of bug IDs to
93+
// their metadata.
94+
let bugMetaMap: BugMetaMap = {};
95+
96+
for (let bug of json.bugs) {
97+
bugMetaMap[bug.id] = {
98+
id: bug.id,
99+
isOpen: bug.is_open,
100+
summary: bug.summary,
101+
};
102+
}
103+
104+
return bugMetaMap;
105+
}
106+
67107
@customElement("app-root")
68108
export class App extends LitElement {
69109
// The Remote Settings environment to use. The default is configured via env
@@ -76,6 +116,11 @@ export class App extends LitElement {
76116
@state()
77117
records: ExceptionListEntry[] = [];
78118

119+
// Holds the metadata for all bugs that are associated with the exceptions
120+
// list.
121+
@state()
122+
bugMeta: BugMetaMap = {};
123+
79124
@state()
80125
loading: boolean = true;
81126

@@ -165,8 +210,15 @@ export class App extends LitElement {
165210
if (this.records.length && this.records[0].bugIds == null) {
166211
throw new Error("Unexpected or outdated format.");
167212
}
213+
168214
// Sort so most recently modified records are at the top.
169215
this.records.sort((a, b) => b.last_modified - a.last_modified);
216+
217+
// Fetch the metadata for all bugs that are associated with the exceptions list.
218+
this.bugMeta = await fetchBugMetadata(
219+
new Set(this.records.flatMap((record) => record.bugIds || [])),
220+
);
221+
170222
this.error = null;
171223
} catch (error: any) {
172224
this.error = error?.message || "Failed to initialize";
@@ -219,6 +271,7 @@ export class App extends LitElement {
219271
<exceptions-table
220272
id="global-baseline"
221273
.entries=${this.records}
274+
.bugMeta=${this.bugMeta}
222275
.filter=${(entry: ExceptionListEntry) =>
223276
!entry.topLevelUrlPattern?.length && entry.category === "baseline"}
224277
></exceptions-table>
@@ -227,6 +280,7 @@ export class App extends LitElement {
227280
<exceptions-table
228281
id="global-convenience"
229282
.entries=${this.records}
283+
.bugMeta=${this.bugMeta}
230284
.filter=${(entry: ExceptionListEntry) =>
231285
!entry.topLevelUrlPattern?.length && entry.category === "convenience"}
232286
></exceptions-table>
@@ -236,6 +290,7 @@ export class App extends LitElement {
236290
<exceptions-table
237291
id="per-site-baseline"
238292
.entries=${this.records}
293+
.bugMeta=${this.bugMeta}
239294
.filter=${(entry: ExceptionListEntry) =>
240295
!!entry.topLevelUrlPattern?.length && entry.category === "baseline"}
241296
></exceptions-table>
@@ -244,6 +299,7 @@ export class App extends LitElement {
244299
<exceptions-table
245300
id="per-site-convenience"
246301
.entries=${this.records}
302+
.bugMeta=${this.bugMeta}
247303
.filter=${(entry: ExceptionListEntry) =>
248304
!!entry.topLevelUrlPattern?.length && entry.category === "convenience"}
249305
></exceptions-table>

src/bug-label.ts

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
import { LitElement, html, css } from "lit";
6-
import { customElement, property, state } from "lit/decorators.js";
6+
import { customElement, property } from "lit/decorators.js";
77
import bugzillaIcon from "./assets/bugzilla-icon.svg";
8+
import { BugMeta } from "./types";
89

910
/**
1011
* A component for displaying a bug label with a bugzilla icon.
1112
* The bug info is asynchronously fetched from the Bugzilla REST API.
1213
*/
1314
@customElement("bug-label")
1415
export class BugLabel extends LitElement {
15-
// ID of the bug to display. Must be a valid bugzilla bug ID.
16-
@property({ type: String }) bugId = "";
17-
18-
// Holds the bug info fetched from the Bugzilla REST API.
19-
@state() private bugInfo: { isOpen: boolean; summary: string } | null = null;
16+
// Holds all bug metadata to display.
17+
@property({ type: Object })
18+
bugMeta: BugMeta = {
19+
id: "",
20+
isOpen: true,
21+
summary: "",
22+
};
2023

2124
static styles = css`
2225
:host {
@@ -41,70 +44,17 @@ export class BugLabel extends LitElement {
4144
}
4245
`;
4346

44-
/**
45-
* Get bug info from Bugzilla REST API
46-
*
47-
* @param {String} bugNumber
48-
* @returns {null|Object} Info object of bug or null if not found
49-
*/
50-
async bugzillaGetBugInfo(
51-
bugNumber: string,
52-
): Promise<{ isOpen: boolean; summary: string } | null> {
53-
const response = await fetch(
54-
`https://bugzilla.mozilla.org/rest/bug/${bugNumber}?include_fields=is_open,summary`,
55-
);
56-
if (!response.ok) {
57-
if (response.status === 404) {
58-
return null;
59-
}
60-
throw new Error("Error while contacting Bugzilla API");
61-
}
62-
const info = await response.json();
63-
64-
if (info.bugs.length === 0) {
65-
return null;
66-
}
67-
return { isOpen: info.bugs[0].is_open, summary: info.bugs[0].summary };
68-
}
69-
70-
/**
71-
* Trigger fetching the bug info when the component is first updated.
72-
*/
73-
firstUpdated() {
74-
this.fetchBugInfo();
75-
}
76-
77-
/**
78-
* Fetch the bug info from the Bugzilla REST API.
79-
*/
80-
private async fetchBugInfo() {
81-
if (!this.bugId) return;
82-
83-
try {
84-
this.bugInfo = await this.bugzillaGetBugInfo(this.bugId);
85-
if (this.bugInfo?.summary) {
86-
this.bugInfo.summary = `Bug ${this.bugId}: ${this.bugInfo.summary}`;
87-
}
88-
} catch (err) {
89-
console.error(`Failed to fetch bug info for bug ${this.bugId}:`, err);
90-
this.bugInfo = {
91-
isOpen: false,
92-
summary: `Error fetching bug info for bug ${this.bugId}`,
93-
};
94-
}
95-
}
96-
9747
render() {
9848
return html`
9949
<a
10050
class="bug-label"
101-
href="https://bugzilla.mozilla.org/show_bug.cgi?id=${this.bugId}"
51+
href="https://bugzilla.mozilla.org/show_bug.cgi?id=${this.bugMeta.id}"
10252
target="_blank"
10353
rel="noopener noreferrer"
104-
title=${this.bugInfo?.summary || "Loading bug info..."}
54+
title=${this.bugMeta.summary}
10555
>
10656
<img
107-
class="bug-icon ${this.bugInfo && !this.bugInfo.isOpen ? "closed" : ""}"
57+
class="bug-icon ${!this.bugMeta.isOpen ? "closed" : ""}"
10858
src=${bugzillaIcon}
10959
alt="Bugzilla Icon"
11060
width="32"

src/exceptions-table.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { LitElement, html, css } from "lit";
66
import { customElement, property } from "lit/decorators.js";
7-
import { ExceptionListEntry } from "./types";
7+
import { BugMetaMap, ExceptionListEntry } from "./types";
88
import "./badge";
99
import "./bug-label";
1010
import "./exception-dialog";
@@ -19,6 +19,10 @@ export class ExceptionsTable extends LitElement {
1919
@property({ type: Array })
2020
entries: ExceptionListEntry[] = [];
2121

22+
// Holds all bug metadata to display.
23+
@property({ type: Object })
24+
bugMeta: BugMetaMap = {};
25+
2226
// An optional function that filters the entries to display.
2327
@property({ attribute: false })
2428
filter: (entry: ExceptionListEntry) => boolean = () => true;
@@ -199,6 +203,22 @@ export class ExceptionsTable extends LitElement {
199203
return html`<span title=${urlPattern}>${host}</span>`;
200204
}
201205

206+
/**
207+
* Renders a list of bug labels for an entry.
208+
* @param entry The entry to render the bug labels for.
209+
* @returns The rendered bug labels.
210+
*/
211+
private renderBugLabels(entry: ExceptionListEntry) {
212+
if (!Array.isArray(entry.bugIds) || entry.bugIds.length === 0) {
213+
return html``;
214+
}
215+
return html`<span class="badges"
216+
>${entry.bugIds.map(
217+
(bugId) => html`<bug-label .bugMeta=${this.bugMeta[bugId]}></bug-label>`,
218+
)}</span
219+
>`;
220+
}
221+
202222
private renderTable() {
203223
return html`
204224
<div class="table-container">
@@ -218,11 +238,7 @@ export class ExceptionsTable extends LitElement {
218238
(entry) => html`
219239
<tr>
220240
<td>
221-
<span class="badges">
222-
${Array.isArray(entry.bugIds)
223-
? entry.bugIds.map((bugId) => html`<bug-label bugId=${bugId}></bug-label>`)
224-
: ""}
225-
</span>
241+
<span class="badges"> ${this.renderBugLabels(entry)} </span>
226242
</td>
227243
<td class="${this.hasGlobalRules ? "" : "hidden-col"}">
228244
${this.renderUrlPattern(entry.topLevelUrlPattern)}

src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
import { URLClassifierExceptionListEntry } from "./url-classifier-exception-list-types";
66

7+
// Metadata about a bug from Bugzilla.
8+
export interface BugMeta {
9+
id: string;
10+
isOpen: boolean;
11+
summary: string;
12+
}
13+
14+
// A map of bug IDs to their metadata.
15+
export interface BugMetaMap {
16+
[bugId: string]: BugMeta;
17+
}
18+
719
// The RemoteSettings entry has a last_modified timestamp that is not exposed via the schema.
820
// Create a type that extends the schema-generated type with the last_modified timestamp.
921
// This will be the main type used throughout the app.

0 commit comments

Comments
 (0)