hale-commenting-system 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hale-commenting-system",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "A commenting system for PatternFly React applications that allows designers and developers to add comments directly on design pages, sync with GitHub Issues, and link Jira tickets.",
|
|
5
5
|
"homepage": "https://www.npmjs.com/package/hale-commenting-system",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,8 +47,17 @@ interface LegacyJiraRecord {
|
|
|
47
47
|
|
|
48
48
|
type JiraStore = Record<string, JiraRecord>;
|
|
49
49
|
|
|
50
|
+
// Jira Issue Cache
|
|
51
|
+
type CachedJiraIssue = {
|
|
52
|
+
ticket: JiraTicket;
|
|
53
|
+
fetchedAt: number; // timestamp
|
|
54
|
+
};
|
|
55
|
+
type JiraIssueCache = Record<string, CachedJiraIssue>;
|
|
56
|
+
|
|
50
57
|
const STORAGE_KEY = 'hale_commenting_jira_v1';
|
|
58
|
+
const CACHE_STORAGE_KEY = 'hale_commenting_jira_cache_v1';
|
|
51
59
|
const GH_JIRA_PATH = '.hale/jira.json';
|
|
60
|
+
const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
52
61
|
|
|
53
62
|
function migrateRecord(record: LegacyJiraRecord): JiraRecord {
|
|
54
63
|
// If it's already in the new format, return as-is
|
|
@@ -100,6 +109,50 @@ function setStore(next: JiraStore) {
|
|
|
100
109
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
101
110
|
}
|
|
102
111
|
|
|
112
|
+
function getIssueCache(): JiraIssueCache {
|
|
113
|
+
if (typeof window === 'undefined') return {};
|
|
114
|
+
try {
|
|
115
|
+
const raw = window.localStorage.getItem(CACHE_STORAGE_KEY);
|
|
116
|
+
if (!raw) return {};
|
|
117
|
+
return JSON.parse(raw) as JiraIssueCache;
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function setIssueCache(cache: JiraIssueCache) {
|
|
124
|
+
if (typeof window === 'undefined') return;
|
|
125
|
+
window.localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cache));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getCachedIssue(key: string): JiraTicket | null {
|
|
129
|
+
const cache = getIssueCache();
|
|
130
|
+
const cached = cache[key];
|
|
131
|
+
if (!cached) return null;
|
|
132
|
+
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const age = now - cached.fetchedAt;
|
|
135
|
+
|
|
136
|
+
// Return cached data if it's still fresh
|
|
137
|
+
if (age < CACHE_TTL_MS) {
|
|
138
|
+
return cached.ticket;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Expired - remove it
|
|
142
|
+
delete cache[key];
|
|
143
|
+
setIssueCache(cache);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function setCachedIssue(key: string, ticket: JiraTicket) {
|
|
148
|
+
const cache = getIssueCache();
|
|
149
|
+
cache[key] = {
|
|
150
|
+
ticket,
|
|
151
|
+
fetchedAt: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
setIssueCache(cache);
|
|
154
|
+
}
|
|
155
|
+
|
|
103
156
|
function normalizePathname(pathname: string): string {
|
|
104
157
|
if (!pathname) return '/';
|
|
105
158
|
const cleaned = pathname.split('?')[0].split('#')[0];
|
|
@@ -339,7 +392,7 @@ export const JiraTab: React.FunctionComponent = () => {
|
|
|
339
392
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
340
393
|
}, []);
|
|
341
394
|
|
|
342
|
-
// Fetch Jira issue details for all keys.
|
|
395
|
+
// Fetch Jira issue details for all keys (with caching).
|
|
343
396
|
React.useEffect(() => {
|
|
344
397
|
const keys = record?.jiraKeys || [];
|
|
345
398
|
if (keys.length === 0) {
|
|
@@ -354,24 +407,50 @@ export const JiraTab: React.FunctionComponent = () => {
|
|
|
354
407
|
const results: Record<string, JiraTicket> = {};
|
|
355
408
|
const errors: Record<string, string> = {};
|
|
356
409
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
410
|
+
// Check cache first
|
|
411
|
+
const keysToFetch: string[] = [];
|
|
412
|
+
for (const key of keys) {
|
|
413
|
+
const cached = getCachedIssue(key);
|
|
414
|
+
if (cached) {
|
|
415
|
+
results[key] = cached;
|
|
416
|
+
} else {
|
|
417
|
+
keysToFetch.push(key);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Fetch uncached issues
|
|
422
|
+
if (keysToFetch.length > 0) {
|
|
423
|
+
await Promise.all(
|
|
424
|
+
keysToFetch.map(async (key) => {
|
|
425
|
+
try {
|
|
426
|
+
const resp = await fetch(`/api/jira-issue?key=${encodeURIComponent(key)}`);
|
|
427
|
+
const payload = await resp.json().catch(() => ({}));
|
|
428
|
+
|
|
429
|
+
if (!resp.ok) {
|
|
430
|
+
// Handle rate limiting specially
|
|
431
|
+
if (resp.status === 429) {
|
|
432
|
+
errors[key] = 'Rate limit exceeded. Please wait a few minutes and refresh the page.';
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const raw = String(payload?.message || `Failed to fetch Jira issue (${resp.status})`);
|
|
437
|
+
const sanitized = raw.trim().startsWith('<') ? 'Unauthorized or non-JSON response from Jira.' : raw;
|
|
438
|
+
const hint = payload?.hint ? ` ${String(payload.hint)}` : '';
|
|
439
|
+
errors[key] = `${sanitized}${hint}`;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const ticket = payload as JiraTicket;
|
|
444
|
+
results[key] = ticket;
|
|
445
|
+
|
|
446
|
+
// Cache the successful result
|
|
447
|
+
setCachedIssue(key, ticket);
|
|
448
|
+
} catch (e: any) {
|
|
449
|
+
errors[key] = e?.message || 'Failed to fetch Jira issue';
|
|
368
450
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
})
|
|
374
|
-
);
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
}
|
|
375
454
|
|
|
376
455
|
setIssues(results);
|
|
377
456
|
setIssueErrors(errors);
|
|
@@ -735,7 +814,14 @@ export const JiraTab: React.FunctionComponent = () => {
|
|
|
735
814
|
|
|
736
815
|
{/* Error state */}
|
|
737
816
|
{error && !isFetchingIssues && (
|
|
738
|
-
<div style={{
|
|
817
|
+
<div style={{ display: 'grid', gap: '0.5rem' }}>
|
|
818
|
+
<div style={{ fontSize: '0.875rem', color: 'var(--pf-t--global--danger--color--100)' }}>{error}</div>
|
|
819
|
+
{error.includes('Rate limit') && (
|
|
820
|
+
<div style={{ fontSize: '0.75rem', color: 'var(--pf-t--global--text--color--subtle)' }}>
|
|
821
|
+
Tip: Jira data is cached for 15 minutes to reduce API calls. Refreshing the page will retry.
|
|
822
|
+
</div>
|
|
823
|
+
)}
|
|
824
|
+
</div>
|
|
739
825
|
)}
|
|
740
826
|
|
|
741
827
|
{/* Issue details */}
|