Skip to content

Commit 58d7067

Browse files
ydubroukaSteKoe
andauthored
fix(#2621): do not show loading spinner, when metrics actuator is not exposed
* state should be loaded by default if it is not loaded and /actuator/metrics is not exposed then loading overlay covers view forever. * chore: add tests --------- Co-authored-by: Stephan Köninger <[email protected]>
1 parent 6dae843 commit 58d7067

File tree

6 files changed

+151
-16
lines changed

6 files changed

+151
-16
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { rest } from 'msw';
2+
3+
const healthEndpoint = [
4+
rest.get('/instances/:instanceId/actuator/health', (req, res, ctx) => {
5+
return res(
6+
ctx.status(200),
7+
ctx.json({
8+
status: 'UP',
9+
details: {
10+
clientConfigServer: {
11+
status: 'UNKNOWN',
12+
details: { error: 'no property sources located' },
13+
},
14+
db: {
15+
status: 'UP',
16+
details: {
17+
database: 'HSQL Database Engine',
18+
validationQuery: 'isValid()',
19+
},
20+
},
21+
discoveryComposite: {
22+
description: 'Discovery Client not initialized',
23+
status: 'UNKNOWN',
24+
details: {
25+
discoveryClient: {
26+
description: 'Discovery Client not initialized',
27+
status: 'UNKNOWN',
28+
},
29+
},
30+
},
31+
diskSpace: {
32+
status: 'UP',
33+
details: {
34+
total: 994662584320,
35+
free: 300063879168,
36+
threshold: 10485760,
37+
exists: true,
38+
},
39+
},
40+
ping: { status: 'UP' },
41+
reactiveDiscoveryClients: {
42+
description: 'Discovery Client not initialized',
43+
status: 'UNKNOWN',
44+
details: {
45+
'Simple Reactive Discovery Client': {
46+
description: 'Discovery Client not initialized',
47+
status: 'UNKNOWN',
48+
},
49+
},
50+
},
51+
refreshScope: { status: 'UP' },
52+
},
53+
})
54+
);
55+
}),
56+
];
57+
58+
export default healthEndpoint;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { rest } from 'msw';
2+
3+
const infoEndpoint = [
4+
rest.get('/instances/:instanceId/actuator/info', (req, res, ctx) => {
5+
return res(ctx.status(200), ctx.json({}));
6+
}),
7+
];
8+
9+
export default infoEndpoint;

spring-boot-admin-server-ui/src/main/frontend/mocks/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { setupServer } from 'msw/node';
22

33
import auditEventsEndpoint from '@/mocks/instance/auditevents';
44
import flywayEndpoints from '@/mocks/instance/flyway';
5+
import healthEndpoint from '@/mocks/instance/health';
6+
import infoEndpoint from '@/mocks/instance/info';
57
import jolokiaEndpoint from '@/mocks/instance/jolokia';
68
import liquibaseEndpoints from '@/mocks/instance/liquibase';
7-
import mappingsEndpoint from '@/mocks/instance/mappings/index.js';
9+
import mappingsEndpoint from '@/mocks/instance/mappings';
810

911
const handler = [
12+
...infoEndpoint,
13+
...healthEndpoint,
1014
...mappingsEndpoint,
1115
...liquibaseEndpoints,
1216
...flywayEndpoints,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { screen, waitFor } from '@testing-library/vue';
2+
import { rest } from 'msw';
3+
import { describe, expect, it, vi } from 'vitest';
4+
5+
import DetailsView from './index.vue';
6+
7+
import { applications } from '@/mocks/applications/data';
8+
import { server } from '@/mocks/server';
9+
import Application from '@/services/application';
10+
import { render } from '@/test-utils';
11+
12+
describe('InstanceDetails', () => {
13+
describe('Metrics', () => {
14+
it('should hide loading spinner, when network call fails', async () => {
15+
const application = new Application(applications[0]);
16+
const instance = application.instances[0];
17+
18+
server.use(
19+
rest.get('/instances/:instanceId/actuator/metrics', (req, res, ctx) => {
20+
return res(ctx.status(404), ctx.json({}));
21+
})
22+
);
23+
24+
render(DetailsView, {
25+
props: {
26+
instance,
27+
application,
28+
},
29+
});
30+
31+
await waitFor(async () => {
32+
expect(
33+
await screen.queryByTestId('instance-section-loading-spinner')
34+
).not.toBeInTheDocument();
35+
});
36+
});
37+
38+
it('should hide loading spinner, when metrics endpoint is not exposed', async () => {
39+
const application = new Application(applications[0]);
40+
const instance = application.instances[0];
41+
42+
instance.hasEndpoint = vi.fn().mockImplementation((endpoint) => {
43+
return endpoint !== 'metrics';
44+
});
45+
46+
render(DetailsView, {
47+
props: {
48+
instance,
49+
application,
50+
},
51+
});
52+
53+
await waitFor(async () => {
54+
expect(
55+
await screen.queryByTestId('instance-section-loading-spinner')
56+
).not.toBeInTheDocument();
57+
});
58+
});
59+
});
60+
});

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
<template>
1818
<sba-instance-section :error="error" :loading="!hasLoaded">
1919
<template #before>
20-
<details-nav :application="application" :instance="instance" />
21-
<details-hero :instance="instance" />
20+
<details-nav :application="application" :instance="instance"/>
21+
<details-hero :instance="instance"/>
2222
</template>
2323

2424
<div class="flex gap-6 flex-col lg:flex-row">
2525
<div class="flex-1">
26-
<details-info v-if="hasInfo" :instance="instance" />
27-
<details-metadata v-if="hasMetadata" :instance="instance" />
26+
<details-info v-if="hasInfo" :instance="instance"/>
27+
<details-metadata v-if="hasMetadata" :instance="instance"/>
2828
</div>
2929
<div class="flex-1">
30-
<details-health :instance="instance" />
30+
<details-health :instance="instance"/>
3131
</div>
3232
</div>
3333

@@ -38,28 +38,28 @@
3838
:instance="instance"
3939
class="break-inside-avoid"
4040
/>
41-
<details-gc v-if="hasGc" :instance="instance" />
41+
<details-gc v-if="hasGc" :instance="instance"/>
4242
</div>
4343
<div class="flex-1">
44-
<details-threads v-if="hasThreads" :instance="instance" />
44+
<details-threads v-if="hasThreads" :instance="instance"/>
4545
</div>
4646
</div>
4747

4848
<div class="flex gap-6 flex-col lg:flex-row">
4949
<div class="flex-1">
50-
<details-memory v-if="hasMemory" :instance="instance" type="heap" />
50+
<details-memory v-if="hasMemory" :instance="instance" type="heap"/>
5151
</div>
5252
<div class="flex-1">
53-
<details-memory v-if="hasMemory" :instance="instance" type="nonheap" />
53+
<details-memory v-if="hasMemory" :instance="instance" type="nonheap"/>
5454
</div>
5555
</div>
5656

5757
<div class="flex gap-6 flex-col lg:flex-row">
5858
<div class="flex-1">
59-
<details-datasources v-if="hasDatasources" :instance="instance" />
59+
<details-datasources v-if="hasDatasources" :instance="instance"/>
6060
</div>
6161
<div class="flex-1">
62-
<details-caches v-if="hasCaches" :instance="instance" />
62+
<details-caches v-if="hasCaches" :instance="instance"/>
6363
</div>
6464
</div>
6565
</sba-instance-section>
@@ -68,7 +68,7 @@
6868
<script>
6969
import Application from '@/services/application';
7070
import Instance from '@/services/instance';
71-
import { VIEW_GROUP } from '@/views/ViewGroup';
71+
import {VIEW_GROUP} from '@/views/ViewGroup';
7272
import detailsCaches from '@/views/instances/details/details-caches';
7373
import detailsDatasources from '@/views/instances/details/details-datasources';
7474
import detailsGc from '@/views/instances/details/details-gc';
@@ -108,7 +108,7 @@ export default {
108108
},
109109
},
110110
data: () => ({
111-
hasLoaded: false,
111+
hasLoaded: true,
112112
error: null,
113113
metrics: [],
114114
}),
@@ -166,7 +166,7 @@ export default {
166166
}
167167
},
168168
},
169-
install({ viewRegistry }) {
169+
install({viewRegistry}) {
170170
viewRegistry.addView({
171171
name: 'instances/details',
172172
parent: 'instances',

spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/sba-instance-section.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
class="mb-6 w-full"
2424
/>
2525

26-
<div v-if="loading" class="loading-spinner-wrapper">
26+
<div
27+
v-if="loading"
28+
class="loading-spinner-wrapper"
29+
data-testid="instance-section-loading-spinner"
30+
>
2731
<div class="loading-spinner-wrapper-container">
2832
<sba-loading-spinner size="sm" />
2933
{{ $t('term.fetching_data') }}

0 commit comments

Comments
 (0)