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_BUILD,
|
||||||
STATUS_BUILDING,
|
STATUS_BUILDING,
|
||||||
STATUS_INACTIVE,
|
STATUS_INACTIVE,
|
||||||
|
STATUS_MISSING_FIELDS_ERROR,
|
||||||
} from "@/constants/constants";
|
} from "@/constants/constants";
|
||||||
import { BuildStatus } from "@/constants/enums";
|
import { BuildStatus } from "@/constants/enums";
|
||||||
|
|
||||||
|
|
@ -58,6 +59,11 @@ const BuildStatusDisplay = ({
|
||||||
return <StatusMessage>{STATUS_INACTIVE}</StatusMessage>;
|
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) {
|
if (!validationStatus) {
|
||||||
return <StatusMessage>{STATUS_BUILD}</StatusMessage>;
|
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 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 CREATE_API_KEY = `Don't have an API key? Sign up at`;
|
||||||
export const STATUS_BUILD = "Build to validate status.";
|
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_INACTIVE = "Execution blocked";
|
||||||
export const STATUS_BUILDING = "Building...";
|
export const STATUS_BUILDING = "Building...";
|
||||||
export const SAVED_HOVER = "Last saved: ";
|
export const SAVED_HOVER = "Last saved: ";
|
||||||
|
|
|
||||||
|
|
@ -162,11 +162,21 @@ export default function ChatInput({
|
||||||
};
|
};
|
||||||
}, [handleFileChange, currentFlowId, isBuilding]);
|
}, [handleFileChange, currentFlowId, isBuilding]);
|
||||||
|
|
||||||
const send = () => {
|
const setChatValueStore = useUtilityStore((state) => state.setChatValueStore);
|
||||||
sendMessage({
|
|
||||||
|
const send = async () => {
|
||||||
|
const storedChatValue = chatValue;
|
||||||
|
try {
|
||||||
|
await sendMessage({
|
||||||
repeat: 1,
|
repeat: 1,
|
||||||
files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
|
files: files
|
||||||
|
.map((file) => file.path ?? "")
|
||||||
|
.filter((file) => file !== ""),
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setChatValueStore(storedChatValue);
|
||||||
|
}
|
||||||
|
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const TextAreaWrapper = ({
|
||||||
data-testid="input-chat-playground"
|
data-testid="input-chat-playground"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (checkSendingOk(event)) {
|
if (checkSendingOk(event)) {
|
||||||
|
event.preventDefault();
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -294,8 +294,8 @@ export default function ChatView({
|
||||||
<CustomChatInput
|
<CustomChatInput
|
||||||
playgroundPage={!!playgroundPage}
|
playgroundPage={!!playgroundPage}
|
||||||
noInput={!inputTypes.includes("ChatInput")}
|
noInput={!inputTypes.includes("ChatInput")}
|
||||||
sendMessage={({ repeat, files }) => {
|
sendMessage={async ({ repeat, files }) => {
|
||||||
sendMessage({ repeat, files });
|
await sendMessage({ repeat, files });
|
||||||
track("Playground Message Sent");
|
track("Playground Message Sent");
|
||||||
}}
|
}}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,7 @@ export default function IOModal({
|
||||||
eventDelivery: eventDeliveryConfig,
|
eventDelivery: eventDeliveryConfig,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import {
|
||||||
checkChatInput,
|
checkChatInput,
|
||||||
cleanEdges,
|
cleanEdges,
|
||||||
detectBrokenEdgesEdges,
|
detectBrokenEdgesEdges,
|
||||||
|
getConnectedSubgraph,
|
||||||
getHandleId,
|
getHandleId,
|
||||||
getNodeId,
|
getNodeId,
|
||||||
scapeJSONParse,
|
scapeJSONParse,
|
||||||
|
|
@ -658,52 +659,56 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
get().setIsBuilding(true);
|
get().setIsBuilding(true);
|
||||||
set({ flowBuildStatus: {} });
|
set({ flowBuildStatus: {} });
|
||||||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
||||||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
|
|
||||||
const edges = get().edges;
|
const edges = get().edges;
|
||||||
let error = false;
|
|
||||||
let errors: string[] = [];
|
let errors: string[] = [];
|
||||||
for (const edge of edges) {
|
|
||||||
const errorsEdge = validateEdge(edge, get().nodes, edges);
|
|
||||||
if (errorsEdge.length > 0) {
|
|
||||||
error = true;
|
|
||||||
errors.push(errorsEdge.join("\n"));
|
|
||||||
useAlertStore.getState().addNotificationToHistory({
|
|
||||||
title: MISSED_ERROR_ALERT,
|
|
||||||
type: "error",
|
|
||||||
list: errorsEdge,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
get().setIsBuilding(false);
|
|
||||||
get().setBuildInfo({ error: errors, success: false });
|
|
||||||
throw new Error("Invalid components");
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateSubgraph(nodes: string[]) {
|
// Only validate upstream nodes/edges if startNodeId is provided
|
||||||
const errorsObjs = validateNodes(
|
let nodesToValidate = get().nodes;
|
||||||
get().nodes.filter((node) => nodes.includes(node.id)),
|
let edgesToValidate = edges;
|
||||||
get().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;
|
||||||
|
}
|
||||||
|
|
||||||
const errors = errorsObjs.map((obj) => obj.errors).flat();
|
for (const edge of edgesToValidate) {
|
||||||
|
const errorsEdge = validateEdge(edge, nodesToValidate, edgesToValidate);
|
||||||
|
if (errorsEdge.length > 0) {
|
||||||
|
errors.push(errorsEdge.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const errorsObjs = validateNodes(nodesToValidate, edges);
|
||||||
|
|
||||||
|
errors = errors.concat(errorsObjs.map((obj) => obj.errors).flat());
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
get().setBuildInfo({ error: errors, success: false });
|
setErrorData({
|
||||||
useAlertStore.getState().addNotificationToHistory({
|
|
||||||
title: MISSED_ERROR_ALERT,
|
title: MISSED_ERROR_ALERT,
|
||||||
type: "error",
|
|
||||||
list: errors,
|
list: errors,
|
||||||
});
|
});
|
||||||
get().setIsBuilding(false);
|
|
||||||
const ids = errorsObjs.map((obj) => obj.id).flat();
|
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().updateBuildStatus(ids, BuildStatus.ERROR);
|
get().setIsBuilding(false);
|
||||||
throw new Error("Invalid components");
|
throw new Error("Invalid components");
|
||||||
}
|
}
|
||||||
// get().updateEdgesRunningByNodes(nodes, true);
|
|
||||||
}
|
function validateSubgraph() {}
|
||||||
function handleBuildUpdate(
|
function handleBuildUpdate(
|
||||||
vertexBuildData: VertexBuildTypeAPI,
|
vertexBuildData: VertexBuildTypeAPI,
|
||||||
status: BuildStatus,
|
status: BuildStatus,
|
||||||
|
|
|
||||||
|
|
@ -551,7 +551,7 @@ export type ChatInputType = {
|
||||||
}: {
|
}: {
|
||||||
repeat: number;
|
repeat: number;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
}) => void;
|
}) => Promise<void>;
|
||||||
playgroundPage: boolean;
|
playgroundPage: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -840,7 +840,7 @@ export type chatViewProps = {
|
||||||
}: {
|
}: {
|
||||||
repeat: number;
|
repeat: number;
|
||||||
files?: string[];
|
files?: string[];
|
||||||
}) => void;
|
}) => Promise<void>;
|
||||||
visibleSession?: string;
|
visibleSession?: string;
|
||||||
focusChat?: string;
|
focusChat?: string;
|
||||||
closeChat?: () => void;
|
closeChat?: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { MISSED_ERROR_ALERT } from "@/constants/alerts_constants";
|
import { MISSED_ERROR_ALERT } from "@/constants/alerts_constants";
|
||||||
import {
|
import {
|
||||||
BASE_URL_API,
|
|
||||||
BUILD_POLLING_INTERVAL,
|
BUILD_POLLING_INTERVAL,
|
||||||
POLLING_MESSAGES,
|
POLLING_MESSAGES,
|
||||||
} from "@/constants/constants";
|
} from "@/constants/constants";
|
||||||
|
|
|
||||||
|
|
@ -2094,3 +2094,44 @@ export function buildPositionDictionary(nodes: AllNodeType[]) {
|
||||||
export function hasStreaming(nodes: AllNodeType[]) {
|
export function hasStreaming(nodes: AllNodeType[]) {
|
||||||
return nodes.some((node) => node.data.node?.template?.stream?.value);
|
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