Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workflow selection and bulk actions #19336

Draft
wants to merge 12 commits into
base: dev
Choose a base branch
from
84 changes: 56 additions & 28 deletions client/src/components/Common/ListHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faAngleDown, faAngleUp, faBars, faGripVertical } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton } from "bootstrap-vue";
import { BButton, BButtonGroup, BFormCheckbox } from "bootstrap-vue";
import { computed, ref } from "vue";

import { useUserStore } from "@/stores/userStore";
Expand All @@ -13,13 +13,25 @@ type ListView = "grid" | "list";
type SortBy = "create_time" | "update_time" | "name";

interface Props {
allSelected?: boolean;
showSelectAll?: boolean;
showViewToggle?: boolean;
selectAllDisabled?: boolean;
indeterminateSelected?: boolean;
}

withDefaults(defineProps<Props>(), {
allSelected: false,
showSelectAll: false,
showViewToggle: false,
selectAllDisabled: false,
indeterminateSelected: false,
});

const emit = defineEmits<{
(e: "select-all"): void;
}>();

const userStore = useUserStore();

const sortDesc = ref(true);
Expand Down Expand Up @@ -47,33 +59,49 @@ defineExpose({

<template>
<div class="list-header">
<div class="list-header-filters">
Sort by:
<BButtonGroup>
<BButton
id="sortby-name"
v-b-tooltip.hover
size="sm"
:title="sortDesc ? 'Sort by name ascending' : 'Sort by name descending'"
:pressed="sortBy === 'name'"
variant="outline-primary"
@click="onSort('name')">
<FontAwesomeIcon v-show="sortBy === 'name'" :icon="sortDesc ? faAngleDown : faAngleUp" />
Name
</BButton>
<div class="list-header-select-all">
<slot name="select-all">
<BFormCheckbox
v-if="showSelectAll"
id="list-header-select-all"
:disabled="selectAllDisabled"
:checked="allSelected"
:indeterminate="indeterminateSelected"
@change="emit('select-all')">
Select all
</BFormCheckbox>
</slot>
</div>

<BButton
id="sortby-update-time"
v-b-tooltip.hover
size="sm"
:title="sortDesc ? 'Sort by update time ascending' : 'Sort by update time descending'"
:pressed="sortBy === 'update_time'"
variant="outline-primary"
@click="onSort('update_time')">
<FontAwesomeIcon v-show="sortBy === 'update_time'" :icon="sortDesc ? faAngleDown : faAngleUp" />
Update time
</BButton>
</BButtonGroup>
<div class="list-header-filters">
<div>
Sort by:
<BButtonGroup>
<BButton
id="sortby-name"
v-b-tooltip.hover
size="sm"
:title="sortDesc ? 'Sort by name ascending' : 'Sort by name descending'"
:pressed="sortBy === 'name'"
variant="outline-primary"
@click="onSort('name')">
<FontAwesomeIcon v-show="sortBy === 'name'" :icon="sortDesc ? faAngleDown : faAngleUp" />
Name
</BButton>

<BButton
id="sortby-update-time"
v-b-tooltip.hover
size="sm"
:title="sortDesc ? 'Sort by update time ascending' : 'Sort by update time descending'"
:pressed="sortBy === 'update_time'"
variant="outline-primary"
@click="onSort('update_time')">
<FontAwesomeIcon v-show="sortBy === 'update_time'" :icon="sortDesc ? faAngleDown : faAngleUp" />
Update time
</BButton>
</BButtonGroup>
</div>

<slot name="extra-filter" />
</div>
Expand Down Expand Up @@ -115,7 +143,7 @@ defineExpose({

.list-header-filters {
display: flex;
gap: 0.25rem;
gap: 1rem;
flex-wrap: wrap;
align-items: center;
}
Expand Down
33 changes: 33 additions & 0 deletions client/src/components/Common/TagsSelectionDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import { BModal } from "bootstrap-vue";
import { ref } from "vue";

import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";

interface Props {
title?: string;
initialTags?: string[];
}

const props = withDefaults(defineProps<Props>(), {
title: "Select tags to add",
initialTags: () => [],
});

const tags = ref(props.initialTags);

const emit = defineEmits<{
(e: "cancel"): void;
(e: "ok", tags: string[]): void;
}>();

function onTagsChange(newTags: string[]) {
tags.value = newTags;
}
</script>

<template>
<BModal visible centered size="lg" :title="title" @ok="emit('ok', tags)" @hide="emit('cancel')">
<StatelessTags :value="tags" @input="onTagsChange($event)" />
</BModal>
</template>
51 changes: 40 additions & 11 deletions client/src/components/Workflow/List/WorkflowCard.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { faPen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton, BLink } from "bootstrap-vue";
import { BButton, BFormCheckbox, BLink } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, ref } from "vue";

import { type StoredWorkflowDetailed } from "@/api/workflows";
import { updateWorkflow } from "@/components/Workflow/workflows.services";
import { useUserStore } from "@/stores/userStore";

Expand All @@ -16,13 +17,15 @@ import WorkflowIndicators from "@/components/Workflow/List/WorkflowIndicators.vu
import WorkflowInvocationsCount from "@/components/Workflow/WorkflowInvocationsCount.vue";

interface Props {
workflow: any;
workflow: StoredWorkflowDetailed;
gridView?: boolean;
hideRuns?: boolean;
filterable?: boolean;
publishedView?: boolean;
editorView?: boolean;
current?: boolean;
selected?: boolean;
selectable?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
Expand All @@ -32,9 +35,12 @@ const props = withDefaults(defineProps<Props>(), {
filterable: true,
editorView: false,
current: false,
selected: false,
selectable: false,
});

const emit = defineEmits<{
(e: "select", workflow: any): void;
(e: "tagClick", tag: string): void;
(e: "refreshList", overlayLoading?: boolean, silent?: boolean): void;
(e: "updateFilter", key: string, value: any): void;
Expand All @@ -56,7 +62,7 @@ const shared = computed(() => {

const description = computed(() => {
if (workflow.value.annotations && workflow.value.annotations.length > 0) {
return workflow.value.annotations[0].trim();
return workflow.value.annotations[0]?.trim();
} else {
return null;
}
Expand Down Expand Up @@ -85,8 +91,17 @@ const dropdownOpen = ref(false);
class="workflow-card-container"
:class="{
'workflow-shared': workflow.published,
'workflow-card-selected': props.selected,
}">
<div class="workflow-card-header">
<BFormCheckbox
v-if="props.selectable && !shared"
v-b-tooltip.hover.noninteractive
:checked="props.selected"
class="workflow-card-select-checkbox"
title="Select for bulk actions"
@change="emit('select', workflow)" />

<WorkflowIndicators
class="workflow-card-indicators"
:workflow="workflow"
Expand Down Expand Up @@ -117,6 +132,7 @@ const dropdownOpen = ref(false);
@click.stop.prevent="emit('preview', props.workflow.id)">
{{ workflow.name }}
</BLink>

<BButton
v-if="!props.current && !shared && !workflow.deleted"
v-b-tooltip.hover.noninteractive
Expand Down Expand Up @@ -190,7 +206,13 @@ const dropdownOpen = ref(false);
gap: 0.5rem;
flex-direction: column;
justify-content: space-between;
border: 1px solid $brand-secondary;
border: 0.1rem solid $brand-secondary;

&.workflow-card-selected {
background-color: $brand-light;
border: 0.1rem solid $brand-primary;
}

border-radius: 0.5rem;
padding: 0.5rem;

Expand All @@ -203,21 +225,28 @@ const dropdownOpen = ref(false);
position: relative;
align-items: start;
grid-template-areas:
"i b"
"n n"
"s s";
"i d b"
"n n n"
"s s s";

grid-template-columns: auto 1fr auto;

&:has(.invocations-count) {
@container workflow-card (max-width: #{$breakpoint-xs}) {
grid-template-areas:
"i b"
"n b"
"s s";
"i d b"
"n n b"
"s s s";
}
}

.workflow-card-indicators {
.workflow-card-select-checkbox {
grid-area: i;
margin: 0%;
}

.workflow-card-indicators {
grid-area: d;
}

.workflow-count-actions {
Expand Down
38 changes: 28 additions & 10 deletions client/src/components/Workflow/List/WorkflowCardList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@ interface Props {
publishedView?: boolean;
editorView?: boolean;
currentWorkflowId?: string;
selectedWorkflowIds?: Workflow[];
}

const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
gridView: false,
hideRuns: false,
filterable: true,
publishedView: false,
editorView: false,
currentWorkflowId: "",
selectedWorkflowIds: () => [],
});

const emit = defineEmits<{
(e: "select", workflow: Workflow): void;
(e: "tagClick", tag: string): void;
(e: "refreshList", overlayLoading?: boolean, silent?: boolean): void;
(e: "updateFilter", key: string, value: any): void;
Expand All @@ -39,6 +49,7 @@ const modalOptions = reactive({
});

const showRename = ref(false);
const showPreview = ref(false);

function onRenameClose() {
showRename.value = false;
Expand All @@ -51,8 +62,6 @@ function onRename(id: string, name: string) {
showRename.value = true;
}

const showPreview = ref(false);

function onPreview(id: string) {
modalOptions.preview.id = id;
showPreview.value = true;
Expand All @@ -74,13 +83,16 @@ function onInsertSteps(workflow: Workflow) {
v-for="workflow in props.workflows"
:key="workflow.id"
:workflow="workflow"
:selectable="!publishedView && !editorView"
:selected="props.selectedWorkflowIds.some((w) => w.id === workflow.id)"
:grid-view="props.gridView"
:hide-runs="props.hideRuns"
:filterable="props.filterable"
:published-view="props.publishedView"
:editor-view="props.editorView"
:current="workflow.id === props.currentWorkflowId"
class="workflow-card"
@select="(...args) => emit('select', ...args)"
@tagClick="(...args) => emit('tagClick', ...args)"
@refreshList="(...args) => emit('refreshList', ...args)"
@updateFilter="(...args) => emit('updateFilter', ...args)"
Expand Down Expand Up @@ -122,6 +134,7 @@ function onInsertSteps(workflow: Workflow) {
@import "_breakpoints.scss";

.workflow-card-list {
overflow: auto;
container: card-list / inline-size;
display: flex;
flex-wrap: wrap;
Expand All @@ -130,15 +143,20 @@ function onInsertSteps(workflow: Workflow) {
width: 100%;
}

&.grid .workflow-card {
width: calc(100% / 3);
&.grid {
// it is overwriting the base non used css for the grid class
padding-top: 0 !important;

@container card-list (max-width: #{$breakpoint-xl}) {
width: calc(100% / 2);
}
.workflow-card {
width: calc(100% / 3);

@container card-list (max-width: #{$breakpoint-xl}) {
width: calc(100% / 2);
}

@container card-list (max-width: #{$breakpoint-sm}) {
width: 100%;
@container card-list (max-width: #{$breakpoint-sm}) {
width: 100%;
}
}
}
}
Expand Down
Loading
Loading