Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add edit option to global variables table #3712

Merged
merged 16 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/backend/base/langflow/api/v1/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from langflow.services.database.models.variable import VariableCreate, VariableRead, VariableUpdate
from langflow.services.deps import get_session, get_settings_service, get_variable_service
from langflow.services.variable.base import VariableService
from langflow.services.variable.service import GENERIC_TYPE, DatabaseVariableService
from langflow.services.variable.constants import GENERIC_TYPE
from langflow.services.variable.service import DatabaseVariableService

router = APIRouter(prefix="/variables", tags=["Variables"])

Expand Down Expand Up @@ -61,7 +62,6 @@ def read_variables(
"""Read all variables."""
try:
return variable_service.get_all(user_id=current_user.id, session=session)

except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4

from pydantic import ValidationInfo, field_validator
from sqlmodel import JSON, Column, DateTime, Field, Relationship, SQLModel, func

from langflow.services.variable.constants import CREDENTIAL_TYPE

if TYPE_CHECKING:
from langflow.services.database.models.user.model import User

Expand Down Expand Up @@ -52,8 +55,16 @@ class VariableRead(SQLModel):
id: UUID
name: Optional[str] = Field(None, description="Name of the variable")
type: Optional[str] = Field(None, description="Type of the variable")
value: Optional[str] = Field(None, description="Encrypted value of the variable")
default_fields: Optional[List[str]] = Field(None, description="Default fields for the variable")

@field_validator("value")
@classmethod
def validate_value(cls, value: str, info: ValidationInfo):
if info.data.get("type") == CREDENTIAL_TYPE:
return None
return value


class VariableUpdate(SQLModel):
id: UUID # Include the ID for updating
Expand Down
2 changes: 2 additions & 0 deletions src/backend/base/langflow/services/variable/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CREDENTIAL_TYPE = "Credential"
GENERIC_TYPE = "Generic"
2 changes: 1 addition & 1 deletion src/backend/base/langflow/services/variable/kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from langflow.services.database.models.variable.model import Variable, VariableCreate
from langflow.services.settings.service import SettingsService
from langflow.services.variable.base import VariableService
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
from langflow.services.variable.kubernetes_secrets import KubernetesSecretManager, encode_user_id
from langflow.services.variable.service import CREDENTIAL_TYPE, GENERIC_TYPE


class KubernetesSecretService(VariableService, Service):
Expand Down
4 changes: 1 addition & 3 deletions src/backend/base/langflow/services/variable/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
from langflow.services.database.models.variable.model import Variable, VariableCreate, VariableUpdate
from langflow.services.deps import get_session
from langflow.services.variable.base import VariableService
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE

if TYPE_CHECKING:
from langflow.services.settings.service import SettingsService

CREDENTIAL_TYPE = "Credential"
GENERIC_TYPE = "Generic"


class DatabaseVariableService(VariableService, Service):
def __init__(self, settings_service: "SettingsService"):
Expand Down
2 changes: 1 addition & 1 deletion src/backend/tests/unit/api/v1/test_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_create_variable(client, body, active_user, logged_in_headers):
assert body["type"] == result["type"]
assert body["default_fields"] == result["default_fields"]
assert "id" in result.keys()
assert "value" not in result.keys()
assert body["value"] != result["value"]


def test_create_variable__variable_name_alread_exists(client, body, active_user, logged_in_headers):
Expand Down
13 changes: 8 additions & 5 deletions src/backend/tests/unit/services/variable/test_service.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from langflow.services.database.models.variable.model import VariableUpdate
import pytest
from datetime import datetime
from unittest.mock import patch
from uuid import uuid4
from datetime import datetime
from sqlmodel import SQLModel, Session, create_engine

import pytest
from sqlmodel import Session, SQLModel, create_engine

from langflow.services.database.models.variable.model import VariableUpdate
from langflow.services.deps import get_settings_service
from langflow.services.variable.service import GENERIC_TYPE, CREDENTIAL_TYPE, DatabaseVariableService
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
from langflow.services.variable.service import DatabaseVariableService


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
useGetGlobalVariables,
usePatchGlobalVariables,
usePostGlobalVariables,
} from "@/controllers/API/queries/variables";
import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields";
import { GlobalVariable } from "@/types/global_variables";
import { useEffect, useState } from "react";
import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
Expand All @@ -17,34 +19,47 @@ import sortByName from "./utils/sort-by-name";

//TODO IMPLEMENT FORM LOGIC

export default function AddNewVariableButton({
export default function GlobalVariableModal({
children,
asChild,
initialData,
open: myOpen,
setOpen: mySetOpen,
}: {
children: JSX.Element;
children?: JSX.Element;
asChild?: boolean;
initialData?: GlobalVariable;
open?: boolean;
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
}): JSX.Element {
const [key, setKey] = useState("");
const [value, setValue] = useState("");
const [type, setType] = useState("Generic");
const [fields, setFields] = useState<string[]>([]);
const [open, setOpen] = useState(false);
const [key, setKey] = useState(initialData?.name ?? "");
const [value, setValue] = useState(initialData?.value ?? "");
const [type, setType] = useState(initialData?.type ?? "Generic");
const [fields, setFields] = useState<string[]>(
initialData?.default_fields ?? [],
);
const [open, setOpen] =
mySetOpen !== undefined && myOpen !== undefined
? [myOpen, mySetOpen]
: useState(false);
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const { mutate: mutateAddGlobalVariable } = usePostGlobalVariables();
const { mutate: updateVariable } = usePatchGlobalVariables();
const { data: globalVariables } = useGetGlobalVariables();
const [availableFields, setAvailableFields] = useState<string[]>([]);

useEffect(() => {
if (globalVariables && componentFields.size > 0) {
const unavailableFields = getUnavailableFields(globalVariables);
const fields = Array.from(componentFields).filter(
(field) => !unavailableFields.hasOwnProperty(field),
(field) => !unavailableFields.hasOwnProperty(field.trim()),
);
setAvailableFields(
sortByName(fields.concat(initialData?.default_fields ?? [])),
);

setAvailableFields(sortByName(fields));
}
}, [globalVariables, componentFields]);
}, [globalVariables, componentFields, initialData]);

const setSuccessData = useAlertStore((state) => state.setSuccessData);

Expand All @@ -71,35 +86,52 @@ export default function AddNewVariableButton({
setOpen(false);

setSuccessData({
title: `Variable ${name} created successfully`,
title: `Variable ${name} ${initialData ? "updated" : "created"} successfully`,
});
},
onError: (error) => {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error creating variable",
title: `Error ${initialData ? "updating" : "creating"} variable`,
list: [
responseError?.response?.data?.detail ??
"An unexpected error occurred while adding a new variable. Please try again.",
`An unexpected error occurred while ${initialData ? "updating a new" : "creating"} variable. Please try again.`,
],
});
},
});
}

function submitForm() {
if (!initialData) {
handleSaveVariable();
} else {
updateVariable({
id: initialData.id,
name: key,
value: value,
default_fields: fields,
});
setOpen(false);
}
}

return (
<BaseModal
open={open}
setOpen={setOpen}
size="x-small"
onSubmit={handleSaveVariable}
onSubmit={submitForm}
>
<BaseModal.Header
description={
"This variable will be encrypted and will be available for you to use in any of your projects."
}
>
<span className="pr-2"> Create Variable </span>
<span className="pr-2">
{" "}
{initialData ? "Update" : "Create"} Variable{" "}
</span>
<ForwardedIconComponent
name="Globe"
className="h-6 w-6 pl-1 text-primary"
Expand All @@ -119,6 +151,7 @@ export default function AddNewVariableButton({
></Input>
<Label>Type (optional)</Label>
<InputComponent
disabled={initialData?.type !== undefined}
setSelectedOption={(e) => {
setType(e);
}}
Expand Down Expand Up @@ -161,7 +194,10 @@ export default function AddNewVariableButton({
</div>
</BaseModal.Content>
<BaseModal.Footer
submit={{ label: "Save Variable", dataTestId: "save-variable-btn" }}
submit={{
label: `${initialData ? "Update" : "Save"} Variable`,
dataTestId: "save-variable-btn",
}}
/>
</BaseModal>
);
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/components/inputGlobalComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton";
import GlobalVariableModal from "../GlobalVariableModal/GlobalVariableModal";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
import { CommandItem } from "../ui/command";
Expand Down Expand Up @@ -72,7 +72,7 @@ export default function InputGlobalComponent({
optionsPlaceholder={"Global Variables"}
optionsIcon="Globe"
optionsButton={
<AddNewVariableButton>
<GlobalVariableModal>
<CommandItem value="doNotFilter-addNewVariable">
<ForwardedIconComponent
name="Plus"
Expand All @@ -81,7 +81,7 @@ export default function InputGlobalComponent({
/>
<span>Add New Variable</span>
</CommandItem>
</AddNewVariableButton>
</GlobalVariableModal>
}
optionButton={(option) => (
<DeleteConfirmationModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function TableAutoCellRender({
setValue,
colDef,
formatter,
api,
}: CustomCellRender) {
function getCellType() {
let format: string = formatter ? formatter : typeof value;
Expand Down Expand Up @@ -63,7 +64,10 @@ export default function TableAutoCellRender({
} else {
return (
<StringReader
editable={!!colDef?.onCellValueChanged}
editable={
!!colDef?.onCellValueChanged ||
!!api.getGridOption("onCellValueChanged")
}
setValue={setValue!}
string={value}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useMutationFunctionType } from "@/types/api";
import { GlobalVariable } from "@/types/global_variables";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";

interface PatchGlobalVariablesParams {
name: string;
value: string;
name?: string;
value?: string;
id: string;
default_fields?: string[];
}

export const usePatchGlobalVariables: useMutationFunctionType<
Expand All @@ -16,15 +18,13 @@ export const usePatchGlobalVariables: useMutationFunctionType<
> = (options?) => {
const { mutate, queryClient } = UseRequestProcessor();

async function patchGlobalVariables({
name,
value,
id,
}: PatchGlobalVariablesParams): Promise<any> {
const res = await api.patch(`${getURL("VARIABLES")}/${id}`, {
name,
value,
});
async function patchGlobalVariables(
GlobalVariable: PatchGlobalVariablesParams,
): Promise<any> {
const res = await api.patch(
`${getURL("VARIABLES")}/${GlobalVariable.id}`,
GlobalVariable,
);
return res.data;
}

Expand Down
Loading
Loading