Merge branch 'dev' into lf/fix_render

This commit is contained in:
Ítalo Johnny 2024-07-01 10:48:54 -03:00 • committed by GitHub
commit 2c507dd7de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 433 additions and 31 deletions

View file

@ -1,6 +1,12 @@
name: Run Frontend Tests
on:
workflow_dispatch:
inputs:
branch:
description: "(Optional) Branch to checkout"
required: false
type: string
pull_request:
merge_group:
@ -19,14 +25,18 @@ jobs:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
STORE_API_KEY: ${{ secrets.STORE_API_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# If branch is passed as input, checkout that branch
# else checkout the default branch
ref: ${{ github.event.inputs.branch || github.ref }}
- name: Setup Node.js
uses: actions/setup-node@v4

View file

@ -80,7 +80,8 @@ async def delete_messages(
current_user: User = Depends(get_current_active_user),
):
try:
session.exec(select(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore
session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore
session.commit()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -70,7 +70,7 @@ class ConditionalRouterComponent(Component):
return self.message
else:
self.stop("true_result")
return None
return None # type: ignore
def false_response(self) -> Message:
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
@ -79,4 +79,4 @@ class ConditionalRouterComponent(Component):
return self.message
else:
self.stop("false_result")
return None
return None # type: ignore

View file

@ -2,7 +2,7 @@ from .ConditionalRouter import ConditionalRouterComponent
from .FlowTool import FlowToolComponent
from .Listen import ListenComponent
from .Notify import NotifyComponent
from .Pass import PassComponent
from .Pass import PassMessageComponent
from .PythonFunction import PythonFunctionComponent
from .RunFlow import RunFlowComponent
from .RunnableExecutor import RunnableExecComponent
@ -16,7 +16,7 @@ __all__ = [
"FlowToolComponent",
"ListenComponent",
"NotifyComponent",
"PassComponent",
"PassMessageComponent",
"PythonFunctionComponent",
"RunFlowComponent",
"RunnableExecComponent",

View file

@ -9,7 +9,10 @@ const useIconStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: VertexBuildTypeAPI | null,
) => {
const conditionSuccess = validationStatus && validationStatus.valid;
const conditionSuccess =
!(!buildStatus || buildStatus === BuildStatus.TO_BUILD) &&
validationStatus &&
validationStatus.valid;
const conditionError = buildStatus === BuildStatus.ERROR;
const conditionInactive = buildStatus === BuildStatus.INACTIVE;

View file

@ -26,6 +26,7 @@ import {
TabsTrigger,
} from "../../components/ui/tabs";
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
import getTabsOrder from "../../modals/apiModal/utils/get-tabs-order";
import { Case } from "../../shared/components/caseComponent";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
@ -56,6 +57,8 @@ export default function CodeTabsComponent({
setActiveTweaks,
activeTweaks,
allowExport = false,
isThereTweaks = false,
isThereWH = false,
}: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
@ -93,6 +96,8 @@ export default function CodeTabsComponent({
return node.data.node.template[templateParam].type;
};
const tabsOrder = getTabsOrder(isThereWH, isThereTweaks);
return (
<Tabs
value={activeTab}
@ -172,7 +177,7 @@ export default function CodeTabsComponent({
className="api-modal-tabs-content overflow-hidden"
key={idx} // Remember to add a unique key prop
>
{idx < 5 ? (
{tabsOrder[idx].toLowerCase() !== "tweaks" ? (
<div className="flex h-full w-full flex-col">
{tab.description && (
<div
@ -188,7 +193,7 @@ export default function CodeTabsComponent({
{tab.code}
</SyntaxHighlighter>
</div>
) : idx === 5 ? (
) : tabsOrder[idx].toLowerCase() === "tweaks" ? (
<>
<div className="api-modal-according-display">
<div

View file

@ -10,9 +10,14 @@ function DataOutputComponent({
columnMode = "union",
}: {
pagination: boolean;
rows: any;
rows: any[];
columnMode?: "intersection" | "union";
}) {
// If the rows are not an array of objects, convert them to an array of objects
if (rows.some((row) => typeof row !== "object")) {
rows = rows.map((row) => ({ data: row }));
}
const columns = extractColumnsFromRows(rows, columnMode);
const columnDefs = columns.map((col, idx) => ({

View file

@ -1124,5 +1124,8 @@ export async function deleteMessagesFn(ids: string[]) {
}
export async function updateMessageApi(data: Message) {
return await api.post(`${BASE_URL_API}monitor/messages/${data.id}`, data);
if (data.files && typeof data.files === "string") {
data.files = JSON.parse(data.files);
}
return await api.put(`${BASE_URL_API}monitor/messages/${data.id}`, data);
}

View file

@ -52,7 +52,6 @@ export default function SessionView({ rows }: { rows: Array<any> }) {
]}
overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => {
console.log(event.api.getSelectedRows());
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
}}
rowSelection="multiple"

View file

@ -44,11 +44,12 @@ const ApiModal = forwardRef(
},
ref,
) => {
const tweaksCode = buildTweaks(flow);
const tweak = useTweaksStore((state) => state.tweak);
const addTweaks = useTweaksStore((state) => state.setTweak);
const setTweaksList = useTweaksStore((state) => state.setTweaksList);
const tweaksList = useTweaksStore((state) => state.tweaksList);
const isThereTweaks = Object.keys(tweaksCode).length > 0;
const [activeTweaks, setActiveTweaks] = useState(false);
const { autoLogin } = useContext(AuthContext);
const [open, setOpen] =
@ -82,7 +83,6 @@ const ApiModal = forwardRef(
const pythonCode = getPythonCode(flow?.name, tweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
const includeWebhook = flow.webhook;
const tweaksCode = buildTweaks(flow);
const codesArray = [
runCurlCode,
webhookCurlCode,
@ -121,7 +121,7 @@ const ApiModal = forwardRef(
filterNodes();
if (Object.keys(tweaksCode).length > 0) {
if (isThereTweaks) {
setActiveTab("0");
setTabs(createTabsArray(codesArray, includeWebhook, true));
} else {
@ -215,7 +215,6 @@ const ApiModal = forwardRef(
);
const pythonCode = getPythonCode(flow?.name, cloneTweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
const isThereTweaks = Object.keys(tweaksCode).length > 0;
const codesObj = getCodesObj({
runCurlCode,
webhookCurlCode,
@ -251,6 +250,8 @@ const ApiModal = forwardRef(
</BaseModal.Header>
<BaseModal.Content overflowHidden>
<CodeTabsComponent
isThereTweaks={isThereTweaks}
isThereWH={includeWebhook ?? false}
flow={flow}
tabs={tabs!}
activeTab={activeTab}

View file

@ -1,10 +1,11 @@
import { cloneDeep } from "lodash";
import { TABS_ORDER } from "../../../constants/constants";
export default function getTabsOrder(
isThereWH: boolean = false,
isThereTweaks: boolean = false,
): string[] {
const defaultOrder = TABS_ORDER;
const defaultOrder = cloneDeep(TABS_ORDER);
if (isThereTweaks) {
defaultOrder.push("tweaks");
}

View file

@ -228,7 +228,13 @@ export default function Page({
}
function handleCopy(e: KeyboardEvent) {
if (!isWrappedWithClass(e, "nocopy")) {
const multipleSelection = lastSelection?.nodes
? lastSelection?.nodes.length > 0
: false;
if (
!isWrappedWithClass(e, "nocopy") &&
(isWrappedWithClass(e, "react-flow__node") || multipleSelection)
) {
e.preventDefault();
(e as unknown as Event).stopImmediatePropagation();
if (window.getSelection()?.toString().length === 0 && lastSelection) {

View file

@ -4,6 +4,7 @@ import {
ColGroupDef,
SelectionChangedEvent,
} from "ag-grid-community";
import { cloneDeep } from "lodash";
import { useState } from "react";
import TableComponent from "../../../../components/tableComponent";
import useAlertStore from "../../../../stores/alertStore";
@ -37,7 +38,7 @@ export default function MessagesPage() {
function handleUpdateMessage(event: CellEditRequestEvent<any, string>) {
const newValue = event.newValue;
const field = event.column.getColId();
const row = event.data;
const row = cloneDeep(event.data);
const data = {
...row,
[field]: newValue,
@ -66,9 +67,7 @@ export default function MessagesPage() {
]}
overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows(
event.api.getSelectedRows().map((row) => row.index),
);
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
}}
rowSelection="multiple"
suppressRowClickSelection={true}

View file

@ -695,6 +695,8 @@ type codeTabsFuncTempType = {
};
export type codeTabsPropsType = {
isThereTweaks?: boolean;
isThereWH?: boolean;
flow?: FlowType;
tabs: Array<tabsArrayType>;
activeTab: string;

View file

@ -6,6 +6,7 @@ type Message = {
sender_name: string;
session_id: string;
timestamp: string;
files: Array<string>;
id: string;
};

View file

@ -99,6 +99,10 @@ export async function updateVerticesOrder(
const runId = orderResponse.data.run_id;
const verticesToRun = orderResponse.data.vertices_to_run;
useFlowStore
.getState()
.updateBuildStatus(verticesToRun, BuildStatus.TO_BUILD);
const verticesIds = orderResponse.data.ids;
useFlowStore.getState().updateVerticesBuild({
verticesLayers,

View file

@ -1117,6 +1117,18 @@ export function updateProxyIdsOnTemplate(
});
}
export function updateProxyIdsOnOutputs(
outputs: OutputFieldType[] | undefined,
idsMap: { [key: string]: string },
) {
if (!outputs) return;
outputs.forEach((output) => {
if (output.proxy && idsMap[output.proxy.id]) {
output.proxy.id = idsMap[output.proxy.id];
}
});
}
export function updateEdgesIds(
edges: Edge[],
idsMap: { [key: string]: string },
@ -1485,6 +1497,7 @@ export function updateGroupRecursion(groupNode: NodeType, edges: Edge[]) {
let newFlow = groupNode.data.node!.flow;
const idsMap = updateIds(newFlow.data!);
updateProxyIdsOnTemplate(groupNode.data.node!.template, idsMap);
updateProxyIdsOnOutputs(groupNode.data.node.outputs, idsMap);
let flowEdges = edges;
updateEdgesIds(flowEdges, idsMap);
}

View file

@ -0,0 +1,97 @@
import { expect, test } from "@playwright/test";
test("Basic Prompting (Hello, World)", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page.goto("/");
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.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByTestId("button_run_chat output").click();
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page
.getByText("No input message provided.", { exact: true })
.last()
.isVisible();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("Say hello as a pirate");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForSelector("text=matey", {
timeout: 100000,
});
await page.getByText("matey").last().isVisible();
await page.getByText("Default Session").last().click();
await page.getByText("timestamp", { exact: true }).last().isVisible();
await page.getByText("text", { exact: true }).last().isVisible();
await page.getByText("sender", { exact: true }).last().isVisible();
await page.getByText("sender_name", { exact: true }).last().isVisible();
await page.getByText("session_id", { exact: true }).last().isVisible();
await page.getByText("files", { exact: true }).last().isVisible();
await page.getByRole("gridcell").last().isVisible();
await page.getByTestId("icon-Trash2").first().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").last().isVisible();
});

View file

@ -80,6 +80,10 @@ test("user must be able to send an image on chat", async ({ page }) => {
{ fileContent },
);
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
// Locate the target element
const element = await page.getByTestId("input-chat-playground");

View file

@ -0,0 +1,122 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
test("should interact with api request", async ({ page }) => {
await page.goto("/");
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.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.waitForTimeout(1000);
await page
.getByTestId("dataAPI Request")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
});
test("erase button should clear the chat messages", async ({ page }) => {
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
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.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
if (!process.env.OPENAI_API_KEY) {
//You must set the OPENAI_API_KEY on .env file to run this test
expect(false).toBe(true);
}
await page
.getByTestId("popover-anchor-input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown-model_name").click();
await page.getByTestId("gpt-4o-0-option").click();
await page.waitForTimeout(2000);
await page.getByText("Playground", { exact: true }).click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").fill("Hello, how are you?");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
let valueUser = await page.getByTestId("sender_name_user").textContent();
await page.waitForSelector('[data-testid="sender_name_ai"]', {
timeout: 30000,
});
let valueAI = await page.getByTestId("sender_name_ai").textContent();
expect(valueUser).toBe("User");
expect(valueAI).toBe("AI");
await page.getByTestId("icon-Eraser").last().click();
await page.getByText("Hello, how are you?").isHidden();
await page.getByText("AI", { exact: true }).last().isHidden();
await page.getByText("User", { exact: true }).last().isHidden();
await page.getByText("Start a conversation").isVisible();
await page.getByText("Langflow Chat").isVisible();
});

Some files were not shown because too many files have changed in this diff Show more