From e72ebf4d7f759e9d93376ca27003e4a9ef65dead Mon Sep 17 00:00:00 2001 From: teja2 Date: Mon, 24 Nov 2025 16:31:09 -0500 Subject: [PATCH 1/2] fix-4858 dedeuplicate --- .../views/journal/deduplicate-events.spec.ts | 67 +++++++++++++++++++ .../views/journal/deduplicate-events.ts | 27 ++++++++ .../src/main/frontend/views/journal/index.vue | 16 +++-- 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts create mode 100644 spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts new file mode 100644 index 00000000000..3188953a5f2 --- /dev/null +++ b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts @@ -0,0 +1,67 @@ +/*! + * Copyright 2014-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, expect, it } from 'vitest'; + +import { InstanceEvent } from '@/views/journal/InstanceEvent'; + +import { deduplicateInstanceEvents } from './deduplicate-events'; + +const createEvent = (instance: string, version: number) => + new InstanceEvent({ + instance, + version, + type: 'REGISTERED', + timestamp: '2024-01-01T10:00:00Z', + registration: { name: instance }, + }); + +describe('deduplicateInstanceEvents', () => { + it('removes events with identical instance and version', () => { + const events = [ + createEvent('instance-1', 1), + createEvent('instance-1', 1), + createEvent('instance-2', 3), + createEvent('instance-2', 3), + createEvent('instance-3', 2), + ]; + + const result = deduplicateInstanceEvents(events); + + expect(result).toHaveLength(3); + expect(result.map((event) => event.key)).toEqual([ + 'instance-1-1', + 'instance-2-3', + 'instance-3-2', + ]); + }); + + it('preserves the order of the first occurrences', () => { + const events = [ + createEvent('instance-1', 2), + createEvent('instance-2', 1), + createEvent('instance-1', 2), + createEvent('instance-3', 4), + ]; + + const result = deduplicateInstanceEvents(events); + + expect(result.map((event) => event.key)).toEqual([ + 'instance-1-2', + 'instance-2-1', + 'instance-3-4', + ]); + }); +}); diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts new file mode 100644 index 00000000000..57ff32d67f0 --- /dev/null +++ b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { InstanceEvent } from '@/views/journal/InstanceEvent'; + +export function deduplicateInstanceEvents(events: InstanceEvent[]) { + const seen = new Set(); + return events.filter((event) => { + if (seen.has(event.key)) { + return false; + } + seen.add(event.key); + return true; + }); +} diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue b/spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue index dd1eaa6af71..b5be1136380 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue @@ -47,6 +47,7 @@ import subscribing from '@/mixins/subscribing'; import Instance from '@/services/instance'; import { compareBy } from '@/utils/collections'; import { InstanceEvent } from '@/views/journal/InstanceEvent'; +import { deduplicateInstanceEvents } from '@/views/journal/deduplicate-events'; import JournalTable from '@/views/journal/JournalTable.vue'; export default { @@ -64,6 +65,7 @@ export default { data: () => ({ Event, events: [], + seenEventKeys: new Set(), listOffset: 0, showPayload: {}, pageSize: 25, @@ -101,7 +103,9 @@ export default { .reverse() .map((e) => new InstanceEvent(e)); - this.events = Object.freeze(events); + const deduplicated = deduplicateInstanceEvents(events); + this.seenEventKeys = new Set(deduplicated.map((event) => event.key)); + this.events = Object.freeze(deduplicated); this.error = null; } catch (error) { console.warn('Fetching events failed:', error); @@ -116,10 +120,12 @@ export default { return Instance.getEventStream().subscribe({ next: (message) => { this.error = null; - this.events = Object.freeze([ - new InstanceEvent(message.data), - ...this.events, - ]); + const incomingEvent = new InstanceEvent(message.data); + if (this.seenEventKeys.has(incomingEvent.key)) { + return; + } + this.seenEventKeys.add(incomingEvent.key); + this.events = Object.freeze([incomingEvent, ...this.events]); this.listOffset += 1; }, error: (error) => { From b6f7bf2146a04cadc725e67ea2bb4678004130ee Mon Sep 17 00:00:00 2001 From: teja2 Date: Mon, 24 Nov 2025 16:33:01 -0500 Subject: [PATCH 2/2] removing comments --- .../views/journal/deduplicate-events.spec.ts | 15 --------------- .../frontend/views/journal/deduplicate-events.ts | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts index 3188953a5f2..dab5a2c98f4 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts @@ -1,18 +1,3 @@ -/*! - * Copyright 2014-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ import { describe, expect, it } from 'vitest'; import { InstanceEvent } from '@/views/journal/InstanceEvent'; diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts index 57ff32d67f0..ac23e4d5d1c 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts @@ -1,18 +1,3 @@ -/* - * Copyright 2014-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ import { InstanceEvent } from '@/views/journal/InstanceEvent'; export function deduplicateInstanceEvents(events: InstanceEvent[]) {