feat: Refactor delete_multiple_flows endpoint to use DELETE method (#2029)
* feat: Refactor delete_multiple_flows endpoint to use DELETE method
The code changes modify the `delete_multiple_flows` endpoint in the `flows.py` file. The endpoint was previously using the `POST` method, but it has been refactored to use the `DELETE` method instead. This change aligns with RESTful API conventions and improves the clarity and consistency of the codebase.
Note: The commit message has been generated based on the provided code changes and recent commits.
* Refactor delete_multiple_flows endpoint to use DELETE method
* Refactor delete_multiple_flows endpoint to use DELETE method
* ♻️ (index.ts): refactor deleteBatch function to use data field instead of params for batch deletion
---------
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
491e3698f6
commit
7bfc1a55c8
4 changed files with 80 additions and 41 deletions
|
|
@ -9,7 +9,7 @@ from loguru import logger
|
||||||
from sqlmodel import Session, col, select
|
from sqlmodel import Session, col, select
|
||||||
|
|
||||||
from langflow.api.utils import remove_api_keys, validate_is_component
|
from langflow.api.utils import remove_api_keys, validate_is_component
|
||||||
from langflow.api.v1.schemas import FlowListCreate, FlowListIds, FlowListRead
|
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
||||||
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
|
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
|
||||||
from langflow.services.auth.utils import get_current_active_user
|
from langflow.services.auth.utils import get_current_active_user
|
||||||
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||||
|
|
@ -258,9 +258,9 @@ async def download_file(
|
||||||
return FlowListRead(flows=flows)
|
return FlowListRead(flows=flows)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/multiple_delete/")
|
@router.delete("/")
|
||||||
async def delete_multiple_flows(
|
async def delete_multiple_flows(
|
||||||
flow_ids: FlowListIds, user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
|
flow_ids: List[UUID], user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Delete multiple flows by their IDs.
|
Delete multiple flows by their IDs.
|
||||||
|
|
@ -274,9 +274,7 @@ async def delete_multiple_flows(
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
deleted_flows = db.exec(
|
deleted_flows = db.exec(select(Flow).where(col(Flow.id).in_(flow_ids)).where(Flow.user_id == user.id)).all()
|
||||||
select(Flow).where(col(Flow.id).in_(flow_ids.flow_ids)).where(Flow.user_id == user.id)
|
|
||||||
).all()
|
|
||||||
for flow in deleted_flows:
|
for flow in deleted_flows:
|
||||||
db.delete(flow)
|
db.delete(flow)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
|
||||||
|
|
@ -739,3 +739,5 @@ export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to displa
|
||||||
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
||||||
|
|
||||||
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
||||||
|
|
||||||
|
export const MAX_BATCH_SIZE = 50;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||||
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
import { Edge, Node, ReactFlowJsonObject } from "reactflow";
|
import { Edge, Node, ReactFlowJsonObject } from "reactflow";
|
||||||
import { BASE_URL_API } from "../../constants/constants";
|
import { BASE_URL_API, MAX_BATCH_SIZE } from "../../constants/constants";
|
||||||
import { api } from "../../controllers/API/api";
|
import { api } from "../../controllers/API/api";
|
||||||
import {
|
import {
|
||||||
APIObjectType,
|
APIObjectType,
|
||||||
|
|
@ -61,7 +61,7 @@ export async function sendAll(data: sendAllProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postValidateCode(
|
export async function postValidateCode(
|
||||||
code: string,
|
code: string
|
||||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ export async function postValidateCode(
|
||||||
export async function postValidatePrompt(
|
export async function postValidatePrompt(
|
||||||
name: string,
|
name: string,
|
||||||
template: string,
|
template: string,
|
||||||
frontend_node: APIClassType,
|
frontend_node: APIClassType
|
||||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||||
return api.post(`${BASE_URL_API}validate/prompt`, {
|
return api.post(`${BASE_URL_API}validate/prompt`, {
|
||||||
name,
|
name,
|
||||||
|
|
@ -149,7 +149,7 @@ export async function saveFlowToDatabase(newFlow: {
|
||||||
* @throws Will throw an error if the update fails.
|
* @throws Will throw an error if the update fails.
|
||||||
*/
|
*/
|
||||||
export async function updateFlowInDatabase(
|
export async function updateFlowInDatabase(
|
||||||
updatedFlow: FlowType,
|
updatedFlow: FlowType
|
||||||
): Promise<FlowType> {
|
): Promise<FlowType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||||
|
|
@ -327,7 +327,7 @@ export async function getHealth() {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export async function getBuildStatus(
|
export async function getBuildStatus(
|
||||||
flowId: string,
|
flowId: string
|
||||||
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
|
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
|
||||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +340,7 @@ export async function getBuildStatus(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export async function postBuildInit(
|
export async function postBuildInit(
|
||||||
flow: FlowType,
|
flow: FlowType
|
||||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +356,7 @@ export async function postBuildInit(
|
||||||
*/
|
*/
|
||||||
export async function uploadFile(
|
export async function uploadFile(
|
||||||
file: File,
|
file: File,
|
||||||
id: string,
|
id: string
|
||||||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
|
|
@ -365,7 +365,7 @@ export async function uploadFile(
|
||||||
|
|
||||||
export async function postCustomComponent(
|
export async function postCustomComponent(
|
||||||
code: string,
|
code: string,
|
||||||
apiClass: APIClassType,
|
apiClass: APIClassType
|
||||||
): Promise<AxiosResponse<APIClassType>> {
|
): Promise<AxiosResponse<APIClassType>> {
|
||||||
// let template = apiClass.template;
|
// let template = apiClass.template;
|
||||||
return await api.post(`${BASE_URL_API}custom_component`, {
|
return await api.post(`${BASE_URL_API}custom_component`, {
|
||||||
|
|
@ -378,7 +378,7 @@ export async function postCustomComponentUpdate(
|
||||||
code: string,
|
code: string,
|
||||||
template: APITemplateType,
|
template: APITemplateType,
|
||||||
field: string,
|
field: string,
|
||||||
field_value: any,
|
field_value: any
|
||||||
): Promise<AxiosResponse<APIClassType>> {
|
): Promise<AxiosResponse<APIClassType>> {
|
||||||
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
||||||
code,
|
code,
|
||||||
|
|
@ -400,7 +400,7 @@ export async function onLogin(user: LoginType) {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
|
|
@ -462,11 +462,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
||||||
|
|
||||||
export async function getUsersPage(
|
export async function getUsersPage(
|
||||||
skip: number,
|
skip: number,
|
||||||
limit: number,
|
limit: number
|
||||||
): Promise<Array<Users>> {
|
): Promise<Array<Users>> {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(
|
const res = await api.get(
|
||||||
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`,
|
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
|
||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -503,7 +503,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
|
||||||
try {
|
try {
|
||||||
const res = await api.patch(
|
const res = await api.patch(
|
||||||
`${BASE_URL_API}users/${user_id}/reset-password`,
|
`${BASE_URL_API}users/${user_id}/reset-password`,
|
||||||
user,
|
user
|
||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -577,7 +577,7 @@ export async function saveFlowStore(
|
||||||
last_tested_version?: string;
|
last_tested_version?: string;
|
||||||
},
|
},
|
||||||
tags: string[],
|
tags: string[],
|
||||||
publicFlow = false,
|
publicFlow = false
|
||||||
): Promise<FlowType> {
|
): Promise<FlowType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.post(`${BASE_URL_API}store/components/`, {
|
const response = await api.post(`${BASE_URL_API}store/components/`, {
|
||||||
|
|
@ -706,7 +706,7 @@ export async function postStoreComponents(component: Component) {
|
||||||
export async function getComponent(component_id: string) {
|
export async function getComponent(component_id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(
|
const res = await api.get(
|
||||||
`${BASE_URL_API}store/components/${component_id}`,
|
`${BASE_URL_API}store/components/${component_id}`
|
||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -721,7 +721,7 @@ export async function searchComponent(
|
||||||
page?: number | null,
|
page?: number | null,
|
||||||
limit?: number | null,
|
limit?: number | null,
|
||||||
status?: string | null,
|
status?: string | null,
|
||||||
tags?: string[],
|
tags?: string[]
|
||||||
): Promise<StoreComponentResponse | undefined> {
|
): Promise<StoreComponentResponse | undefined> {
|
||||||
try {
|
try {
|
||||||
let url = `${BASE_URL_API}store/components/`;
|
let url = `${BASE_URL_API}store/components/`;
|
||||||
|
|
@ -833,7 +833,7 @@ export async function updateFlowStore(
|
||||||
},
|
},
|
||||||
tags: string[],
|
tags: string[],
|
||||||
publicFlow = false,
|
publicFlow = false,
|
||||||
id: string,
|
id: string
|
||||||
): Promise<FlowType> {
|
): Promise<FlowType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
|
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
|
||||||
|
|
@ -917,7 +917,7 @@ export async function deleteGlobalVariable(id: string) {
|
||||||
export async function updateGlobalVariable(
|
export async function updateGlobalVariable(
|
||||||
name: string,
|
name: string,
|
||||||
value: string,
|
value: string,
|
||||||
id: string,
|
id: string
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
|
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
|
||||||
|
|
@ -936,7 +936,7 @@ export async function getVerticesOrder(
|
||||||
startNodeId?: string | null,
|
startNodeId?: string | null,
|
||||||
stopNodeId?: string | null,
|
stopNodeId?: string | null,
|
||||||
nodes?: Node[],
|
nodes?: Node[],
|
||||||
Edges?: Edge[],
|
Edges?: Edge[]
|
||||||
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
||||||
// nodeId is optional and is a query parameter
|
// nodeId is optional and is a query parameter
|
||||||
// if nodeId is not provided, the API will return all vertices
|
// if nodeId is not provided, the API will return all vertices
|
||||||
|
|
@ -956,19 +956,19 @@ export async function getVerticesOrder(
|
||||||
return await api.post(
|
return await api.post(
|
||||||
`${BASE_URL_API}build/${flowId}/vertices`,
|
`${BASE_URL_API}build/${flowId}/vertices`,
|
||||||
data,
|
data,
|
||||||
config,
|
config
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postBuildVertex(
|
export async function postBuildVertex(
|
||||||
flowId: string,
|
flowId: string,
|
||||||
vertexId: string,
|
vertexId: string,
|
||||||
input_value: string,
|
input_value: string
|
||||||
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
||||||
// input_value is optional and is a query parameter
|
// input_value is optional and is a query parameter
|
||||||
return await api.post(
|
return await api.post(
|
||||||
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
|
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
|
||||||
input_value ? { inputs: { input_value: input_value } } : undefined,
|
input_value ? { inputs: { input_value: input_value } } : undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -992,25 +992,54 @@ export async function getFlowPool({
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFlowPool(
|
export async function deleteFlowPool(
|
||||||
flowId: string,
|
flowId: string
|
||||||
): Promise<AxiosResponse<any>> {
|
): Promise<AxiosResponse<any>> {
|
||||||
const config = {};
|
const config = {};
|
||||||
config["params"] = { flow_id: flowId };
|
config["params"] = { flow_id: flowId };
|
||||||
return await api.delete(`${BASE_URL_API}monitor/builds`, config);
|
return await api.delete(`${BASE_URL_API}monitor/builds`, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple flow components by their IDs.
|
||||||
|
* @param flowIds - An array of flow IDs to be deleted.
|
||||||
|
* @param token - The authorization token for the API request.
|
||||||
|
* @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses.
|
||||||
|
*/
|
||||||
export async function multipleDeleteFlowsComponents(
|
export async function multipleDeleteFlowsComponents(
|
||||||
flowIds: string[],
|
flowIds: string[]
|
||||||
): Promise<AxiosResponse<any>> {
|
): Promise<AxiosResponse<any>[]> {
|
||||||
return await api.post(`${BASE_URL_API}flows/multiple_delete/`, {
|
const batches: string[][] = [];
|
||||||
flow_ids: flowIds,
|
|
||||||
|
// Split the flowIds into batches
|
||||||
|
for (let i = 0; i < flowIds.length; i += MAX_BATCH_SIZE) {
|
||||||
|
batches.push(flowIds.slice(i, i + MAX_BATCH_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to delete a batch of flow IDs
|
||||||
|
const deleteBatch = async (batch: string[]): Promise<AxiosResponse<any>> => {
|
||||||
|
try {
|
||||||
|
return await api.delete(`${BASE_URL_API}flows/`, {
|
||||||
|
data: batch,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting flows:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute all delete requests
|
||||||
|
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
|
||||||
|
deleteBatch(batch)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return the responses after all requests are completed
|
||||||
|
return Promise.all(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTransactionTable(
|
export async function getTransactionTable(
|
||||||
id: string,
|
id: string,
|
||||||
mode: "intersection" | "union",
|
mode: "intersection" | "union",
|
||||||
params = {},
|
params = {}
|
||||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||||
const config = {};
|
const config = {};
|
||||||
config["params"] = { flow_id: id };
|
config["params"] = { flow_id: id };
|
||||||
|
|
@ -1025,7 +1054,7 @@ export async function getTransactionTable(
|
||||||
export async function getMessagesTable(
|
export async function getMessagesTable(
|
||||||
id: string,
|
id: string,
|
||||||
mode: "intersection" | "union",
|
mode: "intersection" | "union",
|
||||||
params = {},
|
params = {}
|
||||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||||
const config = {};
|
const config = {};
|
||||||
config["params"] = { flow_id: id };
|
config["params"] = { flow_id: id };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import os
|
|
||||||
from typing import Optional, List
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
@ -13,7 +11,6 @@ from langflow.services.database.models.base import orjson_dumps
|
||||||
from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate
|
from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate
|
||||||
from langflow.services.database.utils import session_getter
|
from langflow.services.database.utils import session_getter
|
||||||
from langflow.services.deps import get_db_service
|
from langflow.services.deps import get_db_service
|
||||||
from langflow.services.settings.base import Settings
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
|
@ -113,6 +110,21 @@ def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_
|
||||||
assert response.json()["message"] == "Flow deleted successfully"
|
assert response.json()["message"] == "Flow deleted successfully"
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
|
||||||
|
# Create ten flows
|
||||||
|
number_of_flows = 10
|
||||||
|
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
|
||||||
|
flow_ids = []
|
||||||
|
for flow in flows:
|
||||||
|
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
|
||||||
|
assert response.status_code == 201
|
||||||
|
flow_ids.append(response.json()["id"])
|
||||||
|
|
||||||
|
response = client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids)
|
||||||
|
assert response.status_code == 200, response.content
|
||||||
|
assert response.json().get("deleted") == number_of_flows
|
||||||
|
|
||||||
|
|
||||||
def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers):
|
def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers):
|
||||||
flow = orjson.loads(json_flow)
|
flow = orjson.loads(json_flow)
|
||||||
data = flow["data"]
|
data = flow["data"]
|
||||||
|
|
@ -263,5 +275,3 @@ def test_load_flows(client: TestClient, load_flows_dir):
|
||||||
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
|
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["name"] == "BasicExample"
|
assert response.json()["name"] == "BasicExample"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue