diff --git a/src/frontend/playwright.config.ts b/src/frontend/playwright.config.ts
index 9535e0a15..cf7d59472 100644
--- a/src/frontend/playwright.config.ts
+++ b/src/frontend/playwright.config.ts
@@ -15,13 +15,13 @@ dotenv.config({ path: path.resolve(__dirname, "../../.env") });
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
- fullyParallel: true,
+ fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
- workers: 1,
+ workers: 5,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
timeout: 120 * 1000,
// reporter: [
@@ -52,18 +52,18 @@ export default defineConfig({
},
},
- {
- name: "firefox",
- use: {
- ...devices["Desktop Firefox"],
- launchOptions: {
- firefoxUserPrefs: {
- "dom.events.asyncClipboard.readText": true,
- "dom.events.testing.asyncClipboard": true,
- },
- },
- },
- },
+ // {
+ // name: "firefox",
+ // use: {
+ // ...devices["Desktop Firefox"],
+ // launchOptions: {
+ // firefoxUserPrefs: {
+ // "dom.events.asyncClipboard.readText": true,
+ // "dom.events.testing.asyncClipboard": true,
+ // },
+ // },
+ // },
+ // },
],
webServer: [
{
diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx
index 0076fa926..61dada650 100644
--- a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx
+++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx
@@ -7,7 +7,6 @@ import { useTypesStore } from "../../stores/typesStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
-import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Textarea } from "../ui/textarea";
@@ -24,19 +23,19 @@ export default function AddNewVariableButton({ children }): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
- Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)),
+ Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields))
);
const availableFields = () => {
const fields = Array.from(componentFields).filter(
- (field) => !unavaliableFields.has(field),
+ (field) => !unavaliableFields.has(field)
);
return sortByName(fields);
};
const addGlobalVariable = useGlobalVariablesStore(
- (state) => state.addGlobalVariable,
+ (state) => state.addGlobalVariable
);
function handleSaveVariable() {
@@ -65,7 +64,7 @@ export default function AddNewVariableButton({ children }): JSX.Element {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error creating variable",
- list: [responseError.response.data.detail ?? "Unknown error"],
+ list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
});
}
@@ -142,7 +141,9 @@ export default function AddNewVariableButton({ children }): JSX.Element {
>
-
+
);
}
diff --git a/src/frontend/src/components/inputListComponent/index.tsx b/src/frontend/src/components/inputListComponent/index.tsx
index f0aa8cca7..ce55ff1d4 100644
--- a/src/frontend/src/components/inputListComponent/index.tsx
+++ b/src/frontend/src/components/inputListComponent/index.tsx
@@ -55,10 +55,11 @@ export default function InputListComponent({
/>
{idx === value.length - 1 ? (
@@ -108,6 +109,7 @@ const SideBarFoldersButtonsComponent = ({
size="icon"
className="px-2"
onClick={handleUploadFlowsToFolder}
+ data-testid="upload-folder-button"
>
@@ -117,7 +119,7 @@ const SideBarFoldersButtonsComponent = ({
<>
{folders.map((item, index) => {
const editFolderName = editFolders?.filter(
- (folder) => folder.name === item.name,
+ (folder) => folder.name === item.name
)[0];
return (
handleChangeFolder!(item.id!)}
>
@@ -203,7 +205,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({
name: obj.name,
edit: false,
- })),
+ }))
);
}
if (e.key === "Enter") {
@@ -236,10 +238,10 @@ const SideBarFoldersButtonsComponent = ({
};
const updatedFolder = await updateFolder(
body,
- item.id!,
+ item.id!
);
const updateFolders = folders.filter(
- (f) => f.name !== item.name,
+ (f) => f.name !== item.name
);
setFolders([...updateFolders, updatedFolder]);
setFoldersNames({});
@@ -247,7 +249,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({
name: obj.name,
edit: false,
- })),
+ }))
);
} else {
setFoldersNames((old) => ({
@@ -258,6 +260,7 @@ const SideBarFoldersButtonsComponent = ({
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
+ data-testid={`input-folder`}
/>
) : (
diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx
index 363576ae0..560519d94 100644
--- a/src/frontend/src/controllers/API/api.tsx
+++ b/src/frontend/src/controllers/API/api.tsx
@@ -2,11 +2,11 @@ import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { renewAccessToken } from ".";
-import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
+import { checkDuplicateRequestAndStoreRequest } from "./helpers/check-duplicate-requests";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
@@ -48,7 +48,7 @@ function ApiInterceptor() {
}
await clearBuildVerticesState(error);
return Promise.reject(error);
- },
+ }
);
const isAuthorizedURL = (url) => {
@@ -65,10 +65,10 @@ function ApiInterceptor() {
const parsedURL = new URL(url);
const isDomainAllowed = authorizedDomains.some(
- (domain) => parsedURL.origin === new URL(domain).origin,
+ (domain) => parsedURL.origin === new URL(domain).origin
);
const isEndpointAllowed = authorizedEndpoints.some((endpoint) =>
- parsedURL.pathname.includes(endpoint),
+ parsedURL.pathname.includes(endpoint)
);
return isDomainAllowed || isEndpointAllowed;
@@ -81,28 +81,12 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use(
(config) => {
- const lastUrl = localStorage.getItem("lastUrlCalled");
- const lastMethodCalled = localStorage.getItem("lastMethodCalled");
+ const checkRequest = checkDuplicateRequestAndStoreRequest(config);
- const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
- config?.url!.includes(request),
- );
-
- if (
- config?.url === lastUrl &&
- !isContained &&
- lastMethodCalled === config.method
- ) {
- return Promise.reject("Duplicate request");
+ if (!checkRequest) {
+ return Promise.reject("Duplicate request.");
}
- localStorage.setItem("lastUrlCalled", config.url ?? "");
- localStorage.setItem("lastMethodCalled", config.method ?? "");
- localStorage.setItem(
- "lastRequestData",
- JSON.stringify(config.data) ?? "",
- );
-
const accessToken = cookies.get("access_token_lf");
if (accessToken && !isAuthorizedURL(config?.url)) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
@@ -112,7 +96,7 @@ function ApiInterceptor() {
},
(error) => {
return Promise.reject(error);
- },
+ }
);
return () => {
@@ -144,7 +128,7 @@ function ApiInterceptor() {
if (error?.config?.headers) {
delete error.config.headers["Authorization"];
error.config.headers["Authorization"] = `Bearer ${cookies.get(
- "access_token_lf",
+ "access_token_lf"
)}`;
const response = await axios.request(error.config);
return response;
diff --git a/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts
new file mode 100644
index 000000000..79a47c7a7
--- /dev/null
+++ b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts
@@ -0,0 +1,30 @@
+import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../../constants/constants";
+
+export function checkDuplicateRequestAndStoreRequest(config) {
+ const lastUrl = localStorage.getItem("lastUrlCalled");
+ const lastMethodCalled = localStorage.getItem("lastMethodCalled");
+ const lastRequestTime = localStorage.getItem("lastRequestTime");
+
+ const currentTime = Date.now();
+
+ const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
+ config?.url!.includes(request),
+ );
+
+ if (
+ config?.url === lastUrl &&
+ !isContained &&
+ lastMethodCalled === config.method &&
+ lastMethodCalled === "get" && // Assuming you want to check only for GET requests
+ lastRequestTime &&
+ currentTime - parseInt(lastRequestTime, 10) < 800
+ ) {
+ return false;
+ }
+
+ localStorage.setItem("lastUrlCalled", config.url ?? "");
+ localStorage.setItem("lastMethodCalled", config.method ?? "");
+ localStorage.setItem("lastRequestTime", currentTime.toString());
+
+ return true;
+}
diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/customNodes/genericNode/index.tsx
index 5c83cad03..0881c6207 100644
--- a/src/frontend/src/customNodes/genericNode/index.tsx
+++ b/src/frontend/src/customNodes/genericNode/index.tsx
@@ -55,14 +55,14 @@ export default function GenericNode({
const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState(
- data.node?.description!,
+ data.node?.description!
);
const [isOutdated, setIsOutdated] = useState(false);
const buildStatus = useFlowStore(
- (state) => state.flowBuildStatus[data.id]?.status,
+ (state) => state.flowBuildStatus[data.id]?.status
);
const lastRunTime = useFlowStore(
- (state) => state.flowBuildStatus[data.id]?.timestamp,
+ (state) => state.flowBuildStatus[data.id]?.timestamp
);
const [validationStatus, setValidationStatus] =
useState(null);
@@ -77,10 +77,10 @@ export default function GenericNode({
// first check if data.type in NATIVE_CATEGORIES
// if not return
if (!data.node?.template?.code?.value) return;
- const thisNodeTemplate = templates[data.type].template;
+ const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
- if (!thisNodeTemplate.code) return;
+ if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const componentsToIgnore = ["Custom Component"];
@@ -115,7 +115,7 @@ export default function GenericNode({
updateNodeInternals(data.id);
},
- [data.id, data.node, setNode, setIsOutdated],
+ [data.id, data.node, setNode, setIsOutdated]
);
if (!data.node!.template) {
@@ -255,7 +255,7 @@ export default function GenericNode({
const isDark = useDarkStore((state) => state.dark);
const renderIconStatus = (
buildStatus: BuildStatus | undefined,
- validationStatus: validationStatusType | null,
+ validationStatus: validationStatusType | null
) => {
if (buildStatus === BuildStatus.BUILDING) {
return ;
@@ -296,7 +296,7 @@ export default function GenericNode({
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
- validationStatus: validationStatusType | null,
+ validationStatus: validationStatusType | null
) => {
let isInvalid = validationStatus && !validationStatus.valid;
@@ -320,11 +320,11 @@ export default function GenericNode({
selected: boolean,
showNode: boolean,
buildStatus: BuildStatus | undefined,
- validationStatus: validationStatusType | null,
+ validationStatus: validationStatusType | null
) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus,
- validationStatus,
+ validationStatus
);
const baseBorderClass = getBaseBorderClass(selected);
@@ -333,7 +333,7 @@ export default function GenericNode({
baseBorderClass,
nodeSizeClass,
"generic-node-div",
- specificClassFromBuildStatus,
+ specificClassFromBuildStatus
);
return names;
};
@@ -393,7 +393,7 @@ export default function GenericNode({
selected,
showNode,
buildStatus,
- validationStatus,
+ validationStatus
)}
>
{data.node?.beta && showNode && (
@@ -416,6 +416,7 @@ export default function GenericNode({
"generic-node-title-arrangement rounded-full" +
(!showNode && " justify-center ")
}
+ data-testid="generic-node-title-arrangement"
>
{iconNodeRender()}
{showNode && (
@@ -523,7 +524,7 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
- templateField,
+ templateField
)}
info={data.node?.template[templateField].info}
name={templateField}
@@ -551,7 +552,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
/>
- ),
+ )
)}
{
setInputDescription(true);
@@ -770,13 +771,13 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
- templateField,
+ templateField
)}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[templateField].input_types?.join(
- "\n",
+ "\n"
) ?? data.node?.template[templateField].type
}
required={data.node!.template[templateField].required}
@@ -803,7 +804,7 @@ export default function GenericNode({
{" "}
diff --git a/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx b/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx
index 3fc3fbe72..7426164f7 100644
--- a/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx
+++ b/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx
@@ -9,7 +9,7 @@ const useFetchDataOnMount = (
handleUpdateValues,
setNode,
renderTooltips,
- setIsLoading,
+ setIsLoading
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);
@@ -40,7 +40,7 @@ const useFetchDataOnMount = (
setErrorData({
title: "Error while updating the Component",
- list: [responseError.response.data.detail ?? "Unknown error"],
+ list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);
diff --git a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
index 03d305ddb..dc416646b 100644
--- a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
+++ b/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
@@ -10,7 +10,7 @@ const useHandleOnNewValue = (
debouncedHandleUpdateValues,
setNode,
renderTooltips,
- setIsLoading,
+ setIsLoading
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);
@@ -44,7 +44,9 @@ const useHandleOnNewValue = (
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
- list: [responseError.response.data.detail.error ?? "Unknown error"],
+ list: [
+ responseError?.response?.data?.detail.error ?? "Unknown error",
+ ],
});
}
setIsLoading(false);
diff --git a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx
index 19f2a3c29..4696aa994 100644
--- a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx
+++ b/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx
@@ -26,7 +26,7 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
setErrorData({
title: "Error while updating the Component",
- list: [responseError.response.data.detail ?? "Unknown error"],
+ list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);
diff --git a/src/frontend/src/modals/baseModal/index.tsx b/src/frontend/src/modals/baseModal/index.tsx
index a899bd071..a9efe3ddf 100644
--- a/src/frontend/src/modals/baseModal/index.tsx
+++ b/src/frontend/src/modals/baseModal/index.tsx
@@ -71,6 +71,7 @@ const Footer: React.FC<{
icon?: ReactNode;
loading?: boolean;
disabled?: boolean;
+ dataTestId?: string;
};
}> = ({ children, submit }) => {
return submit ? (
@@ -83,6 +84,7 @@ const Footer: React.FC<{