Skip to content

Commit 257320b

Browse files
teja2SteKoe
andauthored
fix(#4858): deduplicate events (#4872)
Co-authored-by: teja2 <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> Co-authored-by: Stephan Köninger <[email protected]>
1 parent 95bd055 commit 257320b

File tree

4 files changed

+108
-10
lines changed

4 files changed

+108
-10
lines changed

spring-boot-admin-server-ui/src/main/frontend/views/journal/InstanceEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class InstanceEvent implements IInstanceEvent {
3838
}
3939

4040
get key() {
41-
return `${this.instance}-${this.version}`;
41+
return `${this.instance}-${this.version}-${this.type}-${this.timestamp.getTime()}`;
4242
}
4343
}
4444

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { deduplicateInstanceEvents } from './deduplicate-events';
4+
5+
import {
6+
InstanceEvent,
7+
InstanceEventType,
8+
} from '@/views/journal/InstanceEvent';
9+
10+
const createEvent = (
11+
instance: string,
12+
version: number,
13+
type = InstanceEventType.REGISTERED,
14+
) =>
15+
new InstanceEvent({
16+
instance,
17+
version,
18+
type,
19+
timestamp: '2024-01-01T10:00:00Z',
20+
registration: { name: instance },
21+
});
22+
23+
describe('deduplicateInstanceEvents', () => {
24+
it('removes events with identical instance, type and version', () => {
25+
const events = [
26+
createEvent('instance-1', 1),
27+
createEvent('instance-1', 1, InstanceEventType.DEREGISTERED),
28+
createEvent('instance-2', 3),
29+
createEvent('instance-2', 3),
30+
createEvent('instance-3', 2),
31+
createEvent('instance-3', 2, InstanceEventType.INFO_CHANGED),
32+
createEvent('instance-3', 2, InstanceEventType.INFO_CHANGED),
33+
];
34+
35+
const result = deduplicateInstanceEvents(events);
36+
37+
expect(result).toHaveLength(5);
38+
expect(result.map((event) => event.key)).toEqual([
39+
'instance-1-1-REGISTERED-1704103200000',
40+
'instance-1-1-DEREGISTERED-1704103200000',
41+
'instance-2-3-REGISTERED-1704103200000',
42+
'instance-3-2-REGISTERED-1704103200000',
43+
'instance-3-2-INFO_CHANGED-1704103200000',
44+
]);
45+
});
46+
47+
it('preserves the order of the first occurrences', () => {
48+
const events = [
49+
createEvent('instance-1', 2),
50+
createEvent('instance-2', 1),
51+
createEvent('instance-1', 2),
52+
createEvent('instance-3', 4),
53+
];
54+
55+
const result = deduplicateInstanceEvents(events);
56+
57+
expect(result.map((event) => event.key)).toEqual([
58+
'instance-1-2-REGISTERED-1704103200000',
59+
'instance-2-1-REGISTERED-1704103200000',
60+
'instance-3-4-REGISTERED-1704103200000',
61+
]);
62+
});
63+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { InstanceEvent } from '@/views/journal/InstanceEvent';
2+
3+
/**
4+
* Removes duplicate instance events from an array based on their key property.
5+
*
6+
* This function filters an array of InstanceEvent objects, keeping only the first occurrence
7+
* of each unique event key. Subsequent events with the same key are filtered out.
8+
*
9+
* @param events - Array of InstanceEvent objects to deduplicate
10+
* @returns A new array containing only unique events (by key), preserving the order of first occurrence
11+
*
12+
* @example
13+
* const events = [
14+
* { key: 'event1', ... },
15+
* { key: 'event2', ... },
16+
* { key: 'event1', ... } // duplicate
17+
* ];
18+
* const unique = deduplicateInstanceEvents(events);
19+
* // Returns first two events only
20+
*/
21+
export function deduplicateInstanceEvents(events: InstanceEvent[]) {
22+
const seen = new Set<string>();
23+
return events.filter((event) => {
24+
if (seen.has(event.key)) {
25+
return false;
26+
}
27+
seen.add(event.key);
28+
return true;
29+
});
30+
}

spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,10 @@ import { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';
4646
import subscribing from '@/mixins/subscribing';
4747
import Instance from '@/services/instance';
4848
import { compareBy } from '@/utils/collections';
49-
import {
50-
InstanceEvent,
51-
InstanceEventType,
52-
} from '@/views/journal/InstanceEvent';
49+
import { InstanceEvent } from '@/views/journal/InstanceEvent';
50+
import { InstanceEventType } from '@/views/journal/InstanceEvent';
5351
import JournalTable from '@/views/journal/JournalTable.vue';
52+
import { deduplicateInstanceEvents } from '@/views/journal/deduplicate-events';
5453
5554
export default {
5655
components: { JournalTable, SbaAlert },
@@ -67,6 +66,7 @@ export default {
6766
data: () => ({
6867
Event,
6968
events: [],
69+
seenEventKeys: new Set(),
7070
listOffset: 0,
7171
showPayload: {},
7272
pageSize: 25,
@@ -104,7 +104,10 @@ export default {
104104
.reverse()
105105
.map((e) => new InstanceEvent(e));
106106
107-
this.events = Object.freeze(events);
107+
const deduplicated = deduplicateInstanceEvents(events);
108+
this.seenEventKeys = new Set(deduplicated.map((event) => event.key));
109+
this.events = Object.freeze(deduplicated);
110+
this.listOffset = events.length - deduplicated.length;
108111
this.error = null;
109112
} catch (error) {
110113
console.warn('Fetching events failed:', error);
@@ -119,10 +122,12 @@ export default {
119122
return Instance.getEventStream().subscribe({
120123
next: (message) => {
121124
this.error = null;
122-
this.events = Object.freeze([
123-
new InstanceEvent(message.data),
124-
...this.events,
125-
]);
125+
const incomingEvent = new InstanceEvent(message.data);
126+
if (this.seenEventKeys.has(incomingEvent.key)) {
127+
return;
128+
}
129+
this.seenEventKeys.add(incomingEvent.key);
130+
this.events = Object.freeze([incomingEvent, ...this.events]);
126131
this.listOffset += 1;
127132
},
128133
error: (error) => {

0 commit comments

Comments
 (0)