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:
Lucas Oliveira 2025-07-02 16:24:23 -03:00 committed by GitHub
commit dc671ae204
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 93 additions and 46 deletions

View file

@ -29,7 +29,7 @@ curl -X GET \
```json
[
{
"name": "My Projects",
"name": "Starter Project",
"description": "Manage your own projects. Download and upload projects.",
"id": "1415de42-8f01-4f36-bf34-539f23e47466",
"parent_id": null
@ -116,7 +116,7 @@ curl -X GET \
```json
[
{
"name": "My Projects",
"name": "Starter Project",
"description": "Manage your own projects. Download and upload projects.",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"parent_id": null
@ -216,4 +216,4 @@ curl -X POST \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@20241230_135006_langflow_flows.zip;type=application/zip"
```
```

View file

@ -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)

View file

@ -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.schema.message import Message
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.storage.utils import build_content_type_from_extension
@ -390,7 +389,6 @@ async def install_mcp_config(
logger.debug("Windows detected, using cmd command")
name = project.name
name = NEW_FOLDER_NAME if name == DEFAULT_FOLDER_NAME else name
# Create the MCP configuration
mcp_config = {
@ -517,7 +515,6 @@ async def check_installed_mcp_servers(
# Project server name pattern (must match the logic in install function)
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)]}"
logger.debug(

View file

@ -1,3 +1,2 @@
DEFAULT_FOLDER_DESCRIPTION = "Manage your own flows. Download and upload projects."
DEFAULT_FOLDER_NAME = "My Projects"
NEW_FOLDER_NAME = "Starter Project"
DEFAULT_FOLDER_NAME = "Starter Project"

View file

@ -6,7 +6,7 @@ import {
SelectItem,
SelectTrigger,
} 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 { cn } from "@/utils/utils";
import { handleSelectChange } from "../helpers/handle-select-change";
@ -57,7 +57,7 @@ export const SelectOptions = ({
</SelectTrigger>
</ShadTooltip>
<SelectContent align="end" alignOffset={-16} position="popper">
{item.name !== DEFAULT_FOLDER_DEPRECATED && (
{item.name !== DEFAULT_FOLDER && (
<SelectItem
id="rename-button"
value="rename"

View file

@ -10,10 +10,7 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import {
DEFAULT_FOLDER,
DEFAULT_FOLDER_DEPRECATED,
} from "@/constants/constants";
import { DEFAULT_FOLDER } from "@/constants/constants";
import { useUpdateUser } from "@/controllers/API/queries/auth";
import {
usePatchFolders,
@ -277,7 +274,7 @@ const SideBarFoldersButtonsComponent = ({
};
const handleDoubleClick = (event, item) => {
if (item.name === DEFAULT_FOLDER_DEPRECATED) {
if (item.name === DEFAULT_FOLDER) {
return;
}
@ -424,9 +421,7 @@ const SideBarFoldersButtonsComponent = ({
/>
) : (
<span className="block w-0 grow truncate text-sm opacity-100">
{item.name === DEFAULT_FOLDER_DEPRECATED
? DEFAULT_FOLDER
: item.name}
{item.name}
</span>
)}
</div>

View file

@ -558,7 +558,6 @@ export const NOUNS: string[] = [
export const USER_PROJECTS_HEADER = "My Collection";
export const DEFAULT_FOLDER = "Starter Project";
export const DEFAULT_FOLDER_DEPRECATED = "My Projects";
export const MAX_MCP_SERVER_NAME_LENGTH = 30;

View file

@ -1,7 +1,4 @@
import {
DEFAULT_FOLDER,
DEFAULT_FOLDER_DEPRECATED,
} from "@/constants/constants";
import { DEFAULT_FOLDER } from "@/constants/constants";
import { FolderType } from "@/pages/MainPage/entities";
import useAuthStore from "@/stores/authStore";
import { useFolderStore } from "@/stores/foldersStore";
@ -26,9 +23,7 @@ export const useGetFoldersQuery: useQueryFunctionType<
const res = await api.get(`${getURL("PROJECTS")}/`);
const data = res.data;
const myCollectionId = data?.find(
(f) => f.name === DEFAULT_FOLDER_DEPRECATED,
)?.id;
const myCollectionId = data?.find((f) => f.name === DEFAULT_FOLDER)?.id;
setMyCollectionId(myCollectionId);
setFolders(data);

View file

@ -16,10 +16,6 @@ export const customGetDownloadFolderBlob = (
folderName?: string,
setSuccessData?: (data: any) => void,
) => {
if (folderName === "My Projects") {
folderName = "Starter Project";
}
// Create a blob from the response data
const blob = new Blob([response.data], {
type: "application/x-zip-compressed",

View file

@ -3,10 +3,6 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
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 { useGetDownloadFlows } from "@/controllers/API/queries/flows/use-get-download-flows";
import { ENABLE_MCP } from "@/customization/feature-flags";
@ -15,7 +11,6 @@ import useAlertStore from "@/stores/alertStore";
import { cn } from "@/utils/utils";
import { debounce } from "lodash";
import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
interface HeaderComponentProps {
flowType: "flows" | "components" | "mcp";
@ -113,7 +108,7 @@ const HeaderComponent = ({
</SidebarTrigger>
</div>
</div>
{folderName === DEFAULT_FOLDER_DEPRECATED ? DEFAULT_FOLDER : folderName}
{folderName}
</div>
{!isEmptyFolder && (
<>

View file

@ -3,11 +3,7 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
import {
DEFAULT_FOLDER,
DEFAULT_FOLDER_DEPRECATED,
MAX_MCP_SERVER_NAME_LENGTH,
} from "@/constants/constants";
import { MAX_MCP_SERVER_NAME_LENGTH } from "@/constants/constants";
import { createApiKey } from "@/controllers/API";
import {
useGetFlowsMCP,
@ -193,7 +189,7 @@ const McpServerTab = ({ folderName }: { folderName: string }) => {
const MCP_SERVER_JSON = `{
"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"}",
"args": [
${

View file

@ -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
await page.waitForSelector('[data-testid="sidebar-nav-My Projects"]', {
await page.waitForSelector('[data-testid="sidebar-nav-Starter Project"]', {
timeout: 100000,
});
// Create the DataTransfer and File
@ -94,7 +94,7 @@ test("add a flow into a folder by drag and drop", async ({ page }) => {
}, jsonContent);
// Now dispatch
await page.getByTestId("sidebar-nav-My Projects").dispatchEvent("drop", {
await page.getByTestId("sidebar-nav-Starter Project").dispatchEvent("drop", {
dataTransfer,
});
// 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();
}
await page.getByTestId("sidebar-nav-My Projects").click();
await page.getByTestId("sidebar-nav-Starter Project").click();
await page.waitForSelector("text=Getting Started:", {
timeout: 100000,

View file

@ -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
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();