Skip to content

Commit f27d919

Browse files
committed
Prevent the display of events from projects other than the currently selected project.
Assisted by Claude
1 parent 335ed77 commit f27d919

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { sortEvents, typeFilter } from '../events';
2+
3+
const createMockEvent = (namespace: string, name: string, uid: string, lastTimestamp: string) => ({
4+
metadata: {
5+
uid,
6+
name,
7+
namespace,
8+
resourceVersion: '1',
9+
},
10+
involvedObject: {
11+
kind: 'Pod',
12+
name: `${name}-pod`,
13+
namespace,
14+
},
15+
type: 'Normal',
16+
reason: 'Created',
17+
message: `Created pod ${name}-pod`,
18+
lastTimestamp,
19+
});
20+
21+
describe('Event utility functions', () => {
22+
describe('sortEvents', () => {
23+
it('returns events ordered by lastTimestamp in descending order', () => {
24+
const events = [
25+
createMockEvent('ns1', 'event1', 'uid1', '2025-12-10T10:00:00Z'),
26+
createMockEvent('ns1', 'event2', 'uid2', '2025-12-10T12:00:00Z'),
27+
createMockEvent('ns1', 'event3', 'uid3', '2025-12-10T11:00:00Z'),
28+
];
29+
30+
const sorted = sortEvents(events);
31+
32+
expect(sorted[0].metadata.name).toBe('event2');
33+
expect(sorted[1].metadata.name).toBe('event3');
34+
expect(sorted[2].metadata.name).toBe('event1');
35+
});
36+
37+
it('sorts events by timestamp regardless of namespace', () => {
38+
const events = [
39+
createMockEvent('project-a', 'event-a', 'uid-a', '2025-12-10T10:00:00Z'),
40+
createMockEvent('project-b', 'event-b', 'uid-b', '2025-12-10T12:00:00Z'),
41+
];
42+
43+
const sorted = sortEvents(events);
44+
45+
expect(sorted[0].metadata.namespace).toBe('project-b');
46+
expect(sorted[1].metadata.namespace).toBe('project-a');
47+
});
48+
});
49+
50+
describe('typeFilter', () => {
51+
it('returns true for "all" filter with any event type', () => {
52+
const normalEvent = {
53+
...createMockEvent('ns', 'event', 'uid', '2025-12-10T10:00:00Z'),
54+
type: 'Normal',
55+
};
56+
const warningEvent = {
57+
...createMockEvent('ns', 'event', 'uid', '2025-12-10T10:00:00Z'),
58+
type: 'Warning',
59+
};
60+
61+
expect(typeFilter('all', normalEvent)).toBe(true);
62+
expect(typeFilter('all', warningEvent)).toBe(true);
63+
});
64+
65+
it('returns true only for matching event types', () => {
66+
const normalEvent = {
67+
...createMockEvent('ns', 'event', 'uid', '2025-12-10T10:00:00Z'),
68+
type: 'Normal',
69+
};
70+
const warningEvent = {
71+
...createMockEvent('ns', 'event', 'uid', '2025-12-10T10:00:00Z'),
72+
type: 'Warning',
73+
};
74+
75+
expect(typeFilter('normal', normalEvent)).toBe(true);
76+
expect(typeFilter('normal', warningEvent)).toBe(false);
77+
78+
expect(typeFilter('warning', normalEvent)).toBe(false);
79+
expect(typeFilter('warning', warningEvent)).toBe(true);
80+
});
81+
82+
it('treats events without type property as normal type', () => {
83+
const eventWithoutType = createMockEvent('ns', 'event', 'uid', '2025-12-10T10:00:00Z');
84+
85+
expect(typeFilter('normal', eventWithoutType)).toBe(true);
86+
expect(typeFilter('warning', eventWithoutType)).toBe(false);
87+
});
88+
});
89+
90+
describe('Integration with namespace filtering', () => {
91+
it('returns events sorted by timestamp across multiple namespaces', () => {
92+
const events = [
93+
createMockEvent('project-a', 'event-a1', 'uid-a1', '2025-12-10T10:00:00Z'),
94+
createMockEvent('project-b', 'event-b1', 'uid-b1', '2025-12-10T12:00:00Z'),
95+
createMockEvent('project-a', 'event-a2', 'uid-a2', '2025-12-10T11:00:00Z'),
96+
];
97+
98+
const sorted = sortEvents(events);
99+
100+
expect(sorted[0].metadata.name).toBe('event-b1');
101+
expect(sorted[1].metadata.name).toBe('event-a2');
102+
expect(sorted[2].metadata.name).toBe('event-a1');
103+
});
104+
105+
it('can be filtered by namespace while preserving sort order', () => {
106+
const allEvents = [
107+
createMockEvent('project-a', 'event-a1', 'uid-a1', '2025-12-10T10:00:00Z'),
108+
createMockEvent('project-b', 'event-b1', 'uid-b1', '2025-12-10T12:00:00Z'),
109+
createMockEvent('project-a', 'event-a2', 'uid-a2', '2025-12-10T11:00:00Z'),
110+
];
111+
112+
const sorted = sortEvents(allEvents);
113+
const selectedNamespace = 'project-a';
114+
const filteredEvents = sorted.filter((e) => e.metadata.namespace === selectedNamespace);
115+
116+
expect(filteredEvents).toHaveLength(2);
117+
expect(filteredEvents[0].metadata.name).toBe('event-a2');
118+
expect(filteredEvents[1].metadata.name).toBe('event-a1');
119+
expect(filteredEvents.every((e) => e.metadata.namespace === 'project-a')).toBe(true);
120+
});
121+
122+
it('returns different results when namespace filter changes', () => {
123+
const allEvents = [
124+
createMockEvent('project-a', 'event-a1', 'uid-a1', '2025-12-10T10:00:00Z'),
125+
createMockEvent('project-b', 'event-b1', 'uid-b1', '2025-12-10T12:00:00Z'),
126+
];
127+
128+
const sorted = sortEvents(allEvents);
129+
130+
let selectedNamespace = 'project-a';
131+
let filteredEvents = sorted.filter((e) => e.metadata.namespace === selectedNamespace);
132+
expect(filteredEvents).toHaveLength(1);
133+
expect(filteredEvents[0].metadata.name).toBe('event-a1');
134+
135+
selectedNamespace = 'project-b';
136+
filteredEvents = sorted.filter((e) => e.metadata.namespace === selectedNamespace);
137+
138+
expect(filteredEvents).toHaveLength(1);
139+
expect(filteredEvents[0].metadata.name).toBe('event-b1');
140+
expect(filteredEvents[0].metadata.namespace).toBe('project-b');
141+
});
142+
});
143+
});

frontend/public/components/events.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ const EventStream = ({
394394
// Handle websocket setup and teardown when dependent props change
395395
useEffect(() => {
396396
ws.current?.destroy();
397+
setSortedEvents([]);
397398
if (!mock) {
398399
const webSocketID = `${namespace || 'all'}-sysevents`;
399400
const watchURLOptions = {

0 commit comments

Comments
 (0)