fix: make not filled required fields be validated before sending to the backend (#8711)
* Added validation for nodes before building, make validation message appear as toast instead of build error * Make subgraph validation not occur in flowStore * Added function to search for connected nodes down or upstream * Added validation before flow starts of the connected flows * Catch error when sending message and restore chat value * Made enter not behave as enter on chat input text area * made sendMessage be async and throw errors * Added build status error to the nodes that didn't make it past validation. * Fixed flow not running when not every edge is showed --------- Co-authored-by: Mike Fortman <michael.fortman@datastax.com>
This commit is contained in:
parent
a03e21018a
commit
8bac1094c9
10 changed files with 109 additions and 44 deletions
|
|
@ -3,6 +3,7 @@ import {
|
|||
STATUS_BUILD,
|
||||
STATUS_BUILDING,
|
||||
STATUS_INACTIVE,
|
||||
STATUS_MISSING_FIELDS_ERROR,
|
||||
} from "@/constants/constants";
|
||||
import { BuildStatus } from "@/constants/enums";
|
||||
|
||||
|
|
@ -58,6 +59,11 @@ const BuildStatusDisplay = ({
|
|||
return <StatusMessage>{STATUS_INACTIVE}</StatusMessage>;
|
||||
}
|
||||
|
||||
if (buildStatus === BuildStatus.ERROR && !validationStatus) {
|
||||
// If the build status is error and there is no validation status, it means that it failed before building, so show the Missing Required Fields error message
|
||||
return <StatusMessage>{STATUS_MISSING_FIELDS_ERROR}</StatusMessage>;
|
||||
}
|
||||
|
||||
if (!validationStatus) {
|
||||
return <StatusMessage>{STATUS_BUILD}</StatusMessage>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -735,6 +735,8 @@ export const INSERT_API_KEY = "Insert your Langflow API key.";
|
|||
export const INVALID_API_KEY = "Your API key is not valid. ";
|
||||
export const CREATE_API_KEY = `Don't have an API key? Sign up at`;
|
||||
export const STATUS_BUILD = "Build to validate status.";
|
||||
export const STATUS_MISSING_FIELDS_ERROR =
|
||||
"Please fill all the required fields.";
|
||||
export const STATUS_INACTIVE = "Execution blocked";
|
||||
export const STATUS_BUILDING = "Building...";
|
||||
export const SAVED_HOVER = "Last saved: ";
|
||||
|
|
|
|||
|
|
@ -162,11 +162,21 @@ export default function ChatInput({
|
|||
};
|
||||
}, [handleFileChange, currentFlowId, isBuilding]);
|
||||
|
||||
const send = () => {
|
||||
sendMessage({
|
||||
repeat: 1,
|
||||
files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
|
||||
});
|
||||
const setChatValueStore = useUtilityStore((state) => state.setChatValueStore);
|
||||
|
||||
const send = async () => {
|
||||
const storedChatValue = chatValue;
|
||||
try {
|
||||
await sendMessage({
|
||||
repeat: 1,
|
||||
files: files
|
||||
.map((file) => file.path ?? "")
|
||||
.filter((file) => file !== ""),
|
||||
});
|
||||
} catch (error) {
|
||||
setChatValueStore(storedChatValue);
|
||||
}
|
||||
|
||||
setFiles([]);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const TextAreaWrapper = ({
|
|||
data-testid="input-chat-playground"
|
||||
onKeyDown={(event) => {
|
||||
if (checkSendingOk(event)) {
|
||||
event.preventDefault();
|
||||
send();
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ export default function ChatView({
|
|||
<CustomChatInput
|
||||
playgroundPage={!!playgroundPage}
|
||||
noInput={!inputTypes.includes("ChatInput")}
|
||||
sendMessage={({ repeat, files }) => {
|
||||
sendMessage({ repeat, files });
|
||||
sendMessage={async ({ repeat, files }) => {
|
||||
await sendMessage({ repeat, files });
|
||||
track("Playground Message Sent");
|
||||
}}
|
||||
inputRef={ref}
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ export default function IOModal({
|
|||
eventDelivery: eventDeliveryConfig,
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
checkChatInput,
|
||||
cleanEdges,
|
||||
detectBrokenEdgesEdges,
|
||||
getConnectedSubgraph,
|
||||
getHandleId,
|
||||
getNodeId,
|
||||
scapeJSONParse,
|
||||
|
|
@ -658,52 +659,56 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
get().setIsBuilding(true);
|
||||
set({ flowBuildStatus: {} });
|
||||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
||||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
||||
const setErrorData = useAlertStore.getState().setErrorData;
|
||||
|
||||
const edges = get().edges;
|
||||
let error = false;
|
||||
let errors: string[] = [];
|
||||
for (const edge of edges) {
|
||||
const errorsEdge = validateEdge(edge, get().nodes, edges);
|
||||
|
||||
// Only validate upstream nodes/edges if startNodeId is provided
|
||||
let nodesToValidate = get().nodes;
|
||||
let edgesToValidate = edges;
|
||||
if (startNodeId) {
|
||||
const downstream = getConnectedSubgraph(
|
||||
startNodeId,
|
||||
get().nodes,
|
||||
edges,
|
||||
"downstream",
|
||||
);
|
||||
nodesToValidate = downstream.nodes;
|
||||
edgesToValidate = downstream.edges;
|
||||
} else if (stopNodeId) {
|
||||
const upstream = getConnectedSubgraph(
|
||||
stopNodeId,
|
||||
get().nodes,
|
||||
edges,
|
||||
"upstream",
|
||||
);
|
||||
nodesToValidate = upstream.nodes;
|
||||
edgesToValidate = upstream.edges;
|
||||
}
|
||||
|
||||
for (const edge of edgesToValidate) {
|
||||
const errorsEdge = validateEdge(edge, nodesToValidate, edgesToValidate);
|
||||
if (errorsEdge.length > 0) {
|
||||
error = true;
|
||||
errors.push(errorsEdge.join("\n"));
|
||||
useAlertStore.getState().addNotificationToHistory({
|
||||
title: MISSED_ERROR_ALERT,
|
||||
type: "error",
|
||||
list: errorsEdge,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
const errorsObjs = validateNodes(nodesToValidate, edges);
|
||||
|
||||
errors = errors.concat(errorsObjs.map((obj) => obj.errors).flat());
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: MISSED_ERROR_ALERT,
|
||||
list: errors,
|
||||
});
|
||||
const ids = errorsObjs.map((obj) => obj.id).flat();
|
||||
get().updateBuildStatus(ids, BuildStatus.ERROR); // Set only the build status as error without adding info to the flow pool
|
||||
|
||||
get().setIsBuilding(false);
|
||||
get().setBuildInfo({ error: errors, success: false });
|
||||
throw new Error("Invalid components");
|
||||
}
|
||||
|
||||
function validateSubgraph(nodes: string[]) {
|
||||
const errorsObjs = validateNodes(
|
||||
get().nodes.filter((node) => nodes.includes(node.id)),
|
||||
get().edges,
|
||||
);
|
||||
|
||||
const errors = errorsObjs.map((obj) => obj.errors).flat();
|
||||
if (errors.length > 0) {
|
||||
get().setBuildInfo({ error: errors, success: false });
|
||||
useAlertStore.getState().addNotificationToHistory({
|
||||
title: MISSED_ERROR_ALERT,
|
||||
type: "error",
|
||||
list: errors,
|
||||
});
|
||||
get().setIsBuilding(false);
|
||||
const ids = errorsObjs.map((obj) => obj.id).flat();
|
||||
|
||||
get().updateBuildStatus(ids, BuildStatus.ERROR);
|
||||
throw new Error("Invalid components");
|
||||
}
|
||||
// get().updateEdgesRunningByNodes(nodes, true);
|
||||
}
|
||||
function validateSubgraph() {}
|
||||
function handleBuildUpdate(
|
||||
vertexBuildData: VertexBuildTypeAPI,
|
||||
status: BuildStatus,
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ export type ChatInputType = {
|
|||
}: {
|
||||
repeat: number;
|
||||
files?: string[];
|
||||
}) => void;
|
||||
}) => Promise<void>;
|
||||
playgroundPage: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -840,7 +840,7 @@ export type chatViewProps = {
|
|||
}: {
|
||||
repeat: number;
|
||||
files?: string[];
|
||||
}) => void;
|
||||
}) => Promise<void>;
|
||||
visibleSession?: string;
|
||||
focusChat?: string;
|
||||
closeChat?: () => void;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { MISSED_ERROR_ALERT } from "@/constants/alerts_constants";
|
||||
import {
|
||||
BASE_URL_API,
|
||||
BUILD_POLLING_INTERVAL,
|
||||
POLLING_MESSAGES,
|
||||
} from "@/constants/constants";
|
||||
|
|
|
|||
|
|
@ -2094,3 +2094,44 @@ export function buildPositionDictionary(nodes: AllNodeType[]) {
|
|||
export function hasStreaming(nodes: AllNodeType[]) {
|
||||
return nodes.some((node) => node.data.node?.template?.stream?.value);
|
||||
}
|
||||
|
||||
// Utility to get all connected nodes and edges from a given nodeId, in a given direction
|
||||
export function getConnectedSubgraph(
|
||||
nodeId: string,
|
||||
nodes: AllNodeType[],
|
||||
edges: EdgeType[],
|
||||
direction: "upstream" | "downstream",
|
||||
): { nodes: AllNodeType[]; edges: EdgeType[] } {
|
||||
const visited = new Set<string>();
|
||||
const resultNodes: AllNodeType[] = [];
|
||||
const resultEdges: EdgeType[] = [];
|
||||
|
||||
function dfs(currentId: string) {
|
||||
if (visited.has(currentId)) return;
|
||||
visited.add(currentId);
|
||||
const node = nodes.find((n) => n.id === currentId);
|
||||
if (node) {
|
||||
resultNodes.push(node);
|
||||
if (direction === "upstream") {
|
||||
// Find all incoming edges
|
||||
const incomingEdges = edges.filter((e) => e.target === currentId);
|
||||
for (const edge of incomingEdges) {
|
||||
resultEdges.push(edge);
|
||||
dfs(edge.source);
|
||||
}
|
||||
} else {
|
||||
// downstream: Find all outgoing edges
|
||||
const outgoingEdges = edges.filter((e) => e.source === currentId);
|
||||
for (const edge of outgoingEdges) {
|
||||
resultEdges.push(edge);
|
||||
dfs(edge.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dfs(nodeId);
|
||||
return {
|
||||
nodes: resultNodes,
|
||||
edges: resultEdges,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue