Skip to content

Commit

Permalink
feat: update input ports on different output selection (#2918)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Chang <mwdchang@gmail.com>
  • Loading branch information
shawnyama and mwdchang authored Mar 7, 2024
1 parent 6970e2d commit b88b22e
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ header {
.header-label {
font-size: var(--font-caption);
}
.warning {
.warning,
.invalid {
background-color: var(--surface-warning);
}
Expand All @@ -94,7 +95,8 @@ header {
color: var(--text-color-disabled);
}
.focus {
/* FIXME: Should consider making the color darker programmatically instead of doing it by a case by case basis*/
.default.focus {
background-color: var(--surface-highlight-hover);
}
Expand Down
61 changes: 54 additions & 7 deletions packages/client/hmi-client/src/services/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const addNode = (
value: null
})),
*/
status: OperatorStatus.INVALID,
status: OperatorStatus.DEFAULT,

width: nodeSize.width,
height: nodeSize.height
Expand Down Expand Up @@ -331,6 +331,17 @@ export class WorkflowRegistry {
}
}

export function cascadeInvalidateDownstream(
sourceNode: WorkflowNode<any>,
nodeCache: Map<WorkflowOutput<any>['id'], WorkflowNode<any>[]>
) {
const downstreamNodes = nodeCache.get(sourceNode.id);
downstreamNodes?.forEach((node) => {
node.status = OperatorStatus.INVALID;
cascadeInvalidateDownstream(node, nodeCache); // Recurse
});
}

///
// Operator
///
Expand All @@ -345,25 +356,61 @@ export class WorkflowRegistry {
*/

export function selectOutput(
wf: Workflow,
operator: WorkflowNode<any>,
selectedWorkflowOutputId: WorkflowOutput<any>['id']
) {
operator.outputs.forEach((output) => {
output.isSelected = false;
output.status = WorkflowPortStatus.NOT_CONNECTED;
});

// Update the Operator state with the selected one
const selected = operator.outputs.find((output) => output.id === selectedWorkflowOutputId);
if (selected) {
selected.isSelected = true;
operator.state = Object.assign(operator.state, _.cloneDeep(selected.state));
operator.status = selected.operatorStatus ?? OperatorStatus.DEFAULT;
operator.active = selected.id;
} else {
if (!selected) {
logger.warn(
`Operator Output Id ${selectedWorkflowOutputId} does not exist within ${operator.displayName} Operator ${operator.id}.`
);
return;
}

selected.isSelected = true;
operator.state = Object.assign(operator.state, _.cloneDeep(selected.state));
operator.status = selected.operatorStatus ?? OperatorStatus.DEFAULT;
operator.active = selected.id;

// If this output is connected to input port(s), update the input port(s)
const hasOutgoingEdges = wf.edges.some((edge) => edge.source === operator.id);
if (!hasOutgoingEdges) return;

selected.status = WorkflowPortStatus.CONNECTED;

const nodeMap = new Map<WorkflowNode<any>['id'], WorkflowNode<any>>(
wf.nodes.map((node) => [node.id, node])
);
const nodeCache = new Map<WorkflowOutput<any>['id'], WorkflowNode<any>[]>();
nodeCache.set(operator.id, []);

wf.edges.forEach((edge) => {
// Update the input port of the direct target node
if (edge.source === operator.id) {
const targetNode = wf.nodes.find((node) => node.id === edge.target);
if (!targetNode) return;
// Update the input port of the target node
const targetPort = targetNode.inputs.find((port) => port.id === edge.targetPortId);
if (!targetPort) return;
edge.sourcePortId = selected.id; // Sync edge source port to selected output
targetPort.label = selected.label;
targetPort.value = selected.value;
}

// Collect node cache
if (!edge.source || !edge.target) return;
if (!nodeCache.has(edge.source)) nodeCache.set(edge.source, []);
nodeCache.get(edge.source)?.push(nodeMap.get(edge.target) as WorkflowNode<any>);
});

cascadeInvalidateDownstream(operator, nodeCache);
}

export function updateOutputPort(node: WorkflowNode<any>, updatedOutputPort: WorkflowOutput<any>) {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/hmi-client/src/types/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export enum WorkflowOperationTypes {
FUNMAN = 'Funman',
CODE = 'Code',
MODEL_COMPARISON = 'ModelComparison',
MODEL_CONFIG = 'ModelConfiguraiton',
MODEL_CONFIG = 'ModelConfiguration',
OPTIMIZE_CIEMSS = 'OptimizeCiemss',
MODEL_COUPLING = 'ModelCoupling',
MODEL_EDIT = 'ModelEdit',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
</template>
<template v-else-if="modelType === AMRSchemaNames.REGNET">
<AccordionTab header="Vertices">
<DataTable v-if="!_.isEmpty(vertices)" data-key="id" :value="vertices">
<DataTable v-if="!isEmpty(vertices)" data-key="id" :value="vertices">
<Column field="id" header="Symbol" />
<Column field="name" header="Name" />
<Column field="rate_constant" header="Rate Constant" />
Expand All @@ -137,7 +137,7 @@
</DataTable>
</AccordionTab>
<AccordionTab header="Edges">
<DataTable v-if="!_.isEmpty(edges)" data-key="id" :value="edges">
<DataTable v-if="!isEmpty(edges)" data-key="id" :value="edges">
<Column field="id" header="Symbol" />
<Column field="source" header="Source" />
<Column field="target" header="Target" />
Expand Down Expand Up @@ -242,7 +242,7 @@
</template>

<script setup lang="ts">
import _ from 'lodash';
import { cloneDeep, isEmpty } from 'lodash';
import { computed, ref, watch, onUnmounted, onMounted } from 'vue';
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
Expand Down Expand Up @@ -291,7 +291,7 @@ const props = defineProps<{
}>();
const outputs = computed(() => {
if (!_.isEmpty(props.node.outputs)) {
if (!isEmpty(props.node.outputs)) {
return [
{
label: 'Select outputs to display in operator',
Expand Down Expand Up @@ -397,7 +397,7 @@ const vertices = computed(() => modelConfiguration?.value?.configuration.model?.
// FIXME: Copy pasted in 3 locations, could be written cleaner and in a service
const saveCodeToState = (code: string, hasCodeBeenRun: boolean) => {
const state = _.cloneDeep(props.node.state);
const state = cloneDeep(props.node.state);
state.hasCodeBeenRun = hasCodeBeenRun;
// for now only save the last code executed, may want to save all code executed in the future
Expand Down Expand Up @@ -436,7 +436,7 @@ const extractConfigurationsFromInputs = async () => {
const handleModelPreview = (data: any) => {
if (!model.value) return;
// Only update the keys provided in the model preview (not ID, temporary ect)
Object.assign(model.value, _.cloneDeep(data.content['application/json']));
Object.assign(model.value, cloneDeep(data.content['application/json']));
const ode = model.value?.semantics?.ode;
knobs.value.initials = ode?.initials !== undefined ? ode?.initials : [];
knobs.value.parameters = ode?.parameters !== undefined ? ode?.parameters : [];
Expand Down Expand Up @@ -469,7 +469,7 @@ const model = ref<Model | null>(null);
const modelConfiguration = computed<ModelConfiguration | null>(() => {
if (!model.value) return null;
const cloneModel = _.cloneDeep(model.value);
const cloneModel = cloneDeep(model.value);
const modelConfig: ModelConfiguration = {
id: '',
Expand Down Expand Up @@ -685,7 +685,7 @@ const updateFromConfig = (config: ModelConfiguration) => {
const createConfiguration = async () => {
if (!model.value) return;
const state = _.cloneDeep(props.node.state);
const state = cloneDeep(props.node.state);
const data = await createModelConfiguration(
model.value.id,
knobs.value.name,
Expand Down Expand Up @@ -724,7 +724,7 @@ const fetchConfigurations = async (modelId: string) => {
// Creates a temp config (if doesnt exist in state)
// This is used for beaker context when there are no outputs in the node
const createTempModelConfig = async () => {
const state = _.cloneDeep(props.node.state);
const state = cloneDeep(props.node.state);
if (state.tempConfigId !== '' || !model.value) return;
const data = await createModelConfiguration(
model.value.id,
Expand Down Expand Up @@ -823,7 +823,7 @@ onMounted(async () => {
watch(
() => knobs.value,
async () => {
const state = _.cloneDeep(props.node.state);
const state = cloneDeep(props.node.state);
state.name = knobs.value.name;
state.description = knobs.value.description;
state.initials = knobs.value.initials;
Expand Down
2 changes: 1 addition & 1 deletion packages/client/hmi-client/src/workflow/tera-workflow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ function updateWorkflowNodeState(node: WorkflowNode<any> | null, state: any) {
function selectOutput(node: WorkflowNode<any> | null, selectedOutputId: string) {
if (!node) return;
workflowService.selectOutput(node, selectedOutputId);
workflowService.selectOutput(wf.value, node, selectedOutputId);
workflowDirty = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public enum WorkflowOperationTypes {
MODEL_FROM_CODE = 'ModelFromCode',
FUNMAN = 'Funman',
CODE = 'Code',
MODEL_CONFIG = 'ModelConfiguraiton',
MODEL_CONFIG = 'ModelConfiguration',
OPTIMIZE_CIEMSS = 'OptimizeCiemss',
MODEL_COUPLING = 'ModelCoupling',
MODEL_EDIT = 'ModelEdit',
Expand All @@ -44,7 +44,7 @@ public enum WorkflowOperationTypes {
MODEL_FROM_CODE("ModelFromCode"),
FUNMAN("Funman"),
CODE("Code"),
MODEL_CONFIG("ModelConfiguraiton"),
MODEL_CONFIG("ModelConfiguration"),
OPTIMIZE_CIEMSS("OptimizeCiemss"),
MODEL_COUPLING("ModelCoupling"),
MODEL_EDIT("ModelEdit"),
Expand Down

0 comments on commit b88b22e

Please sign in to comment.