Skip to content

Commit

Permalink
Merge pull request #19168 from ahmedhamidawan/invocation_controls_fix
Browse files Browse the repository at this point in the history
[24.2] Move invocation view running actions and improve styling of annotation section
  • Loading branch information
mvdbeek authored Nov 25, 2024
2 parents 6bc9940 + b04aa3a commit 52b6290
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 72 deletions.
22 changes: 18 additions & 4 deletions client/src/components/Common/TextSummary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ interface Props {
maxLength?: number;
/** The text to summarize */
description: string;
/** If `true`, doesn't let unexpanded text go beyond height of one line */
/** If `true`, doesn't let unexpanded text go beyond height of one line
* and ignores `maxLength` */
oneLineSummary?: boolean;
/** If `true`, doesn't show expand/collapse buttons */
noExpand?: boolean;
Expand All @@ -25,8 +26,17 @@ const props = withDefaults(defineProps<Props>(), {
});
const showDetails = ref(false);
const refOneLineSummary = ref<HTMLElement | null>(null);
const textTooLong = computed(() => props.description.length > props.maxLength);
const textTooLong = computed(() => {
if (!props.oneLineSummary) {
return props.description.length > props.maxLength;
} else if (refOneLineSummary.value) {
return refOneLineSummary.value.scrollWidth > refOneLineSummary.value.clientWidth;
} else {
return false;
}
});
const text = computed(() =>
textTooLong.value && !showDetails.value
? props.description.slice(0, Math.round(props.maxLength - props.maxLength / 2)) + "..."
Expand All @@ -35,8 +45,12 @@ const text = computed(() =>
</script>

<template>
<div>
<component :is="props.component" v-if="props.oneLineSummary" class="one-line-summary">
<div :class="{ 'd-flex': props.oneLineSummary && !noExpand }">
<component
:is="props.component"
v-if="props.oneLineSummary"
ref="refOneLineSummary"
:class="{ 'one-line-summary': !showDetails }">
{{ props.description }}
</component>
<span v-else>{{ text }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
:run-disabled="hasValidationErrors || !canRunOnHistory"
:run-waiting="waitingForRequest"
@on-execute="onExecute">
<template v-slot:workflow-run-actions>
<template v-slot:workflow-title-actions>
<b-dropdown
v-if="showRuntimeSettings(currentUser)"
id="dropdown-form"
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Workflow/Run/WorkflowRunSuccess.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ const wasNewHistoryTarget =

<template>
<div>
<div class="donemessagelarge">
<div v-if="props.invocations.length > 1" class="donemessagelarge">
Successfully invoked workflow <b>{{ props.workflowName }}</b>
<em v-if="props.invocations.length > 1"> - {{ props.invocations.length }} times</em>.
<em> - {{ props.invocations.length }} times</em>.
<span v-if="targetHistories.length > 1">
This workflow will generate results in multiple histories. You can observe progress in the
<router-link to="/histories/view_multiple">history multi-view</router-link>.
Expand Down
8 changes: 6 additions & 2 deletions client/src/components/Workflow/WorkflowAnnotation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,19 @@ async function mountWorkflowAnnotation(version: "run_form" | "invocation", ownsW
}

describe("WorkflowAnnotation renders", () => {
it("(always) the run count and history, not indicators if owned not published", async () => {
it("the run count and history, not indicators if owned not published", async () => {
async function checkHasRunCount(version: "run_form" | "invocation") {
const { wrapper } = await mountWorkflowAnnotation(version);

const runCount = wrapper.find(SELECTORS.RUN_COUNT);
expect(runCount.text()).toContain("workflow runs:");
expect(runCount.text()).toContain(SAMPLE_RUN_COUNT.toString());

expect(wrapper.find(SELECTORS.SWITCH_TO_HISTORY_LINK).text()).toBe(TEST_HISTORY.name);
if (version === "run_form") {
expect(wrapper.find(SELECTORS.SWITCH_TO_HISTORY_LINK).exists()).toBe(false);
} else {
expect(wrapper.find(SELECTORS.SWITCH_TO_HISTORY_LINK).text()).toBe(TEST_HISTORY.name);
}

// Since this is the user's own workflow, the indicators link
// (to view all published workflows by the owner) should not be present
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/Workflow/WorkflowAnnotation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ const workflowTags = computed(() => {
</span>
<UtcDate :date="timeElapsed" mode="elapsed" data-description="workflow annotation date" />
</i>
<span class="d-flex flex-gapx-1 align-items-center">
<FontAwesomeIcon :icon="faHdd" />Input History:
<span v-if="invocationUpdateTime" class="d-flex flex-gapx-1 align-items-center">
<FontAwesomeIcon :icon="faHdd" />History:
<SwitchToHistoryLink :history-id="props.historyId" />
<BBadge
v-if="props.newHistoryTarget && useHistoryStore().currentHistoryId !== props.historyId"
Expand All @@ -92,8 +92,9 @@ const workflowTags = computed(() => {
</div>
</div>
<div v-if="props.showDetails">
<TextSummary v-if="description" class="my-1" :description="description" />
<TextSummary v-if="description" class="my-1" :description="description" one-line-summary component="span" />
<StatelessTags v-if="workflowTags.length" :value="workflowTags" :disabled="true" />
<hr class="mb-0 mt-2" />
</div>
</div>
</template>
55 changes: 40 additions & 15 deletions client/src/components/Workflow/WorkflowNavigationTitle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Props {
workflowId: string;
runDisabled?: boolean;
runWaiting?: boolean;
success?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
Expand Down Expand Up @@ -84,20 +85,20 @@ const workflowImportTitle = computed(() => {

<template>
<div>
<div>
<BAlert v-if="importErrorMessage" variant="danger" dismissible show @dismissed="importErrorMessage = null">
{{ importErrorMessage }}
</BAlert>
<BAlert v-else-if="importedWorkflow" variant="info" dismissible show @dismissed="importedWorkflow = null">
<span>
Workflow <b>{{ importedWorkflow.name }}</b> imported successfully.
</span>
<RouterLink to="/workflows/list">Click here</RouterLink> to view the imported workflow in the workflows
list.
</BAlert>

<BAlert v-if="error" variant="danger" show>{{ error }}</BAlert>

<BAlert v-if="importErrorMessage" variant="danger" dismissible show @dismissed="importErrorMessage = null">
{{ importErrorMessage }}
</BAlert>
<BAlert v-else-if="importedWorkflow" variant="info" dismissible show @dismissed="importedWorkflow = null">
<span>
Workflow <b>{{ importedWorkflow.name }}</b> imported successfully.
</span>
<RouterLink to="/workflows/list">Click here</RouterLink> to view the imported workflow in the workflows
list.
</BAlert>

<BAlert v-if="error" variant="danger" show>{{ error }}</BAlert>

<div class="position-relative">
<div v-if="workflow" class="ui-portlet-section">
<div class="d-flex portlet-header align-items-center">
<div class="flex-grow-1" data-description="workflow heading">
Expand Down Expand Up @@ -136,7 +137,7 @@ const workflowImportTitle = computed(() => {
:action="onImport">
</AsyncButton>

<slot name="workflow-run-actions" />
<slot name="workflow-title-actions" />

<ButtonSpinner
v-if="!props.invocation"
Expand All @@ -163,6 +164,30 @@ const workflowImportTitle = computed(() => {
</BButtonGroup>
</div>
</div>
<div v-if="props.success" class="donemessagelarge">
Successfully invoked workflow
<b>{{ getWorkflowName() }}</b>
</div>
</div>
</div>
</template>

<style scoped lang="scss">
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
display: none;
pointer-events: none;
}
}
.donemessagelarge {
top: 0;
position: absolute;
width: 100%;
animation: fadeOut 3s forwards;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const selectors = {
invocationSummary: ".invocation-overview",
bAlertStub: "balert-stub",
spanElement: "span",
invocationReportTab: "btab-stub[title='Report']",
invocationReportTab: '[titleitemclass="invocation-report-tab"]',
fullPageHeading: "anonymous-stub[h1='true']",
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script setup lang="ts">
import { faDownload, faTimes } from "@fortawesome/free-solid-svg-icons";
import { faExclamation, faSquare, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BNavItem, BTab, BTabs } from "bootstrap-vue";
import { BAlert, BBadge, BButton, BTab, BTabs } from "bootstrap-vue";
import { computed, onUnmounted, ref, watch } from "vue";
import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocationElementView } from "@/api/invocations";
import { useAnimationFrameResizeObserver } from "@/composables/sensors/animationFrameResizeObserver";
import { getRootFromIndexLink } from "@/onload";
import { useInvocationStore } from "@/stores/invocationStore";
import { useWorkflowStore } from "@/stores/workflowStore";
import { errorMessageAsString } from "@/utils/simple-error";
Expand Down Expand Up @@ -67,11 +66,15 @@ watch(
}
);
// Report and PDF generation
const generatePdfTooltip = "Generate PDF report for this workflow invocation";
const invocationPdfLink = computed<string | null>(
() => getRootFromIndexLink() + `api/invocations/${props.invocationId}/report.pdf`
const workflowStore = useWorkflowStore();
const reportTabDisabled = computed(
() =>
!invocationStateSuccess.value ||
!invocation.value ||
!workflowStore.getStoredWorkflowByInstanceId(invocation.value.workflow_id)
);
/** Tooltip message for the report tab when it is disabled */
const disabledReportTooltip = computed(() => {
const state = invocationState.value;
if (state != "scheduled") {
Expand Down Expand Up @@ -175,8 +178,6 @@ const jobStatesStr = computed(() => {
return `${jobStr}.`;
});
const workflowStore = useWorkflowStore();
watch(
() => props.invocationId,
async (id) => {
Expand Down Expand Up @@ -229,9 +230,25 @@ function cancelWorkflowSchedulingLocal() {
<template>
<div v-if="invocation" class="d-flex flex-column w-100" data-description="workflow invocation state">
<WorkflowNavigationTitle
v-if="props.isFullPage && !props.success"
v-if="props.isFullPage"
:invocation="invocation"
:workflow-id="invocation.workflow_id" />
:workflow-id="invocation.workflow_id"
:success="props.success">
<template v-slot:workflow-title-actions>
<BButton
v-if="!invocationAndJobTerminal"
v-b-tooltip.noninteractive.hover
title="Cancel scheduling of workflow invocation"
data-description="header cancel invocation button"
size="sm"
class="text-decoration-none"
variant="link"
@click="onCancel">
<FontAwesomeIcon :icon="faSquare" fixed-width />
Cancel
</BButton>
</template>
</WorkflowNavigationTitle>
<WorkflowAnnotation
v-if="props.isFullPage"
:workflow-id="invocation.workflow_id"
Expand Down Expand Up @@ -301,13 +318,20 @@ function cancelWorkflowSchedulingLocal() {
</BTab> -->
<BTab
v-if="!props.isSubworkflow"
title="Report"
title-item-class="invocation-report-tab"
:disabled="
!invocationStateSuccess || !workflowStore.getStoredWorkflowByInstanceId(invocation.workflow_id)
"
:disabled="reportTabDisabled"
:lazy="reportLazy"
:active.sync="reportActive">
<template v-slot:title>
<span>Report</span>
<BBadge
v-if="reportTabDisabled"
v-b-tooltip.hover.noninteractive
:title="disabledReportTooltip"
variant="warning">
<FontAwesomeIcon :icon="faExclamation" />
</BBadge>
</template>
<InvocationReport v-if="invocationStateSuccess" :invocation-id="invocation.id" />
</BTab>
<BTab title="Export" lazy>
Expand All @@ -322,35 +346,17 @@ function cancelWorkflowSchedulingLocal() {
<WorkflowInvocationMetrics :invocation-id="invocation.id"></WorkflowInvocationMetrics>
</BTab>
<template v-slot:tabs-end>
<BNavItem v-if="!invocationAndJobTerminal" class="ml-auto alert-info mr-1">
<LoadingSpan message="Waiting to complete invocation" />
<BButton
v-b-tooltip.noninteractive.hover
title="Cancel scheduling of workflow invocation"
data-description="cancel invocation button"
size="sm"
variant="danger"
@click="onCancel">
<FontAwesomeIcon :icon="faTimes" fixed-width />
Cancel Workflow
</BButton>
</BNavItem>
<li
role="presentation"
class="nav-item align-self-center mr-2"
:class="{ 'ml-auto': invocationAndJobTerminal }"
data-description="generate pdf report button">
<BButton
v-b-tooltip.hover.bottom.noninteractive
:title="invocationStateSuccess ? generatePdfTooltip : disabledReportTooltip"
:disabled="!invocationStateSuccess"
:href="invocationPdfLink"
size="sm"
target="_blank">
<FontAwesomeIcon :icon="faDownload" fixed-width />
Generate PDF
</BButton>
</li>
<BButton
v-if="!props.isFullPage && !invocationAndJobTerminal"
v-b-tooltip.noninteractive.hover
class="ml-auto my-1"
title="Cancel scheduling of workflow invocation"
data-description="cancel invocation button"
size="sm"
@click="onCancel">
<FontAwesomeIcon :icon="faTimes" fixed-width />
Cancel Workflow
</BButton>
</template>
</BTabs>
</div>
Expand All @@ -365,6 +371,15 @@ function cancelWorkflowSchedulingLocal() {
</BAlert>
</template>

<style lang="scss">
// To show the tooltip on the disabled report tab badge
.invocation-report-tab {
.nav-link.disabled {
pointer-events: auto !important;
}
}
</style>

<style scoped lang="scss">
.progress-bars {
// progress bar shrinks to fit divs on either side
Expand Down

0 comments on commit 52b6290

Please sign in to comment.