Skip to content

Commit 7c7255d

Browse files
committed
Improve editting
1 parent 9b7e27b commit 7c7255d

4 files changed

Lines changed: 137 additions & 69 deletions

File tree

src/Bugzilla.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
interface BugzillaBug {
2+
id: number;
3+
status: string;
4+
summary: string;
5+
resolution?: string;
6+
}
7+
8+
interface BugzillaResponse {
9+
bugs: BugzillaBug[];
10+
}
11+
12+
interface BugStatusResult {
13+
success: boolean;
14+
status?: string;
15+
summary?: string;
16+
resolution?: string;
17+
error?: string;
18+
}
19+
20+
interface CacheEntry {
21+
data: BugStatusResult;
22+
timestamp: number;
23+
}
24+
25+
class BugStatusCache {
26+
private cache = new Map<number, CacheEntry>();
27+
private ttl: number;
28+
29+
constructor(ttlMinutes: number = 1) {
30+
this.ttl = ttlMinutes * 60 * 1000; // Convert to milliseconds
31+
}
32+
33+
get(bugNumber: number): BugStatusResult | null {
34+
const entry = this.cache.get(bugNumber);
35+
if (!entry) return null;
36+
37+
if (Date.now() - entry.timestamp > this.ttl) {
38+
this.cache.delete(bugNumber);
39+
return null;
40+
}
41+
42+
return entry.data;
43+
}
44+
45+
set(bugNumber: number, data: BugStatusResult): void {
46+
this.cache.set(bugNumber, {
47+
data,
48+
timestamp: Date.now(),
49+
});
50+
}
51+
52+
clear(): void {
53+
this.cache.clear();
54+
}
55+
}
56+
57+
const bugCache = new BugStatusCache();
58+
59+
export async function getBugStatus(
60+
bugNumber: number,
61+
): Promise<BugStatusResult> {
62+
try {
63+
const response = await fetch(
64+
`https://bugzilla.mozilla.org/rest/bug/${bugNumber}?include_fields=id,status,summary,resolution`,
65+
);
66+
67+
if (!response.ok) {
68+
if (response.status === 404) {
69+
return {
70+
success: false,
71+
error: `Bug ${bugNumber} not found`,
72+
};
73+
}
74+
return {
75+
success: false,
76+
error: `HTTP error: ${response.status}`,
77+
};
78+
}
79+
80+
const data: BugzillaResponse = await response.json();
81+
82+
if (data.bugs.length === 0) {
83+
return {
84+
success: false,
85+
error: `Bug ${bugNumber} not found`,
86+
};
87+
}
88+
89+
const bug = data.bugs[0];
90+
return {
91+
success: true,
92+
status: bug.status,
93+
summary: bug.summary,
94+
resolution: bug.resolution,
95+
};
96+
} catch (error) {
97+
return {
98+
success: false,
99+
error: `Network error: ${error instanceof Error ? error.message : "Unknown error"}`,
100+
};
101+
}
102+
}
103+
104+
export async function getCachedBugStatus(
105+
bugNumber: number,
106+
): Promise<BugStatusResult> {
107+
// Check cache first
108+
const cached = bugCache.get(bugNumber);
109+
if (cached) {
110+
return cached;
111+
}
112+
113+
// Fetch from API
114+
const result = await getBugStatus(bugNumber);
115+
116+
// Only cache successful results
117+
if (result.success) {
118+
bugCache.set(bugNumber, result);
119+
}
120+
121+
return result;
122+
}
123+
124+
export function clearBugStatusCache(): void {
125+
bugCache.clear();
126+
}

src/NodeViewer.tsx

Lines changed: 6 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from "react";
2-
import { useState, useEffect, useRef, useCallback } from "react";
2+
import { useState } from "react";
33
import {
44
type TechTree,
55
type TechNode,
@@ -10,68 +10,6 @@ import { Modal } from "./Modal.js";
1010
import { Markdown } from "./Markdown.js";
1111
import { NodePickerModal } from "./NodePicker.js";
1212

13-
interface AddNodeModalProps {
14-
onClose: () => void;
15-
onAdd: (title: string, description: string) => void;
16-
}
17-
18-
function AddNodeModal({ onClose, onAdd }: AddNodeModalProps) {
19-
const [title, setTitle] = useState("");
20-
21-
const handleSubmit = (e: React.FormEvent) => {
22-
e.preventDefault();
23-
if (title.trim()) {
24-
onAdd(title.trim(), "");
25-
onClose();
26-
}
27-
};
28-
29-
return (
30-
<Modal onClose={onClose} className="w-[80vh]">
31-
<form onSubmit={handleSubmit} className="p-6">
32-
<h2 className="text-xl font-semibold mb-4">Add Node</h2>
33-
34-
<div className="mb-4">
35-
<label
36-
htmlFor="node-title"
37-
className="block text-sm font-medium text-gray-700 mb-2"
38-
>
39-
Title
40-
</label>
41-
<input
42-
id="node-title"
43-
type="text"
44-
value={title}
45-
autoFocus={true}
46-
autoComplete="off"
47-
onChange={(e) => setTitle(e.target.value)}
48-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
49-
placeholder="Enter title..."
50-
required
51-
/>
52-
</div>
53-
54-
<div className="flex justify-end gap-3">
55-
<button
56-
type="button"
57-
onClick={onClose}
58-
className="px-4 py-2 text-gray-700 bg-gray-100 rounded hover:bg-gray-200 transition-colors"
59-
>
60-
Cancel
61-
</button>
62-
<button
63-
type="submit"
64-
disabled={!title.trim()}
65-
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
66-
>
67-
Add
68-
</button>
69-
</div>
70-
</form>
71-
</Modal>
72-
);
73-
}
74-
7513
interface NodeViewerProps {
7614
nodeId: TechNodeId;
7715
editing: boolean;
@@ -106,7 +44,7 @@ export function NodeViewer({
10644
const handleSetTitle = (title: string) => {
10745
const updatedNode = {
10846
...node,
109-
id: generateId(title),
47+
id: generateId(fullTree, title),
11048
title: title,
11149
};
11250
onUpdateNode(node.id, updatedNode, "set-title", true);
@@ -269,11 +207,12 @@ export function NodeViewer({
269207
<h4 className="font-semibold text-gray-900 mb-2">Bugzilla:</h4>
270208
{editing ? (
271209
<input
272-
type="text"
273-
value={node.bugzillaNumber || ''}
210+
value={node.bugzillaNumber || ""}
274211
onChange={(e) => {
275212
const value = e.target.value;
276-
handleSetBugzillaNumber(value ? parseInt(value, 10) : undefined);
213+
handleSetBugzillaNumber(
214+
value ? parseInt(value, 10) : undefined,
215+
);
277216
}}
278217
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
279218
placeholder="Bugzilla bug number..."

src/TechTree.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,14 @@ export function subgraph(tree: TechTree, root: TechNodeId): TechTree {
288288
};
289289
}
290290

291-
export function generateId(title: string): TechNodeId {
291+
export function generateId(tree: TechTree, title: string): TechNodeId {
292292
const id = title
293293
.toLowerCase()
294294
.replace(/[^a-z0-9]+/g, "-")
295295
.replace(/^-|-$/g, "");
296+
if (!id) {
297+
return generateId(tree, generateTitle(tree, ""));
298+
}
296299
return id;
297300
}
298301

src/TechTreeViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export function TechTreeViewer(props: TechTreeViewerProps) {
205205
blocking = rootNodeId;
206206
}
207207
let title = generateTitle(tree, blocking);
208-
let id = generateId(title);
208+
let id = generateId(tree, title);
209209

210210
// Check if node with this ID already exists
211211
if (tree.nodes.find((node) => node.id === id)) {

0 commit comments

Comments
 (0)