Skip to content

Commit

Permalink
Expose raw data in vSphere cluster form (rancher#10143)
Browse files Browse the repository at this point in the history
* expose raw data in vSphere fields + tooltip for select options with possible long values

* fixed tooltip icon overlapping error containers

* passed empty array to ArrayListSelect, used defaultAddValue prop to override default empty string

* pass empty array as a fallback to networks for getDefaultVappOptions

---------

Co-authored-by: Mo Mesgin <[email protected]>
  • Loading branch information
momesgin and Mo Mesgin authored Dec 18, 2023
1 parent cb96934 commit 00b773a
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 45 deletions.
5 changes: 5 additions & 0 deletions shell/components/form/ArrayListSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export default {
addAllowed() {
return this.filteredOptions.length > 0;
},
defaultAddValue() {
return this.options[0]?.value;
}
},
Expand Down Expand Up @@ -62,6 +66,7 @@ export default {
class="array-list-select"
:add-allowed="addAllowed || loading"
:loading="loading"
:defaultAddValue="defaultAddValue"
@input="$emit('input', $event)"
>
<template v-slot:columns="scope">
Expand Down
121 changes: 100 additions & 21 deletions shell/machine-config/__tests__/vmwarevsphere.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { mount } from '@vue/test-utils';
import vmwarevsphere, { DEFAULT_VALUES } from '@shell/machine-config/vmwarevsphere.vue';
import vmwarevsphere, { DEFAULT_VALUES, SENTINEL } from '@shell/machine-config/vmwarevsphere.vue';
import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';

describe('component: vmwarevsphere', () => {
const defaultGetters = { 'i18n/t': jest.fn().mockImplementation((key: string) => key) };
const defaultSetup = {
propsData: {
poolId: 'poolId',
credentialId: 'credentialId',
disabled: false,
mode: 'create',
value: { initted: false },
provider: 'vmwarevsphere'
},
mocks: {
$fetchState: { pending: false },
$store: { getters: defaultGetters },
},
stubs: { CodeMirror: true },
directives: { cleanHtmlDirective }
};

it('should mount successfully with correct default values', () => {
const defaultGetters = { 'i18n/t': jest.fn() };
const wrapper = mount(vmwarevsphere, {
propsData: {
poolId: 'poolId',
credentialId: 'credentialId',
disabled: false,
mode: 'create',
value: {
initted: false,
network: [],
tag: []
},
provider: 'vmwarevsphere'
},
mocks: {
$fetchState: { pending: false },
$store: { getters: defaultGetters },
},
stubs: { CodeMirror: true }
});
const wrapper = mount(vmwarevsphere, defaultSetup);

const dataCenterElement = wrapper.find(`[data-testid="datacenter"]`).element;
const resourcePoolElement = wrapper.find(`[data-testid="resourcePool"]`).element;
Expand Down Expand Up @@ -69,4 +69,83 @@ describe('component: vmwarevsphere', () => {
expect(cfgparam).toStrictEqual(defaultCfgparam);
expect(os).toStrictEqual(defaultOs);
});

describe('mapPathOptionsToContent', () => {
const testCases = [
[['/Datacenter'], [{ label: '/Datacenter', value: '/Datacenter' }]],
[['Datacenter'], [{ label: 'Datacenter', value: 'Datacenter' }]],
[['/Datacenter/vm/datastore'], [{ label: '/Datacenter/vm/datastore', value: '/Datacenter/vm/datastore' }]],
];

it.each(testCases)('should generate label/value object without manipultion', (rawData, expected) => {
const wrapper = mount(vmwarevsphere, defaultSetup);

expect(wrapper.vm.mapPathOptionsToContent(rawData)).toStrictEqual(expected);
});
});

describe('mapHostOptionsToContent', () => {
const hostPlaceholder = {
label: 'cluster.machineConfig.vsphere.hostOptions.any',
value: SENTINEL
};
const testCases = [
[[''], [hostPlaceholder]],
[['host'], [{ label: 'host', value: 'host' }]],
];

it.each(testCases)('should generate label/value object for host options properly', (rawData, expected) => {
const wrapper = mount(vmwarevsphere, defaultSetup);

expect(wrapper.vm.mapHostOptionsToContent(rawData)).toStrictEqual(expected);
});
});

describe('mapFolderOptionsToContent', () => {
const folderPlaceholder = {
label: '\u00A0',
value: ''
};
const testCases = [
[[undefined], [folderPlaceholder]],
[[null], [folderPlaceholder]],
[[''], [folderPlaceholder]],
[['folder'], [{ label: 'folder', value: 'folder' }]],
];

it.each(testCases)('should generate label/value object for folder options properly', (rawData, expected) => {
const wrapper = mount(vmwarevsphere, defaultSetup);

expect(wrapper.vm.mapFolderOptionsToContent(rawData)).toStrictEqual(expected);
});
});

describe('mapCustomAttributesToContent', () => {
const testCases = [
[[{ name: 'name', key: 'key' }], [{ label: 'name', value: 'key' }]],
[[{ name: 'name', key: 111 }], [{ label: 'name', value: '111' }]],
];

it.each(testCases)('should generate label/value object for custom attributes options properly', (rawData, expected) => {
const wrapper = mount(vmwarevsphere, defaultSetup);

expect(wrapper.vm.mapCustomAttributesToContent(rawData)).toStrictEqual(expected);
});
});

describe('mapTagsToContent', () => {
it('should generate label/value object for tag options properly', () => {
const tag = {
id: 'tag_id',
name: 'tag_name',
category: 'tag_category',
};
const expectedReslut = [{
...tag, label: `${ tag.category } / ${ tag.name }`, value: tag.id
}];
const wrapper = mount(vmwarevsphere, defaultSetup);

expect(wrapper.vm.mapTagsToContent([tag])).toStrictEqual(expectedReslut);
});
});
});
66 changes: 42 additions & 24 deletions shell/machine-config/vmwarevsphere.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import Loading from '@shell/components/Loading';
import CreateEditView from '@shell/mixins/create-edit-view';
import LabeledSelect from '@shell/components/form/LabeledSelect';
// import { Checkbox } from '@components/Form/Checkbox';
import { exceptionToErrorsArray, stringify } from '@shell/utils/error';
import { Banner } from '@components/Banner';
import UnitInput from '@shell/components/form/UnitInput';
Expand All @@ -16,7 +15,7 @@ import { get, set } from '@shell/utils/object';
import { integerString, keyValueStrings } from '@shell/utils/computed';
import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
const SENTINEL = '__SENTINEL__';
export const SENTINEL = '__SENTINEL__';
const VAPP_MODE = {
DISABLED: 'disabled',
AUTO: 'auto',
Expand Down Expand Up @@ -108,7 +107,7 @@ const getInitialVappMode = (c) => {
return VAPP_MODE.DISABLED;
}
const d = getDefaultVappOptions(c.network);
const d = getDefaultVappOptions(c.network || []);
if (
c.vappIpprotocol === d.vappIpprotocol &&
Expand Down Expand Up @@ -326,7 +325,9 @@ export default {
},
templateTooltip() {
return this.failedToLoadTemplates ? this.t('cluster.machineConfig.vsphere.instanceOptions.template.none') : null;
const rawTemplateValue = this.value.cloneFrom;
return this.failedToLoadTemplates ? this.t('cluster.machineConfig.vsphere.instanceOptions.template.none') : rawTemplateValue;
},
host: {
Expand All @@ -350,6 +351,24 @@ export default {
gracefulShutdownTimeout: integerString('value.gracefulShutdownTimeout'),
network: {
get() {
return this.value.network || [];
},
set(newValue) {
set(this.value, 'network', newValue);
}
},
tag: {
get() {
return this.value.tag || [];
},
set(newValue) {
set(this.value, 'tag', newValue);
}
},
showCloudConfigYaml() {
return this.value.creationType !== 'legacy';
},
Expand All @@ -375,7 +394,7 @@ export default {
},
vappMode(value) {
if (value === VAPP_MODE.AUTO) {
const defaultVappOptions = getDefaultVappOptions(this.value.network);
const defaultVappOptions = getDefaultVappOptions(this.value.network || []);
return this.updateVappOptions(defaultVappOptions);
}
Expand Down Expand Up @@ -488,7 +507,7 @@ export default {
const options = await this.requestOptions('resource-pools', this.value.datacenter);
const content = this.mapPoolOptionsToContent(options);
const content = this.mapPathOptionsToContent(options);
this.resetValueIfNecessary('pool', content, options);
Expand Down Expand Up @@ -620,7 +639,7 @@ export default {
resetValueIfNecessary(key, content, options, isArray = false) {
const isValueInContent = () => {
if (isArray) {
return this.value[key].every((value) => content.find((c) => c.value === value));
return this.value[key]?.every((value) => content.find((c) => c.value === value));
}
return content.find((c) => c.value === this.value[key] );
Expand All @@ -645,10 +664,8 @@ export default {
mapPathOptionsToContent(pathOptions) {
return (pathOptions || []).map((pathOption) => {
const split = pathOption.split('/');
return {
label: split[split.length - 1],
label: pathOption,
value: pathOption
};
});
Expand All @@ -669,18 +686,6 @@ export default {
}));
},
mapPoolOptionsToContent(pathOptions) {
return pathOptions.map((pathOption) => {
const splitOptions = pathOption.split('/');
const label = splitOptions.slice(2).join('/');
return {
label,
value: pathOption
};
});
},
mapCustomAttributesToContent(customAttributes) {
return customAttributes.map((customAttribute) => ({
label: customAttribute.name,
Expand Down Expand Up @@ -767,6 +772,7 @@ export default {
:options="dataCenters"
:label="t('cluster.machineConfig.vsphere.scheduling.dataCenter')"
:disabled="disabled"
:tooltip="value.datacenter"
/>
</div>
<div
Expand All @@ -780,6 +786,7 @@ export default {
:options="resourcePools"
:label="t('cluster.machineConfig.vsphere.scheduling.resourcePool')"
:disabled="disabled"
:tooltip="value.pool"
/>
</div>
</div>
Expand All @@ -795,6 +802,7 @@ export default {
:options="dataStores"
:label="t('cluster.machineConfig.vsphere.scheduling.dataStore')"
:disabled="disabled"
:tooltip="value.datastore"
/>
</div>
<div
Expand All @@ -808,6 +816,7 @@ export default {
:options="folders"
:label="t('cluster.machineConfig.vsphere.scheduling.folder')"
:disabled="disabled"
:tooltip="value.folder"
/>
</div>
</div>
Expand All @@ -823,6 +832,7 @@ export default {
:options="hosts"
:label="t('cluster.machineConfig.vsphere.scheduling.host.label')"
:disabled="disabled"
:tooltip="host"
/>
<p class="text-muted mt-5">
{{ t('cluster.machineConfig.vsphere.scheduling.host.note') }}
Expand Down Expand Up @@ -955,6 +965,7 @@ export default {
:searchable="true"
:label="t('cluster.machineConfig.vsphere.instanceOptions.libraryTemplate')"
:disabled="disabled"
:tooltip="value.cloneFrom"
/>
</div>
<div
Expand All @@ -968,6 +979,7 @@ export default {
:options="virtualMachines"
:label="t('cluster.machineConfig.vsphere.instanceOptions.virtualMachine')"
:disabled="disabled"
:tooltip="value.cloneFrom"
/>
</div>
<div
Expand Down Expand Up @@ -1022,7 +1034,7 @@ export default {
{{ t('cluster.machineConfig.vsphere.networks.label') }}
</label>
<ArrayListSelect
v-model="value.network"
v-model="network"
:options="networks"
:array-list-props="{ addLabel: t('cluster.machineConfig.vsphere.networks.add') }"
:loading="networksLoading"
Expand Down Expand Up @@ -1064,7 +1076,7 @@ export default {
</h4>
<div slot="body">
<ArrayListSelect
v-model="value.tag"
v-model="tag"
:options="tags"
:array-list-props="{ addLabel: t('cluster.machineConfig.vsphere.tags.addTag') }"
:loading="tagsLoading"
Expand Down Expand Up @@ -1182,3 +1194,9 @@ export default {
</Card>
</div>
</template>
<style lang="scss" scoped>
::v-deep .labeled-tooltip .status-icon.icon-info {
z-index: 0;
}
</style>

0 comments on commit 00b773a

Please sign in to comment.