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

@ -0,0 +1,73 @@
import { expect, test } from "@playwright/test";
test("user can add components by hovering and clicking the plus icon", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
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(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Search for a component
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500);
// Hover over the component and verify plus icon
const componentLocator = page.getByTestId("inputsChat Input");
// Find the plus icon within the specific component container
const plusIcon = componentLocator.getByTestId("icon-Plus");
// Get the opacity
const opacity = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(plusIcon).toBeVisible();
await expect(opacity).toBe("0");
// Hover over the component
await componentLocator.hover();
// Check if the plus icon is visible and has full opacity
await expect(plusIcon).toBeVisible();
await page.waitForTimeout(500);
const opacityAfterHover = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(opacityAfterHover).toBe("1");
// Click the plus icon associated with this component
await plusIcon.click();
await page.waitForTimeout(500);
// Verify component was added to the flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
});

View file

@ -23,12 +23,14 @@ test("user must see on handle hover a tooltip with possibility connections", asy
}
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("retrievalqa");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("retrievalqa");
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await page.waitForTimeout(1000);
await page

View file

@ -32,13 +32,8 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("api request");
await page.waitForTimeout(1000);
await page
@ -77,6 +72,14 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("disclosure-models")).toBeVisible();
await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("disclosure-chains")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
@ -89,7 +92,17 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("chainsConversationChain")).toBeVisible();
await expect(page.getByTestId("prototypesConditional Router")).toBeVisible();
await page.getByPlaceholder("Search").click();
await expect(page.getByTestId("helpersCurrent Date")).toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).not.toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("helpersCurrent Date")).not.toBeVisible();
await page.getByTestId("sidebar-filter-reset").click();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
@ -117,7 +130,16 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
await expect(page.getByTestId("vectorstoresAstra DB")).toBeVisible();
await expect(page.getByTestId("toolsSearch API")).toBeVisible();
await expect(page.getByTestId("prototypesSub Flow")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("prototypesSub Flow")).toBeVisible();
await expect(
page.getByTestId("retrieversSelf Query Retriever"),
).toBeVisible();

View file

@ -30,9 +30,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//first component
await page.getByTestId("extended-disclosure").click();
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
@ -52,9 +51,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//second component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("url");
await page.waitForTimeout(1000);
await page
@ -74,9 +72,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("split text");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("split text");
await page.waitForTimeout(1000);
await page
@ -96,9 +93,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fourth 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
@ -118,9 +114,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fifth component
await page.getByTestId("extended-disclosure").click();
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(1000);
await page

View file

@ -31,12 +31,8 @@ test("user must be able to save or delete a global variable", async ({
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("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);

View file

@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
test("user can search and add components using keyboard shortcuts", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
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(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Press "/" to activate search
await page.keyboard.press("/");
await page.waitForTimeout(500);
// Verify search is focused and disclosures are closed when search is empty
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Type "chat" to search for chat components
await page.keyboard.type("chat");
await page.waitForTimeout(500);
// Verify disclosures open when search has content
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
// Press Tab to focus first result
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Verify some expected chat-related components are visible
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
await expect(page.getByTestId("outputsChat Output")).toBeVisible();
// Press Space to select the component
await page.keyboard.press("Space");
await page.waitForTimeout(500);
// Verify component was added to flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
// Clear search input and verify disclosures are closed
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Test Enter key selection
await page.keyboard.press("/");
await page.keyboard.type("prompt");
await page.waitForTimeout(500);
// Verify disclosures open with new search
await expect(page.getByTestId("promptsPrompt")).toBeVisible();
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.waitForTimeout(500);
// Verify second component was added
const nodeCount = await page.locator(".react-flow__node").count();
expect(nodeCount).toBe(2);
// Verify search is cleared and disclosures are closed after adding component
await page.keyboard.press("/");
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("sidebar-search-input")).toHaveValue("");
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await page.keyboard.press("Escape");
await expect(page.getByTestId("sidebar-search-input")).not.toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
});

View file

@ -38,12 +38,9 @@ test("fresh start playground", async ({ page }) => {
});
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.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);
await page
@ -52,8 +49,8 @@ test("fresh start playground", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
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(1000);
await page

View file

@ -103,12 +103,8 @@ test.describe("save component tests", () => {
if (replaceButton) {
await page.getByTestId("replace-button").click();
}
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("group");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("group");
await page.waitForTimeout(1000);
await page

View file

@ -33,9 +33,8 @@ test("user must be able to stop a building", async ({ page }) => {
//first component
await page.getByTestId("extended-disclosure").click();
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
@ -55,9 +54,8 @@ test("user must be able to stop a building", async ({ page }) => {
//second component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("url");
// await page.waitForTimeout(1000);
await page
@ -77,9 +75,8 @@ test("user must be able to stop a building", async ({ page }) => {
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("split text");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("split text");
// await page.waitForTimeout(1000);
await page
@ -99,9 +96,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fourth 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
@ -121,9 +117,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fifth component
await page.getByTestId("extended-disclosure").click();
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(1000);
await page
@ -248,17 +243,7 @@ class CustomComponent(Component):
return data
`;
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();

View file

@ -88,12 +88,8 @@ test("check if tweaks are updating when someothing on the flow changes", async (
});
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("Chroma");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Chroma");
await page.waitForTimeout(1000);