Skip to content
Snippets Groups Projects
Verified Commit 8404c846 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Add filtering and sorting for listing workflows

#36
/spend_time 2h 50m 2023-03-03
parent cab64046
No related branches found
No related tags found
2 merge requests!84Remove development branch,!31Resolve "Create List Workflow Page"
...@@ -6,3 +6,11 @@ body { ...@@ -6,3 +6,11 @@ body {
.top-toast { .top-toast {
top: 4rem; top: 4rem;
} }
.w-fit {
width: fit-content;
}
.cursor-pointer {
cursor: pointer;
}
...@@ -142,7 +142,7 @@ watch( ...@@ -142,7 +142,7 @@ watch(
> >
<li><hr class="dropdown-divider" /></li> <li><hr class="dropdown-divider" /></li>
<li> <li>
<a class="dropdown-item pseudo-link" @click="logout">Sign out</a> <a class="dropdown-item cursor-pointer" @click="logout">Sign out</a>
</li> </li>
</ul> </ul>
</div> </div>
...@@ -150,8 +150,4 @@ watch( ...@@ -150,8 +150,4 @@ watch(
</header> </header>
</template> </template>
<style scoped> <style scoped></style>
.pseudo-link {
cursor: pointer;
}
</style>
...@@ -336,7 +336,7 @@ onMounted(() => { ...@@ -336,7 +336,7 @@ onMounted(() => {
<bootstrap-icon <bootstrap-icon
v-if="props.deletable" v-if="props.deletable"
icon="trash-fill" icon="trash-fill"
class="me-2" class="me-2 cursor-pointer"
:class="{ 'delete-icon': !formState.loading }" :class="{ 'delete-icon': !formState.loading }"
data-bs-toggle="modal" data-bs-toggle="modal"
:data-bs-target="'#delete-permission-modal' + randomIDSuffix" :data-bs-target="'#delete-permission-modal' + randomIDSuffix"
...@@ -344,7 +344,7 @@ onMounted(() => { ...@@ -344,7 +344,7 @@ onMounted(() => {
<bootstrap-icon <bootstrap-icon
v-if="formState.readonly && props.editable" v-if="formState.readonly && props.editable"
icon="pencil-fill" icon="pencil-fill"
class="pseudo-link" class="pseudo-link cursor-pointer"
@click="formState.readonly = false" @click="formState.readonly = false"
/> />
</template> </template>
...@@ -547,7 +547,6 @@ onMounted(() => { ...@@ -547,7 +547,6 @@ onMounted(() => {
<style scoped> <style scoped>
.pseudo-link { .pseudo-link {
cursor: pointer;
color: var(--bs-secondary); color: var(--bs-secondary);
} }
.pseudo-link:hover { .pseudo-link:hover {
...@@ -556,7 +555,6 @@ onMounted(() => { ...@@ -556,7 +555,6 @@ onMounted(() => {
.delete-icon { .delete-icon {
color: var(--bs-secondary); color: var(--bs-secondary);
cursor: pointer;
} }
.delete-icon:hover { .delete-icon:hover {
color: var(--bs-danger); color: var(--bs-danger);
......
...@@ -30,9 +30,7 @@ onMounted(() => { ...@@ -30,9 +30,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div <div class="card-hover border border-secondary card text-bg-dark m-2">
class="card-hover border border-secondary card text-bg-dark m-3 align-self-center"
>
<div class="card-body"> <div class="card-body">
<h3 class="card-title"> <h3 class="card-title">
<div v-if="props.loading" class="placeholder-glow"> <div v-if="props.loading" class="placeholder-glow">
...@@ -48,7 +46,7 @@ onMounted(() => { ...@@ -48,7 +46,7 @@ onMounted(() => {
v-else v-else
@click="truncateDescription = false" @click="truncateDescription = false"
:class="{ :class="{
'pointer-cursor': truncateDescription, 'cursor-pointer': truncateDescription,
}" }"
>{{ props.workflow.short_description }}</span >{{ props.workflow.short_description }}</span
> >
...@@ -91,14 +89,10 @@ onMounted(() => { ...@@ -91,14 +89,10 @@ onMounted(() => {
<style scoped> <style scoped>
.card-hover { .card-hover {
transition: transform 0.3s ease-out; transition: transform 0.3s ease-out;
width: 47%; width: 48%;
} }
.card-hover:hover { .card-hover:hover {
transform: translate(0, -5px); transform: translate(0, -5px);
} }
.pointer-cursor {
cursor: pointer;
}
</style> </style>
...@@ -18,8 +18,7 @@ const router = createRouter({ ...@@ -18,8 +18,7 @@ const router = createRouter({
{ {
path: ":bucketName/:subFolders*", path: ":bucketName/:subFolders*",
name: "bucket", name: "bucket",
component: () => component: () => import("../views/object-storage/BucketView.vue"),
import("../components/object-storage/BucketView.vue"),
props: true, props: true,
}, },
], ],
......
<script setup lang="ts"> <script setup lang="ts">
import S3KeyView from "@/components/object-storage/S3KeyView.vue"; import S3KeyView from "@/views/object-storage/S3KeyView.vue";
import BootstrapIcon from "@/components/BootstrapIcon.vue"; import BootstrapIcon from "@/components/BootstrapIcon.vue";
import { reactive, onMounted, computed } from "vue"; import { reactive, onMounted, computed } from "vue";
import type { ComputedRef } from "vue"; import type { ComputedRef } from "vue";
......
...@@ -4,23 +4,53 @@ import type { ComputedRef } from "vue"; ...@@ -4,23 +4,53 @@ import type { ComputedRef } from "vue";
import { useWorkflowStore } from "@/stores/workflows"; import { useWorkflowStore } from "@/stores/workflows";
import type { WorkflowOut } from "@/client/workflow"; import type { WorkflowOut } from "@/client/workflow";
import WorkflowCard from "@/components/workflows/WorkflowCard.vue"; import WorkflowCard from "@/components/workflows/WorkflowCard.vue";
import dayjs from "dayjs";
import BootstrapIcon from "@/components/BootstrapIcon.vue";
const workflowRepository = useWorkflowStore(); const workflowRepository = useWorkflowStore();
const workflowsState = reactive({ const workflowsState = reactive({
loading: true, loading: true,
filterString: "", filterString: "",
sortByAttribute: "name",
sortDesc: true,
} as { } as {
loading: boolean; loading: boolean;
filterString: string; filterString: string;
sortByAttribute: string;
sortDesc: boolean;
}); });
const filteredWorkflows: ComputedRef<WorkflowOut[]> = computed(() => { const bla: Record<string, (a: WorkflowOut, b: WorkflowOut) => boolean> = {
name: (a: WorkflowOut, b: WorkflowOut) =>
workflowsState.sortDesc ? a.name > b.name : a.name < b.name,
release: (a: WorkflowOut, b: WorkflowOut) => {
const a_date = dayjs(a.versions[a.versions.length - 1].created_at);
const b_date = dayjs(b.versions[b.versions.length - 1].created_at);
return workflowsState.sortDesc
? a_date.isBefore(b_date)
: a_date.isAfter(b_date);
},
};
function filterWorkflowByString(workflow: WorkflowOut): boolean {
return workflowsState.filterString.length > 0 return workflowsState.filterString.length > 0
? workflowRepository.workflows.filter((workflow) => ? workflow.name.includes(workflowsState.filterString)
workflow.name.includes(workflowsState.filterString) : true;
) }
: workflowRepository.workflows;
function filterWorkflowWithoutVersion(workflow: WorkflowOut): boolean {
return workflow.versions.length > 0;
}
const processedWorkflows: ComputedRef<WorkflowOut[]> = computed(() => {
return workflowRepository.workflows
.filter(
(workflow) =>
filterWorkflowByString(workflow) &&
filterWorkflowWithoutVersion(workflow)
)
.sort((a, b) => (bla[workflowsState.sortByAttribute](a, b) ? 1 : -1));
}); });
onMounted(() => { onMounted(() => {
...@@ -33,17 +63,116 @@ onMounted(() => { ...@@ -33,17 +63,116 @@ onMounted(() => {
</script> </script>
<template> <template>
<div v-if="!workflowsState.loading" class="d-flex flex-wrap"> <div class="row m-2 border-bottom border-light mb-4">
<workflow-card <div class="col-12"></div>
v-for="workflow in filteredWorkflows" <h1 class="mb-2 text-light">Workflows</h1>
:key="workflow.workflow_id" </div>
:workflow="workflow" <div class="d-flex m-2 mb-3 align-items-center justify-content-between">
:loading="false" <div class="col-5 me-auto">
<div class="input-group">
<span class="input-group-text" id="workflows-search-wrapping"
><bootstrap-icon icon="search"
/></span>
<input
type="text"
class="form-control"
placeholder="Filter Workflows"
aria-label="Filter Workflows"
aria-describedby="workflows-search-wrapping"
:disabled="workflowsState.loading"
v-model.trim="workflowsState.filterString"
maxlength="20"
/>
</div>
</div>
<span class="fs-5 me-3">Sort By</span>
<div
class="btn-group btn-group-sm w-fit"
role="group"
aria-label="Basic radio toggle button group"
>
<input
type="radio"
class="btn-check"
name="btnradio"
id="sortName"
autocomplete="off"
checked
v-model="workflowsState.sortByAttribute"
value="name"
/>
<label class="btn btn-outline-secondary" for="sortName"
>Alphabetical</label
>
<input
type="radio"
class="btn-check"
name="btnradio"
id="sortLatestRelease"
autocomplete="off"
v-model="workflowsState.sortByAttribute"
value="release"
/>
<label class="btn btn-outline-secondary" for="sortLatestRelease"
>Latest Release</label
>
</div>
<bootstrap-icon
:icon="workflowsState.sortDesc ? 'sort-down' : 'sort-up'"
@click="workflowsState.sortDesc = !workflowsState.sortDesc"
class="fs-4 ms-3 cursor-pointer"
/> />
</div> </div>
<div v-else class="d-flex flex-wrap"> <div v-if="!workflowsState.loading">
<div
v-if="workflowRepository.workflows.length === 0"
class="text-center fs-2 mt-5"
>
<bootstrap-icon
icon="x-lg"
class="mb-5"
width="75"
height="75"
style="color: var(--bs-secondary)"
/>
<br />
There are no workflows in the system. Please come again later.
</div>
<div
v-else-if="processedWorkflows.length === 0"
class="text-center fs-2 mt-5"
>
<bootstrap-icon
icon="search"
class="mb-5"
width="75"
height="75"
style="color: var(--bs-secondary)"
/>
<br />
Could not find any Workflows containing<br />'{{
workflowsState.filterString
}}'
</div>
<div
v-else
class="d-flex flex-wrap align-items-center justify-content-between"
>
<workflow-card
v-for="workflow in processedWorkflows"
:key="workflow.workflow_id"
:workflow="workflow"
:loading="false"
/>
</div>
</div>
<div
v-else
class="d-flex flex-wrap align-items-center justify-content-between"
>
<workflow-card <workflow-card
v-for="workflow in 6" v-for="workflow in 4"
:key="workflow" :key="workflow"
:workflow="{ :workflow="{
name: '', name: '',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment