fix: updated folder name to not be the deprecated (#8793)
* Updated folder name to not be the deprecated * Changed backend to use Starter Project as default folder name * Changed docs * Changed frontend to display pure folder name without deprecated * Updated tests * Added migration to change folder name * Refactor migration to rename folder names with unique constraint checks for users. Updated upgrade and downgrade functions to streamline the renaming process for "My Projects" and "Starter Project". --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
d2bee3069b
commit
dc671ae204
13 changed files with 93 additions and 46 deletions
|
|
@ -29,7 +29,7 @@ curl -X GET \
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "My Projects",
|
"name": "Starter Project",
|
||||||
"description": "Manage your own projects. Download and upload projects.",
|
"description": "Manage your own projects. Download and upload projects.",
|
||||||
"id": "1415de42-8f01-4f36-bf34-539f23e47466",
|
"id": "1415de42-8f01-4f36-bf34-539f23e47466",
|
||||||
"parent_id": null
|
"parent_id": null
|
||||||
|
|
@ -116,7 +116,7 @@ curl -X GET \
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "My Projects",
|
"name": "Starter Project",
|
||||||
"description": "Manage your own projects. Download and upload projects.",
|
"description": "Manage your own projects. Download and upload projects.",
|
||||||
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
"parent_id": null
|
"parent_id": null
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
"""Rename default folder
|
||||||
|
|
||||||
|
Revision ID: d9a6ea21edcd
|
||||||
|
Revises: 66f72f04a1de
|
||||||
|
Create Date: 2025-07-02 09:42:46.891585
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
from sqlalchemy.engine.reflection import Inspector
|
||||||
|
from langflow.utils import migration
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'd9a6ea21edcd'
|
||||||
|
down_revision: Union[str, None] = '66f72f04a1de'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
# Check if the folder table exists
|
||||||
|
inspector = sa.inspect(conn)
|
||||||
|
table_names = inspector.get_table_names()
|
||||||
|
|
||||||
|
if "folder" not in table_names:
|
||||||
|
# If folder table doesn't exist, skip this migration
|
||||||
|
return
|
||||||
|
|
||||||
|
# Rename "My Projects" to "Starter Project" only for users who don't already have a "Starter Project" folder
|
||||||
|
# This prevents unique constraint violations
|
||||||
|
update_query = sa.text("""
|
||||||
|
UPDATE folder
|
||||||
|
SET name = 'Starter Project'
|
||||||
|
WHERE name = 'My Projects'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM folder f2
|
||||||
|
WHERE f2.user_id = folder.user_id
|
||||||
|
AND f2.name = 'Starter Project'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
conn.execute(update_query)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
# Check if the folder table exists
|
||||||
|
inspector = sa.inspect(conn)
|
||||||
|
table_names = inspector.get_table_names()
|
||||||
|
|
||||||
|
if "folder" not in table_names:
|
||||||
|
# If folder table doesn't exist, skip this migration
|
||||||
|
return
|
||||||
|
|
||||||
|
# Rename "Starter Project" back to "My Projects" only for users who don't already have a "My Projects" folder
|
||||||
|
# This prevents unique constraint violations
|
||||||
|
update_query = sa.text("""
|
||||||
|
UPDATE folder
|
||||||
|
SET name = 'My Projects'
|
||||||
|
WHERE name = 'Starter Project'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM folder f2
|
||||||
|
WHERE f2.user_id = folder.user_id
|
||||||
|
AND f2.name = 'My Projects'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
conn.execute(update_query)
|
||||||
|
|
@ -36,7 +36,6 @@ from langflow.base.mcp.util import get_flow_snake_case, get_unique_name, sanitiz
|
||||||
from langflow.helpers.flow import json_schema_from_flow
|
from langflow.helpers.flow import json_schema_from_flow
|
||||||
from langflow.schema.message import Message
|
from langflow.schema.message import Message
|
||||||
from langflow.services.database.models import Flow, Folder
|
from langflow.services.database.models import Flow, Folder
|
||||||
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME, NEW_FOLDER_NAME
|
|
||||||
from langflow.services.deps import get_settings_service, get_storage_service, session_scope
|
from langflow.services.deps import get_settings_service, get_storage_service, session_scope
|
||||||
from langflow.services.storage.utils import build_content_type_from_extension
|
from langflow.services.storage.utils import build_content_type_from_extension
|
||||||
|
|
||||||
|
|
@ -390,7 +389,6 @@ async def install_mcp_config(
|
||||||
logger.debug("Windows detected, using cmd command")
|
logger.debug("Windows detected, using cmd command")
|
||||||
|
|
||||||
name = project.name
|
name = project.name
|
||||||
name = NEW_FOLDER_NAME if name == DEFAULT_FOLDER_NAME else name
|
|
||||||
|
|
||||||
# Create the MCP configuration
|
# Create the MCP configuration
|
||||||
mcp_config = {
|
mcp_config = {
|
||||||
|
|
@ -517,7 +515,6 @@ async def check_installed_mcp_servers(
|
||||||
|
|
||||||
# Project server name pattern (must match the logic in install function)
|
# Project server name pattern (must match the logic in install function)
|
||||||
name = project.name
|
name = project.name
|
||||||
name = NEW_FOLDER_NAME if name == DEFAULT_FOLDER_NAME else name
|
|
||||||
project_server_name = f"lf-{sanitize_mcp_name(name)[: (MAX_MCP_SERVER_NAME_LENGTH - 4)]}"
|
project_server_name = f"lf-{sanitize_mcp_name(name)[: (MAX_MCP_SERVER_NAME_LENGTH - 4)]}"
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
DEFAULT_FOLDER_DESCRIPTION = "Manage your own flows. Download and upload projects."
|
DEFAULT_FOLDER_DESCRIPTION = "Manage your own flows. Download and upload projects."
|
||||||
DEFAULT_FOLDER_NAME = "My Projects"
|
DEFAULT_FOLDER_NAME = "Starter Project"
|
||||||
NEW_FOLDER_NAME = "Starter Project"
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@/components/ui/select-custom";
|
} from "@/components/ui/select-custom";
|
||||||
import { DEFAULT_FOLDER_DEPRECATED } from "@/constants/constants";
|
import { DEFAULT_FOLDER } from "@/constants/constants";
|
||||||
import { FolderType } from "@/pages/MainPage/entities";
|
import { FolderType } from "@/pages/MainPage/entities";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
import { handleSelectChange } from "../helpers/handle-select-change";
|
import { handleSelectChange } from "../helpers/handle-select-change";
|
||||||
|
|
@ -57,7 +57,7 @@ export const SelectOptions = ({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
<SelectContent align="end" alignOffset={-16} position="popper">
|
<SelectContent align="end" alignOffset={-16} position="popper">
|
||||||
{item.name !== DEFAULT_FOLDER_DEPRECATED && (
|
{item.name !== DEFAULT_FOLDER && (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
id="rename-button"
|
id="rename-button"
|
||||||
value="rename"
|
value="rename"
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ import {
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import {
|
import { DEFAULT_FOLDER } from "@/constants/constants";
|
||||||
DEFAULT_FOLDER,
|
|
||||||
DEFAULT_FOLDER_DEPRECATED,
|
|
||||||
} from "@/constants/constants";
|
|
||||||
import { useUpdateUser } from "@/controllers/API/queries/auth";
|
import { useUpdateUser } from "@/controllers/API/queries/auth";
|
||||||
import {
|
import {
|
||||||
usePatchFolders,
|
usePatchFolders,
|
||||||
|
|
@ -277,7 +274,7 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDoubleClick = (event, item) => {
|
const handleDoubleClick = (event, item) => {
|
||||||
if (item.name === DEFAULT_FOLDER_DEPRECATED) {
|
if (item.name === DEFAULT_FOLDER) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,9 +421,7 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="block w-0 grow truncate text-sm opacity-100">
|
<span className="block w-0 grow truncate text-sm opacity-100">
|
||||||
{item.name === DEFAULT_FOLDER_DEPRECATED
|
{item.name}
|
||||||
? DEFAULT_FOLDER
|
|
||||||
: item.name}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -558,7 +558,6 @@ export const NOUNS: string[] = [
|
||||||
export const USER_PROJECTS_HEADER = "My Collection";
|
export const USER_PROJECTS_HEADER = "My Collection";
|
||||||
|
|
||||||
export const DEFAULT_FOLDER = "Starter Project";
|
export const DEFAULT_FOLDER = "Starter Project";
|
||||||
export const DEFAULT_FOLDER_DEPRECATED = "My Projects";
|
|
||||||
|
|
||||||
export const MAX_MCP_SERVER_NAME_LENGTH = 30;
|
export const MAX_MCP_SERVER_NAME_LENGTH = 30;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { DEFAULT_FOLDER } from "@/constants/constants";
|
||||||
DEFAULT_FOLDER,
|
|
||||||
DEFAULT_FOLDER_DEPRECATED,
|
|
||||||
} from "@/constants/constants";
|
|
||||||
import { FolderType } from "@/pages/MainPage/entities";
|
import { FolderType } from "@/pages/MainPage/entities";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import { useFolderStore } from "@/stores/foldersStore";
|
import { useFolderStore } from "@/stores/foldersStore";
|
||||||
|
|
@ -26,9 +23,7 @@ export const useGetFoldersQuery: useQueryFunctionType<
|
||||||
const res = await api.get(`${getURL("PROJECTS")}/`);
|
const res = await api.get(`${getURL("PROJECTS")}/`);
|
||||||
const data = res.data;
|
const data = res.data;
|
||||||
|
|
||||||
const myCollectionId = data?.find(
|
const myCollectionId = data?.find((f) => f.name === DEFAULT_FOLDER)?.id;
|
||||||
(f) => f.name === DEFAULT_FOLDER_DEPRECATED,
|
|
||||||
)?.id;
|
|
||||||
setMyCollectionId(myCollectionId);
|
setMyCollectionId(myCollectionId);
|
||||||
setFolders(data);
|
setFolders(data);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ export const customGetDownloadFolderBlob = (
|
||||||
folderName?: string,
|
folderName?: string,
|
||||||
setSuccessData?: (data: any) => void,
|
setSuccessData?: (data: any) => void,
|
||||||
) => {
|
) => {
|
||||||
if (folderName === "My Projects") {
|
|
||||||
folderName = "Starter Project";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a blob from the response data
|
// Create a blob from the response data
|
||||||
const blob = new Blob([response.data], {
|
const blob = new Blob([response.data], {
|
||||||
type: "application/x-zip-compressed",
|
type: "application/x-zip-compressed",
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
import {
|
|
||||||
DEFAULT_FOLDER,
|
|
||||||
DEFAULT_FOLDER_DEPRECATED,
|
|
||||||
} from "@/constants/constants";
|
|
||||||
import { useDeleteDeleteFlows } from "@/controllers/API/queries/flows/use-delete-delete-flows";
|
import { useDeleteDeleteFlows } from "@/controllers/API/queries/flows/use-delete-delete-flows";
|
||||||
import { useGetDownloadFlows } from "@/controllers/API/queries/flows/use-get-download-flows";
|
import { useGetDownloadFlows } from "@/controllers/API/queries/flows/use-get-download-flows";
|
||||||
import { ENABLE_MCP } from "@/customization/feature-flags";
|
import { ENABLE_MCP } from "@/customization/feature-flags";
|
||||||
|
|
@ -15,7 +11,6 @@ import useAlertStore from "@/stores/alertStore";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
interface HeaderComponentProps {
|
interface HeaderComponentProps {
|
||||||
flowType: "flows" | "components" | "mcp";
|
flowType: "flows" | "components" | "mcp";
|
||||||
|
|
@ -113,7 +108,7 @@ const HeaderComponent = ({
|
||||||
</SidebarTrigger>
|
</SidebarTrigger>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{folderName === DEFAULT_FOLDER_DEPRECATED ? DEFAULT_FOLDER : folderName}
|
{folderName}
|
||||||
</div>
|
</div>
|
||||||
{!isEmptyFolder && (
|
{!isEmptyFolder && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,7 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent";
|
import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
|
||||||
import {
|
import { MAX_MCP_SERVER_NAME_LENGTH } from "@/constants/constants";
|
||||||
DEFAULT_FOLDER,
|
|
||||||
DEFAULT_FOLDER_DEPRECATED,
|
|
||||||
MAX_MCP_SERVER_NAME_LENGTH,
|
|
||||||
} from "@/constants/constants";
|
|
||||||
import { createApiKey } from "@/controllers/API";
|
import { createApiKey } from "@/controllers/API";
|
||||||
import {
|
import {
|
||||||
useGetFlowsMCP,
|
useGetFlowsMCP,
|
||||||
|
|
@ -193,7 +189,7 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
|
||||||
|
|
||||||
const MCP_SERVER_JSON = `{
|
const MCP_SERVER_JSON = `{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"lf-${parseString(folderName === DEFAULT_FOLDER_DEPRECATED ? DEFAULT_FOLDER : (folderName ?? "project"), ["snake_case", "no_blank", "lowercase"]).slice(0, MAX_MCP_SERVER_NAME_LENGTH - 4)}": {
|
"lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, MAX_MCP_SERVER_NAME_LENGTH - 4)}": {
|
||||||
"command": "${selectedPlatform === "windows" ? "cmd" : selectedPlatform === "wsl" ? "wsl" : "uvx"}",
|
"command": "${selectedPlatform === "windows" ? "cmd" : selectedPlatform === "wsl" ? "wsl" : "uvx"}",
|
||||||
"args": [
|
"args": [
|
||||||
${
|
${
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ test("add a flow into a folder by drag and drop", async ({ page }) => {
|
||||||
|
|
||||||
// Wait for the target element to be available before evaluation
|
// Wait for the target element to be available before evaluation
|
||||||
|
|
||||||
await page.waitForSelector('[data-testid="sidebar-nav-My Projects"]', {
|
await page.waitForSelector('[data-testid="sidebar-nav-Starter Project"]', {
|
||||||
timeout: 100000,
|
timeout: 100000,
|
||||||
});
|
});
|
||||||
// Create the DataTransfer and File
|
// Create the DataTransfer and File
|
||||||
|
|
@ -94,7 +94,7 @@ test("add a flow into a folder by drag and drop", async ({ page }) => {
|
||||||
}, jsonContent);
|
}, jsonContent);
|
||||||
|
|
||||||
// Now dispatch
|
// Now dispatch
|
||||||
await page.getByTestId("sidebar-nav-My Projects").dispatchEvent("drop", {
|
await page.getByTestId("sidebar-nav-Starter Project").dispatchEvent("drop", {
|
||||||
dataTransfer,
|
dataTransfer,
|
||||||
});
|
});
|
||||||
// wait for the file to be uploaded failed with waitforselector
|
// wait for the file to be uploaded failed with waitforselector
|
||||||
|
|
@ -107,7 +107,7 @@ test("add a flow into a folder by drag and drop", async ({ page }) => {
|
||||||
expect(true).toBeTruthy();
|
expect(true).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.getByTestId("sidebar-nav-My Projects").click();
|
await page.getByTestId("sidebar-nav-Starter Project").click();
|
||||||
|
|
||||||
await page.waitForSelector("text=Getting Started:", {
|
await page.waitForSelector("text=Getting Started:", {
|
||||||
timeout: 100000,
|
timeout: 100000,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ test("user must be able to move flow from folder", async ({ page }) => {
|
||||||
//wait for the project to be created and changed to the new project
|
//wait for the project to be created and changed to the new project
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
await page.getByTestId("sidebar-nav-My Projects").click();
|
await page.getByTestId("sidebar-nav-Starter Project").click();
|
||||||
|
|
||||||
await page.getByText(randomName).hover();
|
await page.getByText(randomName).hover();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue