feat: redesign sidebar with new components and features (#4307)

* Added required ShadCN Components

* Added required colors

* Added required icons

* Changed backend to not return beta tag

* Added sidebar categories api call

* Added required use-mobile hook

* Refactored icons to allow sizing

* Added type for sidebar category

* Allowed no name on shortcut display

* Added flow sidebar component that uses ShadCN sidebar

* Added SidebarDraggableComponent with the new style

* Replaced sidebar with current one on FlowPage

* Added sidebar fixed footer with options to go to store and add custom component

* Updated sidebar categories

* Updated background color for sidebar

* Changed size of Components title

* Added color to PageComponent

* Added required icons

* added CustomComponent category and removed custom component from helpers

* create hook useAddComponent

* Use hook to add components both in PageComponent and flowSidebarComponent

* Implement search

* Implement searching and filtering by clicking on edge

* Added check to see if store is present

* Updated colors to match new color schema

* Changed styling of filter component

* Added Beta and Legacy badges

* Implement Show Beta Components and Show Legacy Components

* Fixed styling for sidebar config when collapsed

* Refactored search to filter for tags

* Refactor useeffect

* Updated config button styling

* Implemented keyboard navigation

* Fixed filtering

* Updated color of canvas

* Implemented disclosure on sidebar settings and fetched bundles

* Added temp sidebar bundles

* Fixed badge styling

* Added bundles to categories response in frontend

* Added legacy to components

* Added link to store instead of langflow.store

* Added required data-testids

* Fixed tests to use new data-testids and new sidebar disposition

* Fix github star bug

* Fixed tests that used the custom component

* Changed test to test beta and legacy checkers

* added a test for keyboard navigation on sidebar

* Added a test to check component add by hover the plus button

* [autofix.ci] apply automated fixes

* updated sidebar switch change

* Removed changes on Backend and used only Frontend constants for categories

* merge fix

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* 📝 (custom_component): Add CustomComponent class with input and output definitions
📝 (custom_component): Create CustomComponent class with display name, description, and documentation link
📝 (custom_component): Define input and output properties for CustomComponent class
📝 (custom_component): Implement build_output method in CustomComponent class
📝 (Ollama): Remove unnecessary whitespace in SvgOllama component

* formatting

* ♻️ (custom_component/__init__.py): refactor import statement to match the correct case of the file name for better consistency and readability

* 🔧 (FlowPage/index.tsx): remove FlowToolbar component when view prop is false to improve UI consistency

* 📝 (integration-side-bar.spec.ts): Comment out unnecessary code blocks related to API requests and modals to improve test readability and focus on the main test scenarios.

* [autofix.ci] apply automated fixes

* ✨ (flowSidebarComponent/index.tsx): add data-testid attribute to Sidebar component for testing purposes
🔧 (integration-side-bar.spec.ts): update test to use new data-testid attribute 'shad-sidebar' for Sidebar component to match changes in the codebase

* ✨ (stop-building.spec.ts): refactor test case to use a more descriptive test element for clicking on the sidebar custom component button

* format

* 🐛 (AstraDB): Fix variable naming inconsistency for isDark to isdark in AstraDB component
🐛 (HCD): Fix variable naming inconsistency for isDark to isdark in HCD component
🐛 (index.tsx): Fix variable naming inconsistency for isDark to isdark in index.tsx files
📝 (flowSidebarComponent): Refactor search functionality to normalize search terms and improve metadata search
📝 (filterEdge-shard-1.spec.ts): Update test to check visibility of specific model specs in the sidebar

* [autofix.ci] apply automated fixes

* ✅ (decisionFlow.spec.ts): add a delay of 500ms to ensure proper timing in the test case execution

* ✨ (decisionFlow.spec.ts): Update the X positions of elements in the flow to improve the visual representation and alignment of the elements.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-10-31 19:51:36 -03:00 • committed by GitHub
commit 794848d5e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
122 changed files with 2365 additions and 802 deletions

View file

@ -1,83 +1,150 @@
import { expect, test } from "@playwright/test";
import { Page, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
// Helper function to wait for element to be ready
async function waitForElement(page: Page, elementId: string, nth: number) {
const element = page.getByTestId(`title-${elementId}`).nth(nth);
// Add this function at the beginning of the file, after the imports
// Wait for element to be visible and stable
await element.waitFor({
state: "visible",
timeout: 30000,
});
// Additional wait to ensure element is fully rendered and interactive
await page.waitForTimeout(1000);
return element;
}
// Improved version of moveElementByX with better error handling and waits
async function moveElementByX(
page: any,
page: Page,
elementId: string,
moveX: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
// Break down mouse movements into smaller steps for more reliability
await page.mouse.move(startX, startY);
await page.waitForTimeout(50);
await page.mouse.down();
await page.mouse.move(startX + moveX, startY);
await page.waitForTimeout(50);
// Move in smaller increments
const steps = 3;
const stepX = moveX / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
// Add this function at the beginning of the file, after the imports
// Improved version of moveElementByY with better error handling and waits
async function moveElementByY(
page: any,
page: Page,
elementId: string,
moveY: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down();
await page.mouse.move(startX, startY + moveY);
await page.waitForTimeout(100);
// Move in smaller increments
const steps = 5;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX, startY + stepY * i);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
// Add this function at the beginning of the file, after the imports
// Improved version of moveElementByXY with better error handling and waits
async function moveElementByXY(
page: any,
page: Page,
elementId: string,
moveX: number,
moveY: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down();
await page.mouse.move(startX + moveX, startY + moveY);
await page.waitForTimeout(100);
// Move in smaller increments
const steps = 5;
const stepX = moveX / steps;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY + stepY * i);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
test("should create a flow with decision", async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
@ -111,21 +178,17 @@ test("should create a flow with decision", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
//---------------------------------- CHAT INPUT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500);
await page
.getByTestId("inputsChat Input")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CREATE LIST
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("list");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("list");
await page.waitForTimeout(500);
await page
.getByTestId("helpersCreate List")
@ -158,8 +221,8 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("inputlist_str_texts_2").last().fill("not cool..");
//---------------------------------- PARSE DATA
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
await page.waitForTimeout(500);
await page
@ -170,8 +233,8 @@ test("should create a flow with decision", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PASS
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("pass");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("pass");
await page.waitForTimeout(500);
await page
.getByTestId("prototypesPass")
@ -185,30 +248,30 @@ test("should create a flow with decision", async ({ page }) => {
.getByTestId("prototypesPass")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PROMPT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("prompt");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("prompt");
await page.waitForTimeout(500);
await page
.getByTestId("promptsPrompt")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- OPENAI
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(500);
await page
.getByTestId("modelsOpenAI")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CONDITIONAL ROUTER
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("conditional router");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("conditional router");
await page.waitForTimeout(500);
await page
.getByTestId("prototypesConditional Router")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CHAT OUTPUT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(500);
await page
.getByTestId("outputsChat Output")
@ -222,32 +285,42 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("fit_view").click();
await moveElementByX(page, "Chat Output", 500, 1);
await page.waitForTimeout(500);
await moveElementByX(page, "Chat Output", 1000, 0);
await moveElementByX(page, "Chat Output", 400, 1);
await page.waitForTimeout(500);
await moveElementByX(page, "Conditional Router", 1500, 0);
await moveElementByX(page, "Chat Output", 700, 0);
await page.waitForTimeout(500);
await moveElementByX(page, "OpenAI", 2000, 0);
await moveElementByX(page, "Conditional Router", 1000, 0);
await page.waitForTimeout(500);
await moveElementByX(page, "Prompt", 2500, 0);
await page.getByTestId("fit_view").click();
await moveElementByX(page, "OpenAI", 980, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 3000, 2);
await moveElementByX(page, "Prompt", 990, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 1000, 2);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 0, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 150, 200, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 300, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 450, 200, 0);
await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 600, 200, 1);
await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 800, 200, 0);
await page.waitForTimeout(500);
await moveElementByXY(page, "Chat Input", 1000, 200, 0);
await page.waitForTimeout(500);
await page.getByTestId("fit_view").click();

View file

@ -31,9 +31,8 @@ test("user must be able to check similarity between embedding texts", async ({
//first component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
// await page.waitForTimeout(1000);
await page
@ -70,9 +69,8 @@ test("user must be able to check similarity between embedding texts", async ({
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text embedder");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text embedder");
// await page.waitForTimeout(1000);
await page
@ -109,9 +107,8 @@ test("user must be able to check similarity between embedding texts", async ({
//fifth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("embedding similarity");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("embedding similarity");
// await page.waitForTimeout(1000);
await page
@ -131,9 +128,8 @@ test("user must be able to check similarity between embedding texts", async ({
//sisxth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
// await page.waitForTimeout(1000);
await page
@ -153,9 +149,8 @@ test("user must be able to check similarity between embedding texts", async ({
//seventh component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
// await page.waitForTimeout(1000);
await page
@ -173,9 +168,8 @@ test("user must be able to check similarity between embedding texts", async ({
await page.mouse.up();
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("filter data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("filter data");
// await page.waitForTimeout(1000);
await page

View file

@ -41,19 +41,9 @@ test("TextInputOutputComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => {
const focusElements = await page.getByTestId("extended-disclosure");
focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text input");
await page.waitForTimeout(1000);
await page
@ -62,8 +52,8 @@ test("TextInputOutputComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);
await page
@ -120,8 +110,8 @@ test("TextInputOutputComponent", async ({ page }) => {
// Release the mouse
await page.mouse.up();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
await page
.getByTestId("outputsText Output")