Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vfg): convert field-select + more tests [khcp-11336] #1584

Merged
merged 10 commits into from
Sep 24, 2024
Merged
5 changes: 1 addition & 4 deletions packages/core/forms/sandbox/model.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"service-id": null,
"name": null,
"protocols": [
"http",
"https"
],
"protocols": "http,https",
"snis": null,
"hosts": null,
"sources": null,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/forms/src/components/fields/FieldRadio.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
:key="option.value"
v-model="inputValue"
:label="option.name"
:label-attributes="{ info: schema.help }"
:name="schema.name"
:required="schema.required || undefined"
:selected-value="option.value"
@change="onChange"
/>
Expand Down
248 changes: 129 additions & 119 deletions packages/core/forms/src/components/fields/FieldSelect.vue
Original file line number Diff line number Diff line change
@@ -1,138 +1,148 @@
<template lang="pug">
select.form-control(v-model="value", :disabled="disabled || null", :name="schema.inputName", :id="getFieldID(schema)", :class="schema.fieldClasses", v-attributes="'input'")
option(v-if="!selectOptions.hideNoneSelectedText", :disabled="schema.required || null", :value="null") {{ selectOptions.noneSelectedText || "&lt;Nothing selected&gt;" }}

template(v-for="item in items")
optgroup(v-if="item.group", :label="getGroupName(item)")
option(v-if="item.ops", v-for="i in item.ops", :value="getItemValue(i)") {{ getItemName(i) }}

option(v-if="!item.group", :value="getItemValue(item)") {{ getItemName(item) }}
<template>
<KSelect
:id="getFieldID(schema)"
v-model="inputValue"
:class="schema.fieldClasses"
:clearable="!schema.required"
:disabled="disabled || undefined"
:items="items"
:kpop-attributes="{ 'data-testid': `${getFieldID(schema)}-items` }"
:label-attributes="{ info: schema.help }"
:name="schema.inputName"
:placeholder="!selectOptions.hideNoneSelectedText ? selectOptions.noneSelectedText || 'Nothing Selected' : undefined"
:required="schema.required || undefined"
width="100%"
/>
</template>

<script>
<script lang="ts" setup>
import { computed, toRefs, type PropType } from 'vue'
import type { SelectItem } from '@kong/kongponents'
import isObject from 'lodash-es/isObject'
import isNil from 'lodash-es/isNil'
import find from 'lodash-es/find'
import abstractField from './abstractField'

export default {
mixins: [abstractField],

computed: {
selectOptions() {
return this.schema.selectOptions || {}
},
import composables from '../../composables'

items() {
const values = this.schema.values
if (typeof values === 'function') {
return this.groupValues(values.apply(this, [this.model, this.schema]))
} else return this.groupValues(values)
},
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
formOptions: {
type: Object as PropType<Record<string, any>>,
default: () => undefined,
},
model: {
type: Object as PropType<Record<string, any>>,
default: () => undefined,
},
schema: {
type: Object as PropType<Record<string, any>>,
required: true,
},
vfg: {
type: Object,
required: true,
},
/**
* TODO: stronger type
* TODO: pass this down to KInput error and errorMessage
*/
errors: {
type: Array,
default: () => [],
},
hint: {
type: String,
default: '',
},
})

methods: {
formatValueToField(value) {
if (isNil(value)) {
return null
}
return value
},

groupValues(values) {
const array = []
let arrayElement = {}

values.forEach(item => {
arrayElement = null

if (item.group && isObject(item)) {
// There is in a group.

// Find element with this group.
arrayElement = find(array, i => i.group === item.group)

if (arrayElement) {
// There is such a group.

arrayElement.ops.push({
id: item.id,
name: item.name,
})
} else {
// There is not such a group.

// Initialising.
arrayElement = {
group: '',
ops: [],
}

// Set group.
arrayElement.group = item.group

// Set Group element.
arrayElement.ops.push({
id: item.id,
name: item.name,
})
const emit = defineEmits<{
(event: 'modelUpdated', value: any, model: Record<string, any>): void
}>()

// Add array.
array.push(arrayElement)
}
} else {
// There is not in a group.
array.push(item)
}
})
const selectOptions = computed((): Record<string, any> => props.schema.selectOptions || {})

// With Groups.
return array
},
const formatValueToField = (value: Record<string, any>) => {
if (isNil(value)) {
return null
}
return value
}

getGroupName(item) {
if (item && item.group) {
return item.group
}
const propsRefs = toRefs(props)

throw new Error('Group name is missing! https://icebob.gitbooks.io/vueformgenerator/content/fields/select.html#select-field-with-object-items')
},
const { getFieldID, clearValidationErrors, value: inputValue } = composables.useAbstractFields({
model: propsRefs.model,
schema: props.schema,
formOptions: props.formOptions,
formatValueToField,
emitModelUpdated: (data: { value: any, model: Record<string, any> }): void => {
emit('modelUpdated', data.value, data.model)
},
})

defineExpose({
clearValidationErrors,
})

const items = computed((): SelectItem[] => {
// values to be used in the select items
const selectOptions = props.schema.values

if (typeof selectOptions === 'function') {
return getItemsFromValues(selectOptions.apply(this, [props.model, props.schema]))
} else {
return getItemsFromValues(selectOptions)
}
})

const getItemsFromValues = (values: Record<string, any>[] | string[] | number[]): SelectItem[] => {
const itemArray: SelectItem[] = []

values.forEach(item => {
itemArray.push({
label: getItemName(item),
value: getItemValue(item),
disabled: typeof item === 'object' ? item.disabled || undefined : undefined,
group: typeof item === 'object' ? String(item.group || '').toUpperCase() || undefined : undefined,
})
})

// With Groups.
return itemArray
}

getItemValue(item) {
if (isObject(item)) {
if (typeof this.schema.selectOptions !== 'undefined' && typeof this.schema.selectOptions.value !== 'undefined') {
return item[this.schema.selectOptions.value]
} else {
// Use 'id' instead of 'value' cause of backward compatibility
if (typeof item.id !== 'undefined') {
return item.id
} else {
throw new Error('`id` is not defined. If you want to use another key name, add a `value` property under `selectOptions` in the schema. https://icebob.gitbooks.io/vueformgenerator/content/fields/select.html#select-field-with-object-items')
}
}
const getItemValue = (item: string | number | Record<string, any>): string | number => {
if (isObject(item)) {
if (selectOptions.value && typeof selectOptions.value.value !== 'undefined') {
return item[selectOptions.value.value]
} else {
// Use 'id' instead of 'value' cause of backward compatibility
if (typeof item.id !== 'undefined') {
return String(item.id)
} else {
return item
throw new Error('`id` is not defined. If you want to use another key name, add a `value` property under `selectOptions` in the schema. https://icebob.gitbooks.io/vueformgenerator/content/fields/select.html#select-field-with-object-items')
}
},
}
} else {
return item
}
}

getItemName(item) {
if (isObject(item)) {
if (typeof this.schema.selectOptions !== 'undefined' && typeof this.schema.selectOptions.name !== 'undefined') {
return item[this.schema.selectOptions.name]
} else {
if (typeof item.name !== 'undefined') {
return item.name
} else {
throw new Error('`name` is not defined. If you want to use another key name, add a `name` property under `selectOptions` in the schema. https://icebob.gitbooks.io/vueformgenerator/content/fields/select.html#select-field-with-object-items')
}
}
const getItemName = (item: string | number | Record<string, any>): string => {
if (isObject(item)) {
if (selectOptions.value && typeof selectOptions.value.name !== 'undefined') {
return item[selectOptions.value.name]
} else {
if (typeof item.name !== 'undefined') {
return String(item.name)
} else {
return item
throw new Error('`name` is not defined. If you want to use another key name, add a `name` property under `selectOptions` in the schema. https://icebob.gitbooks.io/vueformgenerator/content/fields/select.html#select-field-with-object-items')
}
},
},
}
} else {
return String(item)
}
}
</script>

<style lang="sass">
</style>
2 changes: 2 additions & 0 deletions packages/core/forms/src/components/fields/FieldSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
:class="schema.fieldClasses"
:disabled="disabled || undefined"
:label="inputValue ? schema.textOn || t('vfg.labels.on') : schema.textOff || t('vfg.labels.off')"
:label-attributes="{ info: schema.help }"
:name="schema.inputName"
:required="schema.required || undefined"
/>
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ describe('<FieldTester /> - FieldCheckbox', () => {
model: fieldKey,
id: fieldKey,
label: fieldLabel,
help: 'Check if the cat is cool.',
required: true,
}],
}

Expand All @@ -33,6 +35,19 @@ describe('<FieldTester /> - FieldCheckbox', () => {
// check VFG label is set correctly
cy.get(`.form-group-label[for="${fieldKey}"]`).should('be.visible')
cy.get(`.form-group-label[for="${fieldKey}"]`).should('contain.text', fieldLabel)

// check required state
if (schema.fields[0].required) {
cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('exist')
} else {
cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist')
}

// check help text
if (schema.fields[0].help) {
cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible')
cy.get(`label[for="${fieldKey}"]`).should('contain.text', schema.fields[0].help)
}
})

it('renders default state correctly - with model', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ describe('<FieldTester /> - FieldInput', () => {
id: fieldKey,
inputType: 'text',
label: fieldLabel,
help: 'The name of the cat.',
required: true,
}],
}

Expand All @@ -34,6 +36,19 @@ describe('<FieldTester /> - FieldInput', () => {
// check VFG label is set correctly
cy.get(`.form-group-label[for="${fieldKey}"]`).should('be.visible')
cy.get(`.form-group-label[for="${fieldKey}"]`).should('contain.text', fieldLabel)

// check required state
if (schema.fields[0].required) {
cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('exist')
} else {
cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist')
}

// check help text
if (schema.fields[0].help) {
cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible')
cy.get(`label[for="${fieldKey}"]`).should('contain.text', schema.fields[0].help)
}
})

it('renders default state correctly - with model', () => {
Expand Down
Loading
Loading