diff --git a/ui/src/__tests__/subscription-group-card-status.test.js b/ui/src/__tests__/subscription-group-card-status.test.js new file mode 100644 index 0000000..387e098 --- /dev/null +++ b/ui/src/__tests__/subscription-group-card-status.test.js @@ -0,0 +1,60 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { mount } from '@vue/test-utils' +import SubscriptionGroupCard from '../components/SubscriptionGroupCard.vue' + +function makeGroup(extra = {}) { + return { + key: 'service:prov:aws', + name: 'Provisioning AWS', + kind: 'cloud', + color: '#ff7a18', + icon: () => null, + health: 0.5, + rows: [{ name: 'p1', status: 'ok', pills: [], sub: { id: 1, node: { id: 'service:prov:aws:enedis' } } }], + nodeIds: ['service:prov:aws:enedis'], + ...extra, + } +} + +function mountCard(group) { + return mount(SubscriptionGroupCard, { + props: { group }, + global: { + stubs: { 'v-icon': true, 'v-expand-transition': true, PluginFeatures: true }, + directives: { appear: {} }, + }, + }) +} + +describe('SubscriptionGroupCard — global node status', () => { + beforeEach(() => { setActivePinia(createPinia()) }) + + it('renders the 3-state bar from group.nodeStatus', () => { + const w = mountCard(makeGroup({ nodeStatus: { total: 10, up: 7, down: 2, noStatus: 1 } })) + expect(w.find('.nbar').exists()).toBe(true) + // The legacy health bar is replaced by the status bar. + expect(w.find('.barh').exists()).toBe(false) + // Segment widths are proportional to the totals (7/10, 1/10, 2/10). + expect(w.find('.seg.up').attributes('style')).toContain('width: 70%') + expect(w.find('.seg.nost').attributes('style')).toContain('width: 10%') + expect(w.find('.seg.down').attributes('style')).toContain('width: 20%') + expect(w.find('.ncount').text()).toBe('7/10') + }) + + it('emits refresh-node with the group key and node ids on button click', async () => { + const w = mountCard(makeGroup({ nodeStatus: { total: 10, up: 7, down: 2, noStatus: 1 } })) + await w.find('.nrefresh').trigger('click') + expect(w.emitted('refresh-node')).toBeTruthy() + expect(w.emitted('refresh-node')[0][0]).toEqual({ + key: 'service:prov:aws', + nodeIds: ['service:prov:aws:enedis'], + }) + }) + + it('falls back to the health bar when no nodeStatus is present', () => { + const w = mountCard(makeGroup()) + expect(w.find('.nbar').exists()).toBe(false) + expect(w.find('.barh').exists()).toBe(true) + }) +}) diff --git a/ui/src/components/SubscriptionGroupCard.vue b/ui/src/components/SubscriptionGroupCard.vue index d7a8fd6..d3d3118 100644 --- a/ui/src/components/SubscriptionGroupCard.vue +++ b/ui/src/components/SubscriptionGroupCard.vue @@ -24,7 +24,23 @@
{{ group.kind }}
- + + + + + + + + {{ group.nodeStatus.up }}/{{ group.nodeStatus.total }} + + + {{ Math.round(group.health * 100) }}% {{ group.rows.length }} @@ -74,11 +90,22 @@ const props = defineProps({ // Collapse is controlled by the parent panel (so "collapse all" can drive it). collapsed: { type: Boolean, default: false }, }) -defineEmits(['rowmenu', 'toggle', 'row-appear']) +defineEmits(['rowmenu', 'toggle', 'row-appear', 'refresh-node']) const t = useI18nStore().t const expanded = ref(false) const shownRows = computed(() => (expanded.value ? props.group.rows : props.group.rows.slice(0, 4))) + +// Segment width as a percentage of the node's total subscriptions. +function pct(n) { + const total = props.group.nodeStatus?.total || 0 + return total ? Math.round((n / total) * 100) : 0 +} +const statusTooltip = computed(() => { + const s = props.group.nodeStatus + if (!s) return '' + return `${t('home.nodeStatus.up')} ${s.up} · ${t('home.nodeStatus.noStatus')} ${s.noStatus} · ${t('home.nodeStatus.down')} ${s.down}` +})