Skip to content

Commit

Permalink
show workflow input value on graph
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedhamidawan committed Sep 3, 2024
1 parent 9e92723 commit 0841ef9
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 42 deletions.
20 changes: 17 additions & 3 deletions client/src/components/Workflow/Editor/NodeInvocationText.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { faCheckSquare, faSquare } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { isWorkflowInput } from "@/components/Workflow/constants";
Expand All @@ -24,11 +25,24 @@ const props = defineProps<{
</span>
</div>
</div>
<div v-else-if="isWorkflowInput(props.invocationStep.type)">
<!-- TODO: Maybe put a ContentItem here? -->
This is an input
<div v-else-if="isWorkflowInput(props.invocationStep.type)" class="truncate w-100">
<span v-if="typeof props.invocationStep.nodeText === 'boolean'">
<FontAwesomeIcon :icon="props.invocationStep.nodeText ? faCheckSquare : faSquare" />
{{ props.invocationStep.nodeText }}
</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-else-if="props.invocationStep.nodeText !== undefined" v-html="props.invocationStep.nodeText" />
<span v-else>This is an input</span>
</div>
<div v-else-if="props.invocationStep.type === 'subworkflow'">This is a subworkflow.</div>
<div v-else>This step has no jobs as of yet.</div>
</div>
</template>

<style scoped>
.truncate {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>
102 changes: 63 additions & 39 deletions client/src/composables/useInvocationGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { storeToRefs } from "pinia";
import { computed, type Ref, ref, set } from "vue";

import { GalaxyApi } from "@/api";
import { fetchCollectionDetails } from "@/api/datasetCollections";
import { fetchDatasetDetails } from "@/api/datasets";
import { type InvocationStep, type StepJobSummary, type WorkflowInvocationElementView } from "@/api/invocations";
import { getContentItemState } from "@/components/History/Content/model/states";
import { isWorkflowInput } from "@/components/Workflow/constants";
import { fromSimple } from "@/components/Workflow/Editor/modules/model";
import { getWorkflowFull } from "@/components/Workflow/workflows.services";
Expand Down Expand Up @@ -41,6 +44,7 @@ export interface GraphStep extends Step {
headerClass?: Record<string, boolean>;
headerIcon?: IconDefinition;
headerIconSpin?: boolean;
nodeText?: string | boolean;
}
interface InvocationGraph extends Workflow {
steps: { [index: number]: GraphStep };
Expand Down Expand Up @@ -129,20 +133,13 @@ export function useInvocationGraph(
rethrowSimple(error);
}

// if the steps have not been populated or the job states have changed, update the steps
// TODO: What if the state of something not in the stepsJobsSummary has changed? (e.g.: subworkflows...)
if (
!stepsPopulated.value ||
JSON.stringify(stepsJobsSummary) !== JSON.stringify(lastStepsJobsSummary.value)
) {
updateSteps(stepsJobsSummary);

// Load the invocation graph into the editor the first time
if (!stepsPopulated.value) {
invocationGraph.value!.steps = { ...steps.value };
await fromSimple(storeId.value, invocationGraph.value as any);
stepsPopulated.value = true;
}
await updateSteps(stepsJobsSummary);

// Load the invocation graph into the editor the first time
if (!stepsPopulated.value) {
invocationGraph.value!.steps = { ...steps.value };
await fromSimple(storeId.value, invocationGraph.value as any);
stepsPopulated.value = true;
}
} catch (e) {
rethrowSimple(e);
Expand All @@ -153,7 +150,7 @@ export function useInvocationGraph(
* if they haven't been populated yet.
* @param stepsJobsSummary - The job summary for each step in the invocation
* */
function updateSteps(stepsJobsSummary: StepJobSummary[]) {
async function updateSteps(stepsJobsSummary: StepJobSummary[]) {
/** Initialize with the original steps of the workflow, else update the existing graph steps */
const fullSteps: Record<string, Step | GraphStep> = !stepsPopulated.value
? { ...loadedWorkflow.value.steps }
Expand All @@ -172,7 +169,13 @@ export function useInvocationGraph(
/** The raw invocation step */
const invocationStep = invocation.value.steps[i];

if (!isWorkflowInput(graphStepFromWfStep.type)) {
// TODO: What if the state of something not in the stepsJobsSummary has changed? (e.g.: subworkflows...)
/** if the steps have not been populated or the job states have changed, update the step */
const updateNonInputStep =
!stepsPopulated.value ||
JSON.stringify(stepsJobsSummary) !== JSON.stringify(lastStepsJobsSummary.value);

if (updateNonInputStep && !isWorkflowInput(graphStepFromWfStep.type)) {
let invocationStepSummary: StepJobSummary | undefined;
if (invocationStep) {
invocationStepSummary = stepsJobsSummary.find((stepJobSummary: StepJobSummary) => {
Expand All @@ -184,6 +187,8 @@ export function useInvocationGraph(
});
}
updateStep(graphStepFromWfStep, invocationStep, invocationStepSummary);
} else if (invocationStep && graphStepFromWfStep.nodeText === undefined) {
await initializeGraphInput(graphStepFromWfStep, invocationStep);
}

// add the graph step to the steps object if it doesn't exist yet
Expand Down Expand Up @@ -275,16 +280,7 @@ export function useInvocationGraph(
// if the state has changed, update the graph step
if (graphStep.state !== newState) {
graphStep.state = newState;

/** Setting the header class for the graph step */
graphStep.headerClass = getHeaderClass(graphStep.state as string);
// TODO: maybe a different one for inputs? Currently they have no state either.

/** Setting the header icon for the graph step */
if (graphStep.state) {
graphStep.headerIcon = iconClasses[graphStep.state]?.icon;
graphStep.headerIconSpin = iconClasses[graphStep.state]?.spin;
}
setHeaderClass(graphStep);
}
}

Expand All @@ -308,19 +304,47 @@ export function useInvocationGraph(
return undefined;
}

// TODO: Maybe we can use this to layout the graph after the steps are loaded (for neatness)?
// async function layoutGraph() {
// const newSteps = await autoLayout(storeId.value, steps.value);
// if (newSteps) {
// newSteps?.map((step: any) => stepStore.updateStep(step));
// // Object.assign(steps.value, {...steps.value, ...stepStore.steps});
// Object.keys(steps.value).forEach((key) => {
// steps.value[key] = { ...steps.value[key], ...(stepStore.steps[key] as GraphStep) };
// });
// }
// invocationGraph.value!.steps = steps.value;
// await fromSimple(storeId.value, invocationGraph.value as any);
// }
function setHeaderClass(graphStep: GraphStep) {
/** Setting the header class for the graph step */
graphStep.headerClass = getHeaderClass(graphStep.state as string);

/** Setting the header icon for the graph step */
if (graphStep.state) {
graphStep.headerIcon = iconClasses[graphStep.state]?.icon;
graphStep.headerIconSpin = iconClasses[graphStep.state]?.spin;
}
}

async function initializeGraphInput(graphStep: GraphStep, invocationStep: InvocationStep) {
const inputItem = invocation.value.inputs[graphStep.id];
const inputParam = getWorkflowInputParam(invocation.value, invocationStep);
if (inputItem && inputItem?.id !== undefined && inputItem?.id !== null) {
if (inputItem.src === "hda") {
const hda = await fetchDatasetDetails({ id: inputItem.id });
// TODO: There is a type mismatch for `hda.state` and `GraphStep["state"]`
set(graphStep, "state", getContentItemState(hda));
set(graphStep, "nodeText", `${hda.hid}: <b>${hda.name}</b>`);
} else {
const hdca = await fetchCollectionDetails({ id: inputItem.id });
// TODO: Same type mismatch as above
set(graphStep, "state", getContentItemState(hdca));
set(graphStep, "nodeText", `${hdca.hid}: <b>${hdca.name}</b>`);
}
} else if (inputParam) {
if (typeof inputParam.parameter_value === "boolean") {
set(graphStep, "nodeText", inputParam.parameter_value);
} else {
set(graphStep, "nodeText", `<b>${inputParam.parameter_value}</b>`);
}
}
setHeaderClass(graphStep);
}

function getWorkflowInputParam(invocation: WorkflowInvocationElementView, invocationStep: InvocationStep) {
return Object.values(invocation.input_step_parameters).find(
(param) => param.workflow_step_id === invocationStep.workflow_step_id
);
}

return {
/** An id used to scope the store to the invocation's id */
Expand Down

0 comments on commit 0841ef9

Please sign in to comment.