From a75f17877e069b123456e2a1ad307cd0c25cde39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ko=CC=88ninger?= Date: Tue, 24 Mar 2026 21:51:00 +0100 Subject: [PATCH 1/4] fix: implement refreshApplications method and update refreshContext to use it --- .../src/main/frontend/services/application.ts | 4 ++++ .../src/main/frontend/views/applications/index.vue | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/services/application.ts b/spring-boot-admin-server-ui/src/main/frontend/services/application.ts index b544057f589..77fab72204e 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/services/application.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/services/application.ts @@ -124,6 +124,10 @@ class Application { return this.hasEndpoint('restart'); } + static async refreshApplications() { + return axios.post('applications'); + } + static async list() { return axios.get('applications', { headers: { Accept: 'application/json', 'X-SBA-REQUEST': true }, diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue index 4a74efe14e5..aceb0979568 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue @@ -213,7 +213,6 @@ import { useApplicationStore } from '@/composables/useApplicationStore'; import Application from '@/services/application'; import Instance from '@/services/instance'; import NotificationFilter from '@/services/notification-filter'; -import axios from '@/utils/axios'; import { anyValueMatches } from '@/utils/collections'; import { Subject, concatMap, mergeWith, timer } from '@/utils/rxjs'; import { useRouterState } from '@/utils/useRouterState'; @@ -348,10 +347,13 @@ const grouped = computed(() => { return sortBy(list, [(item) => getApplicationStatus(item)]); }); -const refreshContext = () => { - axios.post('/applications').then(() => { +const refreshContext = async () => { + try { + await Application.refreshApplications(); notificationCenter.success(t('applications.refreshed')); - }); + } catch (error) { + console.error(error); + } }; function getApplicationStatus(item: InstancesListType): string { From 76318175aeb2128e3954f9fa3932c2e62a03bad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20K=C3=B6ninger?= Date: Wed, 25 Mar 2026 08:08:43 +0100 Subject: [PATCH 2/4] Update spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/main/frontend/views/applications/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue index aceb0979568..9a1dada7a42 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue @@ -347,7 +347,7 @@ const grouped = computed(() => { return sortBy(list, [(item) => getApplicationStatus(item)]); }); -const refreshContext = async () => { +const refreshApplications = async () => { try { await Application.refreshApplications(); notificationCenter.success(t('applications.refreshed')); From 7d060a196e40a5db01ccbe3dd278ed46baf5dac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ko=CC=88ninger?= Date: Wed, 25 Mar 2026 08:32:41 +0100 Subject: [PATCH 3/4] fix: update refresh button to invoke refreshApplications and handle success/error states --- .../views/applications/applications.spec.ts | 62 +++++++++++++++++++ .../frontend/views/applications/index.vue | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts index f0f3dcbe1b9..15a6368aacc 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts @@ -228,5 +228,67 @@ describe('Applications', () => { expect(indexRestricted).toBeLessThan(indexUp); }); }); + + describe('refresh button', () => { + it('clicking the refresh button invokes Application.refreshApplications', async () => { + const refreshSpy = vi.spyOn(Application, 'refreshApplications'); + + const refreshButton = screen.getByTitle('Refresh applications'); + // First click - enters confirm mode + await userEvent.click(refreshButton); + + // Second click - confirms and executes + const confirmButton = await screen.findByText('Confirm'); + await userEvent.click(confirmButton); + + await waitFor(() => { + expect(refreshSpy).toHaveBeenCalled(); + }); + }); + + it('displays success toast when refresh completes successfully', async () => { + vi.spyOn(Application, 'refreshApplications').mockResolvedValue( + undefined, + ); + + const refreshButton = screen.getByTitle('Refresh applications'); + // First click - enters confirm mode + await userEvent.click(refreshButton); + + // Second click - confirms and executes + const confirmButton = await screen.findByText('Confirm'); + await userEvent.click(confirmButton); + + await waitFor(() => { + expect( + screen.getByText('Applications refreshed.'), + ).toBeInTheDocument(); + }); + }); + + it('logs error when refresh fails without throwing', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + const testError = new Error('Refresh failed'); + vi.spyOn(Application, 'refreshApplications').mockRejectedValue( + testError, + ); + + const refreshButton = screen.getByTitle('Refresh applications'); + // First click - enters confirm mode + await userEvent.click(refreshButton); + + // Second click - confirms and executes + const confirmButton = await screen.findByText('Confirm'); + await userEvent.click(confirmButton); + + await waitFor(() => { + expect(consoleErrorSpy).toHaveBeenCalledWith(testError); + }); + + consoleErrorSpy.mockRestore(); + }); + }); }); }); diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue index 9a1dada7a42..1047774086a 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue @@ -24,7 +24,7 @@ From 1652d8743cca66ee379b6ea3376dfb89e12c0a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ko=CC=88ninger?= Date: Wed, 25 Mar 2026 09:40:25 +0100 Subject: [PATCH 4/4] fix: enhance refresh button tests to handle success and error scenarios --- .../views/applications/applications.spec.ts | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts index 15a6368aacc..3a3a8a6f9da 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts @@ -1,9 +1,11 @@ import userEvent from '@testing-library/user-event'; import { screen, waitFor } from '@testing-library/vue'; +import { HttpResponse, http } from 'msw'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Ref, ref } from 'vue'; import { useApplicationStore } from '@/composables/useApplicationStore'; +import { server } from '@/mocks/server'; import Application from '@/services/application'; import Instance, { Registration } from '@/services/instance'; import { render } from '@/test-utils'; @@ -230,6 +232,14 @@ describe('Applications', () => { }); describe('refresh button', () => { + beforeEach(() => { + server.use( + http.post('/applications', () => { + return HttpResponse.json({}); + }), + ); + }); + it('clicking the refresh button invokes Application.refreshApplications', async () => { const refreshSpy = vi.spyOn(Application, 'refreshApplications'); @@ -246,34 +256,16 @@ describe('Applications', () => { }); }); - it('displays success toast when refresh completes successfully', async () => { - vi.spyOn(Application, 'refreshApplications').mockResolvedValue( - undefined, + it('logs error when refresh fails without throwing', async () => { + server.use( + http.post('/applications', () => { + return HttpResponse.json({}, { status: 500 }); + }), ); - const refreshButton = screen.getByTitle('Refresh applications'); - // First click - enters confirm mode - await userEvent.click(refreshButton); - - // Second click - confirms and executes - const confirmButton = await screen.findByText('Confirm'); - await userEvent.click(confirmButton); - - await waitFor(() => { - expect( - screen.getByText('Applications refreshed.'), - ).toBeInTheDocument(); - }); - }); - - it('logs error when refresh fails without throwing', async () => { const consoleErrorSpy = vi .spyOn(console, 'error') .mockImplementation(() => {}); - const testError = new Error('Refresh failed'); - vi.spyOn(Application, 'refreshApplications').mockRejectedValue( - testError, - ); const refreshButton = screen.getByTitle('Refresh applications'); // First click - enters confirm mode @@ -284,7 +276,7 @@ describe('Applications', () => { await userEvent.click(confirmButton); await waitFor(() => { - expect(consoleErrorSpy).toHaveBeenCalledWith(testError); + expect(consoleErrorSpy).toHaveBeenCalled(); }); consoleErrorSpy.mockRestore();