refactor: change endpoint monitor/builds to use useQuery (#2622)
* ✨ (constants.ts): add BUILDS endpoint to URLs constants ✨ (index.ts): create index file for builds-related queries ✨ (use-delete-builds.ts): implement useDeleteFLowPool hook for deleting builds * ✨ (chatView): integrate useDeleteFlowPool hook for deleting flow pool ♻️ (chatView): refactor clearChat function to use mutateFlowPool for deletion * ♻️ (use-delete-builds.ts): rename useDeleteFLowPool to useDeleteBuilds for clarity ♻️ (index.tsx): update import and usage of useDeleteFLowPool to useDeleteBuilds * ✨ (API): add use-get-builds query to fetch build data ✨ (PageComponent): integrate use-get-builds query for fetching builds ✨ (flowStore): add setters for inputs, outputs, and hasIO in flowStore * ♻️ (flowStore.ts): refactor hasIO to derive its value from inputs and outputs 🔥 (flowStore.ts): remove unused resetFlow function to clean up the codebase * ♻️ (use-get-builds.ts): refactor useGetBuildsQuery to remove unused params ✨ (PageComponent): add logic to handle flow state, inputs, outputs, and viewport ♻️ (flowStore): refactor and add resetFlow method to handle flow state reset * ✅ (chatComponent): add data-testid attributes for better testability ✅ (generalBugs-shard-3.spec.ts): add end-to-end test for playground button state --------- Co-authored-by: anovazzi1 <otavio2204@gmail.com>
This commit is contained in:
parent
0fcba55f51
commit
a811834b93
10 changed files with 203 additions and 25 deletions
|
|
@ -113,7 +113,10 @@ export default function FlowToolbar(): JSX.Element {
|
|||
<div className="flex h-full w-full gap-1 rounded-sm transition-all">
|
||||
{hasIO ? (
|
||||
<IOModal open={open} setOpen={setOpen} disable={!hasIO}>
|
||||
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-hover">
|
||||
<div
|
||||
data-testid="playground-btn-flow-io"
|
||||
className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-hover"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="BotMessageSquareIcon"
|
||||
className={"h-5 w-5 transition-all"}
|
||||
|
|
@ -124,6 +127,7 @@ export default function FlowToolbar(): JSX.Element {
|
|||
) : (
|
||||
<div
|
||||
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out`}
|
||||
data-testid="playground-btn-flow"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="BotMessageSquareIcon"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const URLs = {
|
|||
FILES: `files`,
|
||||
VERSION: `version`,
|
||||
MESSAGES: `monitor/messages`,
|
||||
BUILDS: `monitor/builds`,
|
||||
STORE: `store`,
|
||||
USERS: "users",
|
||||
LOGOUT: `logout`,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./use-delete-builds";
|
||||
export * from "./use-get-builds";
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IDeleteBuilds {
|
||||
flowId: string;
|
||||
}
|
||||
|
||||
// add types for error handling and success
|
||||
export const useDeleteBuilds: useMutationFunctionType<IDeleteBuilds> = (
|
||||
options,
|
||||
) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const deleteBuildsFn = async (payload: IDeleteBuilds): Promise<any> => {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: payload.flowId };
|
||||
const res = await api.delete<any>(`${getURL("BUILDS")}`, config);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
const mutation = mutate(["useDeleteBuilds"], deleteBuildsFn, options);
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { FlowPoolType } from "@/types/zustand/flow";
|
||||
import { cleanEdges } from "@/utils/reactflowUtils";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useQueryFunctionType } from "../../../../types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface BuildsQueryParams {
|
||||
flowId?: string;
|
||||
nodeId?: string;
|
||||
}
|
||||
|
||||
export const useGetBuildsQuery: useQueryFunctionType<
|
||||
BuildsQueryParams,
|
||||
AxiosResponse<{ vertex_builds: FlowPoolType }>
|
||||
> = ({}) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const setFlowPool = useFlowStore((state) => state.setFlowPool);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
|
||||
const getBuildsFn = async (
|
||||
params: BuildsQueryParams,
|
||||
): Promise<AxiosResponse<{ vertex_builds: FlowPoolType }>> => {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: params.flowId };
|
||||
|
||||
if (params.nodeId) {
|
||||
config["params"] = { nodeId: params.nodeId };
|
||||
}
|
||||
|
||||
return await api.get<any>(`${getURL("BUILDS")}`, config);
|
||||
};
|
||||
|
||||
const responseFn = async () => {
|
||||
const response = await getBuildsFn({
|
||||
flowId: currentFlow!.id,
|
||||
});
|
||||
|
||||
if (currentFlow) {
|
||||
const flowPool = response.data.vertex_builds;
|
||||
setFlowPool(flowPool);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const queryResult = query(["useGetBuildsQuery"], responseFn, {
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
return queryResult;
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { useDeleteBuilds } from "@/controllers/API/queries/_builds";
|
||||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
|
|
@ -40,6 +41,7 @@ export default function ChatView({
|
|||
const outputIds = outputs.map((obj) => obj.id);
|
||||
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
|
||||
const [id, setId] = useState<string>("");
|
||||
const { mutate: mutateDeleteFlowPool } = useDeleteBuilds();
|
||||
|
||||
//build chat history
|
||||
useEffect(() => {
|
||||
|
|
@ -116,9 +118,15 @@ export default function ChatView({
|
|||
|
||||
function clearChat(): void {
|
||||
setChatHistory([]);
|
||||
deleteFlowPool(currentFlowId).then((_) => {
|
||||
CleanFlowPool();
|
||||
});
|
||||
|
||||
mutateDeleteFlowPool(
|
||||
{ flowId: currentFlowId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
CleanFlowPool();
|
||||
},
|
||||
},
|
||||
);
|
||||
//TODO tell backend to clear chat session
|
||||
if (lockChat) setLockChat(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import {
|
||||
KeyboardEvent,
|
||||
|
|
@ -35,6 +37,7 @@ import { APIClassType } from "../../../../types/api";
|
|||
import { FlowType, NodeType } from "../../../../types/flow";
|
||||
import {
|
||||
checkOldComponents,
|
||||
cleanEdges,
|
||||
generateFlow,
|
||||
generateNodeFromFlow,
|
||||
getNodeId,
|
||||
|
|
@ -88,7 +91,6 @@ export default function Page({
|
|||
const redo = useFlowsManagerStore((state) => state.redo);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const paste = useFlowStore((state) => state.paste);
|
||||
const resetFlow = useFlowStore((state) => state.resetFlow);
|
||||
const lastCopiedSelection = useFlowStore(
|
||||
(state) => state.lastCopiedSelection,
|
||||
);
|
||||
|
|
@ -106,6 +108,13 @@ export default function Page({
|
|||
const [lastSelection, setLastSelection] =
|
||||
useState<OnSelectionChangeParams | null>(null);
|
||||
|
||||
const setFlowState = useFlowStore((state) => state.setFlowState);
|
||||
const setInputs = useFlowStore((state) => state.setInputs);
|
||||
const setOutputs = useFlowStore((state) => state.setOutputs);
|
||||
const setHasIO = useFlowStore((state) => state.setHasIO);
|
||||
const { inputs, outputs } = getInputsAndOutputs(flow.data!.nodes);
|
||||
const viewport = flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
function handleGroupNode() {
|
||||
takeSnapshot();
|
||||
if (validateSelection(lastSelection!, edges).length === 0) {
|
||||
|
|
@ -113,14 +122,15 @@ export default function Page({
|
|||
const clonedEdges = cloneDeep(edges);
|
||||
const clonedSelection = cloneDeep(lastSelection);
|
||||
updateIds({ nodes: clonedNodes, edges: clonedEdges }, clonedSelection!);
|
||||
const { newFlow, removedEdges } = generateFlow(
|
||||
const { newFlow } = generateFlow(
|
||||
clonedSelection!,
|
||||
clonedNodes,
|
||||
clonedEdges,
|
||||
getRandomName(),
|
||||
);
|
||||
|
||||
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
|
||||
// const newEdges = reconnectEdges(newGroupNode, removedEdges);
|
||||
|
||||
setNodes([
|
||||
...clonedNodes.filter(
|
||||
(oldNodes) =>
|
||||
|
|
@ -130,17 +140,6 @@ export default function Page({
|
|||
),
|
||||
newGroupNode,
|
||||
]);
|
||||
// setEdges([
|
||||
// ...clonedEdges.filter(
|
||||
// (oldEdge) =>
|
||||
// !clonedSelection!.nodes.some(
|
||||
// (selectionNode) =>
|
||||
// selectionNode.id === oldEdge.target ||
|
||||
// selectionNode.id === oldEdge.source,
|
||||
// ),
|
||||
// ),
|
||||
// ...newEdges,
|
||||
// ]);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: INVALID_SELECTION_ERROR_ALERT,
|
||||
|
|
@ -149,7 +148,6 @@ export default function Page({
|
|||
}
|
||||
}
|
||||
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (event) => {
|
||||
position.current = { x: event.clientX, y: event.clientY };
|
||||
|
|
@ -164,14 +162,24 @@ export default function Page({
|
|||
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance && currentFlowId) {
|
||||
resetFlow({
|
||||
nodes: flow?.data?.nodes ?? [],
|
||||
edges: flow?.data?.edges ?? [],
|
||||
viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 },
|
||||
});
|
||||
reactFlowInstance!.setViewport(viewport);
|
||||
}
|
||||
}, [currentFlowId, reactFlowInstance]);
|
||||
|
||||
const { isFetching } = useGetBuildsQuery({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
let newEdges = cleanEdges(flow.data!.nodes, flow.data!.edges);
|
||||
setNodes(flow.data!.nodes);
|
||||
setEdges(newEdges);
|
||||
setFlowState(undefined);
|
||||
setInputs(inputs);
|
||||
setOutputs(outputs);
|
||||
setHasIO(inputs.length > 0 || outputs.length > 0);
|
||||
}
|
||||
}, [isFetching]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkOldComponents({ nodes: flow?.data?.nodes ?? [] })) {
|
||||
setNoticeData({
|
||||
|
|
|
|||
|
|
@ -59,12 +59,21 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
edges: [],
|
||||
isBuilding: false,
|
||||
isPending: true,
|
||||
hasIO: false,
|
||||
setHasIO: (hasIO) => {
|
||||
set({ hasIO });
|
||||
},
|
||||
reactFlowInstance: null,
|
||||
lastCopiedSelection: null,
|
||||
flowPool: {},
|
||||
setInputs: (inputs) => {
|
||||
set({ inputs });
|
||||
},
|
||||
setOutputs: (outputs) => {
|
||||
set({ outputs });
|
||||
},
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
hasIO: get()?.inputs?.length > 0 || get()?.outputs?.length > 0,
|
||||
setFlowPool: (flowPool) => {
|
||||
set({ flowPool });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ export type FlowStoreType = {
|
|||
onFlowPage: boolean;
|
||||
setOnFlowPage: (onFlowPage: boolean) => void;
|
||||
flowPool: FlowPoolType;
|
||||
setHasIO: (hasIO: boolean) => void;
|
||||
setInputs: (
|
||||
inputs: Array<{ type: string; id: string; displayName: string }>,
|
||||
) => void;
|
||||
setOutputs: (
|
||||
outputs: Array<{ type: string; id: string; displayName: string }>,
|
||||
) => void;
|
||||
inputs: Array<{
|
||||
type: string;
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -196,3 +196,58 @@ test("should copy code from playground modal", async ({ page }) => {
|
|||
expect(clipboardContent.length).toBeGreaterThan(0);
|
||||
expect(clipboardContent).toContain("Hello");
|
||||
});
|
||||
|
||||
test("playground button should be enabled or disabled", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
let modalCount = 0;
|
||||
try {
|
||||
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||
if (modalTitleElement) {
|
||||
modalCount = await modalTitleElement.count();
|
||||
}
|
||||
} catch (error) {
|
||||
modalCount = 0;
|
||||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("playground-btn-flow").click({ force: true });
|
||||
|
||||
expect(await page.getByText("Langflow Chat").isHidden());
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('//*[@id="outputsChat Output"]')
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
await page.waitForSelector('[title="fit view"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("playground-btn-flow-io").click({ force: true });
|
||||
|
||||
expect(await page.getByText("Langflow Chat").isVisible());
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue