Create TableFilter component
Global TableFilter component and TableFilterMixin can be used
with any table. The TableFilterMixin will return filtered
data with items that match any of the filter tags.
When the table search component is built, it should use the
BoostrapVue Table :filter prop.
- Filter by status added to Sensors table
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I57ebab1686b2d267383cb0e1be252627bf42c98c
diff --git a/src/assets/styles/_badge.scss b/src/assets/styles/_badge.scss
index 99c758a..68e7482 100644
--- a/src/assets/styles/_badge.scss
+++ b/src/assets/styles/_badge.scss
@@ -3,6 +3,14 @@
// for pill variant because global $enable-rounded
// Boostrap setting removes rounded pill style
border-radius: 10rem;
+ fill: currentColor;
+ font-weight: 400;
+ .close {
+ font-size: 1em;
+ margin-left: $spacer/2;
+ font-weight: inherit;
+ color: inherit;
+ }
}
.badge-primary {
diff --git a/src/assets/styles/_dropdown.scss b/src/assets/styles/_dropdown.scss
new file mode 100644
index 0000000..0eb310f
--- /dev/null
+++ b/src/assets/styles/_dropdown.scss
@@ -0,0 +1,21 @@
+.dropdown-item {
+ padding-left: $spacer/2;
+}
+
+.b-dropdown-form {
+ padding: $spacer/2;
+ .form-group {
+ margin-bottom: $spacer/2;
+ }
+}
+
+.table-filter {
+ // Adding component style to global stylesheet because
+ // single-file component scoped styles aren't
+ // being applied to dynamically appended elements
+ // The overflow menu should be above the table
+ .dropdown-menu {
+ z-index: $zindex-dropdown + 1;
+ padding: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/assets/styles/_obmc-custom.scss b/src/assets/styles/_obmc-custom.scss
index 544e585..4e0e1c5 100644
--- a/src/assets/styles/_obmc-custom.scss
+++ b/src/assets/styles/_obmc-custom.scss
@@ -42,6 +42,7 @@
@import "./alerts";
@import "./badge";
@import "./buttons";
+@import "./dropdown";
@import "./form-components";
@import "./modal";
@import "./table";
diff --git a/src/components/Global/TableFilter.vue b/src/components/Global/TableFilter.vue
new file mode 100644
index 0000000..d466d4e
--- /dev/null
+++ b/src/components/Global/TableFilter.vue
@@ -0,0 +1,93 @@
+<template>
+ <div class="table-filter d-inline-block">
+ <p class="d-inline-block mb-0">
+ <b-badge v-for="(tag, index) in tags" :key="index" pill>
+ {{ tag }}
+ <b-button-close
+ :disabled="dropdownVisible"
+ :aria-hidden="true"
+ @click="removeTag(index)"
+ />
+ </b-badge>
+ </p>
+ <b-dropdown
+ variant="link"
+ no-caret
+ right
+ @hide="dropdownVisible = false"
+ @show="dropdownVisible = true"
+ >
+ <template v-slot:button-content>
+ <icon-filter />
+ {{ $t('global.action.filter') }}
+ </template>
+ <b-dropdown-form @change="onChange">
+ <b-form-group
+ v-for="(filter, index) of filters"
+ :key="index"
+ :label="filter.label"
+ >
+ <b-form-checkbox-group v-model="tags" :options="filter.values">
+ </b-form-checkbox-group>
+ </b-form-group>
+ </b-dropdown-form>
+ <b-dropdown-item-button variant="primary" @click="clearAllTags">
+ {{ $t('global.action.clearAll') }}
+ </b-dropdown-item-button>
+ </b-dropdown>
+ </div>
+</template>
+
+<script>
+import IconFilter from '@carbon/icons-vue/es/settings--adjust/20';
+
+export default {
+ name: 'TableFilter',
+ components: { IconFilter },
+ props: {
+ filters: {
+ type: Array,
+ default: () => [],
+ validator: prop => {
+ return prop.every(
+ filter =>
+ filter.hasOwnProperty('label') && filter.hasOwnProperty('values')
+ );
+ }
+ }
+ },
+ data() {
+ return {
+ tags: [],
+ dropdownVisible: false
+ };
+ },
+ methods: {
+ removeTag(index) {
+ this.tags = this.tags.filter((_, i) => i !== index);
+ this.emitChange();
+ },
+ clearAllTags() {
+ this.tags = [];
+ this.emitChange();
+ },
+ emitChange() {
+ this.$emit('filterChange', {
+ activeFilters: this.tags
+ });
+ },
+ onChange() {
+ this.emitChange();
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+p {
+ font-size: 1.2rem;
+}
+.badge {
+ margin-right: $spacer / 2;
+}
+</style>
diff --git a/src/components/Mixins/TableFilterMixin.js b/src/components/Mixins/TableFilterMixin.js
new file mode 100644
index 0000000..25c7497
--- /dev/null
+++ b/src/components/Mixins/TableFilterMixin.js
@@ -0,0 +1,23 @@
+import { includes } from 'lodash';
+
+const TableFilterMixin = {
+ methods: {
+ getFilteredTableData(tableData = [], filters = []) {
+ if (filters.length === 0) return tableData;
+ // will return all items that match
+ // any of the filter tags (not all)
+ return tableData.filter(row => {
+ let returnRow = false;
+ for (const filter of filters) {
+ if (includes(row, filter)) {
+ returnRow = true;
+ break;
+ }
+ }
+ return returnRow;
+ });
+ }
+ }
+};
+
+export default TableFilterMixin;
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 699b016..022eefb 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -2,13 +2,15 @@
"global": {
"action": {
"add": "Add",
+ "cancel": "Cancel",
+ "clearAll": "Clear all",
"confirm": "Confirm",
"copy": "Copy",
- "cancel": "Cancel",
"delete": "Delete",
"disable": "Disable",
"download": "Download",
"enable": "Enable",
+ "filter": "Filter",
"replace": "Replace",
"save": "Save",
"selected": "Selected"
diff --git a/src/main.js b/src/main.js
index 4b0722e..5adc5ef 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,6 +8,7 @@
ButtonPlugin,
BVConfigPlugin,
CollapsePlugin,
+ DropdownPlugin,
FormPlugin,
FormCheckboxPlugin,
FormFilePlugin,
@@ -66,9 +67,13 @@
BFormTags: {
tagVariant: 'primary',
addButtonVariant: 'link-primary'
+ },
+ BBadge: {
+ variant: 'primary'
}
});
Vue.use(CollapsePlugin);
+Vue.use(DropdownPlugin);
Vue.use(FormPlugin);
Vue.use(FormCheckboxPlugin);
Vue.use(FormFilePlugin);
diff --git a/src/views/Health/Sensors/Sensors.vue b/src/views/Health/Sensors/Sensors.vue
index 70d4f90..ae31cc3 100644
--- a/src/views/Health/Sensors/Sensors.vue
+++ b/src/views/Health/Sensors/Sensors.vue
@@ -2,13 +2,18 @@
<b-container fluid>
<page-title />
<b-row>
+ <b-col xl="12" class="text-right">
+ <table-filter :filters="tableFilters" @filterChange="onFilterChange" />
+ </b-col>
+ </b-row>
+ <b-row>
<b-col xl="12">
<b-table
sort-icon-left
no-sort-reset
sticky-header="75vh"
sort-by="status"
- :items="allSensors"
+ :items="filteredSensors"
:fields="fields"
:sort-desc="true"
:sort-compare="sortCompare"
@@ -41,6 +46,10 @@
<script>
import PageTitle from '../../../components/Global/PageTitle';
import StatusIcon from '../../../components/Global/StatusIcon';
+import TableFilter from '../../../components/Global/TableFilter';
+import TableFilterMixin from '../../../components/Mixins/TableFilterMixin';
+
+const SENSOR_STATUS = ['OK', 'Warning', 'Critical'];
const valueFormatter = value => {
if (value === null || value === undefined) {
@@ -51,7 +60,8 @@
export default {
name: 'Sensors',
- components: { PageTitle, StatusIcon },
+ components: { PageTitle, StatusIcon, TableFilter },
+ mixins: [TableFilterMixin],
data() {
return {
fields: [
@@ -91,12 +101,27 @@
formatter: valueFormatter,
label: this.$t('pageSensors.table.upperCritical')
}
- ]
+ ],
+ tableFilters: [
+ {
+ label: this.$t('pageSensors.table.status'),
+ values: SENSOR_STATUS
+ }
+ ],
+ activeFilters: []
};
},
computed: {
allSensors() {
return this.$store.getters['sensors/sensors'];
+ },
+ filteredSensors: {
+ get: function() {
+ return this.getFilteredTableData(this.allSensors, this.activeFilters);
+ },
+ set: function(newVal) {
+ return newVal;
+ }
}
},
created() {
@@ -105,11 +130,11 @@
methods: {
statusIcon(status) {
switch (status) {
- case 'OK':
+ case SENSOR_STATUS[0]:
return 'success';
- case 'Warning':
+ case SENSOR_STATUS[1]:
return 'warning';
- case 'Critical':
+ case SENSOR_STATUS[2]:
return 'danger';
default:
return '';
@@ -117,9 +142,17 @@
},
sortCompare(a, b, key) {
if (key === 'status') {
- const status = ['OK', 'Warning', 'Critical'];
- return status.indexOf(a.status) - status.indexOf(b.status);
+ return (
+ SENSOR_STATUS.indexOf(a.status) - SENSOR_STATUS.indexOf(b.status)
+ );
}
+ },
+ onFilterChange({ activeFilters }) {
+ this.activeFilters = activeFilters;
+ this.filteredSensors = this.getFilteredTableData(
+ this.allSensors,
+ activeFilters
+ );
}
}
};