Skip to content

Commit

Permalink
#225 added editable_list type, where a user can either select value o…
Browse files Browse the repository at this point in the history
…r type his own
  • Loading branch information
bugy committed Mar 25, 2021
1 parent f083252 commit b1bab1f
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 22 deletions.
10 changes: 10 additions & 0 deletions samples/configs/parameterized.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,16 @@
".log",
"TXT"
]
},
{
"name": "Editable list",
"param": "--editable_list",
"type": "editable_list",
"values": [
"Value A",
"Value B",
"Value C"
]
}
]
}
1 change: 1 addition & 0 deletions src/config/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PARAM_TYPE_SERVER_FILE = 'server_file'
PARAM_TYPE_MULTISELECT = 'multiselect'
PARAM_TYPE_EDITABLE_LIST = 'editable_list'
FILE_TYPE_FILE = 'file'
FILE_TYPE_DIR = 'dir'
5 changes: 3 additions & 2 deletions src/model/parameter_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from collections import OrderedDict
from ipaddress import ip_address, IPv4Address, IPv6Address

from config.constants import PARAM_TYPE_SERVER_FILE, FILE_TYPE_FILE, PARAM_TYPE_MULTISELECT, FILE_TYPE_DIR
from config.constants import PARAM_TYPE_SERVER_FILE, FILE_TYPE_FILE, PARAM_TYPE_MULTISELECT, FILE_TYPE_DIR, \
PARAM_TYPE_EDITABLE_LIST
from config.script.list_values import ConstValuesProvider, ScriptValuesProvider, EmptyValuesProvider, \
DependantScriptValuesProvider, NoneValuesProvider, FilesProvider
from model import model_helper
Expand Down Expand Up @@ -172,7 +173,7 @@ def _create_values_provider(self, values_config, type, constant):
if self._is_plain_server_file():
return FilesProvider(self._list_files_dir, self.file_type, self.file_extensions)

if (type != 'list') and (type != PARAM_TYPE_MULTISELECT):
if (type != 'list') and (type != PARAM_TYPE_MULTISELECT) and (type != PARAM_TYPE_EDITABLE_LIST):
return NoneValuesProvider()

if is_empty(values_config):
Expand Down
14 changes: 14 additions & 0 deletions src/tests/parameter_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ def test_allowed_values_for_non_list(self):
'values': {'script': 'echo "123\n" "456"'}})
self.assertEqual(None, parameter_model.values)

def test_allowed_values_for_editable_list(self):
parameter_model = _create_parameter_model({
'name': 'def_param',
'type': 'editable_list',
'values': {'script': 'echo "123\n" "456"'}})
self.assertEqual(['123', ' 456'], parameter_model.values)

def test_ip_uppercase(self):
parameter_model = _create_parameter_model({
'name': 'def_param',
Expand Down Expand Up @@ -478,6 +485,13 @@ def test_list_parameter_when_not_matches(self):
error = parameter.validate_value('val4')
self.assert_error(error)

def test_editable_list_parameter_when_not_matches(self):
parameter = create_parameter_model(
'param', type='editable_list', allowed_values=['val1', 'val2', 'val3'])

error = parameter.validate_value('val4')
self.assertIsNone(error)

def test_multiselect_when_empty_string(self):
parameter = create_parameter_model(
'param', type=PARAM_TYPE_MULTISELECT, allowed_values=['val1', 'val2', 'val3'])
Expand Down
3 changes: 2 additions & 1 deletion web-src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"dompurify": "^2.2.6",
"marked": "^1.2.7",
"material-design-icons": "^3.0.1",
"materialize-css": "git+https://github.com/bugy/materialize.git#91c2699",
"materialize-css": "git+https://github.com/bugy/materialize.git#90803fb",
"typeface-roboto": "^1.1.13",
"vue": "^2.6.11",
"vue-router": "^3.1.5",
Expand All @@ -19,6 +19,7 @@
"@stryker-mutator/javascript-mutator": "^3.1.0",
"@stryker-mutator/karma-runner": "^3.1.0",
"@stryker-mutator/webpack-transpiler": "^3.1.0",
"@testing-library/jest-dom": "^5.11.9",
"@vue/cli": "^4.5.10",
"@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-router": "^4.5.10",
Expand Down
5 changes: 5 additions & 0 deletions web-src/src/admin/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import Vue from 'vue';
import AdminApp from './AdminApp';
import './AdminApp';
import router from './router/router';
import vueDirectives from '@/common/vueDirectives'
import {forEachKeyValue} from '@/common/utils/common'

forEachKeyValue(vueDirectives, (id, definition) => {
Vue.directive(id, definition)
})

//noinspection JSAnnotator
new Vue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
<Textfield v-model="max" :config="maxField" class="col s6 offset-s1"
@error="handleError(maxField, $event)"/>
</div>
<div v-if="(selectedType === 'list' || selectedType === 'multiselect')" class="row">
<div v-if="(selectedType === 'list' || selectedType === 'multiselect' || selectedType === 'editable_list')"
class="row">
<Textfield v-if="allowedValuesFromScript" v-model="allowedValuesScript"
:config="allowedValuesScriptField"
class="col s9" @error="handleError(allowedValuesScriptField, $event)"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const envVarField = {
export const typeField = {
name: 'Type',
type: 'list',
values: ['text', 'int', 'list', 'multiselect', 'file_upload', 'server_file', 'ip', 'ip4', 'ip6']
values: ['text', 'int', 'list', 'multiselect', 'editable_list', 'file_upload', 'server_file', 'ip', 'ip4', 'ip6']
};

export const noValueField = {
Expand Down
4 changes: 4 additions & 0 deletions web-src/src/assets/css/materializecss/material-textfield.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ input[type=search]:not(.browser-default),
textarea.materialize-textarea {
color: var(--font-color-main);
}

.script-server .autocomplete-content li .highlight {
color: var(--font-color-main);
}
2 changes: 1 addition & 1 deletion web-src/src/common/components/ComboboxSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:value="value"
@blur="focused = false"
@focus="focused = true"
autocomplete="off"
autocomplete="one-time-code"
@input="inputFieldChanged"/>

<label :for="id" v-bind:class="{ active: labelActive }">Search</label>
Expand Down
44 changes: 40 additions & 4 deletions web-src/src/common/components/textfield.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
<div :data-error="error" :title="config.description" class="input-field">
<input :id="id"
ref="textField"
:class="{validate : !disabled}"
:autocomplete="autofillName"
:disabled="disabled"
:required="config.required"
:type="fieldType"
:value="value"
:autocomplete="config.name"
:class="{validate : !disabled, autocomplete: autocomplete}"
@input="inputFieldChanged"/>
<label :for="id" v-bind:class="{ active: labelActive }">{{ config.name }}</label>
</div>
</template>

<script>
import '@/common/materializecss/imports/input-fields';
import '@/common/materializecss/imports/autocomplete';
import {isBlankString, isEmptyString, isNull} from '@/common/utils/common';
export default {
Expand All @@ -31,7 +32,8 @@ export default {
data: function () {
return {
error: '',
id: null
id: null,
autocompleteWrapper: null
}
},
Expand All @@ -57,12 +59,33 @@ export default {
}
return false;
},
autocomplete() {
return this.config.type === 'editable_list'
},
autofillName() {
return this.config.name.replace(/\s+/, '-')
}
},
mounted: function () {
this.inputFieldChanged();
this.id = this._uid;
if (this.autocomplete) {
this.autocompleteWrapper = M.Autocomplete.init(this.$refs.textField, {minLength: 0})
this.updateAutocompleteData()
this.$refs.textField.addEventListener('change', () => this.inputFieldChanged())
}
},
beforeDestroy: function () {
if (this.autocompleteWrapper) {
this.autocompleteWrapper.destroy();
}
},
watch: {
Expand Down Expand Up @@ -94,6 +117,14 @@ export default {
handler() {
this.triggerRevalidationOnWatch();
}
},
'config.values': {
immediate: true,
handler() {
if (this.autocompleteWrapper) {
this.updateAutocompleteData()
}
}
}
},
Expand Down Expand Up @@ -146,7 +177,12 @@ export default {
M.validate_field(cash(this.$refs.textField));
}
});
}
},
updateAutocompleteData() {
const data = Object.assign({}, ...this.config.values.map((x) => ({[x]: null})))
this.autocompleteWrapper.updateData(data)
},
}
}
Expand Down
5 changes: 5 additions & 0 deletions web-src/src/common/materializecss/imports/autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// DO NOT TOUCH ORDER
import './global'
import './dropdown'

import 'materialize-css/js/autocomplete';
19 changes: 17 additions & 2 deletions web-src/tests/unit/admin/ParameterConfigForm_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ describe('Test ParameterConfigForm', function () {
assert.isUndefined(_findField('allowed values', false));
assert.isTrue(_findField('load from script').value);
});

it('Test allowed values when array and type editable_list', async function () {
form.setProps({
value: {
type: 'editable_list',
values: ['abc', '123', 'xyz']
}
});

await vueTicks();

assert.isUndefined(_findField('script', false));
assert.deepEqual(['abc', '123', 'xyz'], _findField('allowed values').value);
assert.isFalse(_findField('load from script').value);
});
});

describe('Test update values in form', function () {
Expand Down Expand Up @@ -600,7 +615,7 @@ describe('Test ParameterConfigForm', function () {
assert.isDefined(_findField('Separator'));
});

it('Test server_file fields when type multiselect set via props', async function () {
it('Test server_file fields when type server_file set via props', async function () {
await _setValueByUser('type', 'server_file');

assert.isDefined(_findField('File directory'));
Expand All @@ -609,7 +624,7 @@ describe('Test ParameterConfigForm', function () {
assert.isDefined(_findField('Allowed file extensions'));
});

it('Test server_file fields when type multiselect set by user', async function () {
it('Test server_file fields when type server_file set by user', async function () {
await _setValueByUser('Type', 'server_file');

assert.isDefined(_findField('File directory'));
Expand Down
3 changes: 3 additions & 0 deletions web-src/tests/unit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import expect from 'expect';
import extendedMatchers from 'jest-extended/dist/matchers'
import $ from 'jquery';

const domMatches = require('@testing-library/jest-dom/matchers')

expect.extend(domMatches);
expect.extend(extendedMatchers);
window.expect = expect;
window.$ = $;
Expand Down
18 changes: 18 additions & 0 deletions web-src/tests/unit/test_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,22 @@ export const createScriptServerTestVue = () => {
vue.directive(id, definition)
})
return vue
}

export const attachToDocument = () => {
const element = document.createElement('div')
document.body.appendChild(element)
return element
}

export const mapArrayWrapper = (arrayWrapper, mapFunction) => {
const result = []

for (let i = 0; i < arrayWrapper.length; i++) {
const element = arrayWrapper.at(i)

result.push(mapFunction(element))
}

return result
}
Loading

0 comments on commit b1bab1f

Please sign in to comment.