mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-11-15 13:26:15 +08:00
fix: show quotes in high-performance storage hints and keep Custom profile (#1204)
* fix: show quotes in high-performance storage hints and keep Custom profile The Native I/O and I/O threads policy hints were passed to Banner as :label="t(...)". t() escapes HTML and Banner escapes the label again, so the quotes showed up as ". Use label-key instead, which renders the string once. Picking the Custom performance profile and then choosing options that match the Default or High Performance preset flipped the selector back to that preset. Keep an explicit Custom choice and only infer the profile from the disk values otherwise. Related: harvester/harvester#11550 Signed-off-by: Alejandro Bonilla <abonilla@suse.com> * fix: only keep Custom profile when the user picked it syncProfile() skipped re-detection whenever the profile was Custom, including a Custom that was inferred from the disk values. In an existing-volume row, switching from a volume with a WriteBack override to one with High Performance values left the selector on Custom. Track the user's explicit Custom choice in its own flag and use that in the guard, so an inferred Custom still follows the disk values. Related: harvester/harvester#11550 Signed-off-by: Alejandro Bonilla <abonilla@suse.com> * test: cover the label-key hints of VMPerformanceOptions Check that the I/O threads policy and no virtio disk hints are passed to Banner as label keys, so their quotes are not escaped twice, and that the I/O threads policy hint is hidden once a policy is set. Signed-off-by: Alejandro Bonilla <abonilla@suse.com> --------- Signed-off-by: Alejandro Bonilla <abonilla@suse.com>
This commit is contained in:
+20
-7
@@ -40,6 +40,8 @@ export default {
|
||||
// Bus the disk used before the "High Performance" preset switched it to
|
||||
// virtio, so returning to "Default" can put it back.
|
||||
busBeforeHighProfile: null,
|
||||
// True only while the user has explicitly picked "Custom" in the selector.
|
||||
userSelectedCustom: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -103,13 +105,13 @@ export default {
|
||||
watch: {
|
||||
// Keep the profile selector in sync if the row is repopulated (e.g. editing an existing VM).
|
||||
'value.cache'() {
|
||||
this.profile = this.detectProfile();
|
||||
this.syncProfile();
|
||||
},
|
||||
'value.io'() {
|
||||
this.profile = this.detectProfile();
|
||||
this.syncProfile();
|
||||
},
|
||||
'value.dedicatedIOThread'() {
|
||||
this.profile = this.detectProfile();
|
||||
this.syncProfile();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -128,6 +130,16 @@ export default {
|
||||
return CUSTOM;
|
||||
},
|
||||
|
||||
syncProfile() {
|
||||
// An explicit "Custom" choice sticks, even if the options happen to
|
||||
// match the Default or High Performance preset. An inferred "Custom"
|
||||
// still follows the disk values.
|
||||
if (this.userSelectedCustom) {
|
||||
return;
|
||||
}
|
||||
this.profile = this.detectProfile();
|
||||
},
|
||||
|
||||
cacheLabel(value) {
|
||||
if (value === '') {
|
||||
return this.t('harvester.virtualMachine.volume.performance.cacheMode.default');
|
||||
@@ -140,6 +152,7 @@ export default {
|
||||
const previous = this.profile;
|
||||
|
||||
this.profile = profile;
|
||||
this.userSelectedCustom = profile === CUSTOM;
|
||||
|
||||
if (profile === DEFAULT) {
|
||||
this.value.cache = '';
|
||||
@@ -231,7 +244,7 @@ export default {
|
||||
<Banner
|
||||
v-if="profile === DISK_PERFORMANCE_PROFILE.HIGH"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.performance.highProfileTip')"
|
||||
label-key="harvester.virtualMachine.volume.performance.highProfileTip"
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -287,17 +300,17 @@ export default {
|
||||
<Banner
|
||||
v-if="isNativeIo"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip')"
|
||||
label-key="harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showFilesystemCacheWarning"
|
||||
color="warning"
|
||||
:label="t('harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning')"
|
||||
label-key="harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning"
|
||||
/>
|
||||
<Banner
|
||||
v-if="showBusTip"
|
||||
color="warning"
|
||||
:label="t('harvester.virtualMachine.volume.performance.busTip')"
|
||||
label-key="harvester.virtualMachine.volume.performance.busTip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -135,7 +135,7 @@ export default {
|
||||
<Banner
|
||||
v-if="!hasVirtioDisk && !isView"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip')"
|
||||
label-key="harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip"
|
||||
/>
|
||||
|
||||
<div class="row mb-10">
|
||||
@@ -171,7 +171,7 @@ export default {
|
||||
<Banner
|
||||
v-if="hasDedicatedIothread && !ioThreadsPolicy"
|
||||
color="info"
|
||||
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip')"
|
||||
label-key="harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip"
|
||||
/>
|
||||
</template>
|
||||
</InfoBox>
|
||||
|
||||
+57
@@ -1,5 +1,7 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { Banner } from '@components/Banner';
|
||||
import DiskPerformanceOptions from '../DiskPerformanceOptions.vue';
|
||||
|
||||
const mountOptions = (value, { featureEnabled = true } = {}) => ({
|
||||
@@ -122,4 +124,59 @@ describe('component: DiskPerformanceOptions', () => {
|
||||
expect(disabledByValue['writeback']).toBe(true);
|
||||
expect(disabledByValue['writethrough']).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['Default', {
|
||||
cache: '', io: '', dedicatedIOThread: false
|
||||
}],
|
||||
['High Performance', {
|
||||
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||
}],
|
||||
])('stays on Custom when the options match the %s preset', async(_label, opts) => {
|
||||
const value = hardDisk({ cache: 'writeback' });
|
||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
|
||||
|
||||
wrapper.vm.onProfileChange('custom');
|
||||
await wrapper.setProps({ value: { ...value, ...opts } });
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.profile).toBe('custom');
|
||||
});
|
||||
|
||||
it('detects the profile from an existing disk', async() => {
|
||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk()));
|
||||
|
||||
expect(wrapper.vm.profile).toBe('default');
|
||||
|
||||
await wrapper.setProps({
|
||||
value: hardDisk({
|
||||
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||
})
|
||||
});
|
||||
|
||||
expect(wrapper.vm.profile).toBe('highPerformance');
|
||||
});
|
||||
|
||||
it('updates an automatically detected Custom profile when preset values are loaded', async() => {
|
||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ cache: 'writeback' })));
|
||||
|
||||
expect(wrapper.vm.profile).toBe('custom');
|
||||
|
||||
await wrapper.setProps({
|
||||
value: hardDisk({
|
||||
cache: 'none', io: 'native', dedicatedIOThread: true
|
||||
})
|
||||
});
|
||||
|
||||
expect(wrapper.vm.profile).toBe('highPerformance');
|
||||
});
|
||||
|
||||
it('passes the Native I/O hint as a label key so its quotes are not double-escaped', () => {
|
||||
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ io: 'native' })));
|
||||
const banner = wrapper.findAllComponents(Banner)
|
||||
.find((b) => b.props('labelKey') === 'harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip');
|
||||
|
||||
expect(banner).toBeDefined();
|
||||
expect(banner?.props('label')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { _CREATE } from '@shell/config/query-params';
|
||||
import { Banner } from '@components/Banner';
|
||||
import VMPerformanceOptions from '../VMPerformanceOptions.vue';
|
||||
|
||||
const AUTO_ENABLED_TIP = 'harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip';
|
||||
const NO_VIRTIO_TIP = 'harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip';
|
||||
|
||||
function mountOptions(props = {}) {
|
||||
return mount(VMPerformanceOptions, {
|
||||
props: { mode: _CREATE, ...props },
|
||||
global: {
|
||||
mocks: {
|
||||
$store: {
|
||||
getters: {
|
||||
'harvester-common/getFeatureEnabled': () => true,
|
||||
'i18n/t': (key) => key,
|
||||
'i18n/exists': () => true,
|
||||
}
|
||||
},
|
||||
t: (key) => key,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function expand(wrapper) {
|
||||
await wrapper.find('.expand-toggle').trigger('click');
|
||||
}
|
||||
|
||||
function bannerFor(wrapper, labelKey) {
|
||||
return wrapper.findAllComponents(Banner).find((b) => b.props('labelKey') === labelKey);
|
||||
}
|
||||
|
||||
describe('component: VMPerformanceOptions', () => {
|
||||
it('should pass the I/O threads policy hint as a label key so its quotes are not double-escaped', async() => {
|
||||
const wrapper = mountOptions({ hasDedicatedIothread: true });
|
||||
|
||||
await expand(wrapper);
|
||||
const banner = bannerFor(wrapper, AUTO_ENABLED_TIP);
|
||||
|
||||
expect(banner).toBeDefined();
|
||||
expect(banner.props('label')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not show the I/O threads policy hint when a policy is set', async() => {
|
||||
const wrapper = mountOptions({ hasDedicatedIothread: true, ioThreadsPolicy: 'shared' });
|
||||
|
||||
expect(bannerFor(wrapper, AUTO_ENABLED_TIP)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should pass the no virtio disk hint as a label key', async() => {
|
||||
const wrapper = mountOptions({ hasVirtioDisk: false });
|
||||
|
||||
await expand(wrapper);
|
||||
const banner = bannerFor(wrapper, NO_VIRTIO_TIP);
|
||||
|
||||
expect(banner).toBeDefined();
|
||||
expect(banner.props('label')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user