Skip to content

Commit e7a2005

Browse files
Cristhianzlitalojohnnyautofix-ci[bot]ogabrielluiz
authored
fix: Improve update_flow data consistency, refine error handling, and add folder-moving tests (langflow-ai#5516)
* 🐛 (flows.py): Fix issue where flow data was not being properly updated in the database during flow update 📝 (flows.py): Improve error handling and rollback database session in case of exceptions during flow update 📝 (flows.py): Refactor code to handle unique constraint errors and provide more informative error messages 📝 (utils.py): Refactor get_webhook_component_in_flow function to handle cases where flow_data may not have 'nodes' attribute * ✨ (sideBarFolderButtons/index.tsx): add unique id attribute to sidebar folder buttons for improved accessibility and testing ✨ (general-bugs-move-flow-from-folder.spec.ts): add test to ensure user can move flow from one folder to another in the frontend application * 🐛 (flows.py): remove unnecessary session rollback to prevent potential data inconsistency ♻️ (service.py): refactor with_session method to handle session commit and rollback more effectively * style: adjust line breaks for readability * style: reorder imports * fix: ruff error try300 * [autofix.ci] apply automated fixes * fix: mypy error module has no attribute "timeout" * 🐛 (flows.py): remove unnecessary error handling code and improve exception handling for better error propagation and clarity * [autofix.ci] apply automated fixes * Update src/backend/base/langflow/services/database/service.py Co-authored-by: Gabriel Luiz Freitas Almeida <[email protected]> * use model dump besides overwrite value * [autofix.ci] apply automated fixes * 📝 (chat.py): improve code readability by refactoring session handling and adding comments for clarity 🔧 (chat.py): refactor code to create a fresh session for database operations and improve session management in build_flow function * [autofix.ci] apply automated fixes * refactor: remove unused session parameter from build_flow function in chat.py --------- Co-authored-by: italojohnny <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida <[email protected]>
1 parent 02c76a9 commit e7a2005

File tree

6 files changed

+93
-25
lines changed

6 files changed

+93
-25
lines changed

src/backend/base/langflow/api/v1/chat.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ async def build_flow(
152152
start_component_id: str | None = None,
153153
log_builds: bool | None = True,
154154
current_user: CurrentActiveUser,
155-
session: DbSession,
156155
):
157156
chat_service = get_chat_service()
158157
telemetry_service = get_telemetry_service()
@@ -164,15 +163,20 @@ async def build_graph_and_get_order() -> tuple[list[str], list[str], Graph]:
164163
components_count = None
165164
try:
166165
flow_id_str = str(flow_id)
167-
if not data:
168-
graph = await build_graph_from_db(flow_id=flow_id, session=session, chat_service=chat_service)
169-
else:
170-
async with session_scope() as new_session:
171-
result = await new_session.exec(select(Flow.name).where(Flow.id == flow_id))
166+
# Create a fresh session for database operations
167+
async with session_scope() as fresh_session:
168+
if not data:
169+
graph = await build_graph_from_db(flow_id=flow_id, session=fresh_session, chat_service=chat_service)
170+
else:
171+
result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
172172
flow_name = result.first()
173-
graph = await build_graph_from_data(
174-
flow_id=flow_id_str, payload=data.model_dump(), user_id=str(current_user.id), flow_name=flow_name
175-
)
173+
graph = await build_graph_from_data(
174+
flow_id=flow_id_str,
175+
payload=data.model_dump(),
176+
user_id=str(current_user.id),
177+
flow_name=flow_name,
178+
)
179+
176180
graph.validate_stream()
177181
if stop_component_id or start_component_id:
178182
try:

src/backend/base/langflow/api/v1/flows.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,18 @@ async def update_flow(
271271
user_id=current_user.id,
272272
settings_service=settings_service,
273273
)
274-
except Exception as e:
275-
raise HTTPException(status_code=500, detail=str(e)) from e
276274

277-
if not db_flow:
278-
raise HTTPException(status_code=404, detail="Flow not found")
275+
if not db_flow:
276+
raise HTTPException(status_code=404, detail="Flow not found")
277+
278+
update_data = flow.model_dump(exclude_unset=True, exclude_none=True)
279279

280-
try:
281-
flow_data = flow.model_dump(exclude_unset=True)
282280
if settings_service.settings.remove_api_keys:
283-
flow_data = remove_api_keys(flow_data)
284-
for key, value in flow_data.items():
281+
update_data = remove_api_keys(update_data)
282+
283+
for key, value in update_data.items():
285284
setattr(db_flow, key, value)
285+
286286
webhook_component = get_webhook_component_in_flow(db_flow.data)
287287
db_flow.webhook = webhook_component is not None
288288
db_flow.updated_at = datetime.now(timezone.utc)
@@ -291,24 +291,25 @@ async def update_flow(
291291
default_folder = (await session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME))).first()
292292
if default_folder:
293293
db_flow.folder_id = default_folder.id
294+
294295
session.add(db_flow)
295296
await session.commit()
296297
await session.refresh(db_flow)
298+
297299
except Exception as e:
298-
# If it is a validation error, return the error message
299-
if hasattr(e, "errors"):
300-
raise HTTPException(status_code=400, detail=str(e)) from e
301300
if "UNIQUE constraint failed" in str(e):
302301
# Get the name of the column that failed
303302
columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
304303
# UNIQUE constraint failed: flow.user_id, flow.name
305304
# or UNIQUE constraint failed: flow.name
306305
# if the column has id in it, we want the other column
307306
column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
308-
309307
raise HTTPException(
310308
status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
311309
) from e
310+
311+
if hasattr(e, "status_code"):
312+
raise HTTPException(status_code=e.status_code, detail=str(e)) from e
312313
raise HTTPException(status_code=500, detail=str(e)) from e
313314

314315
return db_flow

src/backend/base/langflow/services/database/models/flow/utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
def get_webhook_component_in_flow(flow_data: dict):
77
"""Get webhook component in flow data."""
8-
for node in flow_data.get("nodes", []):
9-
if "Webhook" in node.get("id"):
10-
return node
8+
if hasattr(flow_data, "nodes"):
9+
for node in flow_data.get("nodes", []):
10+
if "Webhook" in node.get("id"):
11+
return node
1112
return None
1213

1314

src/backend/base/langflow/services/database/service.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,14 @@ def on_connection(self, dbapi_connection, _connection_record) -> None:
136136
@asynccontextmanager
137137
async def with_session(self):
138138
async with AsyncSession(self.engine, expire_on_commit=False) as session:
139-
yield session
139+
try:
140+
yield session
141+
if session.is_active:
142+
await session.commit()
143+
except Exception:
144+
logger.error("An error occurred during the session scope.")
145+
await session.rollback()
146+
raise
140147

141148
async def assign_orphaned_flows_to_superuser(self) -> None:
142149
"""Assign orphaned flows to the default superuser when auto login is enabled."""

src/frontend/src/components/core/folderSidebarComponent/components/sideBarFolderButtons/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ const SideBarFoldersButtonsComponent = ({
373373
onDrop={(e) => onDrop(e, item.id!)}
374374
key={item.id}
375375
data-testid={`sidebar-nav-${item.name}`}
376+
id={`sidebar-nav-${item.name}`}
376377
isActive={checkPathName(item.id!)}
377378
onClick={() => handleChangeFolder!(item.id!)}
378379
className={cn(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expect, test } from "@playwright/test";
2+
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
3+
4+
test("user must be able to move flow from folder", async ({ page }) => {
5+
const randomName = Math.random().toString(36).substring(2, 15);
6+
7+
await awaitBootstrapTest(page);
8+
9+
await page.getByTestId("side_nav_options_all-templates").click();
10+
await page.getByRole("heading", { name: "Basic Prompting" }).click();
11+
12+
await page.waitForSelector('[data-testid="flow_name"]', {
13+
timeout: 3000,
14+
});
15+
16+
await page.getByTestId("flow_name").click();
17+
await page.getByText("Flow Settings").first().click();
18+
await page.getByPlaceholder("Flow name").fill(randomName);
19+
20+
await page.getByTestId("save-flow-settings").click();
21+
22+
await page.getByText("Changes saved successfully").isVisible();
23+
24+
await page.getByTestId("icon-ChevronLeft").click();
25+
await page.waitForSelector('[data-testid="add-folder-button"]', {
26+
timeout: 3000,
27+
});
28+
29+
await page.getByTestId("add-folder-button").click();
30+
31+
//wait for the folder to be created and changed to the new folder
32+
await page.waitForTimeout(1000);
33+
34+
await page.getByTestId("sidebar-nav-My Projects").click();
35+
36+
await page.getByText(randomName).hover();
37+
38+
await page
39+
.getByTestId("list-card")
40+
.first()
41+
.dragTo(page.locator('//*[@id="sidebar-nav-New Folder"]'));
42+
43+
//wait for the drag and drop to be completed
44+
await page.waitForTimeout(1000);
45+
46+
await page.getByTestId("sidebar-nav-New Folder").click();
47+
48+
await page.waitForSelector('[data-testid="list-card"]', {
49+
timeout: 3000,
50+
});
51+
52+
const flowNameCount = await page.getByText(randomName).count();
53+
expect(flowNameCount).toBeGreaterThan(0);
54+
});

0 commit comments

Comments
 (0)