From e3f12c5d4674d6d19ab39f4260146ac9090d62d0 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Thu, 27 Jun 2024 04:52:05 -0400 Subject: [PATCH] Latest prototype of a minimal workflow UI. --- .../components/ActivityBar/ActivityBar.vue | 14 ++-- .../components/Workflow/List/WorkflowCard.vue | 7 +- .../components/Workflow/List/WorkflowList.vue | 22 +++++- .../Workflow/List/WorkflowListActions.vue | 9 +++ .../Workflow/Run/WorkflowRunFormSimple.vue | 2 +- .../src/entry/analysis/modules/Analysis.vue | 10 ++- client/src/entry/analysis/modules/Home.vue | 9 +++ .../analysis/modules/WorkflowLanding.vue | 16 +++++ client/src/stores/activitySetup.ts | 69 +++++++++++++++---- client/src/stores/activityStore.ts | 49 +++++++++++-- client/src/stores/userStore.ts | 7 ++ lib/galaxy/config/schemas/config_schema.yml | 27 ++++++++ lib/galaxy/managers/configuration.py | 2 + 13 files changed, 211 insertions(+), 32 deletions(-) create mode 100644 client/src/entry/analysis/modules/WorkflowLanding.vue diff --git a/client/src/components/ActivityBar/ActivityBar.vue b/client/src/components/ActivityBar/ActivityBar.vue index cde4d7b190ad..6e2641b616cb 100644 --- a/client/src/components/ActivityBar/ActivityBar.vue +++ b/client/src/components/ActivityBar/ActivityBar.vue @@ -132,12 +132,16 @@ function onToggleSidebar(toggle: string = "", to: string | null = null) { userStore.toggleSideBar(toggle); } -watch( - () => hashedUserId.value, - () => { - activityStore.sync(); +const syncActivities = () => { + activityStore.sync(); + if (config.value && ["workflow_centric", "workflow_runner"].indexOf(config.value.client_mode) >= 0) { + userStore.untoggleToolbarIfNeeded(); } -); +}; + +watch(() => hashedUserId.value, syncActivities); + +watch(isConfigLoaded, syncActivities); - + @@ -20,6 +25,7 @@ export default { CenterFrame, ToolForm, WorkflowRun, + WorkflowLanding, }, props: { config: { @@ -32,6 +38,9 @@ export default { }, }, computed: { + isWorkflowCentric() { + return ["workflow_centric", "workflow_runner"].indexOf(this.config.client_mode) >= 0; + }, isController() { return this.query.m_c && this.query.m_a; }, diff --git a/client/src/entry/analysis/modules/WorkflowLanding.vue b/client/src/entry/analysis/modules/WorkflowLanding.vue new file mode 100644 index 000000000000..f733c1d1993c --- /dev/null +++ b/client/src/entry/analysis/modules/WorkflowLanding.vue @@ -0,0 +1,16 @@ + + + diff --git a/client/src/stores/activitySetup.ts b/client/src/stores/activitySetup.ts index 17551fc89e2b..9323fad6958e 100644 --- a/client/src/stores/activitySetup.ts +++ b/client/src/stores/activitySetup.ts @@ -1,22 +1,42 @@ /** * List of built-in activities */ -import { type Activity } from "@/stores/activityStore"; +import { type Activity, type ClientMode, type RawActivity } from "@/stores/activityStore"; import { type EventData } from "@/stores/eventStore"; -export const Activities = [ +function isWorkflowCentric(clientMode: ClientMode): boolean { + return ["workflow_centric", "workflow_runner"].indexOf(clientMode) >= 0; +} + +function unlessWorkflowCentric(clientMode: ClientMode): boolean { + if (isWorkflowCentric(clientMode)) { + return false; + } else { + return true; + } +} + +function ifWorkflowCentric(clientMode: ClientMode): boolean { + if (isWorkflowCentric(clientMode)) { + return true; + } else { + return false; + } +} + +export const ActivitiesRaw: RawActivity[] = [ { anonymous: false, description: "Displays currently running interactive tools (ITs), if these are enabled by the administrator.", icon: "fa-laptop", id: "interactivetools", mutable: false, - optional: false, + optional: ifWorkflowCentric, panel: false, title: "Interactive Tools", tooltip: "Show active interactive tools", to: "/interactivetool_entry_points/list", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: true, @@ -24,12 +44,12 @@ export const Activities = [ icon: "upload", id: "upload", mutable: false, - optional: false, + optional: ifWorkflowCentric, panel: false, title: "Upload", to: null, tooltip: "Download from URL or upload files from disk", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: true, @@ -37,12 +57,12 @@ export const Activities = [ icon: "wrench", id: "tools", mutable: false, - optional: false, + optional: ifWorkflowCentric, panel: true, title: "Tools", - to: null, + to: "/tools", tooltip: "Search and run tools", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: true, @@ -81,7 +101,7 @@ export const Activities = [ title: "Visualization", to: null, tooltip: "Visualize datasets", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: true, @@ -94,7 +114,7 @@ export const Activities = [ title: "Histories", tooltip: "Show all histories", to: "/histories/list", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: false, @@ -107,7 +127,7 @@ export const Activities = [ title: "History Multiview", tooltip: "Select histories to show in History Multiview", to: "/histories/view_multiple", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: false, @@ -120,7 +140,7 @@ export const Activities = [ title: "Datasets", tooltip: "Show all datasets", to: "/datasets/list", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: true, @@ -133,7 +153,7 @@ export const Activities = [ title: "Pages", tooltip: "Show all pages", to: "/pages/list", - visible: true, + visible: unlessWorkflowCentric, }, { anonymous: false, @@ -146,10 +166,29 @@ export const Activities = [ title: "Libraries", tooltip: "Access data libraries", to: "/libraries", - visible: true, + visible: unlessWorkflowCentric, }, ]; +function resolveActivity(activity: RawActivity, clientMode: ClientMode): Activity { + let optional = activity.optional; + let visible = activity.visible; + if (typeof optional === "function") { + optional = optional(clientMode); + } + if (typeof visible === "function") { + visible = visible(clientMode); + } + return { ...activity, optional, visible }; +} + +export function getActivities(clientMode: ClientMode) { + const resolve = (activity: RawActivity) => { + return resolveActivity(activity, clientMode); + }; + return ActivitiesRaw.map(resolve); +} + export function convertDropData(data: EventData): Activity | null { if (data.history_content_type === "dataset") { return { diff --git a/client/src/stores/activityStore.ts b/client/src/stores/activityStore.ts index 377a68e08964..9bd46940b7b2 100644 --- a/client/src/stores/activityStore.ts +++ b/client/src/stores/activityStore.ts @@ -5,9 +5,10 @@ import { defineStore } from "pinia"; import { type Ref } from "vue"; +import { useConfig } from "@/composables/config"; import { useUserLocalStorage } from "@/composables/userLocalStorage"; -import { Activities } from "./activitySetup"; +import { getActivities } from "./activitySetup"; export interface Activity { // determine wether an anonymous user can access this activity @@ -34,6 +35,34 @@ export interface Activity { visible: boolean; } +export type ClientMode = "full" | "workflow_centric" | "workflow_runner"; + +// config materializes a RawActivity into an Activity +export interface RawActivity { + // determine wether an anonymous user can access this activity + anonymous: boolean; + // description of the activity + description: string; + // unique identifier + id: string; + // icon to be displayed in activity bar + icon: string; + // indicate if this activity can be modified and/or deleted + mutable: boolean; + // indicate wether this activity can be disabled by the user + optional: boolean | ((mode: ClientMode) => boolean); + // specifiy wether this activity utilizes the side panel + panel: boolean; + // title to be displayed in the activity bar + title: string; + // route to be executed upon selecting the activity + to: string | null; + // tooltip to be displayed when hovering above the icon + tooltip: string; + // indicate wether the activity should be visible by default + visible: boolean | ((mode: ClientMode) => boolean); +} + export const useActivityStore = defineStore("activityStore", () => { const activities: Ref> = useUserLocalStorage("activity-store-activities", []); @@ -41,7 +70,12 @@ export const useActivityStore = defineStore("activityStore", () => { * Restores the default activity bar items */ function restore() { - activities.value = Activities.slice(); + const { config, isConfigLoaded } = useConfig(); + if (!isConfigLoaded.value) { + return; + } + + activities.value = getActivities(config.value.client_mode); } /** @@ -50,11 +84,18 @@ export const useActivityStore = defineStore("activityStore", () => { * to the user stored activities which are persisted in local cache. */ function sync() { + const { config, isConfigLoaded } = useConfig(); + if (!isConfigLoaded.value) { + return; + } + // create a map of built-in activities const activitiesMap: Record = {}; - Activities.forEach((a) => { + const activityDefs = getActivities(config.value.client_mode); + activityDefs.forEach((a) => { activitiesMap[a.id] = a; }); + // create an updated array of activities const newActivities: Array = []; const foundActivity = new Set(); @@ -76,7 +117,7 @@ export const useActivityStore = defineStore("activityStore", () => { } }); // add new built-in activities - Activities.forEach((a) => { + activityDefs.forEach((a) => { if (!foundActivity.has(a.id)) { newActivities.push({ ...a }); } diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts index 55a49d491209..9379bcb89634 100644 --- a/client/src/stores/userStore.ts +++ b/client/src/stores/userStore.ts @@ -146,6 +146,12 @@ export const useUserStore = defineStore("userStore", () => { }; } + function untoggleToolbarIfNeeded() { + if (toggledSideBar.value == "tools") { + toggledSideBar.value = ""; + } + } + return { currentUser, currentPreferences, @@ -163,6 +169,7 @@ export const useUserStore = defineStore("userStore", () => { addFavoriteTool, removeFavoriteTool, toggleSideBar, + untoggleToolbarIfNeeded, $reset, }; }); diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index afcb6ec9c3ed..b9b7e1485db6 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -3134,6 +3134,33 @@ mapping: When false, the most recently added compatible item in the history will be used for each "Set at Runtime" input, independent of others in the workflow. + client_mode: + type: str + default: 'full' + enum: ['full', 'workflow_centric', 'workflow_runner'] + required: false + per_host: true + desc: | + This will change the modality and focus on the client UI. The traditional + full Galaxy with default activity bar is the default of 'full'. + 'workflow_centric' & 'workflow_runner' yield client applications + that are geared to center a collection of workflows in Galaxy and attempts + to hide the concept of histories from users. The 'workflow_centric' view + still allows the user to manage & edit a collection of their own workflows. + 'workflow_runner' is a mode that disables workflow management to even further + simplify the UI - this may be appropriate for instances that really just want + enable particular workflows as-is. + + simplified_workflow_landing_initial_filter_text: + type: str + required: false + per_host: true + desc: | + If the Galaxy client is in 'workflow_centric' or 'workflow_runner' "client mode", + this controls the initial filtering of the workflow search textbox. This can + be used to foreground workflows in the published workflow list by tar (e.g. 'tag:XXX') + or username (e.g. 'username:XXXX'). + simplified_workflow_run_ui: type: str default: 'prefer' diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index d0ccfa8d53c2..8a6d30e8454d 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -164,6 +164,8 @@ def _config_is_truthy(item, key, **context): "enable_unique_workflow_defaults": _use_config, "enable_beta_markdown_export": _use_config, "enable_beacon_integration": _use_config, + "client_mode": _use_config, + "simplified_workflow_landing_initial_filter_text": _use_config, "simplified_workflow_run_ui": _use_config, "simplified_workflow_run_ui_target_history": _use_config, "simplified_workflow_run_ui_job_cache": _use_config,