refactor(web): name the V2V list summary after its source and pool pair, not a lock

This commit is contained in:
Olivier Floch
2026-11-05 10:10:35 +01:00
parent 2a4c19d281
commit 23ffab2373
12 changed files with 42 additions and 43 deletions

View File

@@ -1505,7 +1505,7 @@
"v2v:existing-list-info": "This browser already holds a migration list for {migration} ({vms}). Continuing reopens it, with the current migration state of every VM.",
"v2v:filter-vms-info": "filter by datastore, status or name with the query builder",
"v2v:global-defaults-info": "Applied to every VM, disk and network adapter. Each VM can override them in step 4 (Mapping). The SR and network are prefilled with the pool default SR and the management network of the pool master.",
"v2v:list-lock-info": "One source and one destination per list: choosing another source or pool opens its own list.",
"v2v:list-pair-info": "One source and one destination per list: choosing another source or pool opens its own list.",
"v2v:mapping-locked": "mapping locked",
"v2v:name-from-migration-list": "{name} from the migration list",
"v2v:not-found-in-source": "Not found in the source",

View File

@@ -1505,7 +1505,7 @@
"v2v:existing-list-info": "Ce navigateur contient déjà une liste de migration pour {migration} ({vms}). Continuer la rouvre, avec l’état de migration actuel de chaque VM.",
"v2v:filter-vms-info": "filtrer par banque de données, statut ou nom avec le constructeur de requête",
"v2v:global-defaults-info": "Appliquées à chaque VM, disque et carte réseau. Chaque VM peut les remplacer à l’étape 4 (Correspondance). Le SR et le réseau sont préremplis avec le SR par défaut du pool et le réseau de management du maître du pool.",
"v2v:list-lock-info": "Une source et une destination par liste : choisir une autre source ou un autre pool ouvre sa propre liste.",
"v2v:list-pair-info": "Une source et une destination par liste : choisir une autre source ou un autre pool ouvre sa propre liste.",
"v2v:mapping-locked": "correspondance verrouillée",
"v2v:name-from-migration-list": "{name} de la liste de migration",
"v2v:not-found-in-source": "Introuvable dans la source",

View File

@@ -32,7 +32,7 @@ Domain objects are created through factories in `src/test/`, one per object type
| `createV2vMigrationList`, `createV2vDestinationDefaults`, `createV2vMigrationListVms` | `V2vMigrationList`, its defaults and `count` VMs |
| `createV2vVmRow`, `createV2vVmRowsInput` | `V2vVmRow` of an inventory VM of its own id, built by `buildV2vVmRows`, then overrides / the empty `V2vVmRowsInput` |
| `createTableSelectionBindings` | `SelectionBindings` of a table with nothing selected, for a table component given its selection as props |
| `createV2vSelectedVm`, `createV2vListLock` | `V2vSelectedVm` (literal labels) / `V2vListLock` (locked), the props of the selected VMs panel components |
| `createV2vSelectedVm`, `createV2vListPair` | `V2vSelectedVm` (literal labels) / `V2vListPair` (source host and pool name), the props of the selected VMs panel components |
`createPoolStats` composes `createHostStats`, since pool stats are one entry per host: pass a `Partial<XapiHostStats>` per host id, or an `{ error }` entry for a host whose stats the pool could not fetch.

View File

@@ -1,31 +1,31 @@
import V2vListLockSummary from '@/modules/v2v/components/selected-vms/V2vListLockSummary.vue'
import type { V2vListLock } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import { createV2vListLock } from '@/test/create-v2v-selected-vms.ts'
import V2vListPairSummary from '@/modules/v2v/components/selected-vms/V2vListPairSummary.vue'
import type { V2vListPair } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import { createV2vListPair } from '@/test/create-v2v-selected-vms.ts'
import { findCardLabelledValues } from '@/test/find-rendered-values.ts'
import { createGlobalTestConfig } from '@/test/global-test-config.ts'
import { t } from '@/test/i18n.ts'
import { mount } from '@vue/test-utils'
const LIST = createV2vListLock()
const LIST = createV2vListPair()
function mountLockSummary(props: V2vListLock = LIST) {
return mount(V2vListLockSummary, { props, global: createGlobalTestConfig() })
function mountListPair(props: V2vListPair = LIST) {
return mount(V2vListPairSummary, { props, global: createGlobalTestConfig() })
}
it('names the source and the destination of the list', () => {
expect(findCardLabelledValues(mountLockSummary())).toEqual({
expect(findCardLabelledValues(mountListPair())).toEqual({
[t('source')]: LIST.sourceHost,
[t('destination')]: LIST.poolName,
})
})
it('leaves the values empty while unknown', () => {
expect(findCardLabelledValues(mountLockSummary({}))).toEqual({
expect(findCardLabelledValues(mountListPair({}))).toEqual({
[t('source')]: '',
[t('destination')]: '',
})
})
it('explains that another source or pool has its own list', () => {
expect(mountLockSummary().get('.note').text()).toBe(t('v2v:list-lock-info'))
expect(mountListPair().get('.note').text()).toBe(t('v2v:list-pair-info'))
})

View File

@@ -1,5 +1,5 @@
<template>
<div class="v2v-list-lock-summary">
<div class="v2v-list-pair-summary">
<VtsCardRowKeyValue>
<template #key>{{ t('source') }}</template>
<template #value>{{ sourceHost }}</template>
@@ -8,22 +8,22 @@
<template #key>{{ t('destination') }}</template>
<template #value>{{ poolName }}</template>
</VtsCardRowKeyValue>
<p class="note typo-body-regular-small">{{ t('v2v:list-lock-info') }}</p>
<p class="note typo-body-regular-small">{{ t('v2v:list-pair-info') }}</p>
</div>
</template>
<script lang="ts" setup>
import type { V2vListLock } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import type { V2vListPair } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
import { useI18n } from 'vue-i18n'
defineProps<V2vListLock>()
defineProps<V2vListPair>()
const { t } = useI18n()
</script>
<style lang="postcss" scoped>
.v2v-list-lock-summary {
.v2v-list-pair-summary {
display: flex;
flex-direction: column;
gap: 0.4rem;

View File

@@ -1,8 +1,8 @@
import V2vListLockSummary from '@/modules/v2v/components/selected-vms/V2vListLockSummary.vue'
import V2vListPairSummary from '@/modules/v2v/components/selected-vms/V2vListPairSummary.vue'
import V2vSelectedVmItem from '@/modules/v2v/components/selected-vms/V2vSelectedVmItem.vue'
import V2vSelectedVmsPanel from '@/modules/v2v/components/selected-vms/V2vSelectedVmsPanel.vue'
import type { V2vAccentedLabel } from '@/modules/v2v/composables/use-v2v-vm-labels.composable.ts'
import { createV2vListLock, createV2vSelectedVm } from '@/test/create-v2v-selected-vms.ts'
import { createV2vListPair, createV2vSelectedVm } from '@/test/create-v2v-selected-vms.ts'
import { findAccentedTags } from '@/test/find-accented-labels.ts'
import { findParts } from '@/test/find-parts.ts'
import { findButton, getButton } from '@/test/get-button.ts'
@@ -28,14 +28,14 @@ const READINESS_TAGS: V2vAccentedLabel<TagAccent>[] = [
{ label: '1 blocking', accent: 'danger' },
]
const LOCK_SUMMARY = createV2vListLock()
const LIST_PAIR = createV2vListPair()
const EMPTY = {
items: [],
count: 0,
isEmpty: true,
readinessTags: [],
lockSummary: createV2vListLock(),
listPair: createV2vListPair(),
}
function mountPanel(props: Partial<ComponentProps<typeof V2vSelectedVmsPanel>> = {}) {
@@ -46,7 +46,7 @@ function mountPanel(props: Partial<ComponentProps<typeof V2vSelectedVmsPanel>> =
isEmpty: false,
isBusy: false,
readinessTags: READINESS_TAGS,
lockSummary: LOCK_SUMMARY,
listPair: LIST_PAIR,
...props,
},
global: createGlobalTestConfig(),
@@ -86,8 +86,8 @@ describe('with VMs', () => {
])
})
it('summarizes the lock of the list', () => {
expect(getSummaryCard(mountPanel()).getComponent(V2vListLockSummary).props()).toEqual(LOCK_SUMMARY)
it('summarizes the source and pool of the list', () => {
expect(getSummaryCard(mountPanel()).getComponent(V2vListPairSummary).props()).toEqual(LIST_PAIR)
})
it('tags the readiness of the VMs', () => {
@@ -133,11 +133,11 @@ describe('busy', () => {
expect(findButton(wrapper, t('action:clear'))).toBeUndefined()
})
it('still titles the selected VMs with their count and summarizes the lock of the list', () => {
it('still titles the selected VMs with their count and summarizes the source and pool of the list', () => {
const wrapper = mountPanel({ isBusy: true })
expect(getSummaryCard(wrapper).getComponent(UiCounter).text()).toBe('2')
expect(getSummaryCard(wrapper).getComponent(V2vListLockSummary).props()).toEqual(LOCK_SUMMARY)
expect(getSummaryCard(wrapper).getComponent(V2vListPairSummary).props()).toEqual(LIST_PAIR)
})
})

View File

@@ -22,7 +22,7 @@
<span>{{ t('v2v:saved-in-this-browser') }}</span>
<span>{{ t('v2v:sent-to-xo-at-launch') }}</span>
</InlineParts>
<V2vListLockSummary v-bind="lockSummary" />
<V2vListPairSummary v-bind="listPair" />
<UiTagsList v-if="areItemsShown">
<UiTag v-for="tag of readinessTags" :key="tag.label" :accent="tag.accent" variant="secondary">
{{ tag.label }}
@@ -37,9 +37,9 @@
</template>
<script lang="ts" setup>
import V2vListLockSummary from '@/modules/v2v/components/selected-vms/V2vListLockSummary.vue'
import V2vListPairSummary from '@/modules/v2v/components/selected-vms/V2vListPairSummary.vue'
import V2vSelectedVmItem from '@/modules/v2v/components/selected-vms/V2vSelectedVmItem.vue'
import type { V2vListLock, V2vSelectedVm } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import type { V2vListPair, V2vSelectedVm } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import type { V2vAccentedLabel } from '@/modules/v2v/composables/use-v2v-vm-labels.composable.ts'
import InlineParts from '@/shared/components/InlineParts.vue'
import VtsSidePanel from '@core/components/panel/VtsSidePanel.vue'
@@ -59,7 +59,7 @@ const { isEmpty, isBusy } = defineProps<{
isEmpty: boolean
isBusy: boolean
readinessTags: V2vAccentedLabel<TagAccent>[]
lockSummary: V2vListLock
listPair: V2vListPair
}>()
const emit = defineEmits<{

View File

@@ -257,7 +257,7 @@ describe('selected VMs', () => {
isEmpty: false,
isBusy: false,
readinessTags: [{ label: t('n-ready', { n: 1 }), accent: 'success' }],
lockSummary: { sourceHost: SOURCE_HOST, poolName: POOL.name_label },
listPair: { sourceHost: SOURCE_HOST, poolName: POOL.name_label },
})
expect(findPanelItemIds(wrapper)).toEqual([V2V_LISTED_VM.id])
})

View File

@@ -46,7 +46,7 @@
:is-empty
:is-busy
:readiness-tags
:lock-summary
:list-pair
@remove="id => remove(id)"
@clear="clear()"
/>
@@ -92,8 +92,7 @@ const {
selectedVmsContext,
} = useV2vVmsStep()
const { items, count, isEmpty, isBusy, readinessTags, lockSummary, remove, clear } =
useV2vSelectedVms(selectedVmsContext)
const { items, count, isEmpty, isBusy, readinessTags, listPair, remove, clear } = useV2vSelectedVms(selectedVmsContext)
const panelStore = usePanelStore()
</script>

View File

@@ -38,7 +38,7 @@ export type V2vSelectedVm = {
mappingRoute: RouteLocationRaw
}
export type V2vListLock = {
export type V2vListPair = {
sourceHost?: string
poolName?: string
}
@@ -117,7 +117,7 @@ export function useV2vSelectedVms({ migrationList, rows, areRowsReady, poolId }:
return getReadinessTags(countOccurrences(V2V_READINESS_LEVELS, levels))
})
const lockSummary = computed((): V2vListLock => ({ sourceHost: sourceHost.value, poolName: poolName.value }))
const listPair = computed((): V2vListPair => ({ sourceHost: sourceHost.value, poolName: poolName.value }))
// One confirmation for removing one VM or all of them: either way, only the list changes
async function confirmListChange(subject: string, confirmLabel: string) {
@@ -150,5 +150,5 @@ export function useV2vSelectedVms({ migrationList, rows, areRowsReady, poolId }:
}
}
return { items, count, isEmpty, isBusy, readinessTags, lockSummary, remove, clear }
return { items, count, isEmpty, isBusy, readinessTags, listPair, remove, clear }
}

View File

@@ -264,11 +264,11 @@ describe('readinessTags', () => {
})
})
describe('lockSummary', () => {
describe('listPair', () => {
it('names the source and the pool of the list', async () => {
const { result } = await mountSelectedVms()
expect(result.lockSummary).toEqual({ sourceHost: createV2vConnection().host, poolName: POOL.name_label })
expect(result.listPair).toEqual({ sourceHost: createV2vConnection().host, poolName: POOL.name_label })
})
it('names no pool while the pools load', async () => {
@@ -277,14 +277,14 @@ describe('lockSummary', () => {
xoCollectionsMock.pools.value = []
xoCollectionsMock.arePoolsReady.value = false
expect(result.lockSummary.poolName).toBeUndefined()
expect(result.listPair.poolName).toBeUndefined()
expect(findItemIds(result)).toEqual(LIST_IDS)
})
it('names no pool before one is chosen', async () => {
const { result } = await mountSelectedVms({ pool: null })
expect(result.lockSummary).toEqual({ sourceHost: createV2vConnection().host, poolName: undefined })
expect(result.listPair).toEqual({ sourceHost: createV2vConnection().host, poolName: undefined })
})
})

View File

@@ -1,4 +1,4 @@
import type { V2vListLock, V2vSelectedVm } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import type { V2vListPair, V2vSelectedVm } from '@/modules/v2v/composables/use-v2v-selected-vms.composable.ts'
import { DISKOR_POOL_NAME } from '@/test/create-pool.ts'
import { createV2vConnection } from '@/test/create-v2v-connection.ts'
import type { XoV2vVm } from '@vates/types'
@@ -18,6 +18,6 @@ export function createV2vSelectedVm(overrides: Partial<V2vSelectedVm> = {}): V2v
}
}
export function createV2vListLock(overrides: Partial<V2vListLock> = {}): V2vListLock {
export function createV2vListPair(overrides: Partial<V2vListPair> = {}): V2vListPair {
return { sourceHost: createV2vConnection().host, poolName: DISKOR_POOL_NAME, ...overrides }
}