Merge pull request #15813 from assuntad23/12875/form_drilldown_vue_conversion

12875/form drilldown vue conversion
This commit is contained in:
Dannon
2023-05-04 17:04:07 -04:00
committed by GitHub
10 changed files with 322 additions and 1 deletions
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { computed, type ComputedRef } from "vue";
import FormDrilldownList from "./FormDrilldownList.vue";
import { getAllValues, type Option, type Value } from "./utilities";
const props = withDefaults(
defineProps<{
id: string;
value?: Value;
options: Array<Option>;
multiple: boolean;
}>(),
{
value: null,
multiple: true,
}
);
const emit = defineEmits<{
(e: "input", value: Value): void;
}>();
const hasOptions = computed(() => {
return props.options.length > 0;
});
// Determine all available values
const allValues: ComputedRef<string[]> = computed(() => {
return getAllValues(props.options);
});
// Determine current value
const currentValue: ComputedRef<string[]> = computed(() => {
if (props.value === null || props.value === "") {
return [];
} else if (Array.isArray(props.value)) {
return props.value;
} else {
return [props.value];
}
});
// Determine if select all is checked
const selectAllChecked: ComputedRef<boolean> = computed(() => {
return allValues.value.length === currentValue.value.length;
});
// Determine if select all state undetermined
const selectAllIndeterminate: ComputedRef<boolean> = computed(() => {
return ![0, allValues.value.length].includes(currentValue.value.length);
});
// Handle click on individual check/radio element
function handleClick(value: string): void {
if (props.multiple) {
const newValue: string[] = currentValue.value.slice();
const index: number = newValue.indexOf(value);
if (index !== -1) {
newValue.splice(index, 1);
} else {
newValue.push(value);
}
if (newValue.length === 0) {
emit("input", null);
} else {
emit("input", newValue);
}
} else {
emit("input", value);
}
}
// Handle click on select all checkbox to either select or unselect all values
function onSelectAll(selected: boolean): void {
emit("input", selected ? allValues.value : null);
}
</script>
<template>
<div v-if="hasOptions">
<b-form-checkbox
v-if="multiple"
v-localize
:checked="selectAllChecked"
:indeterminate="selectAllIndeterminate"
class="d-inline select-all-checkbox"
@change="onSelectAll">
Select/Unselect All
</b-form-checkbox>
<form-drilldown-list
:multiple="multiple"
:current-value="currentValue"
:options="options"
:handle-click="handleClick" />
</div>
</template>
@@ -0,0 +1,23 @@
<script setup lang="ts">
import FormDrilldownOption from "./FormDrilldownOption.vue";
import type { Option } from "./utilities";
defineProps<{
currentValue: string[];
handleClick: Function;
multiple: boolean;
options: Array<Option>;
}>();
</script>
<template>
<div>
<div v-for="option in options" :key="option.name" class="ui-drilldown">
<form-drilldown-option
:current-value="currentValue"
:handle-click="handleClick"
:multiple="multiple"
:option="option" />
</div>
</div>
</template>
@@ -0,0 +1,72 @@
<script setup lang="ts">
import { computed, onMounted, ref, type ComputedRef } from "vue";
import { BFormCheckbox, BFormRadio } from "bootstrap-vue";
import { getAllValues, type Option } from "./utilities";
import FormDrilldownList from "./FormDrilldownList.vue";
const props = defineProps<{
currentValue: string[];
option: Option;
handleClick: Function;
multiple: boolean;
}>();
const showChildren = ref(false);
const hasOptions: ComputedRef<boolean> = computed(() => {
return props.option.options.length > 0;
});
// indicates if the value has been selected or not
const isChecked: ComputedRef<boolean> = computed(() => {
const optionValue = props.option.value;
return props.currentValue.includes(optionValue);
});
// determine required input element type
const isComponent = computed(() => {
return props.multiple ? BFormCheckbox : BFormRadio;
});
function toggleChildren(): void {
showChildren.value = !showChildren.value;
}
function toggleInitialization(): void {
const childValues = getAllValues(props.option.options);
for (const childValue of childValues) {
if (props.currentValue.includes(childValue)) {
showChildren.value = true;
break;
}
}
}
onMounted(() => {
toggleInitialization();
});
</script>
<template>
<div>
<b-button v-if="hasOptions" variant="link" class="btn p-0" @click="toggleChildren">
<i v-if="showChildren" class="fa fa-minus-square" />
<i v-else class="fa fa-plus-square" />
</b-button>
<component
:is="isComponent"
class="drilldown-option d-inline"
value="true"
:checked="isChecked"
@change="handleClick(option.value)">
{{ option.name }}
</component>
<form-drilldown-list
v-if="hasOptions"
v-show="showChildren"
class="pl-5"
:current-value="currentValue"
:multiple="multiple"
:options="option.options"
:handle-click="handleClick" />
</div>
</template>
@@ -0,0 +1,66 @@
import { getAllValues } from "./utilities";
describe("drilldown component utilities", () => {
it("get all values", () => {
const options = [
{
value: "a",
options: [
{
value: "aa",
options: [],
},
{
value: "ab",
options: [
{
value: "aba",
options: [
{
value: "abaa",
options: [],
},
],
},
{
value: "abb",
options: [
{
value: "abba",
options: [],
},
],
},
],
},
],
},
{
value: "b",
options: [
{
value: "ba",
options: [],
},
{
value: "bb",
options: [
{
value: "bba",
options: [
{
value: "bbaa",
options: [],
},
],
},
],
},
],
},
];
expect(String(getAllValues(options))).toBe(
String(["a", "b", "ba", "bb", "bba", "bbaa", "aa", "ab", "aba", "abb", "abba", "abaa"])
);
});
});
@@ -0,0 +1,29 @@
export interface Option {
name: string;
value: string;
options: Array<Option>;
}
export type Value = string[] | string | null;
/**
* Returns an array of values from nested drill down options provided
* @param headOptions Array of options to get values from
* @returns values: string[]
*/
export function getAllValues(headOptions: Array<Option>): string[] {
let options = null;
const values: string[] = [];
const stack: Array<Array<Option>> = [headOptions];
while ((options = stack.pop())) {
options.forEach((option) => {
if (option.value) {
values.push(option.value);
}
if (option.options.length > 0) {
stack.push(option.options);
}
});
}
return values;
}
@@ -11,6 +11,7 @@ import FormText from "./Elements/FormText.vue";
import FormOptionalText from "./Elements/FormOptionalText.vue";
import FormRulesEdit from "./Elements/FormRulesEdit.vue";
import FormUpload from "./Elements/FormUpload.vue";
import FormDrilldown from "./Elements/FormDrilldown/FormDrilldown.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { ref, computed, useAttrs } from "vue";
import { library } from "@fortawesome/fontawesome-svg-core";
@@ -263,6 +264,12 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
:options="attrs.options"
:optional="attrs.optional"
:multiple="attrs.multiple" />
<FormDrilldown
v-else-if="props.type === 'drill_down'"
:id="id"
v-model="currentValue"
:options="attrs.options"
:multiple="attrs.multiple" />
<FormColor v-else-if="props.type === 'color'" :id="props.id" v-model="currentValue" />
<FormDirectory v-else-if="props.type === 'directory_uri'" v-model="currentValue" />
<FormUpload v-else-if="props.type === 'upload'" v-model="currentValue" />
+4
View File
@@ -29,10 +29,12 @@ import {
faList,
faLink,
faLock,
faMinusSquare,
faPause,
faPen,
faPlay,
faPlus,
faPlusSquare,
faQuestion,
faShareAlt,
faSitemap,
@@ -75,10 +77,12 @@ library.add(
faLink,
faList,
faLock,
faMinusSquare,
faPause,
faPen,
faPlay,
faPlus,
faPlusSquare,
faQuestion,
faShareAlt,
faSitemap,
@@ -519,6 +519,10 @@ tool_form:
reference: '.formatted-reference'
about: '.tool-footer'
storage_options: '.tool-storage'
drilldown_select_all: 'div.ui-form-element[id="form-element-${parameter}"] div.select-all-checkbox'
drilldown_option: '.drilldown-option'
drilldown_expand: '.fa-plus-square'
labels:
generate_tour: 'Generate Tour'
+1 -1
View File
@@ -504,7 +504,7 @@ class UsesHistoryItemAssertions(NavigatesGalaxyMixin):
def assert_item_peek_includes(self, hid, expected):
item_body = self.history_panel_item_component(hid=hid)
peek_text = item_body.peek.wait_for_text()
assert expected in peek_text
assert expected in peek_text, f"Expected peek [{expected}] not found in peek [{peek_text}]"
def assert_item_info_includes(self, hid, expected):
item_body = self.history_panel_item_component(hid=hid)
@@ -74,6 +74,26 @@ class TestToolForm(SeleniumTestCase, UsesHistoryItemAssertions):
generic_item.find_element(By.CSS_SELECTOR, "[title='Run Job Again']").click()
self.components.tool_form.execute.wait_for_visible()
@selenium_test
def test_drilldown_tool(self):
self._open_drilldown_test_tool()
# click first option in first drilldown component
self.wait_for_and_click(self.components.tool_form.drilldown_expand)
self.wait_for_and_click(self.components.tool_form.drilldown_option)
# click select all in second drilldown component
self.wait_for_and_click(self.components.tool_form.drilldown_select_all(parameter="dd_recurse"))
self.tool_form_execute()
self.history_panel_wait_for_hid_ok(1)
# click hid 1 in history panel
self.history_panel_click_item_title(hid=1)
# assert that the dataset peek is d = a dd_recurse = a,b,c
self.assert_item_peek_includes(1, "dd a")
self.assert_item_peek_includes(1, "dd_recurse aa,aba,abb,ba,bba,bbb")
def _open_drilldown_test_tool(self):
self.home()
self.tool_open("drill_down")
@staticmethod
def click_menu_item(menu, text):
for element in menu.find_elements(By.CSS_SELECTOR, "a"):