Merge branch 'saveComponent' into feature/store
This commit is contained in:
commit
91d0da573d
21 changed files with 268 additions and 509 deletions
572
poetry.lock
generated
572
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.5.1"
|
version = "0.5.3"
|
||||||
description = "A Python package with a built-in web application"
|
description = "A Python package with a built-in web application"
|
||||||
authors = ["Logspace <contact@logspace.ai>"]
|
authors = ["Logspace <contact@logspace.ai>"]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ def set_var_for_macos_issue():
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
|
os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
|
||||||
|
# https://stackoverflow.com/questions/75747888/uwsgi-segmentation-fault-with-flask-python-app-behind-nginx-after-running-for-2 # noqa
|
||||||
|
os.environ["no_proxy"] = "*" # to avoid error with gunicorn
|
||||||
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
|
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import exc
|
||||||
import sqlmodel # noqa: F401
|
import sqlmodel # noqa: F401
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|
@ -20,16 +21,21 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
connection = op.get_bind()
|
||||||
try:
|
try:
|
||||||
op.drop_table("flowstyle")
|
op.drop_table("flowstyle")
|
||||||
with op.batch_alter_table("component", schema=None) as batch_op:
|
with op.batch_alter_table("component", schema=None) as batch_op:
|
||||||
batch_op.drop_index("ix_component_frontend_node_id")
|
batch_op.drop_index("ix_component_frontend_node_id")
|
||||||
batch_op.drop_index("ix_component_name")
|
batch_op.drop_index("ix_component_name")
|
||||||
|
except exc.SQLAlchemyError:
|
||||||
|
connection.execute("ROLLBACK")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
op.drop_table("component")
|
op.drop_table("component")
|
||||||
|
except exc.SQLAlchemyError:
|
||||||
|
connection.execute("ROLLBACK")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.services.auth import utils as auth_utils
|
from langflow.services.auth import utils as auth_utils
|
||||||
from langflow.services.database.models.flow.flow import Flow
|
from langflow.services.database.models.flow.flow import Flow, FlowCreate
|
||||||
from langflow.services.database.models.user.user import User
|
from langflow.services.database.models.user.user import User
|
||||||
from langflow.services.deps import (
|
from langflow.services.deps import (
|
||||||
get_session,
|
get_session,
|
||||||
|
|
@ -21,11 +21,15 @@ router = APIRouter(prefix="/store", tags=["Components Store"])
|
||||||
|
|
||||||
@router.post("/", response_model=ComponentResponse)
|
@router.post("/", response_model=ComponentResponse)
|
||||||
def create_component(
|
def create_component(
|
||||||
component: Flow,
|
component: FlowCreate,
|
||||||
store_service: StoreService = Depends(get_store_service),
|
store_service: StoreService = Depends(get_store_service),
|
||||||
user=Depends(auth_utils.get_current_active_user),
|
user=Depends(auth_utils.get_current_active_user),
|
||||||
settings_service=Depends(get_settings_service),
|
settings_service=Depends(get_settings_service),
|
||||||
):
|
):
|
||||||
|
if not user.store_api_key:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="You must have a store API key set."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
api_key = user.store_api_key
|
api_key = user.store_api_key
|
||||||
decrypted = auth_utils.decrypt_api_key(api_key, settings_service)
|
decrypted = auth_utils.decrypt_api_key(api_key, settings_service)
|
||||||
|
|
|
||||||
|
|
@ -79,4 +79,5 @@ class ConversationalAgent(CustomComponent):
|
||||||
memory=memory,
|
memory=memory,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
return_intermediate_steps=True,
|
return_intermediate_steps=True,
|
||||||
|
handle_parsing_errors=True,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ class Vertex:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Error building node {self.vertex_type}: {str(exc)}"
|
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class FlowBase(SQLModelSerializable):
|
||||||
name: str = Field(index=True)
|
name: str = Field(index=True)
|
||||||
description: Optional[str] = Field(index=True)
|
description: Optional[str] = Field(index=True)
|
||||||
data: Optional[Dict] = Field(default=None, nullable=True)
|
data: Optional[Dict] = Field(default=None, nullable=True)
|
||||||
is_component: bool = Field(default=False, nullable=True)
|
is_component: Optional[bool] = Field(default=False, nullable=True)
|
||||||
|
|
||||||
@validator("data")
|
@validator("data")
|
||||||
def validate_json(v):
|
def validate_json(v):
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,35 @@ class DatabaseService(Service):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def init_alembic(self):
|
||||||
|
logger.info("Initializing alembic")
|
||||||
|
alembic_cfg = Config()
|
||||||
|
alembic_cfg.set_main_option("script_location", str(self.script_location))
|
||||||
|
alembic_cfg.set_main_option("sqlalchemy.url", self.database_url)
|
||||||
|
command.stamp(alembic_cfg, "head")
|
||||||
|
logger.info("Alembic initialized")
|
||||||
|
|
||||||
def run_migrations(self):
|
def run_migrations(self):
|
||||||
|
# First we need to check if alembic has been initialized
|
||||||
|
# If not, we need to initialize it
|
||||||
|
# if not self.script_location.exists(): # this is not the correct way to check if alembic has been initialized
|
||||||
|
# We need to check if the alembic_version table exists
|
||||||
|
# if not, we need to initialize alembic
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
# If the table does not exist it throws an error
|
||||||
|
# so we need to catch it
|
||||||
|
try:
|
||||||
|
session.execute("SELECT * FROM alembic_version")
|
||||||
|
except Exception:
|
||||||
|
logger.info("Alembic not initialized")
|
||||||
|
try:
|
||||||
|
self.init_alembic()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error initializing alembic: {exc}")
|
||||||
|
raise RuntimeError("Error initializing alembic") from exc
|
||||||
|
else:
|
||||||
|
logger.info("Alembic already initialized")
|
||||||
|
|
||||||
logger.info(f"Running DB migrations in {self.script_location}")
|
logger.info(f"Running DB migrations in {self.script_location}")
|
||||||
alembic_cfg = Config()
|
alembic_cfg = Config()
|
||||||
alembic_cfg.set_main_option("script_location", str(self.script_location))
|
alembic_cfg.set_main_option("script_location", str(self.script_location))
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -42,9 +42,7 @@ export default function App() {
|
||||||
successData,
|
successData,
|
||||||
successOpen,
|
successOpen,
|
||||||
setSuccessOpen,
|
setSuccessOpen,
|
||||||
setErrorData,
|
|
||||||
loading,
|
loading,
|
||||||
setLoading,
|
|
||||||
} = useContext(alertContext);
|
} = useContext(alertContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { fetchError } = useContext(typesContext);
|
const { fetchError } = useContext(typesContext);
|
||||||
|
|
|
||||||
|
|
@ -438,11 +438,7 @@ export default function ParameterComponent({
|
||||||
) : left === true && type === "prompt" ? (
|
) : left === true && type === "prompt" ? (
|
||||||
<div className="mt-2 w-full">
|
<div className="mt-2 w-full">
|
||||||
<PromptAreaComponent
|
<PromptAreaComponent
|
||||||
readonly={
|
readonly={data.node?.flow ? true : false}
|
||||||
data.node?.flow && data.node.template[name].dynamic
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
field_name={name}
|
field_name={name}
|
||||||
setNodeClass={(nodeClass) => {
|
setNodeClass={(nodeClass) => {
|
||||||
data.node = nodeClass;
|
data.node = nodeClass;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ export default function PromptAreaComponent({
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value !== "" && !editNode && !readonly) {
|
//prevent update from prompt template after group node if prompt is wrongly marked as not dynamic
|
||||||
|
if (value !== "" && !editNode && !readonly && !nodeClass?.flow) {
|
||||||
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
|
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
setNodeClass!(apiReturn.data.frontend_node);
|
setNodeClass!(apiReturn.data.frontend_node);
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
|
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uid());
|
const [id, setId] = useState(uid());
|
||||||
const { templates, reactFlowInstance } = useContext(typesContext);
|
const { templates, reactFlowInstance, setData } = useContext(typesContext);
|
||||||
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
||||||
nodes: any;
|
nodes: any;
|
||||||
edges: any;
|
edges: any;
|
||||||
|
|
@ -139,11 +139,24 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDBData(DbData: FlowType[]) {
|
function processDBData(DbData: FlowType[]) {
|
||||||
|
let storeComponents: { [key: string]: APIClassType } = {};
|
||||||
DbData.forEach((flow: FlowType) => {
|
DbData.forEach((flow: FlowType) => {
|
||||||
try {
|
try {
|
||||||
if (!flow.data) {
|
if (!flow.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (flow.data && flow.is_component) {
|
||||||
|
storeComponents[(flow.data.nodes[0].data as NodeDataType).type] = (
|
||||||
|
flow.data.nodes[0].data as NodeDataType
|
||||||
|
).node!;
|
||||||
|
}
|
||||||
|
setData((prev) => {
|
||||||
|
let newData = _.cloneDeep(prev);
|
||||||
|
Object.keys(storeComponents).forEach((key) => {
|
||||||
|
newData["custom_components"][key] = storeComponents[key];
|
||||||
|
});
|
||||||
|
return newData;
|
||||||
|
});
|
||||||
processDataFromFlow(flow, false);
|
processDataFromFlow(flow, false);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ import {
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Edge, Node, ReactFlowInstance } from "reactflow";
|
import { Edge, Node, ReactFlowInstance } from "reactflow";
|
||||||
import { getAll, getHealth } from "../controllers/API";
|
import { getAll, getHealth, saveFlowToDatabase } from "../controllers/API";
|
||||||
import { APIClassType, APIKindType } from "../types/api";
|
import { APIClassType, APIKindType } from "../types/api";
|
||||||
import { localStorageUserType } from "../types/entities";
|
import { localStorageUserType } from "../types/entities";
|
||||||
import { NodeDataType } from "../types/flow";
|
import { NodeDataType } from "../types/flow";
|
||||||
import { typesContextType } from "../types/typesContext";
|
import { typesContextType } from "../types/typesContext";
|
||||||
|
import { createFlowComponent } from "../utils/reactflowUtils";
|
||||||
import {
|
import {
|
||||||
checkLocalStorageKey,
|
checkLocalStorageKey,
|
||||||
getSetFromObject,
|
getSetFromObject,
|
||||||
|
|
@ -71,17 +72,19 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
if (isMounted && result?.status === 200) {
|
if (isMounted && result?.status === 200) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
let { data } = _.cloneDeep(result);
|
let { data } = _.cloneDeep(result);
|
||||||
const savedComponents = autoLogin
|
// const savedComponents = autoLogin
|
||||||
? localStorage.getItem("auto")
|
// ? localStorage.getItem("auto")
|
||||||
: localStorage.getItem(userData?.id!);
|
// : localStorage.getItem(userData?.id!);
|
||||||
if (savedComponents !== null) {
|
// if (savedComponents !== null) {
|
||||||
const { components }: localStorageUserType = JSON.parse(
|
// // const { components }: localStorageUserType = JSON.parse(
|
||||||
savedComponents!
|
// // savedComponents!
|
||||||
);
|
// // );
|
||||||
Object.keys(components).forEach((key) => {
|
// Object.keys(components).forEach((key) => {
|
||||||
data["custom_components"][key] = components[key].node!;
|
// data["custom_components"][key] = (
|
||||||
});
|
// components[key].data?.nodes[0].data! as NodeDataType
|
||||||
}
|
// ).node!;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
setData(data);
|
setData(data);
|
||||||
setTemplates(
|
setTemplates(
|
||||||
Object.keys(data).reduce((acc, curr) => {
|
Object.keys(data).reduce((acc, curr) => {
|
||||||
|
|
@ -147,12 +150,12 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveComponent(component: NodeDataType, id: string) {
|
function saveComponent(component: NodeDataType, id: string) {
|
||||||
let savedComponentsJSON: localStorageUserType = { components: {} };
|
// let savedComponentsJSON: localStorageUserType = { components: {} };
|
||||||
if (checkLocalStorageKey(id)) {
|
// if (checkLocalStorageKey(id)) {
|
||||||
let savedComponents = localStorage.getItem(id)!;
|
// let savedComponents = localStorage.getItem(id)!;
|
||||||
savedComponentsJSON = JSON.parse(savedComponents);
|
// savedComponentsJSON = JSON.parse(savedComponents);
|
||||||
}
|
// }
|
||||||
let components = savedComponentsJSON.components;
|
// let components = savedComponentsJSON.components;
|
||||||
let key = component.type;
|
let key = component.type;
|
||||||
if (data["custom_components"][key] !== undefined) {
|
if (data["custom_components"][key] !== undefined) {
|
||||||
let { newKey, increment } = IncrementObjectKey(
|
let { newKey, increment } = IncrementObjectKey(
|
||||||
|
|
@ -181,9 +184,10 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
component.node!.official = false;
|
component.node!.official = false;
|
||||||
components[key] = component;
|
// components[key] = createFlowComponent(component);
|
||||||
savedComponentsJSON.components = components;
|
saveFlowToDatabase(createFlowComponent(component));
|
||||||
localStorage.setItem(id, JSON.stringify(savedComponentsJSON));
|
// savedComponentsJSON.components = components;
|
||||||
|
// localStorage.setItem(id, JSON.stringify(savedComponentsJSON));
|
||||||
setData((prev) => {
|
setData((prev) => {
|
||||||
let newData = { ...prev };
|
let newData = { ...prev };
|
||||||
//clone to prevent reference erro
|
//clone to prevent reference erro
|
||||||
|
|
|
||||||
|
|
@ -112,12 +112,14 @@ export async function saveFlowToDatabase(newFlow: {
|
||||||
data: ReactFlowJsonObject | null;
|
data: ReactFlowJsonObject | null;
|
||||||
description: string;
|
description: string;
|
||||||
style?: FlowStyleType;
|
style?: FlowStyleType;
|
||||||
|
is_component?: boolean;
|
||||||
}): Promise<FlowType> {
|
}): Promise<FlowType> {
|
||||||
try {
|
try {
|
||||||
const response = await api.post(`${BASE_URL_API}flows/`, {
|
const response = await api.post(`${BASE_URL_API}flows/`, {
|
||||||
name: newFlow.name,
|
name: newFlow.name,
|
||||||
data: newFlow.data,
|
data: newFlow.data,
|
||||||
description: newFlow.description,
|
description: newFlow.description,
|
||||||
|
is_component: newFlow.is_component,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status !== 201) {
|
if (response.status !== 201) {
|
||||||
|
|
@ -536,3 +538,44 @@ export async function addApiKeyStore(key: string) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a new flow to the database.
|
||||||
|
*
|
||||||
|
* @param {FlowType} newFlow - The flow data to save.
|
||||||
|
* @returns {Promise<any>} The saved flow data.
|
||||||
|
* @throws Will throw an error if saving fails.
|
||||||
|
*/
|
||||||
|
export async function saveFlowStore(newFlow: {
|
||||||
|
name: string;
|
||||||
|
data: ReactFlowJsonObject | null;
|
||||||
|
description: string;
|
||||||
|
style?: FlowStyleType;
|
||||||
|
is_component?: boolean;
|
||||||
|
}): Promise<FlowType> {
|
||||||
|
try {
|
||||||
|
const response = await api.post(`${BASE_URL_API}store/`, {
|
||||||
|
name: newFlow.name,
|
||||||
|
data: newFlow.data,
|
||||||
|
description: newFlow.description,
|
||||||
|
is_component: newFlow.is_component,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 201) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the flows from the store.
|
||||||
|
* @returns {Promise<>} A promise that resolves to an AxiosResponse containing the build status.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export async function getFlowsStore(): Promise<AxiosResponse<FlowType[]>> {
|
||||||
|
return await api.get(`${BASE_URL_API}store/`);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -438,12 +438,7 @@ const EditNodeModal = forwardRef(
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
<PromptAreaComponent
|
<PromptAreaComponent
|
||||||
readonly={
|
readonly={
|
||||||
myData.current.node?.flow &&
|
myData.current.node?.flow ? true : false
|
||||||
myData.current.node.template[
|
|
||||||
templateParam
|
|
||||||
].dynamic
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
}
|
||||||
field_name={templateParam}
|
field_name={templateParam}
|
||||||
editNode={true}
|
editNode={true}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { NodeDataType } from "../flow";
|
import { FlowType } from "../flow";
|
||||||
|
|
||||||
export type sidebarNavigationItemType = {
|
export type sidebarNavigationItemType = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -8,5 +8,5 @@ export type sidebarNavigationItemType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type localStorageUserType = {
|
export type localStorageUserType = {
|
||||||
components: { [key: string]: NodeDataType };
|
components: { [key: string]: FlowType };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export type FlowType = {
|
||||||
data: ReactFlowJsonObject | null;
|
data: ReactFlowJsonObject | null;
|
||||||
description: string;
|
description: string;
|
||||||
style?: FlowStyleType;
|
style?: FlowStyleType;
|
||||||
isNode?: boolean;
|
is_component?: boolean;
|
||||||
};
|
};
|
||||||
export type NodeType = {
|
export type NodeType = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -437,8 +437,8 @@ export function convertValuesToNumbers(arr) {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
if (obj.hasOwnProperty(key)) {
|
if (obj.hasOwnProperty(key)) {
|
||||||
let value = obj[key];
|
let value = obj[key];
|
||||||
if (/\s/g.test(value)) {
|
if (/^\d+$/.test(value)) {
|
||||||
value = value.trim();
|
value = value?.toString().trim();
|
||||||
}
|
}
|
||||||
newObj[key] =
|
newObj[key] =
|
||||||
value === "" || isNaN(value) ? value.toString() : Number(value);
|
value === "" || isNaN(value) ? value.toString() : Number(value);
|
||||||
|
|
@ -1079,9 +1079,9 @@ export function createFlowComponent(nodeData: NodeDataType): FlowType {
|
||||||
viewport: { x: 1, y: 1, zoom: 1 },
|
viewport: { x: 1, y: 1, zoom: 1 },
|
||||||
},
|
},
|
||||||
description: nodeData.node?.description || "",
|
description: nodeData.node?.description || "",
|
||||||
name: nodeData.node?.display_name || "",
|
name: nodeData.node?.display_name || nodeData.type || "",
|
||||||
id: nodeData.id || "",
|
id: nodeData.id || "",
|
||||||
isNode: true,
|
is_component: true,
|
||||||
};
|
};
|
||||||
return flowNode;
|
return flowNode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,17 @@ test("KeypairListComponent", async ({ page }) => {
|
||||||
await page.locator('//*[@id="keypair0"]').click();
|
await page.locator('//*[@id="keypair0"]').click();
|
||||||
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
|
await page.locator('//*[@id="keypair0"]').fill("testtesttesttest");
|
||||||
await page.locator('//*[@id="keypair100"]').click();
|
await page.locator('//*[@id="keypair100"]').click();
|
||||||
await page.locator('//*[@id="keypair100"]').fill("testtesttesttesttesttest");
|
await page
|
||||||
|
.locator('//*[@id="keypair100"]')
|
||||||
|
.fill("test test test test test test");
|
||||||
|
|
||||||
|
const valueWithSpace = await page
|
||||||
|
.locator('//*[@id="keypair100"]')
|
||||||
|
.inputValue();
|
||||||
|
|
||||||
|
if (valueWithSpace !== "test test test test test test") {
|
||||||
|
expect(false).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
const plusButtonLocatorNode = page.locator('//*[@id="plusbtn0"]');
|
const plusButtonLocatorNode = page.locator('//*[@id="plusbtn0"]');
|
||||||
const elementCountNode = await plusButtonLocatorNode.count();
|
const elementCountNode = await plusButtonLocatorNode.count();
|
||||||
|
|
@ -94,7 +104,7 @@ test("KeypairListComponent", async ({ page }) => {
|
||||||
await page.locator('//*[@id="keypair100"]').click();
|
await page.locator('//*[@id="keypair100"]').click();
|
||||||
await page
|
await page
|
||||||
.locator('//*[@id="keypair100"]')
|
.locator('//*[@id="keypair100"]')
|
||||||
.fill("testtesttesttesttesttest");
|
.fill("test test test test test test");
|
||||||
|
|
||||||
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
|
const plusButtonLocator = page.locator('//*[@id="plusbtn0"]');
|
||||||
const elementCount = await plusButtonLocator.count();
|
const elementCount = await plusButtonLocator.count();
|
||||||
|
|
@ -132,7 +142,7 @@ test("KeypairListComponent", async ({ page }) => {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
key1 === "testtesttesttest" &&
|
key1 === "testtesttesttest" &&
|
||||||
value1 === "testtesttesttesttesttest" &&
|
value1 === "test test test test test test" &&
|
||||||
key2 === "testtesttesttest2" &&
|
key2 === "testtesttesttest2" &&
|
||||||
value2 === "testtesttesttesttesttest2"
|
value2 === "testtesttesttesttesttest2"
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue