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 434 additions and 32 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

@ -1,5 +1,4 @@
import { expect, test } from "@playwright/test";
import path from "path";
test("Basic Prompting (Hello, World)", async ({ page }) => {
if (!process?.env?.OPENAI_API_KEY) {
@ -67,10 +66,18 @@ test("Basic Prompting (Hello, World)", async ({ page }) => {
.getByTestId("input-chat-playground")
.last()
.fill("Say hello as a pirate");
await page.getByTestId("icon-LucideSend").last().click();
await page.waitForTimeout(3000);
await page.getByText("Ahoy").last().isVisible();
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();

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,89 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
test("should delete rows from table message", 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.getByTestId("icon-LucideSend").last().click();
await page.waitForSelector("text=matey", {
timeout: 100000,
});
await page.getByText("Close").last().click();
await page.getByTestId("user-profile-settings").last().click();
await page.getByText("Settings").last().click();
await page.getByText("Messages").last().click();
const label = "Press Space to toggle all rows selection (unchecked)";
await page.getByLabel(label).first().click();
await page.getByTestId("icon-Trash2").first().click();
await page.waitForSelector("text=No Data Available", { timeout: 30000 });
await page.getByText("No Data Available").isVisible();
});

View file

@ -0,0 +1,72 @@
import { expect, test } from "@playwright/test";
test("should use webhook component on API", 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.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("webhook");
await page.waitForTimeout(1000);
await page
.getByTestId("dataWebhook Input")
.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.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.waitForTimeout(2000);
await page.getByText("API", { exact: true }).click();
await page.getByText("Webhook cURL", { exact: true }).click();
await page.getByRole("tab", { name: "Webhook cURL" }).click();
await page.getByTestId("icon-Copy").last().click();
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.length).toBeGreaterThan(0);
expect(clipboardContent).toContain("curl -X POST");
expect(clipboardContent).toContain("webhook");
await page.getByRole("tab", { name: "Tweaks" }).click();
// await page.getByText("Webhook Input").isVisible();
// await page.getByText("Webhook Input").click();
});

View file

@ -0,0 +1,181 @@
import { expect, test } from "@playwright/test";
test("should copy code from playground modal", 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("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.waitForTimeout(1000);
await page
.getByTestId("outputsChat Output")
.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();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.waitForTimeout(1000);
await page
.getByTestId("inputsChat Input")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
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();
await page
.getByTestId("modelsOpenAI")
.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.getByTitle("fit view").click();
await page.getByTitle("zoom out").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 ?? "");
const elementsChatInput = await page
.locator('[data-testid="handle-chatinput-shownode-message-right"]')
.all();
let visibleElementHandle;
for (const element of elementsChatInput) {
if (await element.isVisible()) {
visibleElementHandle = element;
break;
}
}
// Click and hold on the first element
await visibleElementHandle.hover();
await page.mouse.down();
const elementsOpenAiInput = await page
.locator('[data-testid="handle-openaimodel-shownode-input-left"]')
.all();
for (const element of elementsOpenAiInput) {
if (await element.isVisible()) {
visibleElementHandle = element;
break;
}
}
await visibleElementHandle.hover();
await page.mouse.up();
const elementsOpenAiOutput = await page
.locator('[data-testid="handle-openaimodel-shownode-text-right"]')
.all();
for (const element of elementsOpenAiOutput) {
if (await element.isVisible()) {
visibleElementHandle = element;
break;
}
}
// Click and hold on the first element
await visibleElementHandle.hover();
await page.mouse.down();
// Move to the second element
const elementsChatOutput = await page
.getByTestId("handle-chatoutput-shownode-text-left")
.all();
for (const element of elementsChatOutput) {
if (await element.isVisible()) {
visibleElementHandle = element;
break;
}
}
await visibleElementHandle.hover();
await page.mouse.up();
await page.getByLabel("fit view").click();
await page.getByText("Playground", { exact: true }).click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
await page.getByTestId("input-chat-playground").click();
await page
.getByTestId("input-chat-playground")
.fill("Could you provide a Python example for a 'Hello, World!' program?");
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.getByTestId("icon-LucideSend").click();
await page.getByRole("tab", { name: "python" }).isVisible({
timeout: 100000,
});
await page.getByTestId("icon-Copy").first().click();
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.length).toBeGreaterThan(0);
expect(clipboardContent).toContain("Hello");
});

View file

@ -91,10 +91,6 @@ test("TextInputOutputComponent", async ({ page }) => {
await visibleElementHandle.hover();
await page.mouse.down();
const elementsOpenAiInput = await page.getByTestId(
"handle-openaimodel-shownode-input-left",
);
for (const element of elementsTextInputOutput) {
if (await element.isVisible()) {
visibleElementHandle = element;

View file

@ -34,7 +34,7 @@
"tests/end-to-end/floatComponent.spec.ts",
"tests/end-to-end/flowPage.spec.ts",
"tests/end-to-end/flowSettings.spec.ts",
"tests/end-to-end/generalBugs.spec.ts",
"tests/end-to-end/generalBugs-shard-0.spec.ts",
"tests/end-to-end/globalVariables.spec.ts",
"tests/end-to-end/group.spec.ts",
"tests/end-to-end/folders.spec.ts",