Skip to content

Commit

Permalink
replace IndexFilter in WorkflowList with FilterMenu
Browse files Browse the repository at this point in the history
also:
- create `WorkflowFilters.js` that exports reusable `Filtering`
- allow `MultiTags` filter type for `FilterMenu`, which allows you to
  add a `StatelessTags` field in the menu, allowing for array values
  as filter
- allow `is:filter` filters that are Boolean filters, except, they
  only be set to true
- apart from `q=filter1&qv=val&q=filter2&qv=val&...` query strings
  for the backend, the `Filtering` class can now also create
  `backendFormatted` strings in the form `filter1:val filter2:val`
  • Loading branch information
ahmedhamidawan committed Aug 28, 2023
1 parent 2f8d07a commit daae119
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 175 deletions.
5 changes: 5 additions & 0 deletions client/src/components/Common/DelayedInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export default {
this.setQuery(queryNew);
},
},
created() {
if (this.query) {
this.setQuery(this.query);
}
},
methods: {
clearTimer() {
if (this.queryTimer) {
Expand Down
43 changes: 40 additions & 3 deletions client/src/components/Common/FilterMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { type Alias, getOperatorForAlias } from "@/utils/filtering";
import DelayedInput from "@/components/Common/DelayedInput.vue";
import FilterMenuBoolean from "@/components/Common/FilterMenuBoolean.vue";
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
interface BackendFilterError {
err_msg: string;
Expand All @@ -26,6 +27,8 @@ const props = withDefaults(
name?: string;
/** A placeholder for the main search input */
placeholder?: string;
/** The delay (in ms) before the main filter is applied */
debounceDelay?: number;
/** The `Filtering` class to use */
filterClass: Filtering<any>;
/** The current filter text in the main field */
Expand All @@ -46,6 +49,7 @@ const props = withDefaults(
{
name: "Menu",
placeholder: "search for items",
debounceDelay: 500,
filterText: "",
menuType: "linked",
showAdvanced: false,
Expand Down Expand Up @@ -82,7 +86,8 @@ const showHelp = ref(false);
/**
* Reactively storing and getting all filters from validFilters which are `.type == Date`
* (This was done to make the datepickers store values in the `filters` object)
* and .type == 'MultiTags'
* (This was done to make the datepickers and tags store values in the `filters` object)
*/
const dateFilters: Ref<{ [key: string]: string }> = ref({});
const dateKeys = Object.keys(validFilters.value).filter((key) => validFilters.value[key]?.type == Date);
Expand All @@ -92,6 +97,15 @@ dateKeys.forEach((key: string) => {
dateKeys.push(key + "_gt");
}
});
const multiTagFilters: Ref<{ [key: string]: Ref<string[]> }> = ref({});
const multiTagKeys = Object.keys(validFilters.value).filter((key) => validFilters.value[key]?.type == "MultiTags");
multiTagKeys.forEach((key: string) => {
if (filters.value[key] !== undefined) {
multiTagFilters.value[key] = ref(filters.value[key] as string[]);
} else {
multiTagFilters.value[key] = ref([]);
}
});
watch(
() => filters.value,
(newFilters: { [k: string]: any }) => {
Expand All @@ -102,6 +116,13 @@ watch(
delete dateFilters.value[key];
}
});
multiTagKeys.forEach((key: string) => {
if (newFilters[key]) {
(multiTagFilters.value[key] as Ref<string[]>).value = newFilters[key];
} else {
(multiTagFilters.value[key] as Ref<string[]>).value = [];
}
});
}
);
Expand Down Expand Up @@ -149,6 +170,9 @@ function onSearch() {
Object.keys(dateFilters.value).forEach((key) => {
onOption(key, dateFilters.value[key]);
});
Object.keys(multiTagFilters.value).forEach((key) => {
onOption(key, (multiTagFilters.value[key] as Ref<string[]>).value);
});
const newFilterText = props.filterClass.getFilterText(filters.value);
if (props.menuType !== "linked") {
emit("on-search", filters.value, newFilterText);
Expand All @@ -157,6 +181,10 @@ function onSearch() {
}
}
function onTags(filter: string, tags: string[]) {
(multiTagFilters.value[filter] as Ref<string[]>).value = tags;
}
function onToggle() {
emit("update:show-advanced", !props.showAdvanced);
}
Expand All @@ -173,7 +201,7 @@ function updateFilterText(newFilterText: string) {
v-show="props.menuType == 'linked' || (props.menuType == 'separate' && !props.showAdvanced)"
:class="props.filterText && 'font-weight-bold'"
:query="props.filterText"
:delay="400"
:delay="props.debounceDelay"
:loading="props.loading"
:show-advanced="props.showAdvanced"
enable-advanced
Expand All @@ -187,7 +215,6 @@ function updateFilterText(newFilterText: string) {
aria-haspopup="true"
size="sm"
:pressed="props.showAdvanced"
:variant="props.showAdvanced ? 'info' : 'secondary'"
:title="`Toggle Advanced Search`"
data-description="reset query"
@click="onToggle">
Expand Down Expand Up @@ -273,6 +300,16 @@ function updateFilterText(newFilterText: string) {
</b-input-group>
</span>

<!-- is a MultiTags filter -->
<span v-else-if="validFilters[filter]?.type == 'MultiTags'">
<small>Filter by {{ validFilters[filter]?.placeholder }}:</small>
<b-input-group>
<StatelessTags
:value="multiTagFilters[filter]?.value"
@input="(tags) => onTags(filter, tags)" />
</b-input-group>
</span>

<!-- is any other non-Ranged/non-Boolean filter -->
<span v-else>
<small>Filter by {{ validFilters[filter]?.placeholder }}:</small>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/History/Modals/SelectorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ async function loadMore(noScroll = false) {
<BBadge v-if="filter && !validFilter" class="alert-danger w-100">Search string too short!</BBadge>
<b-alert v-else-if="!busy && hasNoResults" variant="danger" show>No histories found.</b-alert>

<div ref="scrollableDiv" class="history-selector-modal-list">
<div v-show="!showAdvanced" ref="scrollableDiv" class="history-selector-modal-list">
<BListGroup>
<BListGroupItem
v-for="history in filtered"
Expand Down
12 changes: 6 additions & 6 deletions client/src/components/Indices/SharingIndicators.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@ const props = defineProps<SharingIndicatorsProps>();
<span>
<BButton
v-if="props.object.published"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
class="sharing-indicator-published"
size="sm"
variant="link"
:title="'Find all published items' | localize"
@click.prevent="$emit('filter', 'is:published')">
@click.prevent="$emit('filter', 'published')">
<FontAwesomeIcon icon="globe" />
</BButton>
<BButton
v-if="props.object.importable"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
class="sharing-indicator-importable"
size="sm"
variant="link"
:title="'Find all importable items' | localize"
@click.prevent="$emit('filter', 'is:importable')">
@click.prevent="$emit('filter', 'importable')">
<FontAwesomeIcon icon="link" />
</BButton>
<BButton
v-if="props.object.shared"
v-b-tooltip.hover
v-b-tooltip.hover.noninteractive
class="sharing-indicator-shared"
size="sm"
variant="link"
:title="'Find all items shared with me' | localize"
@click.prevent="$emit('filter', 'is:shared_with_me')">
@click.prevent="$emit('filter', 'shared_with_me')">
<FontAwesomeIcon icon="share-alt" />
</BButton>
</span>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Page/PageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
</template>
<template v-slot:cell(sharing)="row">
<span v-if="row.item.published || row.item.shared || row.item.importable">
<SharingIndicators :object="row.item" @filter="(filter) => appendFilter(filter, true)" />
<SharingIndicators :object="row.item" @filter="(filter) => appendFilter(`is:${filter}`, true)" />
<CopyToClipboard
:title="'Copy URL' | localize"
:text="copyUrl(row.item)"
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Panels/Common/ToolSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ function onAdvancedSearch(filters: any, filterText?: string) {
:class="!propShowAdvanced && 'mb-3'"
name="Tool Search"
:placeholder="props.placeholder"
:debounce-delay="200"
:filter-class="ToolFilters"
:filter-text.sync="localFilterText"
has-help
Expand Down Expand Up @@ -218,7 +219,7 @@ function onAdvancedSearch(filters: any, filterText?: string) {
v-else
class="mb-3"
:query="props.query"
:delay="100"
:delay="200"
:loading="queryPending"
:placeholder="placeholder"
@change="checkQuery" />
Expand Down
71 changes: 3 additions & 68 deletions client/src/components/Panels/WorkflowBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { computed } from "vue";
import { useRouter } from "vue-router/composables";
import { helpHtml, WorkflowFilters } from "@/components/Workflow/WorkflowFilters";
import { useUserStore } from "@/stores/userStore";
import Filtering, { contains, type Converter, equals, toBool, type ValidFilter } from "@/utils/filtering";
import { withPrefix } from "@/utils/redirect";
import FilterMenu from "@/components/Common/FilterMenu.vue";
Expand All @@ -16,45 +16,9 @@ const router = useRouter();
// @ts-ignore bad library types
library.add(faUpload, faGlobe);
const validFilters: Record<string, ValidFilter<string | boolean>> = {
name: { placeholder: "name", type: String, handler: contains("name"), menuItem: true },
tag: { placeholder: "tag", type: String, handler: contains("tag"), menuItem: true },
published: {
placeholder: "Filter on published workflows",
type: Boolean,
boolType: "is",
handler: equals("published", "published", toBool as Converter<string | boolean>),
menuItem: true,
},
importable: {
placeholder: "Filter on importable workflows",
type: Boolean,
boolType: "is",
handler: equals("importable", "importable", toBool as Converter<string | boolean>),
menuItem: true,
},
shared_with_me: {
placeholder: "Filter on workflows shared with me",
type: Boolean,
boolType: "is",
handler: equals("shared_with_me", "shared_with_me", toBool as Converter<string | boolean>),
menuItem: true,
},
deleted: {
placeholder: "Filter on deleted workflows",
type: Boolean,
boolType: "is",
handler: equals("deleted", "deleted", toBool as Converter<string | boolean>),
menuItem: true,
},
};
const WorkflowFilters: Filtering<string | boolean> = new Filtering(validFilters, undefined, false);
const isAnonymous = computed(() => useUserStore().isAnonymous);
function onSearch(filters: Record<string, string | boolean>, filterText?: string) {
const query = filterText;
function onSearch(filters: Record<string, string | boolean>, query?: string) {
const path = "/workflows/list";
const routerParams = query ? { path, query: { query } } : { path };
router.push(routerParams);
Expand Down Expand Up @@ -123,36 +87,7 @@ function userTitle(title: string) {
menu-type="standalone"
@on-search="onSearch">
<template v-slot:menu-help-text>
<div>
<p>This menu can be used to filter workflows in <code>workflows/list</code>.</p>

<p>
Filters entered here will be searched against workflow names and workflow tags, along with
the provided checkbox filters. Notice by default the search is not case-sensitive. If the
quoted version of tag is used, the search is case sensitive and only full matches will be
returned. So <code>name:'RNAseq'</code> would show only workflows named exactly
<code>RNAseq</code>.
</p>

<p>The available filtering tags are:</p>
<dl>
<dt><code>name</code></dt>
<dd>Shows workflows with the given sequence of characters in their names.</dd>
<dt><code>tag</code></dt>
<dd>
Shows workflows with the given workflow tag. You may also click on a tag to filter on
that tag directly.
</dd>
<dt><code>is:published</code></dt>
<dd>Shows published workflows.</dd>
<dt><code>is:importable</code></dt>
<dd>Shows importable workflows (this also means they are URL generated).</dd>
<dt><code>is:shared_with_me</code></dt>
<dd>Shows workflows shared by another user directly with you.</dd>
<dt><code>is:deleted</code></dt>
<dd>Shows deleted workflows.</dd>
</dl>
</div>
<div v-html="helpHtml"></div>
</template>
</FilterMenu>
</div>
Expand Down
Loading

0 comments on commit daae119

Please sign in to comment.