Skip to content

Commit

Permalink
fix bugs with stateStore.activeNodeId not syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedhamidawan committed Apr 26, 2024
1 parent d974823 commit 8fa85d6
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 87 deletions.
128 changes: 60 additions & 68 deletions client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import {
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useElementBounding } from "@vueuse/core";
import { BAlert, BButton, BCard, BCardBody, BCardHeader } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, onUnmounted, ref, watch } from "vue";
import { components } from "@/api/schema";
import ExpandedItems from "@/components/History/Content/ExpandedItems";
import { JobProvider } from "@/components/providers";
import { useDatatypesMapper } from "@/composables/datatypesMapper";
import { useInvocationGraph } from "@/composables/useInvocationGraph";
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
import { type Step } from "@/stores/workflowStepStore";
import type { Workflow } from "@/stores/workflowStore";
import { withPrefix } from "@/utils/redirect";
Expand Down Expand Up @@ -102,6 +101,7 @@ watch(
);
const stateStore = useWorkflowStateStore(storeId.value);
const { activeNodeId } = storeToRefs(stateStore);
watch(
() => props.zoom,
Expand All @@ -116,9 +116,9 @@ watch(
if (!newVal && steps.value) {
const errorStep = Object.values(steps.value).find((step) => step.state === "error");
if (errorStep) {
stateStore.activeNodeId = errorStep.id;
activeNodeId.value = errorStep.id;
} else if (props.isTerminal) {
stateStore.activeNodeId = Object.values(steps.value)?.slice(-1)[0]?.id || null;
activeNodeId.value = Object.values(steps.value)?.slice(-1)[0]?.id || null;
}
}
},
Expand All @@ -130,7 +130,7 @@ watch(
() => hideGraph.value,
() => {
showingJobId.value = undefined;
stateStore.activeNodeId = null;
activeNodeId.value = null;
}
);
Expand Down Expand Up @@ -202,16 +202,12 @@ function scrollJobToView() {
}
function toggleActiveStep(stepId: number) {
if (stateStore.activeNodeId === stepId) {
stateStore.activeNodeId = null;
if (activeNodeId.value === stepId) {
activeNodeId.value = null;
} else {
stateStore.activeNodeId = stepId;
activeNodeId.value = stepId;
}
}
function getStepKey(step: Step) {
return step.id;
}
</script>

<template>
Expand All @@ -226,66 +222,62 @@ function getStepKey(step: Step) {
<BAlert v-else show variant="danger"> Unknown Error </BAlert>
</div>
<div v-else-if="steps && datatypesMapper">
<ExpandedItems
explicit-key="expanded-invocation-steps"
:scope-key="props.invocation.id"
:get-item-key="getStepKey">
<div ref="invocationContainer" class="d-flex">
<div v-if="!hideGraph" class="position-relative w-100">
<BCard no-body>
<WorkflowGraph
:steps="steps"
:datatypes-mapper="datatypesMapper"
:initial-position="initialPosition"
:scroll-to-id="stateStore.activeNodeId"
:show-minimap="props.showMinimap"
:show-zoom-controls="props.showZoomControls"
is-invocation
readonly />
</BCard>
<BButton
class="position-absolute text-decoration-none m-2"
style="top: 0; right: 0"
size="sm"
@click="hideGraph = true">
<FontAwesomeIcon :icon="faTimes" class="mr-1" />
<span v-localize>Hide Graph</span>
</BButton>
</div>
<div ref="invocationContainer" class="d-flex">
<div v-if="!hideGraph" class="position-relative w-100">
<BCard no-body>
<WorkflowGraph
:steps="steps"
:datatypes-mapper="datatypesMapper"
:initial-position="initialPosition"
:scroll-to-id="activeNodeId"
:show-minimap="props.showMinimap"
:show-zoom-controls="props.showZoomControls"
is-invocation
readonly />
</BCard>
<BButton
v-else
v-b-tooltip.noninteractive.hover.right="'Show Graph'"
class="position-absolute text-decoration-none m-2"
style="top: 0; right: 0"
size="sm"
class="p-0"
style="width: min-content"
@click="hideGraph = false">
<FontAwesomeIcon :icon="faSitemap" />
<div v-localize>Show Graph</div>
@click="hideGraph = true">
<FontAwesomeIcon :icon="faTimes" class="mr-1" />
<span v-localize>Hide Graph</span>
</BButton>
<component
:is="!hideGraph ? FlexPanel : 'div'"
v-if="containerWidth"
side="right"
:collapsible="false"
class="ml-2"
:class="{ 'w-100': hideGraph }"
:min-width="minWidth"
:max-width="maxWidth"
:default-width="containerWidth * 0.4">
<WorkflowInvocationSteps
class="graph-steps-aside"
:steps="steps"
:store-id="storeId"
:invocation="invocationRef"
:workflow="props.workflow"
:is-full-page="props.isFullPage"
:hide-graph="hideGraph"
:showing-job-id="showingJobId || ''"
@update:showing-job-id="(jobId) => (showingJobId = jobId)"
@focus-on-step="toggleActiveStep" />
</component>
</div>
</ExpandedItems>
<BButton
v-else
v-b-tooltip.noninteractive.hover.right="'Show Graph'"
size="sm"
class="p-0"
style="width: min-content"
@click="hideGraph = false">
<FontAwesomeIcon :icon="faSitemap" />
<div v-localize>Show Graph</div>
</BButton>
<component
:is="!hideGraph ? FlexPanel : 'div'"
v-if="containerWidth"
side="right"
:collapsible="false"
class="ml-2"
:class="{ 'w-100': hideGraph }"
:min-width="minWidth"
:max-width="maxWidth"
:default-width="containerWidth * 0.4">
<WorkflowInvocationSteps
class="graph-steps-aside"
:steps="steps"
:store-id="storeId"
:invocation="invocationRef"
:workflow="props.workflow"
:is-full-page="props.isFullPage"
:hide-graph="hideGraph"
:showing-job-id="showingJobId || ''"
:active-node-id="activeNodeId !== null ? activeNodeId : undefined"
@update:showing-job-id="(jobId) => (showingJobId = jobId)"
@focus-on-step="toggleActiveStep" />
</component>
</div>
<BCard v-if="!hideGraph" ref="jobCard" class="mt-1" no-body>
<BCardHeader class="d-flex justify-content-between align-items-center">
<Heading inline size="md">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faChevronDown, faChevronUp, faSignInAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { storeToRefs } from "pinia";
import { computed, ref, watch } from "vue";
import { components } from "@/api/schema";
import { isWorkflowInput } from "@/components/Workflow/constants";
import type { GraphStep } from "@/composables/useInvocationGraph";
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
import type { Workflow } from "@/stores/workflowStore";
import WorkflowInvocationStep from "@/components/WorkflowInvocationState/WorkflowInvocationStep.vue";
Expand All @@ -30,6 +28,8 @@ interface Props {
showingJobId: string;
/** Whether the steps are being rendered on the dedicated invocation page/route */
isFullPage?: boolean;
/** The active node on the graph */
activeNodeId?: number;
}
const emit = defineEmits<{
Expand All @@ -39,36 +39,36 @@ const emit = defineEmits<{
const props = withDefaults(defineProps<Props>(), {
hideGraph: true,
activeNodeId: undefined,
});
const stepsDiv = ref<HTMLDivElement>();
const expandInvocationInputs = ref(false);
const stateStore = useWorkflowStateStore(props.storeId);
const { activeNodeId } = storeToRefs(stateStore);
const workflowInputSteps = Object.values(props.workflow.steps).filter((step) => isWorkflowInput(step.type));
const hasSingularInput = computed(() => workflowInputSteps.length === 1);
const workflowRemainingSteps = hasSingularInput.value
? Object.values(props.workflow.steps)
: Object.values(props.workflow.steps).filter((step) => !isWorkflowInput(step.type));
watch(
() => [activeNodeId.value, stepsDiv.value],
() => [props.activeNodeId, stepsDiv.value],
async ([nodeId, card]) => {
// if the active node id is an input step, expand the inputs section, else, collapse it
const isAnInput = workflowInputSteps.findIndex((step) => step.id === props.activeNodeId) !== -1;
expandInvocationInputs.value = isAnInput;
// on full page view, scroll to the active step card in the steps section
if (props.isFullPage) {
if (nodeId !== undefined && card) {
// if the active node id is an input step, expand the inputs section, else, collapse it
const isAnInput = workflowInputSteps.findIndex((step) => step.id === activeNodeId.value) !== -1;
expandInvocationInputs.value = isAnInput;
// scroll to the input steps header
if (isAnInput) {
const inputHeader = stepsDiv.value?.querySelector(`.invocation-inputs-header`);
inputHeader?.scrollIntoView({ behavior: "smooth" });
}
// scroll to the active step card
const stepCard = stepsDiv.value?.querySelector(`[data-index="${activeNodeId.value}"]`);
const stepCard = stepsDiv.value?.querySelector(`[data-index="${props.activeNodeId}"]`);
stepCard?.scrollIntoView();
}
}
Expand Down Expand Up @@ -112,7 +112,7 @@ function showJob(jobId: string | undefined) {
:workflow-step="step"
:in-graph-view="!props.hideGraph"
:graph-step="steps[step.id]"
:expanded="props.hideGraph ? undefined : activeNodeId === step.id"
:expanded="props.hideGraph ? undefined : props.activeNodeId === step.id"
:showing-job-id="props.showingJobId"
@show-job="showJob"
@update:expanded="emit('focus-on-step', step.id)" />
Expand All @@ -128,7 +128,7 @@ function showJob(jobId: string | undefined) {
:workflow-step="step"
:in-graph-view="!props.hideGraph"
:graph-step="steps[step.id]"
:expanded="props.hideGraph ? undefined : activeNodeId === step.id"
:expanded="props.hideGraph ? undefined : props.activeNodeId === step.id"
:showing-job-id="props.showingJobId"
@show-job="showJob"
@update:expanded="emit('focus-on-step', step.id)" />
Expand Down
40 changes: 33 additions & 7 deletions client/src/components/Workflow/InvocationsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
style="right: 0"
:data-invocation-id="row.item.id">
<b>Last updated: <UtcDate :date="row.item.update_time" mode="elapsed" />;</b>
<b
>Invocation ID:
<router-link :to="invocationLink(row.item)">{{ row.item.id }}</router-link></b
>
<b>
Invocation ID:
<a :href="invocationLink(row.item)" @click.prevent="(e) => openInvocation(e, row)">{{
row.item.id
}}</a>
</b>
</small>
<WorkflowInvocationState :invocation-id="row.item.id" @invocation-cancelled="refresh" />
</b-card>
Expand All @@ -61,12 +63,13 @@
</template>
<template v-slot:cell(workflow_id)="data">
<div class="truncate">
<router-link
<a
v-b-tooltip.hover.html
:title="`<b>View run for</b><br />${getStoredWorkflowNameByInstanceId(data.item.workflow_id)}`"
:to="invocationLink(data.item)">
:href="invocationLink(data.item)"
@click="(e) => openInvocation(e, data)">
{{ getStoredWorkflowNameByInstanceId(data.item.workflow_id) }}
</router-link>
</a>
</div>
</template>
<template v-slot:cell(history_id)="data">
Expand Down Expand Up @@ -114,6 +117,7 @@ import { invocationsProvider } from "components/providers/InvocationsProvider";
import UtcDate from "components/UtcDate";
import WorkflowInvocationState from "components/WorkflowInvocationState/WorkflowInvocationState";
import { mapActions, mapState } from "pinia";
import Vue from "vue";
import { useHistoryStore } from "@/stores/historyStore";
import { useWorkflowStore } from "@/stores/workflowStore";
Expand Down Expand Up @@ -233,6 +237,28 @@ export default {
invocationLink(item) {
return `/workflows/invocations/${item.id}`;
},
openInvocation(e, row) {
if (e.ctrlKey || e.metaKey) {
// open in new tab and ignore toggling details
return;
}
// otherwise, manually toggle details and navigate
e.preventDefault();
const { item, detailsShowing } = row;
if (detailsShowing) {
// If you route when an invocation is expanded, somehow the workflowStores are not disposed
// entirely when you get to the full page view
// therefore, we close the details
row.toggleDetails();
// and then wait for stores to be disposed before routing
Vue.nextTick(() => {
this.$router.push(this.invocationLink(item));
});
} else {
this.$router.push(this.invocationLink(item));
}
},
},
};
</script>
Expand Down

0 comments on commit 8fa85d6

Please sign in to comment.