Skip to content

Commit

Permalink
Move task filter controls into own component
Browse files Browse the repository at this point in the history
  • Loading branch information
MetRonnie committed Dec 16, 2022
1 parent bd55f9f commit 6263298
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 211 deletions.
106 changes: 106 additions & 0 deletions src/components/cylc/TaskFilter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!--
Copyright (C) NIWA & British Crown (Met Office) & Contributors.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<!-- Controls for filtering tasks in views. -->

<template>
<v-row no-gutters>
<v-col
cols="12"
md="6"
class="pr-md-2 mb-2 mb-md-0"
>
<v-text-field
data-cy="filter-task-name"
clearable
dense
flat
hide-details
outlined
placeholder="Filter by task name"
v-model="localValue.name"
ref="filterNameInput"
></v-text-field>
</v-col>
<v-col
cols="12"
md="6"
class="mb-2 mb-md-0"
>
<v-select
data-cy="filter-task-states"
:items="allStates"
clearable
dense
flat
hide-details
multiple
outlined
placeholder="Filter by task state"
v-model="localValue.states"
>
<template v-slot:item="slotProps">
<Task :task="{ state: slotProps.item }" />
<span class="ml-2">{{ slotProps.item }}</span>
</template>
<template v-slot:selection="slotProps">
<div class="mr-2" v-if="slotProps.index >= 0 && slotProps.index < maxVisibleStates">
<Task :task="{ state: slotProps.item }" />
</div>
<span
v-if="slotProps.index === maxVisibleStates"
class="grey--text caption"
>
(+{{ localValue.states.length - maxVisibleStates }})
</span>
</template>
</v-select>
</v-col>
</v-row>
</template>

<script>
import Task from '@/components/cylc/Task'
import { TaskStateUserOrder } from '@/model/TaskState.model'
export default {
name: 'TaskFilter',
components: {
Task
},
props: {
value: Object // { name, states }
},
data () {
return {
maxVisibleStates: 4,
allStates: TaskStateUserOrder.map(ts => ts.name)
}
},
computed: {
localValue: {
get () {
return this.value
},
set (value) {
// Update 'value' prop by notifying parent component's v-model for this component
this.$emit('input', value)
}
}
}
}
</script>
115 changes: 5 additions & 110 deletions src/components/cylc/table/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,62 +29,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-if="filterable"
class=""
>
<v-row class="no-gutters">
<v-col
cols="12"
md="6"
class="pr-md-2 mb-2 mb-md-0"
>
<v-text-field
id="c-table-filter-task-name"
clearable
dense
flat
hide-details
outlined
placeholder="Filter by task name"
v-model.trim="tasksFilter.name"
@keyup="filterTasks"
@click:clear="clearInput"
ref="filterNameInput"
></v-text-field>
</v-col>
<v-col
cols="12"
md="6"
class="mb-2 mb-md-0"
>
<v-select
id="c-table-filter-task-states"
:items="taskStates"
clearable
dense
flat
hide-details
multiple
outlined
placeholder="Filter by task state"
v-model="tasksFilter.states"
@change="filterTasks"
>
<template v-slot:item="slotProps">
<Task :task="{ state: slotProps.item.value }" />
<span class="ml-2">{{ slotProps.item.value }}</span>
</template>
<template v-slot:selection="slotProps">
<div class="mr-2" v-if="slotProps.index >= 0 && slotProps.index < maximumTasks">
<Task :task="{ state: slotProps.item.value }" />
</div>
<span
v-if="slotProps.index === maximumTasks"
class="grey--text caption"
>
(+{{ tasksFilter.states.length - maximumTasks }})
</span>
</template>
</v-select>
</v-col>
</v-row>
<TaskFilter v-model="tasksFilter"/>
</v-col>
</v-row>
<v-row
Expand Down Expand Up @@ -217,14 +162,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>

<script>
import TaskState from '@/model/TaskState.model'
import Task from '@/components/cylc/Task'
import Job from '@/components/cylc/Job'
import cloneDeep from 'lodash/cloneDeep'
import { mdiChevronDown, mdiArrowDown } from '@mdi/js'
import { DEFAULT_COMPARATOR } from '@/components/cylc/common/sort'
import { datetimeComparator } from '@/components/cylc/table/sort'
import { matchNode } from '@/components/cylc/common/filter'
import TaskFilter from '@/components/cylc/TaskFilter.vue'
export default {
name: 'TableComponent',
Expand All @@ -240,7 +184,8 @@ export default {
},
components: {
Task,
Job
Job,
TaskFilter
},
data () {
return {
Expand Down Expand Up @@ -303,63 +248,13 @@ export default {
sort: (a, b) => parseInt(a ?? 0) - parseInt(b ?? 0)
}
],
tasksFilter: {
name: '',
states: []
},
activeFilters: null,
maximumTasks: 4
tasksFilter: {}
}
},
computed: {
taskStates () {
return TaskState.enumValues.map(taskState => {
return {
text: taskState.name.replace(/_/g, ' '),
value: taskState.name
}
}).sort((left, right) => {
return left.text.localeCompare(right.text)
})
},
tasksFilterStates () {
return this.activeFilters.states
},
filteredTasks () {
return this.tasks.filter(({ task }) => matchNode(task.node, this.tasksFilter.name, this.tasksFilter.states))
}
},
methods: {
filterByTaskName () {
return this.activeFilters &&
this.activeFilters.name !== undefined &&
this.activeFilters.name !== null &&
this.activeFilters.name !== ''
},
filterByTaskState () {
return this.activeFilters &&
this.activeFilters.states !== undefined &&
this.activeFilters.states !== null &&
this.activeFilters.states.length > 0
},
filterTasks () {
const taskNameFilterSet = this.tasksFilter.name !== undefined &&
this.tasksFilter.name !== null &&
this.tasksFilter.name !== ''
const taskStatesFilterSet = this.tasksFilter.states !== undefined &&
this.tasksFilter.states !== null &&
this.tasksFilter.states.length > 0
if (taskNameFilterSet || taskStatesFilterSet) {
this.activeFilters = cloneDeep(this.tasksFilter)
} else {
this.activeFilters = null
}
},
clearInput (event) {
// I don't really like this, but we need to somehow force the 'change detection' to run again once the clear has taken place
this.tasksFilter.name = null
this.$refs.filterNameInput.$el.querySelector('input').dispatchEvent(new Event('keyup'))
}
}
}
</script>
82 changes: 9 additions & 73 deletions src/components/cylc/tree/Tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,64 +29,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-if="filterable"
class="grow"
>
<v-row
no-gutters
>
<v-col
cols="12"
md="6"
class="pr-md-2 mb-2 mb-md-0"
>
<v-text-field
id="c-tree-filter-task-name"
clearable
dense
flat
hide-details
outlined
placeholder="Filter by task name"
v-model="tasksFilter.name"
@keyup="filterTasks"
@click:clear="clearInput"
ref="filterNameInput"
></v-text-field>
</v-col>
<v-col
cols="12"
md="6"
class="mb-2 mb-md-0"
>
<v-select
id="c-tree-filter-task-states"
:items="taskStates"
clearable
dense
flat
hide-details
multiple
outlined
placeholder="Filter by task state"
v-model="tasksFilter.states"
@change="filterTasks"
>
<template v-slot:item="slotProps">
<Task :task="{ state: slotProps.item }" />
<span class="ml-2">{{ slotProps.item }}</span>
</template>
<template v-slot:selection="slotProps">
<div class="mr-2" v-if="slotProps.index >= 0 && slotProps.index < maximumTasks">
<Task :task="{ state: slotProps.item }" />
</div>
<span
v-if="slotProps.index === maximumTasks"
class="grey--text caption"
>
(+{{ tasksFilter.states.length - maximumTasks }})
</span>
</template>
</v-select>
</v-col>
</v-row>
<TaskFilter v-model="tasksFilter"/>
</v-col>
<!-- Expand, collapse all -->
<v-col
Expand Down Expand Up @@ -160,9 +103,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script>
import Vue from 'vue'
import { mdiPlus, mdiMinus } from '@mdi/js'
import { TaskStateUserOrder } from '@/model/TaskState.model'
import TreeItem from '@/components/cylc/tree/TreeItem'
import Task from '@/components/cylc/Task'
import TaskFilter from '@/components/cylc/TaskFilter'
import { matchNode } from '@/components/cylc/common/filter'
import { getNodeChildren } from '@/components/cylc/tree/util'
Expand Down Expand Up @@ -211,7 +153,7 @@ export default {
}
},
components: {
Task,
TaskFilter,
TreeItem
},
data () {
Expand All @@ -222,12 +164,7 @@ export default {
expanded: true,
expandedFilter: null,
collapseFilter: null,
tasksFilter: {
name: '',
states: []
},
taskStates: TaskStateUserOrder.map(ts => ts.name),
maximumTasks: 4,
tasksFilter: {},
svgPaths: {
expandIcon: mdiPlus,
collapseIcon: mdiMinus
Expand Down Expand Up @@ -271,9 +208,13 @@ export default {
}
},
watch: {
tasksFilter: {
deep: true,
handler: 'filterTasks'
},
workflows: {
deep: true,
handler: function () {
handler () {
if (this.filterByTaskName || this.filterByTaskState) {
this.$nextTick(() => {
this.filterNodes(this.workflows)
Expand All @@ -290,11 +231,6 @@ export default {
this.removeAllFilters()
}
},
clearInput (event) {
// I don't really like this, but we need to somehow force the 'change detection' to run again once the clear has taken place
this.tasksFilter.name = null
this.$refs.filterNameInput.$el.querySelector('input').dispatchEvent(new Event('keyup'))
},
filterNodes (nodes) {
for (const node of nodes) {
this.filterNode(node)
Expand Down
Loading

0 comments on commit 6263298

Please sign in to comment.