Implement "In Progress" Status for Component Building (#1449)

This pull request introduces a new "In Progress" status for the
component building process within our application. This status will
bridge the gap between "Not Started" and "Completed," providing more
granular visibility into the component's lifecycle.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-02-20 11:33:57 -03:00 • committed by GitHub
commit 98ae37f24e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 138 additions and 43 deletions

View file

@ -7,6 +7,7 @@ import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { priorityFields } from "../../constants/constants"; import { priorityFields } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
@ -71,7 +72,6 @@ export default function GenericNode({
setHandles(count); setHandles(count);
} }
useEffect(() => { useEffect(() => {
countHandles(); countHandles();
}, [data, data.node]); }, [data, data.node]);
@ -157,6 +157,78 @@ export default function GenericNode({
); );
}; };
const getIconPlayOrPauseComponent = (name, className) => (
<IconComponent
name={name}
className={`absolute h-5 stroke-2 ${className} ml-0.5`}
/>
);
const getStatusClassName = (
validationStatus: validationStatusType | null,
isBuilding: boolean
) => {
if (validationStatus && validationStatus.valid) {
return "green-status";
} else if (validationStatus && !validationStatus.valid) {
return "red-status";
} else if (!validationStatus || isBuilding) {
return "yellow-status";
} else {
return "status-build-animation";
}
};
const renderIconPlayOrPauseComponents = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null,
isBuilding: boolean
) => {
if (buildStatus === BuildStatus.BUILDING) {
return getIconPlayOrPauseComponent("Square", "red-status");
} else {
const className = getStatusClassName(validationStatus, isBuilding);
return <>{getIconPlayOrPauseComponent("Play", className)}</>;
}
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
) => {
if (
buildStatus === BuildStatus.BUILDED &&
validationStatus &&
!validationStatus.valid
) {
return "border-none ring ring-red-300";
} else if (buildStatus === BuildStatus.BUILDING) {
return "border-none ring";
} else {
return "";
}
};
const getNodeBorderClassName = (
selected: boolean,
showNode: boolean,
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
) => {
return classNames(
getBaseBorderClass(selected),
getNodeSizeClass(showNode),
"generic-node-div",
getSpecificClassFromBuildStatus(buildStatus, validationStatus)
);
};
const getBaseBorderClass = (selected) =>
selected ? "border border-ring" : "border";
const getNodeSizeClass = (showNode) =>
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
return ( return (
<> <>
<NodeToolbar> <NodeToolbar>
@ -181,10 +253,11 @@ export default function GenericNode({
</NodeToolbar> </NodeToolbar>
<div <div
className={classNames( className={getNodeBorderClassName(
selected ? "border border-ring" : "border", selected,
showNode ? " w-96 rounded-lg" : " w-26 h-26 rounded-full", showNode,
"generic-node-div" data?.build_status,
validationStatus
)} )}
> >
{data.node?.beta && showNode && ( {data.node?.beta && showNode && (
@ -396,7 +469,8 @@ export default function GenericNode({
<div> <div>
<Tooltip <Tooltip
title={ title={
isBuilding ? ( isBuilding ||
data?.build_status === BuildStatus.BUILDING ? (
<span>Building...</span> <span>Building...</span>
) : !validationStatus ? ( ) : !validationStatus ? (
<span className="flex"> <span className="flex">
@ -421,33 +495,11 @@ export default function GenericNode({
} }
> >
<div className="generic-node-status-position flex items-center justify-center"> <div className="generic-node-status-position flex items-center justify-center">
<IconComponent {renderIconPlayOrPauseComponents(
name="Play" data?.build_status,
className={classNames( validationStatus,
validationStatus && validationStatus.valid isBuilding
? "green-status" )}
: "status-build-animation",
"absolute h-5 stroke-2"
)}
/>
<IconComponent
name="AlertCircle"
className={classNames(
validationStatus && !validationStatus.valid
? "red-status"
: "status-build-animation",
"absolute h-5 stroke-2"
)}
/>
<IconComponent
name="Play"
className={classNames(
!validationStatus || isBuilding
? "yellow-status"
: "status-build-animation",
"absolute h-5 stroke-2"
)}
/>
</div> </div>
</Tooltip> </Tooltip>
</div> </div>

View file

@ -6,3 +6,9 @@ export enum TypeModal {
TEXT = 1, TEXT = 1,
PROMPT = 2, PROMPT = 2,
} }
export enum BuildStatus {
BUILDING = "BUILDING",
TO_BUILD = "TO_BUILD",
BUILDED = "BUILDED",
}

View file

@ -205,7 +205,6 @@ export default function FormModal({
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
//set chat history //set chat history
setChatHistory((_) => { setChatHistory((_) => {
console.log(data);
let newChatHistory: ChatMessageType[] = []; let newChatHistory: ChatMessageType[] = [];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt" && data[i].prompt) { if (data[i].type === "prompt" && data[i].prompt) {

View file

@ -11,6 +11,7 @@ import {
} from "reactflow"; } from "reactflow";
import { create } from "zustand"; import { create } from "zustand";
import { INPUT_TYPES, OUTPUT_TYPES } from "../constants/constants"; import { INPUT_TYPES, OUTPUT_TYPES } from "../constants/constants";
import { BuildStatus } from "../constants/enums";
import { getFlowPool, updateFlowInDatabase } from "../controllers/API"; import { getFlowPool, updateFlowInDatabase } from "../controllers/API";
import { import {
NodeDataType, NodeDataType,
@ -380,6 +381,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
const setNoticeData = useAlertStore.getState().setNoticeData; const setNoticeData = useAlertStore.getState().setNoticeData;
function handleBuildUpdate(data: any) { function handleBuildUpdate(data: any) {
get().addDataToFlowPool(data.data[data.id], data.id); get().addDataToFlowPool(data.data[data.id], data.id);
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILDED);
} }
await updateFlowInDatabase({ await updateFlowInDatabase({
data: { data: {
@ -403,9 +405,14 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
} }
}, },
onBuildUpdate: handleBuildUpdate, onBuildUpdate: handleBuildUpdate,
onBuildError: (title, list) => { onBuildError: (title, list, idList) => {
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDED);
setErrorData({ list, title }); setErrorData({ list, title });
}, },
onBuildStart: (idList) => {
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
}); });
}, },
getFlow: () => { getFlow: () => {
@ -415,6 +422,15 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
viewport: get().reactFlowInstance?.getViewport()!, viewport: get().reactFlowInstance?.getViewport()!,
}; };
}, },
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
nodeIdList.forEach((id) => {
const nodeToUpdate = get().nodes.find((node) => node.id === id);
if (nodeToUpdate) {
nodeToUpdate.data.build_status = status;
get().setNodes(get().nodes);
}
});
},
})); }));
export default useFlowStore; export default useFlowStore;

View file

@ -1,4 +1,5 @@
import { Edge, Node, Viewport } from "reactflow"; import { Edge, Node, Viewport } from "reactflow";
import { BuildStatus } from "../../constants/enums";
import { FlowType } from "../flow"; import { FlowType } from "../flow";
//kind and class are just representative names to represent the actual structure of the object received by the API //kind and class are just representative names to represent the actual structure of the object received by the API
export type APIDataType = { [key: string]: APIKindType }; export type APIDataType = { [key: string]: APIKindType };
@ -36,6 +37,7 @@ export type APIClassType = {
| CustomFieldsType | CustomFieldsType
| boolean | boolean
| undefined; | undefined;
build_status?: BuildStatus;
}; };
export type TemplateVariableType = { export type TemplateVariableType = {

View file

@ -625,7 +625,7 @@ export type crashComponentPropsType = {
export type validationStatusType = { export type validationStatusType = {
id: string; id: string;
data: object; data: object | any;
params: string; params: string;
progress: number; progress: number;
valid: boolean; valid: boolean;

View file

@ -1,4 +1,5 @@
import { ReactFlowJsonObject, XYPosition } from "reactflow"; import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { BuildStatus } from "../../constants/enums";
import { APIClassType } from "../api/index"; import { APIClassType } from "../api/index";
export type FlowType = { export type FlowType = {
@ -26,6 +27,7 @@ export type NodeDataType = {
node?: APIClassType; node?: APIClassType;
id: string; id: string;
output_types?: string[]; output_types?: string[];
build_status?: BuildStatus;
}; };
// FlowStyleType is the type of the style object that is used to style the // FlowStyleType is the type of the style object that is used to style the
// Flow card with an emoji and a color. // Flow card with an emoji and a color.

View file

@ -7,6 +7,7 @@ import {
ReactFlowInstance, ReactFlowInstance,
Viewport, Viewport,
} from "reactflow"; } from "reactflow";
import { BuildStatus } from "../../../constants/enums";
import { FlowState } from "../../tabs"; import { FlowState } from "../../tabs";
export type chatInputType = { export type chatInputType = {
@ -85,4 +86,5 @@ export type FlowStoreType = {
unselectAll: () => void; unselectAll: () => void;
buildFlow: (nodeId?: string) => Promise<void>; buildFlow: (nodeId?: string) => Promise<void>;
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
}; };

View file

@ -1,5 +1,7 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { BuildStatus } from "../constants/enums";
import { getVerticesOrder, postBuildVertex } from "../controllers/API"; import { getVerticesOrder, postBuildVertex } from "../controllers/API";
import useFlowStore from "../stores/flowStore";
import { VertexBuildTypeAPI } from "../types/api"; import { VertexBuildTypeAPI } from "../types/api";
type BuildVerticesParams = { type BuildVerticesParams = {
@ -8,7 +10,8 @@ type BuildVerticesParams = {
onProgressUpdate?: (progress: number) => void; // Replace number with the actual type if it's not a number onProgressUpdate?: (progress: number) => void; // Replace number with the actual type if it's not a number
onBuildUpdate?: (data: any) => void; // Replace any with the actual type of data onBuildUpdate?: (data: any) => void; // Replace any with the actual type of data
onBuildComplete?: (allNodesValid: boolean) => void; onBuildComplete?: (allNodesValid: boolean) => void;
onBuildError?: (title, list) => void; onBuildError?: (title, list, idList: string[]) => void;
onBuildStart?: (idList: string[]) => void;
}; };
export async function buildVertices({ export async function buildVertices({
@ -18,10 +21,12 @@ export async function buildVertices({
onBuildUpdate, onBuildUpdate,
onBuildComplete, onBuildComplete,
onBuildError, onBuildError,
onBuildStart,
}: BuildVerticesParams) { }: BuildVerticesParams) {
let orderResponse = await getVerticesOrder(flowId, nodeId); let orderResponse = await getVerticesOrder(flowId, nodeId);
let verticesOrder: Array<Array<string>> = orderResponse.data.ids; let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
let vertices: Array<Array<string>> = []; let vertices: Array<Array<string>> = [];
if (nodeId) { if (nodeId) {
for (let i = 0; i < verticesOrder.length; i += 1) { for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i]; const innerArray = verticesOrder[i];
@ -40,10 +45,13 @@ export async function buildVertices({
vertices = verticesOrder; vertices = verticesOrder;
} }
// Set each vertex state to building const verticesIds = vertices.flatMap((v) => v);
useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD);
// Set each vertex state to building
const buildResults: Array<boolean> = []; const buildResults: Array<boolean> = [];
for (let i = 0; i < vertices.length; i += 1) { for (let i = 0; i < vertices.length; i += 1) {
if (onBuildStart) onBuildStart(vertices[i]);
await Promise.all( await Promise.all(
vertices[i].map(async (id) => { vertices[i].map(async (id) => {
try { try {
@ -54,7 +62,11 @@ export async function buildVertices({
let data = {}; let data = {};
if (!buildData.valid) { if (!buildData.valid) {
if (onBuildError) { if (onBuildError) {
onBuildError("Error Building Component", [buildData.params]); onBuildError(
"Error Building Component",
[buildData.params],
verticesIds
);
} }
} }
data[buildData.id] = buildData; data[buildData.id] = buildData;
@ -64,10 +76,14 @@ export async function buildVertices({
} catch (error) { } catch (error) {
if (onBuildError) { if (onBuildError) {
console.log(error); console.log(error);
onBuildError("Error Building Component", [ onBuildError(
(error as AxiosError<any>).response?.data?.detail ?? "Error Building Component",
"Unknown Error", [
]); (error as AxiosError<any>).response?.data?.detail ??
"Unknown Error",
],
verticesIds
);
} }
} }
}) })