Skip to content

Commit 51cac99

Browse files
authored
fix: format values for disk space health indicators in HealthDetails component (#4633)
1 parent bed00f3 commit 51cac99

File tree

2 files changed

+87
-51
lines changed

2 files changed

+87
-51
lines changed

spring-boot-admin-server-ui/src/main/frontend/views/instances/details/health-details.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,21 @@ describe('HealthDetails', () => {
4040
exists: true,
4141
},
4242
},
43+
diskSpace2: {
44+
status: 'UP',
45+
details: {
46+
total: 1024,
47+
free: 2048,
48+
threshold: 4096,
49+
exists: false,
50+
},
51+
},
4352
},
4453
};
4554

4655
render(HealthDetails, {
4756
props: {
57+
name: 'Name',
4858
health: healthMock,
4959
},
5060
});
@@ -67,6 +77,34 @@ describe('HealthDetails', () => {
6777
).toHaveTextContent(status);
6878
},
6979
);
80+
81+
it('should format diskSpace details correctly', async () => {
82+
const diskSpaceInfo = await screen.findByRole('definition', {
83+
name: 'diskSpace2',
84+
});
85+
86+
// Assert pretty-printed numbers via pretty-bytes and other primitive values
87+
const dsi = within(diskSpaceInfo);
88+
// total: 994662584320 bytes -> 995 GB (rounded)
89+
expect(
90+
await dsi.findByRole('definition', { name: 'total' }),
91+
).toHaveTextContent('1.02 kB');
92+
93+
// free: 300063879168 bytes -> 300 GB (rounded)
94+
expect(
95+
await dsi.findByRole('definition', { name: 'free' }),
96+
).toHaveTextContent('2.05 kB');
97+
98+
// threshold: 10485760 bytes -> 10 MB
99+
expect(
100+
await dsi.findByRole('definition', { name: 'threshold' }),
101+
).toHaveTextContent('4.1 kB');
102+
103+
// exists: boolean unchanged
104+
expect(
105+
await dsi.findByRole('definition', { name: 'exists' }),
106+
).toHaveTextContent('false');
107+
});
70108
});
71109

72110
describe('Health .components', () => {

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

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,42 @@
1919
class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
2020
:class="{ 'bg-white': index % 2 === 0, 'bg-gray-50': index % 2 !== 0 }"
2121
>
22-
<dt
23-
:id="'health-detail__' + name"
24-
class="text-sm font-medium text-gray-500"
25-
>
22+
<dt :id="`health-${id}__${name}`" class="text-sm font-medium text-gray-500">
2623
{{ name }}
2724
</dt>
2825
<dd
2926
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
30-
:aria-labelledby="'health-detail__' + name"
27+
:aria-labelledby="`health-${id}__` + name"
3128
>
3229
<sba-status-badge v-if="health.status" :status="health.status" />
3330

3431
<dl v-if="details && details.length > 0" class="grid grid-cols-2 mt-2">
3532
<template v-for="detail in details" :key="detail.name">
36-
<dt class="font-medium" v-text="detail.name" />
33+
<dt
34+
class="font-medium"
35+
:id="`health-detail-${id}__${detail.name}`"
36+
v-text="detail.name"
37+
/>
3738
<dd
38-
v-if="name === 'diskSpace' && typeof detail.value === 'number'"
39+
v-if="
40+
name.toLowerCase().startsWith('diskspace') &&
41+
typeof detail.value === 'number'
42+
"
43+
:aria-labelledby="`health-detail-${id}__${detail.name}`"
3944
v-text="prettyBytes(detail.value)"
4045
/>
41-
<dd v-else-if="typeof detail.value === 'object'">
46+
<dd
47+
v-else-if="typeof detail.value === 'object'"
48+
:aria-labelledby="`health-detail-${id}__${detail.name}`"
49+
>
4250
<pre
4351
class="break-words whitespace-pre-wrap"
44-
v-text="toJson(detail.value)"
52+
v-text="JSON.stringify(detail.value)"
4553
/>
4654
</dd>
4755
<dd
4856
v-else
57+
:aria-labelledby="`health-detail-${id}__${detail.name}`"
4958
class="break-words whitespace-pre-wrap"
5059
v-text="detail.value"
5160
/>
@@ -63,52 +72,41 @@
6372
/>
6473
</template>
6574

66-
<script>
75+
<script lang="ts" setup>
6776
import prettyBytes from 'pretty-bytes';
77+
import { computed, useId } from 'vue';
78+
79+
const id = useId();
6880
6981
const isChildHealth = (value) => {
7082
return value !== null && typeof value === 'object' && 'status' in value;
7183
};
7284
73-
export default {
74-
name: 'HealthDetails',
75-
props: {
76-
name: {
77-
type: String,
78-
required: true,
79-
},
80-
health: {
81-
type: Object,
82-
required: true,
83-
},
84-
index: {
85-
type: Number,
86-
default: 0,
87-
},
88-
},
89-
computed: {
90-
details() {
91-
if (this.health.details || this.health.components) {
92-
return Object.entries(this.health.details || this.health.components)
93-
.filter(([, value]) => !isChildHealth(value))
94-
.map(([name, value]) => ({ name, value }));
95-
}
96-
return [];
97-
},
98-
childHealth() {
99-
if (this.health.details || this.health.components) {
100-
return Object.entries(this.health.details || this.health.components)
101-
.filter(([, value]) => isChildHealth(value))
102-
.map(([name, value]) => ({ name, value }));
103-
}
104-
return [];
105-
},
106-
},
107-
methods: {
108-
prettyBytes,
109-
toJson(obj) {
110-
return JSON.stringify(obj, null, 2);
111-
},
112-
},
113-
};
85+
const {
86+
health,
87+
name,
88+
index = 0,
89+
} = defineProps<{
90+
name: string;
91+
health: Record<string, any>;
92+
index?: number;
93+
}>();
94+
95+
const details = computed(() => {
96+
if (health.details || health.components) {
97+
return Object.entries(health.details || health.components)
98+
.filter(([, value]) => !isChildHealth(value))
99+
.map(([name, value]) => ({ name, value }));
100+
}
101+
return [];
102+
});
103+
104+
const childHealth = computed(() => {
105+
if (health.details || health.components) {
106+
return Object.entries(health.details || health.components)
107+
.filter(([, value]) => isChildHealth(value))
108+
.map(([name, value]) => ({ name, value }));
109+
}
110+
return [];
111+
});
114112
</script>

0 commit comments

Comments
 (0)