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 WorkflowBox to Activity bar Side Panel #15944

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions client/src/components/ActivityBar/ActivityBar.vue
Expand Up @@ -3,6 +3,7 @@ import { useUserStore } from "@/stores/userStore";
import UploadItem from "./Items/UploadItem.vue";
import ToolBox from "@/components/Panels/ProviderAwareToolBox.vue";
import FlexPanel from "@/components/Panels/FlexPanel.vue";
import WorkflowBox from "@/components/Panels/WorkflowBox.vue";
import ActivityItem from "./ActivityItem";

const userStore = useUserStore();
Expand All @@ -25,14 +26,15 @@ function onToggleSidebar(toggle) {
icon="wrench"
title="Tools"
tooltip="Search and run tools"
:is-active="sidebarIsActive('search')"
@click="onToggleSidebar('search')" />
:is-active="sidebarIsActive('tools')"
@click="onToggleSidebar('tools')" />
<ActivityItem
id="activity-workflow"
title="Workflow"
icon="sitemap"
tooltip="Chain tools into workflows"
to="/workflows/list" />
:is-active="sidebarIsActive('workflows')"
@click="onToggleSidebar('workflows')" />
<ActivityItem
id="activity-visualization"
icon="chart-bar"
Expand All @@ -49,9 +51,12 @@ function onToggleSidebar(toggle) {
to="/user" />
</b-nav>
</div>
<FlexPanel v-if="sidebarIsActive('search')" key="search" side="left" :collapsible="false">
<FlexPanel v-if="sidebarIsActive('tools')" key="tools" side="left" :collapsible="false">
<ToolBox />
</FlexPanel>
<FlexPanel v-else-if="sidebarIsActive('workflows')" key="workflows" side="left" :collapsible="false">
<WorkflowBox />
</FlexPanel>
</div>
</template>

Expand Down
73 changes: 73 additions & 0 deletions client/src/components/Panels/WorkflowBox.vue
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { withPrefix } from "@/utils/redirect";
import { useUserStore } from "@/stores/userStore";
import { computed } from "vue";
import WorkflowSearch from "@/components/Workflow/WorkflowSearch.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faUpload, faGlobe } from "@fortawesome/free-solid-svg-icons";
import { library } from "@fortawesome/fontawesome-svg-core";

// @ts-ignore bad library types
library.add(faUpload, faGlobe);

const isAnonymous = computed(() => useUserStore().isAnonymous);

function userTitle(title: string) {
if (isAnonymous.value == true) {
return `Log in to ${title}`;
} else {
return title;
}
}
</script>

<template>
<div class="unified-panel" aria-labelledby="workflowbox-heading">
<div unselectable="on">
<div class="unified-panel-header-inner">
<nav class="d-flex justify-content-between mx-3 my-2">
<h2 v-localize class="m-1 h-sm">Workflows</h2>
<b-button-group>
<b-button
v-b-tooltip.bottom.hover
data-description="create new workflow"
size="sm"
variant="link"
:title="userTitle('Create new workflow')"
:disabled="isAnonymous"
@click="$router.push('/workflows/create')">
<Icon fixed-width icon="plus" />
</b-button>
<b-button
v-b-tooltip.bottom.hover
data-description="import workflow"
size="sm"
variant="link"
:title="userTitle('Import workflow')"
:disabled="isAnonymous"
@click="$router.push('/workflows/import')">
<FontAwesomeIcon icon="upload" />
</b-button>
<b-button
v-b-tooltip.bottom.hover
data-description="published workflows"
size="sm"
variant="link"
title="Published workflows"
@click="$router.push('/workflows/list_published')">
<FontAwesomeIcon icon="fa-globe" />
</b-button>
</b-button-group>
</nav>
</div>
</div>
<div class="unified-panel-controls">
<div v-if="isAnonymous">
<b-badge class="alert-info w-100">
Please <a :href="withPrefix('/login')">log in or register</a> to create workflows.
</b-badge>
</div>
<WorkflowSearch v-else />
</div>
</div>
</template>
20 changes: 19 additions & 1 deletion client/src/components/Panels/utilities.js
@@ -1,11 +1,29 @@
/**
* Utilities file for Tool Search (panel/client search + advanced/backend search)
* Utilities file for Panel Searches (panel/client search + advanced/backend search)
*/
import { orderBy } from "lodash";

const TOOLS_RESULTS_SORT_LABEL = "apiSort";
const TOOLS_RESULTS_SECTIONS_HIDE = ["Expression Tools"];

// Converts filterSettings { key: value } to query = "key:value"
export function createWorkflowQuery(filterSettings) {
let query = "";
query = Object.entries(filterSettings)
.filter(([filter, value]) => value)
.map(([filter, value]) => {
if (value === true) {
return `is:${filter}`;
}
return `${filter}:${value}`;
})
.join(" ");
if (Object.keys(filterSettings).length == 1 && filterSettings.name) {
return filterSettings.name;
}
return query;
}

// - Takes filterSettings = {"name": "Tool Name", "section": "Collection", ...}
// - Takes panelView (if not 'default', does ontology search at backend)
// - Takes toolbox (to find ontology id if given ontology name)
Expand Down
15 changes: 15 additions & 0 deletions client/src/components/Panels/utilities.test.js
@@ -1,6 +1,7 @@
import toolsList from "components/ToolsView/testData/toolsList";
import {
createWhooshQuery,
createWorkflowQuery,
determineWidth,
filterTools,
filterToolSections,
Expand Down Expand Up @@ -94,3 +95,17 @@ describe("test helpers in tool searching utilities and panel handling", () => {
expect(filterTools(toolsList, ids)).toHaveLength(2);
});
});

describe("test helpers in workflow searching utilities", () => {
it("test helper that creates workflow query given filters", async () => {
const settings = {
name: "extract",
tag: "Genomic",
published: null,
shared_with_me: true,
deleted: false,
};
const q = createWorkflowQuery(settings);
expect(q).toEqual("name:extract tag:Genomic is:shared_with_me");
});
});
8 changes: 8 additions & 0 deletions client/src/components/Workflow/WorkflowList.vue
Expand Up @@ -187,6 +187,11 @@ export default {
type: Boolean,
default: false,
},
query: {
type: String,
required: false,
default: "",
},
},
data() {
const fields = this.published ? PUBLISHED_FIELDS : PERSONAL_FIELDS;
Expand Down Expand Up @@ -224,6 +229,9 @@ export default {
created() {
this.root = getAppRoot();
this.services = new Services();
if (this.query) {
this.filter = this.query;
}
},
methods: {
decorateData(item) {
Expand Down
77 changes: 77 additions & 0 deletions client/src/components/Workflow/WorkflowSearch.vue
@@ -0,0 +1,77 @@
<script setup lang="ts">
import { ref, type Ref } from "vue";
import { useRouter } from "vue-router/composables";
import _l from "@/utils/localization";
import { createWorkflowQuery } from "@/components/Panels/utilities";

const router = useRouter();

type FilterSettings = {
name?: string;
tag?: string;
published?: boolean;
shared_with_me?: boolean;
deleted?: boolean;
};

const options = [
{ text: "Yes", value: true },
{ text: "No", value: false },
];

const filterSettings: Ref<FilterSettings> = ref({
published: false,
shared_with_me: false,
deleted: false,
});

function onSearch() {
const query = createWorkflowQuery(filterSettings.value);
const path = "/workflows/list";
const routerParams = query ? { path, query: { query } } : { path };
router.push(routerParams);
}
</script>
<template>
<div>
<div description="advanced workflow filters" @keyup.enter="onSearch">
<small class="mt-1">Filter by name:</small>
<b-form-input v-model="filterSettings.name" size="sm" placeholder="any name" />
<small class="mt-1">Filter by tag:</small>
<b-form-input v-model="filterSettings.tag" size="sm" placeholder="any tag" />
<small>Search published workflows:</small>
<b-form-group class="m-0">
<b-form-radio-group
v-model="filterSettings.published"
:options="options"
size="sm"
buttons
description="filter published" />
</b-form-group>
<small>Search shared workflows:</small>
<b-form-group class="m-0">
<b-form-radio-group
v-model="filterSettings.shared_with_me"
:options="options"
size="sm"
buttons
description="filter shared" />
</b-form-group>
<small>Search deleted workflows:</small>
<b-form-group class="m-0">
<b-form-radio-group
v-model="filterSettings.deleted"
:options="options"
size="sm"
buttons
description="filter deleted" />
</b-form-group>
<div class="mt-3">
<b-button class="mr-1" size="sm" variant="primary" @click="onSearch">
<icon icon="search" />
<span>{{ _l("Search") }}</span>
</b-button>
</div>
</div>
</div>
</template>
1 change: 1 addition & 0 deletions client/src/entry/analysis/router.js
Expand Up @@ -462,6 +462,7 @@ export function getRouter(Galaxy) {
props: (route) => ({
importMessage: route.query["message"],
importStatus: route.query["status"],
query: route.query["query"],
}),
},
{
Expand Down
2 changes: 1 addition & 1 deletion client/src/stores/userStore.ts
Expand Up @@ -22,7 +22,7 @@ interface Preferences {
export const useUserStore = defineStore(
"userStore",
() => {
const toggledSideBar = ref("search");
const toggledSideBar = ref("tools");
const showActivityBar = ref(false);
const currentUser = ref<User | null>(null);
const currentPreferences = ref<Preferences | null>(null);
Expand Down