From 0ee46bfd0fb45085bb226959ed9905f28db9cd72 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Fri, 25 Oct 2024 09:17:14 +0530 Subject: [PATCH 1/7] Refactor list view components for better performance across pages and added sorting functionality --- .../AddCustomToolFormModal.jsx | 4 +- .../list-of-tools/ListOfTools.jsx | 387 ++++-------------- .../list-of-tools/ListOfToolsModal.jsx | 35 ++ .../custom-tools/view-tools/ViewTools.jsx | 38 +- .../navigations/tool-nav-bar/ToolNavBar.css | 7 + .../navigations/tool-nav-bar/ToolNavBar.jsx | 55 ++- .../list-of-items/ListOfItems.css | 14 +- .../tool-settings/ToolSettings.jsx | 359 ++++++++-------- .../tool-settings/ToolSettingsModal.jsx | 52 +++ .../src/components/view-projects/ListView.jsx | 156 +++++++ .../components/view-projects/ViewProjects.css | 52 +++ .../components/widgets/list-view/ListView.css | 10 +- .../components/widgets/list-view/ListView.jsx | 353 ++++++++++------ .../workflows/new-workflow/NewWorkflow.jsx | 2 +- .../workflows/workflow/WorkflowModal.jsx | 80 ++++ .../workflows/workflow/Workflows.jsx | 306 +++----------- frontend/src/hooks/useListManager.js | 132 ++++++ 17 files changed, 1106 insertions(+), 936 deletions(-) create mode 100644 frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx create mode 100644 frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx create mode 100644 frontend/src/components/view-projects/ListView.jsx create mode 100644 frontend/src/components/view-projects/ViewProjects.css create mode 100644 frontend/src/components/workflows/workflow/WorkflowModal.jsx create mode 100644 frontend/src/hooks/useListManager.js diff --git a/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.jsx b/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.jsx index 12401fd17..f8b325d74 100644 --- a/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.jsx +++ b/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.jsx @@ -73,7 +73,9 @@ function AddCustomToolFormModal({ }); setOpen(false); clearFormDetails(); - navigate(success?.tool_id); + if (!isEdit) { + navigate(success?.tool_id); + } }) .catch((err) => { handleException(err, "", setBackendErrors); diff --git a/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx b/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx index f610b7b5d..edb73fdf4 100644 --- a/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx +++ b/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx @@ -1,335 +1,104 @@ import { PlusOutlined } from "@ant-design/icons"; -import { useEffect, useState } from "react"; - -import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; -import { useAlertStore } from "../../../store/alert-store"; -import { useSessionStore } from "../../../store/session-store"; -import { CustomButton } from "../../widgets/custom-button/CustomButton"; -import { AddCustomToolFormModal } from "../add-custom-tool-form-modal/AddCustomToolFormModal"; -import { ViewTools } from "../view-tools/ViewTools"; -import "./ListOfTools.css"; -import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; -import { ToolNavBar } from "../../navigations/tool-nav-bar/ToolNavBar"; -import { SharePermission } from "../../widgets/share-permission/SharePermission"; -import usePostHogEvents from "../../../hooks/usePostHogEvents.js"; - -let OnboardMessagesModal; -let slides; -try { - OnboardMessagesModal = - require("../../../plugins/onboarding-messages/OnboardMessagesModal.jsx").OnboardMessagesModal; - slides = - require("../../../plugins/onboarding-messages/login-slides.jsx").LoginSlides; -} catch (err) { - OnboardMessagesModal = null; - slides = []; -} +import { useCallback, useMemo } from "react"; +import usePostHogEvents from "../../../hooks/usePostHogEvents"; +import { useListManager } from "../../../hooks/useListManager"; +import ListOfToolsModal from "./ListOfToolsModal"; +import { ListView } from "../../view-projects/ListView"; function ListOfTools() { - const [isListLoading, setIsListLoading] = useState(false); - const [openAddTool, setOpenAddTool] = useState(false); - const [editItem, setEditItem] = useState(null); - const { sessionDetails } = useSessionStore(); - const { loginOnboardingMessage } = sessionDetails; const { setPostHogCustomEvent } = usePostHogEvents(); - const { setAlertDetails } = useAlertStore(); - const axiosPrivate = useAxiosPrivate(); - - const [listOfTools, setListOfTools] = useState([]); - const [filteredListOfTools, setFilteredListOfTools] = useState([]); - const handleException = useExceptionHandler(); - const [isEdit, setIsEdit] = useState(false); - const [promptDetails, setPromptDetails] = useState(null); - const [openSharePermissionModal, setOpenSharePermissionModal] = - useState(false); - const [isPermissionEdit, setIsPermissionEdit] = useState(false); - const [isShareLoading, setIsShareLoading] = useState(false); - const [allUserList, setAllUserList] = useState([]); - const [loginModalOpen, setLoginModalOpen] = useState(true); - - useEffect(() => { - getListOfTools(); - }, []); - - useEffect(() => { - setFilteredListOfTools(listOfTools); - }, [listOfTools]); - - const getListOfTools = () => { + const getListApiCall = useCallback(({ axiosPrivate, sessionDetails }) => { const requestOptions = { method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/ `, + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, }; + return axiosPrivate(requestOptions); + }, []); - setIsListLoading(true); - axiosPrivate(requestOptions) - .then((res) => { - const data = res?.data; - setListOfTools(data); - setFilteredListOfTools(data); - }) - .catch((err) => { - setAlertDetails( - handleException(err, "Failed to get the list of tools") - ); - }) - .finally(() => { - setIsListLoading(false); - }); - }; - - const handleAddNewTool = (body) => { - let method = "POST"; - let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`; - const isEdit = editItem && Object.keys(editItem)?.length > 0; - if (isEdit) { - method = "PATCH"; - url += `${editItem?.tool_id}/`; - } - return new Promise((resolve, reject) => { + const addItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemData }) => { const requestOptions = { - method, - url, + method: "POST", + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, "Content-Type": "application/json", }, - data: body, + data: itemData, }; + return axiosPrivate(requestOptions); + }, + [] + ); - axiosPrivate(requestOptions) - .then((res) => { - const tool = res?.data; - updateList(isEdit, tool); - setOpenAddTool(false); - resolve(res?.data); - }) - .catch((err) => { - reject(err); - }); - }); - }; - - const updateList = (isEdit, data) => { - let tools = [...listOfTools]; - - if (isEdit) { - tools = tools.map((item) => - item?.tool_id === data?.tool_id ? data : item - ); - setEditItem(null); - } else { - tools.push(data); - } - setListOfTools(tools); - }; - - const handleEdit = (_event, tool) => { - const editToolData = [...listOfTools].find( - (item) => item?.tool_id === tool.tool_id - ); - if (!editToolData) { - return; - } - setIsEdit(true); - setEditItem(editToolData); - setOpenAddTool(true); - }; - - const handleDelete = (_event, tool) => { - const requestOptions = { - method: "DELETE", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${tool.tool_id}`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - }; - - axiosPrivate(requestOptions) - .then(() => { - const tools = [...listOfTools].filter( - (filterToll) => filterToll?.tool_id !== tool.tool_id - ); - setListOfTools(tools); - setAlertDetails({ - type: "success", - content: `${tool?.tool_name} - Deleted successfully`, - }); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to Delete")); - }); - }; - - const onSearch = (search, setSearch) => { - if (search?.length === 0) { - setSearch(listOfTools); - } - const filteredList = [...listOfTools].filter((tool) => { - const name = tool.tool_name?.toUpperCase(); - const searchUpperCase = search.toUpperCase(); - return name.includes(searchUpperCase); - }); - setSearch(filteredList); - }; - - const showAddTool = () => { - setEditItem(null); - setIsEdit(false); - setOpenAddTool(true); - }; - - const handleNewProjectBtnClick = () => { - showAddTool(); - - try { - setPostHogCustomEvent("intent_new_ps_project", { - info: "Clicked on '+ New Project' button", - }); - } catch (err) { - // If an error occurs while setting custom posthog event, ignore it and continue - } - }; - - const CustomButtons = () => { - return ( - } - onClick={handleNewProjectBtnClick} - > - New Project - - ); - }; - - const handleShare = (_event, promptProject, isEdit) => { - const requestOptions = { - method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/users/${promptProject?.tool_id}`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - }; - setIsShareLoading(true); - getAllUsers(); - axiosPrivate(requestOptions) - .then((res) => { - setOpenSharePermissionModal(true); - setPromptDetails(res?.data); - setIsPermissionEdit(isEdit); - }) - .catch((err) => { - setAlertDetails(handleException(err)); - }) - .finally(() => { - setIsShareLoading(false); - }); - }; - - const getAllUsers = () => { - setIsShareLoading(true); - const requestOptions = { - method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/users/`, - }; + const editItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemData, itemId }) => { + const requestOptions = { + method: "PATCH", + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${itemId}/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + "Content-Type": "application/json", + }, + data: itemData, + }; + return axiosPrivate(requestOptions); + }, + [] + ); - axiosPrivate(requestOptions) - .then((response) => { - const users = response?.data?.members || []; - setAllUserList( - users.map((user) => ({ - id: user?.id, - email: user?.email, - })) - ); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to load")); - }) - .finally(() => { - setIsShareLoading(false); - }); - }; + const deleteItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemId }) => { + const requestOptions = { + method: "DELETE", + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${itemId}`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + }, + }; + return axiosPrivate(requestOptions); + }, + [] + ); - const onShare = (userIds, adapter) => { - const requestOptions = { - method: "PATCH", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${adapter?.tool_id}`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - data: { shared_users: userIds }, - }; - axiosPrivate(requestOptions) - .then((response) => { - setOpenSharePermissionModal(false); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to load")); - }); - }; + const useListManagerHook = useListManager({ + getListApiCall, + addItemApiCall, + editItemApiCall, + deleteItemApiCall, + searchProperty: "tool_name", + itemIdProp: "tool_id", + itemNameProp: "tool_name", + itemDescriptionProp: "description", + itemType: "Prompt Project", + }); + + const itemProps = useMemo( + () => ({ + titleProp: "tool_name", + descriptionProp: "description", + iconProp: "icon", + idProp: "tool_id", + type: "Prompt Project", + }), + [] + ); return ( - <> - -
-
-
- -
-
-
- {openAddTool && ( - - )} - - {!loginOnboardingMessage && OnboardMessagesModal && ( - - )} - + } + itemProps={itemProps} + setPostHogCustomEvent={setPostHogCustomEvent} + newButtonEventName="intent_new_ps_project" + /> ); } diff --git a/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx b/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx new file mode 100644 index 000000000..8d2ba9560 --- /dev/null +++ b/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx @@ -0,0 +1,35 @@ +import PropTypes from "prop-types"; +import { useCallback } from "react"; +import { AddCustomToolFormModal } from "../add-custom-tool-form-modal/AddCustomToolFormModal"; + +function ListOfToolsModal({ open, setOpen, editItem, isEdit, handleAddItem }) { + const handleAddNewTool = useCallback( + (itemData) => handleAddItem(itemData, isEdit, editItem?.tool_id), + [handleAddItem, isEdit, editItem?.tool_id] + ); + + return ( + + ); +} + +ListOfToolsModal.propTypes = { + open: PropTypes.bool.isRequired, + setOpen: PropTypes.func.isRequired, + editItem: PropTypes.shape({ + tool_id: PropTypes.number, + tool_name: PropTypes.string, + description: PropTypes.string, + icon: PropTypes.string, + }), + isEdit: PropTypes.bool.isRequired, + handleAddItem: PropTypes.func.isRequired, +}; + +export default ListOfToolsModal; diff --git a/frontend/src/components/custom-tools/view-tools/ViewTools.jsx b/frontend/src/components/custom-tools/view-tools/ViewTools.jsx index 18ae88572..76a213554 100644 --- a/frontend/src/components/custom-tools/view-tools/ViewTools.jsx +++ b/frontend/src/components/custom-tools/view-tools/ViewTools.jsx @@ -1,10 +1,11 @@ import PropTypes from "prop-types"; import { Typography } from "antd"; +import { useCallback } from "react"; import { ListView } from "../../widgets/list-view/ListView"; -import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader.jsx"; +import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader"; import "./ViewTools.css"; -import { EmptyState } from "../../widgets/empty-state/EmptyState.jsx"; +import { EmptyState } from "../../widgets/empty-state/EmptyState"; function ViewTools({ isLoading, @@ -17,28 +18,31 @@ function ViewTools({ descriptionProp, iconProp, idProp, - centered, + centered = false, isClickable = true, handleShare, - showOwner, + showOwner = false, type, }) { + const handleEmptyStateClick = useCallback(() => { + setOpenAddTool(true); + }, [setOpenAddTool]); + if (isLoading) { return ; } if (isEmpty) { - let text = "No tools available"; - let btnText = "New Tool"; - if (type) { - text = `No ${type.toLowerCase()} available`; - btnText = type; - } + const text = type + ? `No ${type.toLowerCase()} available` + : "No tools available"; + const btnText = type || "New Tool"; + return ( setOpenAddTool(true)} + handleClick={handleEmptyStateClick} /> ); } @@ -89,4 +93,16 @@ ViewTools.propTypes = { type: PropTypes.string, }; +ViewTools.defaultProps = { + listOfTools: [], + setOpenAddTool: () => {}, + descriptionProp: "", + iconProp: "", + centered: false, + isClickable: true, + handleShare: null, + showOwner: false, + type: "", +}; + export { ViewTools }; diff --git a/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css b/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css index cee99b0cd..1780a5116 100644 --- a/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css +++ b/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.css @@ -29,3 +29,10 @@ .tool-bar-segment { background-color: #0000000f; } + +.tool-nav-title { + font-weight: 600; + font-size: 18px; + display: inline; + line-height: 24px; +} diff --git a/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx b/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx index 79a995632..dde71bd24 100644 --- a/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx +++ b/frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx @@ -5,6 +5,7 @@ import { ArrowLeftOutlined } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; import { debounce } from "lodash"; import PropTypes from "prop-types"; +import { useCallback, useMemo, useEffect } from "react"; function ToolNavBar({ title, @@ -17,9 +18,38 @@ function ToolNavBar({ onSearch, }) { const navigate = useNavigate(); - const onSearchDebounce = debounce(({ target: { value } }) => { - onSearch(value, setSearchList); - }, 600); + + const handleBackClick = useCallback(() => { + if (previousRoute) { + navigate(previousRoute); + } + }, [previousRoute]); + + const onSearchDebounce = useMemo(() => { + const debouncedFunction = debounce(({ target: { value } }) => { + if (onSearch) { + onSearch(value, setSearchList); + } + }, 600); + return debouncedFunction; + }, [onSearch, setSearchList]); + + // Cleanup debounced function on unmount + useEffect(() => { + return () => { + onSearchDebounce.cancel(); + }; + }, [onSearchDebounce]); + + // Handle segment change + const handleSegmentChange = useCallback( + (value) => { + if (segmentFilter) { + segmentFilter(value); + } + }, + [segmentFilter] + ); return ( @@ -29,25 +59,14 @@ function ToolNavBar({ type="text" shape="circle" icon={} - onClick={() => navigate(previousRoute)} + onClick={handleBackClick} /> )} - {title && ( - - {title} - - )} + {title && {title}} {segmentFilter && segmentOptions && ( )} @@ -70,7 +89,7 @@ function ToolNavBar({ ToolNavBar.propTypes = { title: PropTypes.string, enableSearch: PropTypes.bool, - CustomButtons: PropTypes.func, + CustomButtons: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]), setSearchList: PropTypes.func, previousRoute: PropTypes.string, segmentOptions: PropTypes.array, diff --git a/frontend/src/components/tool-settings/list-of-items/ListOfItems.css b/frontend/src/components/tool-settings/list-of-items/ListOfItems.css index fbde11618..22f02ea3e 100644 --- a/frontend/src/components/tool-settings/list-of-items/ListOfItems.css +++ b/frontend/src/components/tool-settings/list-of-items/ListOfItems.css @@ -1,14 +1,14 @@ /* Styles for ListOfItems */ .list-of-items-empty { - height: 100%; - display: flex; - justify-content: center; - align-items: center; + height: 100%; + display: flex; + justify-content: center; + align-items: center; } .cover-img .fit-cover { - object-fit: cover; - width: 100%; - height: auto; + object-fit: cover; + width: 100%; + height: auto; } diff --git a/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx b/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx index 3f633e4a0..3350e18ba 100644 --- a/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx +++ b/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx @@ -1,20 +1,16 @@ import { PlusOutlined } from "@ant-design/icons"; import PropTypes from "prop-types"; -import { useEffect, useState } from "react"; +import { useState, useCallback, useMemo } from "react"; -import { IslandLayout } from "../../../layouts/island-layout/IslandLayout"; -import { AddSourceModal } from "../../input-output/add-source-modal/AddSourceModal"; -import "../../input-output/data-source-card/DataSourceCard.css"; -import "./ToolSettings.css"; -import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; -import { useAlertStore } from "../../../store/alert-store"; import { useSessionStore } from "../../../store/session-store"; -import { CustomButton } from "../../widgets/custom-button/CustomButton"; -import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; -import { ToolNavBar } from "../../navigations/tool-nav-bar/ToolNavBar"; -import { ViewTools } from "../../custom-tools/view-tools/ViewTools"; -import { SharePermission } from "../../widgets/share-permission/SharePermission"; import usePostHogEvents from "../../../hooks/usePostHogEvents"; +import { useListManager } from "../../../hooks/useListManager"; +import { ListView } from "../../view-projects/ListView"; +import ToolSettingsModal from "./ToolSettingsModal"; +import { SharePermission } from "../../widgets/share-permission/SharePermission"; +import { useAlertStore } from "../../../store/alert-store"; +import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; +import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; const titles = { llm: "LLMs", @@ -33,119 +29,91 @@ const btnText = { }; function ToolSettings({ type }) { - const [tableRows, setTableRows] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [isShareLoading, setIsShareLoading] = useState(false); + const { setPostHogCustomEvent, posthogEventText } = usePostHogEvents(); + const { setAlertDetails } = useAlertStore(); + const handleException = useExceptionHandler(); + const axiosPrivate = useAxiosPrivate(); + const { sessionDetails } = useSessionStore(); + const [adapterDetails, setAdapterDetails] = useState(null); const [userList, setUserList] = useState([]); - const [openAddSourcesModal, setOpenAddSourcesModal] = useState(false); + const [isShareLoading, setIsShareLoading] = useState(false); const [openSharePermissionModal, setOpenSharePermissionModal] = useState(false); - const [isPermissonEdit, setIsPermissionEdit] = useState(false); - const [editItemId, setEditItemId] = useState(null); - const { sessionDetails } = useSessionStore(); - const { setAlertDetails } = useAlertStore(); - const axiosPrivate = useAxiosPrivate(); - const handleException = useExceptionHandler(); - const { posthogEventText, setPostHogCustomEvent } = usePostHogEvents(); - - useEffect(() => { - setTableRows([]); - if (!type) { - return; - } - getAdapters(); - }, [type]); - - const getAdapters = () => { - const requestOptions = { - method: "GET", - url: `/api/v1/unstract/${ - sessionDetails?.orgId - }/adapter?adapter_type=${type.toUpperCase()}`, - }; - setIsLoading(true); - axiosPrivate(requestOptions) - .then((res) => { - setTableRows(res?.data); - }) - .catch((err) => { - setAlertDetails(handleException(err)); - }) - .finally(() => { - setIsLoading(false); - }); - }; - - const addNewItem = (row, isEdit) => { - if (isEdit) { - const rowsModified = [...tableRows].map((tableRow) => { - if (tableRow?.id !== row?.id) { - return tableRow; - } - tableRow["adapter_name"] = row?.adapter_name; - return tableRow; - }); - setTableRows(rowsModified); - } else { - const rowsModified = [...tableRows]; - rowsModified.push(row); - setTableRows(rowsModified); - } - }; + const [isPermissionEdit, setIsPermissionEdit] = useState(false); + + const getListApiCall = useCallback( + ({ axiosPrivate, sessionDetails }) => { + const requestOptions = { + method: "GET", + url: `/api/v1/unstract/${ + sessionDetails?.orgId + }/adapter?adapter_type=${type.toUpperCase()}`, + }; + return axiosPrivate(requestOptions); + }, + [type] + ); - const handleDelete = (_event, adapter) => { - const requestOptions = { - method: "DELETE", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${adapter?.id}/`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - }; + const addItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemData }) => { + const requestOptions = { + method: "POST", + url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + "Content-Type": "application/json", + }, + data: itemData, + }; + return axiosPrivate(requestOptions); + }, + [] + ); - setIsLoading(true); - axiosPrivate(requestOptions) - .then((res) => { - const filteredList = tableRows.filter((row) => row?.id !== adapter?.id); - setTableRows(filteredList); - setAlertDetails({ - type: "success", - content: "Successfully deleted", - }); - }) - .catch((err) => { - setAlertDetails(handleException(err)); - }) - .finally(() => { - setIsLoading(false); - }); - }; + const editItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemData, itemId }) => { + const requestOptions = { + method: "PATCH", + url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${itemId}/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + "Content-Type": "application/json", + }, + data: itemData, + }; + return axiosPrivate(requestOptions); + }, + [] + ); - const handleShare = (_event, adapter, isEdit) => { - const requestOptions = { - method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/users/${adapter.id}/`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - }; - setIsShareLoading(true); - getAllUsers(); - axiosPrivate(requestOptions) - .then((res) => { - setOpenSharePermissionModal(true); - setAdapterDetails(res?.data); - setIsPermissionEdit(isEdit); - }) - .catch((err) => { - setAlertDetails(handleException(err)); - }) - .finally(() => { - setIsShareLoading(false); - }); - }; + const deleteItemApiCall = useCallback( + ({ axiosPrivate, sessionDetails, itemId }) => { + const requestOptions = { + method: "DELETE", + url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${itemId}/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + }, + }; + return axiosPrivate(requestOptions); + }, + [] + ); - const getAllUsers = () => { + const useListManagerHook = useListManager({ + getListApiCall, + addItemApiCall, + editItemApiCall, + deleteItemApiCall, + searchProperty: "adapter_name", + itemIdProp: "id", + itemNameProp: "adapter_name", + itemDescriptionProp: "description", + itemType: "Adapter", + }); + + const getAllUsers = useCallback(() => { setIsShareLoading(true); const requestOptions = { method: "GET", @@ -157,8 +125,8 @@ function ToolSettings({ type }) { const users = response?.data?.members || []; setUserList( users.map((user) => ({ - id: user?.id, - email: user?.email, + id: user.id, + email: user.email, })) ); }) @@ -168,95 +136,100 @@ function ToolSettings({ type }) { .finally(() => { setIsShareLoading(false); }); - }; - - const onShare = (userIds, adapter) => { - const requestOptions = { - method: "PATCH", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${adapter?.id}/`, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - }, - data: { shared_users: userIds }, - }; - axiosPrivate(requestOptions) - .then((response) => { - setOpenSharePermissionModal(false); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to load")); - }); - }; + }, []); + + // Memoized handleShare function + const handleShare = useCallback( + (adapter, isEdit) => { + const requestOptions = { + method: "GET", + url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/users/${adapter.id}/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + }, + }; + setIsShareLoading(true); + getAllUsers(); + axiosPrivate(requestOptions) + .then((res) => { + setOpenSharePermissionModal(true); + setAdapterDetails(res.data); + setIsPermissionEdit(isEdit); + }) + .catch((err) => { + setAlertDetails(handleException(err)); + }) + .finally(() => { + setIsShareLoading(false); + }); + }, + [ + getAllUsers, + setOpenSharePermissionModal, + setAdapterDetails, + setIsPermissionEdit, + ] + ); - const handleOpenAddSourceModal = () => { - setOpenAddSourcesModal(true); + const onShare = useCallback( + (userIds, adapter) => { + const requestOptions = { + method: "PATCH", + url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${adapter.id}/`, + headers: { + "X-CSRFToken": sessionDetails?.csrfToken, + }, + data: { shared_users: userIds }, + }; + axiosPrivate(requestOptions) + .then(() => { + setOpenSharePermissionModal(false); + }) + .catch((err) => { + setAlertDetails(handleException(err, "Failed to load")); + }); + }, + [setOpenSharePermissionModal] + ); - try { - setPostHogCustomEvent(posthogEventText[type], { - info: `Clicked on '+ ${btnText[type]}' button`, - }); - } catch (err) { - // If an error occurs while setting custom posthog event, ignore it and continue - } - }; + const itemProps = useMemo( + () => ({ + titleProp: "adapter_name", + descriptionProp: "description", + iconProp: "icon", + idProp: "id", + type: "Adapter", + handleShare, + showOwner: true, + isClickable: false, + centered: true, + }), + [handleShare] + ); return ( -
- + { - return ( - } - > - {btnText[type]} - - ); - }} - /> - -
-
- setEditItemId(item?.id)} - idProp="id" - titleProp="adapter_name" - descriptionProp="description" - iconProp="icon" - isEmpty={!tableRows?.length} - centered - isClickable={false} - handleShare={handleShare} - showOwner={true} - type="Adapter" - /> -
-
-
- } + itemProps={itemProps} + setPostHogCustomEvent={setPostHogCustomEvent} + newButtonEventName={posthogEventText[type]} type={type} - addNewItem={addNewItem} - editItemId={editItemId} - setEditItemId={setEditItemId} /> -
+ ); } diff --git a/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx new file mode 100644 index 000000000..462e13795 --- /dev/null +++ b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx @@ -0,0 +1,52 @@ +import PropTypes from "prop-types"; +import { useCallback } from "react"; +import { useAlertStore } from "../../../store/alert-store"; +import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; +import { AddSourceModal } from "../../input-output/add-source-modal/AddSourceModal"; + +function ToolSettingsModal({ + open, + setOpen, + editItem, + isEdit, + handleAddItem, + type, +}) { + const { setAlertDetails } = useAlertStore(); + const handleException = useExceptionHandler(); + + const handleAddNewItem = useCallback( + (itemData) => { + handleAddItem(itemData, isEdit, editItem?.id) + .then(() => { + setOpen(false); + }) + .catch((err) => { + setAlertDetails(handleException(err, "Failed to add/edit adapter")); + }); + }, + [isEdit, editItem?.id] + ); + + return ( + {}} + /> + ); +} + +ToolSettingsModal.propTypes = { + open: PropTypes.bool.isRequired, + setOpen: PropTypes.func.isRequired, + editItem: PropTypes.object, + isEdit: PropTypes.bool.isRequired, + handleAddItem: PropTypes.func.isRequired, + type: PropTypes.string.isRequired, +}; + +export default ToolSettingsModal; diff --git a/frontend/src/components/view-projects/ListView.jsx b/frontend/src/components/view-projects/ListView.jsx new file mode 100644 index 000000000..f0084c92e --- /dev/null +++ b/frontend/src/components/view-projects/ListView.jsx @@ -0,0 +1,156 @@ +import { useEffect, useState, useCallback } from "react"; +import PropTypes from "prop-types"; +import { CustomButton } from "../widgets/custom-button/CustomButton"; +import { ToolNavBar } from "../navigations/tool-nav-bar/ToolNavBar"; +import { ViewTools } from "../custom-tools/view-tools/ViewTools"; +import "./ViewProjects.css"; + +function ListView({ + title, + useListManagerHook, + CustomModalComponent, + customButtonText, + customButtonIcon, + onCustomButtonClick, + handleEditItem, + handleDeleteItem, + itemProps, + setPostHogCustomEvent, + newButtonEventName, + type, +}) { + const { + list, + filteredList, + loading, + fetchList, + handleSearch, + handleAddItem, + handleDeleteItem: deleteItem, + } = useListManagerHook; + + const [openModal, setOpenModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + const [isEdit, setIsEdit] = useState(false); + + useEffect(() => { + fetchList(); + }, [title]); + + const handleCustomButtonClick = useCallback(() => { + setEditingItem(null); + setIsEdit(false); + setOpenModal(true); + if (onCustomButtonClick) onCustomButtonClick(); + try { + setPostHogCustomEvent?.(newButtonEventName, { + info: `Clicked on '${customButtonText}' button`, + }); + } catch (err) { + // Ignore error + } + }, [onCustomButtonClick, newButtonEventName, customButtonText]); + + const CustomButtons = useCallback( + () => ( + + {customButtonText} + + ), + [customButtonIcon, handleCustomButtonClick, customButtonText] + ); + + const handleEdit = useCallback( + (_event, item) => { + setEditingItem(item); + setIsEdit(true); + setOpenModal(true); + if (handleEditItem) handleEditItem(item); + }, + [handleEditItem] + ); + + const handleDelete = useCallback( + (_event, item) => { + deleteItem(item?.[itemProps?.idProp]); + if (handleDeleteItem) handleDeleteItem(item); + }, + [deleteItem, handleDeleteItem, itemProps?.idProp] + ); + + return ( + <> + +
+
+
+ +
+
+
+ {openModal && CustomModalComponent && ( + + )} + + ); +} + +ListView.propTypes = { + title: PropTypes.string.isRequired, + useListManagerHook: PropTypes.shape({ + list: PropTypes.array.isRequired, + filteredList: PropTypes.array.isRequired, + loading: PropTypes.bool.isRequired, + fetchList: PropTypes.func.isRequired, + handleSearch: PropTypes.func.isRequired, + handleAddItem: PropTypes.func.isRequired, + handleDeleteItem: PropTypes.func.isRequired, + }).isRequired, + CustomModalComponent: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.elementType, + ]), + customButtonText: PropTypes.string.isRequired, + customButtonIcon: PropTypes.element, + onCustomButtonClick: PropTypes.func, + handleEditItem: PropTypes.func, + handleDeleteItem: PropTypes.func, + itemProps: PropTypes.shape({ + titleProp: PropTypes.string.isRequired, + descriptionProp: PropTypes.string, + iconProp: PropTypes.string, + idProp: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + }).isRequired, + setPostHogCustomEvent: PropTypes.func, + newButtonEventName: PropTypes.string, + type: PropTypes.string, +}; + +export { ListView }; diff --git a/frontend/src/components/view-projects/ViewProjects.css b/frontend/src/components/view-projects/ViewProjects.css new file mode 100644 index 000000000..1407e7692 --- /dev/null +++ b/frontend/src/components/view-projects/ViewProjects.css @@ -0,0 +1,52 @@ +/* Styles for ListOfTools */ + +.list-of-tools-layout { + height: 100%; + background-color: var(--page-bg-2); + padding: 12px; + display: flex; + flex-direction: column; + overflow-y: hidden; +} + +.list-of-tools-island { + background-color: var(--page-bg-1); + flex: 1; + padding: 20px; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.list-of-tools-wrap { + height: 100%; + display: flex; + flex-direction: column; +} + +.list-of-tools-header { + display: grid; + grid-template-columns: auto 1fr; +} + +.list-of-tools-title { + font-size: 18px; + font-weight: 600; +} + +.list-of-tools-header2 { + display: grid; + grid-auto-flow: column; + column-gap: 10px; + justify-self: end; +} + +.list-of-tools-divider { + margin-top: 12px; +} + +.list-of-tools-body { + flex: 1; + display: flex; + justify-content: center; +} diff --git a/frontend/src/components/widgets/list-view/ListView.css b/frontend/src/components/widgets/list-view/ListView.css index 5d586fec9..2a9113a57 100644 --- a/frontend/src/components/widgets/list-view/ListView.css +++ b/frontend/src/components/widgets/list-view/ListView.css @@ -52,17 +52,11 @@ } .adapter-cover-img .fit-cover { - width: 120px; - height: 90px; + width: 50px; + height: 50px; object-fit: contain; } -.adapters-list-profile-container { - align-items: center; - display: flex; - justify-content: center; -} - .adapters-list-user-prefix { margin: 0 5px; font-weight: 500; diff --git a/frontend/src/components/widgets/list-view/ListView.jsx b/frontend/src/components/widgets/list-view/ListView.jsx index e34011539..9a9608fee 100644 --- a/frontend/src/components/widgets/list-view/ListView.jsx +++ b/frontend/src/components/widgets/list-view/ListView.jsx @@ -1,9 +1,11 @@ +// ListView.jsx + import { Avatar, - Flex, Image, - List, Popconfirm, + Space, + Table, Tooltip, Typography, } from "antd"; @@ -17,6 +19,8 @@ import { UserOutlined, } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; +import moment from "moment"; +import { useMemo, useCallback } from "react"; import { useSessionStore } from "../../../store/session-store"; @@ -31,150 +35,229 @@ function ListView({ idProp, centered, isClickable = true, - showOwner = true, type, }) { const navigate = useNavigate(); const { sessionDetails } = useSessionStore(); - const handleDeleteClick = (event, tool) => { - event.stopPropagation(); // Stop propagation to prevent list item click - handleDelete(event, tool); - }; - - const handleShareClick = (event, tool, isEdit) => { - event.stopPropagation(); // Stop propagation to prevent list item click - handleShare(event, tool, isEdit); - }; - - const renderTitle = (item) => { - let title = null; - if (iconProp && item[iconProp].length > 4) { - title = ( -
- + + const handleRowClick = useCallback( + (record) => { + if (isClickable) { + navigate(`${record[idProp]}`); + } + }, + [navigate, idProp, isClickable] + ); + + const renderNameColumn = useCallback( + (text, record) => { + let titleContent = null; + if (iconProp && record[iconProp]?.length > 4) { + titleContent = ( + + + + {record[titleProp]} + + + ); + } else if (iconProp) { + titleContent = ( - {item[titleProp]} + {`${record[iconProp]} ${record[titleProp]}`} -
- ); - } else if (iconProp) { - title = ( - - {`${item[iconProp]} ${item[titleProp]}`} - + ); + } else { + titleContent = ( + + {record[titleProp]} + + ); + } + return ( + <> + + {titleContent} + + + + {record?.[descriptionProp]} + + + ); - } else { - title = ( - - {item[titleProp]} - + }, + [iconProp, titleProp, descriptionProp] + ); + + const renderOwnedByColumn = useCallback( + (text) => { + const owner = text === sessionDetails?.email ? "Me" : text || "-"; + return ( +
+ } + /> + + Owned By: + + {owner} +
); - } - - return ( - (text ? moment(text).format("YYYY-MM-DD HH:mm:ss") : "-"), + [] + ); + + const handleEditClick = useCallback( + (event, record) => { + event.stopPropagation(); + handleEdit(event, record); + }, + [handleEdit] + ); + + const handleShareClick = useCallback( + (event, record) => { + event.stopPropagation(); + handleShare?.(record, true); + }, + [handleShare] + ); + + const handleDeleteClick = useCallback( + (event, record) => { + event.stopPropagation(); + handleDelete(event, record); + }, + [handleDelete] + ); + + const renderActionsColumn = useCallback( + (text, record) => ( +
event.stopPropagation()} + role="none" > -
-
- {title} -
- {showOwner && ( -
- } - /> - - Owned By: - - - {item?.created_by_email - ? item?.created_by_email === sessionDetails.email - ? "Me" - : item?.created_by_email - : "-"} - -
- )} -
-
event.stopPropagation()} - role="none" - > - handleEdit(event, item)} - className="action-icon-buttons edit-icon" + handleEditClick(event, record)} + className="action-icon-buttons edit-icon" + /> + {handleShare && ( + handleShareClick(event, record)} /> - {handleShare && ( - handleShareClick(event, item, true)} - /> - )} - } - onConfirm={(event) => { - handleDeleteClick(event, item); - }} - > - - - - -
- - ); - }; + )} + } + onConfirm={(event) => { + handleDeleteClick(event, record); + }} + > + + + + +
+ ), + [ + idProp, + titleProp, + type, + handleEditClick, + handleShare, + handleShareClick, + handleDeleteClick, + ] + ); + + const columns = useMemo( + () => [ + { + title: "Name", + dataIndex: titleProp, + key: "name", + sorter: (a, b) => + a[titleProp] + ?.toLowerCase() + .localeCompare(b[titleProp]?.toLowerCase()), + render: renderNameColumn, + }, + { + title: "Owned By", + dataIndex: "created_by_email", + key: "ownedBy", + sorter: (a, b) => + (a.created_by_email || "") + .toLowerCase() + .localeCompare((b.created_by_email || "").toLowerCase()), + render: renderOwnedByColumn, + }, + { + title: "Created At", + dataIndex: "created_at", + key: "createdAt", + sorter: (a, b) => + new Date(a.created_at).getTime() - new Date(b.created_at).getTime(), + render: renderDateColumn, + }, + { + title: "Modified At", + dataIndex: "modified_at", + key: "modifiedAt", + sorter: (a, b) => + new Date(a.modified_at).getTime() - new Date(b.modified_at).getTime(), + render: renderDateColumn, + }, + { + title: "Actions", + key: "actions", + align: "center", + render: renderActionsColumn, + }, + ], + [ + titleProp, + renderNameColumn, + renderOwnedByColumn, + renderDateColumn, + renderActionsColumn, + ] + ); return ( - { - return ( - { - isClickable - ? navigate(`${item[idProp]}`) - : handleShareClick(event, item, false); - }} - className={`cur-pointer ${centered ? "centered" : ""}`} - > - - - {item[descriptionProp]} - - - } - > - - ); - }} + onRow={(record) => ({ + onClick: () => handleRowClick(record), + className: `cur-pointer ${centered ? "centered" : ""}`, + })} + className="width-100" /> ); } @@ -194,4 +277,14 @@ ListView.propTypes = { type: PropTypes.string, }; +ListView.defaultProps = { + handleShare: null, + descriptionProp: "", + iconProp: "", + centered: false, + isClickable: true, + showOwner: false, + type: "", +}; + export { ListView }; diff --git a/frontend/src/components/workflows/new-workflow/NewWorkflow.jsx b/frontend/src/components/workflows/new-workflow/NewWorkflow.jsx index d6f0420b0..8024fc933 100644 --- a/frontend/src/components/workflows/new-workflow/NewWorkflow.jsx +++ b/frontend/src/components/workflows/new-workflow/NewWorkflow.jsx @@ -9,7 +9,7 @@ function NewWorkflow({ description = "", onDone = () => {}, onClose = () => {}, - loading = {}, + loading, toggleModal = () => {}, openModal = {}, backendErrors, diff --git a/frontend/src/components/workflows/workflow/WorkflowModal.jsx b/frontend/src/components/workflows/workflow/WorkflowModal.jsx new file mode 100644 index 000000000..b67f1d0e6 --- /dev/null +++ b/frontend/src/components/workflows/workflow/WorkflowModal.jsx @@ -0,0 +1,80 @@ +import PropTypes from "prop-types"; +import { useState } from "react"; +import { LazyLoader } from "../../widgets/lazy-loader/LazyLoader.jsx"; +import { useAlertStore } from "../../../store/alert-store"; +import { useExceptionHandler } from "../../../hooks/useExceptionHandler.jsx"; +import { useWorkflowStore } from "../../../store/workflow-store"; +import { useNavigate } from "react-router-dom"; +import { useSessionStore } from "../../../store/session-store"; + +function WorkflowModal({ + open, + setOpen, + editItem, + isEdit, + handleAddItem, + loading, + backendErrors, +}) { + const { setAlertDetails } = useAlertStore(); + const handleException = useExceptionHandler(); + const [localBackendErrors, setLocalBackendErrors] = useState(backendErrors); + const navigate = useNavigate(); + const { updateWorkflow } = useWorkflowStore(); + const sessionDetails = useSessionStore((state) => state?.sessionDetails); + const orgName = sessionDetails?.orgName ?? ""; + + const handleOnDone = (name, description) => { + handleAddItem({ name, description }, isEdit, editItem?.id) + .then((project) => { + if (!isEdit) { + updateWorkflow({ projectName: project?.workflow_name ?? "" }); + navigate(`/${orgName}/workflows/${project?.id ?? ""}`); + } + setAlertDetails({ + type: "success", + content: "Workflow updated successfully", + }); + setOpen(false); + }) + .catch((err) => { + handleException(err, "", setLocalBackendErrors); + }); + }; + + const handleOnClose = () => { + setOpen(false); + }; + + return ( + import("../new-workflow/NewWorkflow.jsx")} + componentName="NewWorkflow" + name={editItem?.workflow_name ?? ""} + description={editItem?.description ?? ""} + onDone={handleOnDone} + onClose={handleOnClose} + loading={loading} + openModal={open} + backendErrors={localBackendErrors} + setBackendErrors={setLocalBackendErrors} + /> + ); +} + +WorkflowModal.propTypes = { + open: PropTypes.bool.isRequired, + setOpen: PropTypes.func.isRequired, + editItem: PropTypes.shape({ + id: PropTypes.number, + workflow_name: PropTypes.string, + description: PropTypes.string, + }), + isEdit: PropTypes.bool.isRequired, + handleAddItem: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, + backendErrors: PropTypes.any, + setBackendErrors: PropTypes.func.isRequired, +}; + +export default WorkflowModal; diff --git a/frontend/src/components/workflows/workflow/Workflows.jsx b/frontend/src/components/workflows/workflow/Workflows.jsx index 08839404f..4c327fd3d 100644 --- a/frontend/src/components/workflows/workflow/Workflows.jsx +++ b/frontend/src/components/workflows/workflow/Workflows.jsx @@ -1,273 +1,63 @@ -import { PlusOutlined, UserOutlined } from "@ant-design/icons"; -import { Typography } from "antd"; -import isEmpty from "lodash/isEmpty"; -import PropTypes from "prop-types"; -import { useEffect, useRef, useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; - -import { useAlertStore } from "../../../store/alert-store"; -import { useSessionStore } from "../../../store/session-store"; -import { useWorkflowStore } from "../../../store/workflow-store"; -import { CustomButton } from "../../widgets/custom-button/CustomButton.jsx"; -import { EmptyState } from "../../widgets/empty-state/EmptyState.jsx"; -import { LazyLoader } from "../../widgets/lazy-loader/LazyLoader.jsx"; -import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader.jsx"; -import "./Workflows.css"; +import { PlusOutlined } from "@ant-design/icons"; +import usePostHogEvents from "../../../hooks/usePostHogEvents"; +import { useListManager } from "../../../hooks/useListManager"; +import WorkflowModal from "./WorkflowModal"; +import { ListView } from "../../view-projects/ListView"; import { workflowService } from "./workflow-service"; -import { useExceptionHandler } from "../../../hooks/useExceptionHandler.jsx"; -import { ToolNavBar } from "../../navigations/tool-nav-bar/ToolNavBar.jsx"; -import { ViewTools } from "../../custom-tools/view-tools/ViewTools.jsx"; -import usePostHogEvents from "../../../hooks/usePostHogEvents.js"; - -const PROJECT_FILTER_OPTIONS = [ - { label: "My Workflows", value: "mine" }, - { label: "Organization Workflows", value: "all" }, -]; - -const { Title, Text } = Typography; function Workflows() { - const navigate = useNavigate(); - const location = useLocation(); - const projectApiService = workflowService(); - const handleException = useExceptionHandler(); const { setPostHogCustomEvent } = usePostHogEvents(); + const projectApiService = workflowService(); - const [projectList, setProjectList] = useState(); - const [editingProject, setEditProject] = useState(); - const [loading, setLoading] = useState(false); - const [openModal, toggleModal] = useState(true); - const projectListRef = useRef(); - const filterViewRef = useRef(PROJECT_FILTER_OPTIONS[0].value); - const [backendErrors, setBackendErrors] = useState(null); - - const { setAlertDetails } = useAlertStore(); - const sessionDetails = useSessionStore((state) => state?.sessionDetails); - const { updateWorkflow } = useWorkflowStore(); - const orgName = sessionDetails?.orgName; - - useEffect(() => { - if (location.pathname === `/${orgName}/workflows`) { - getProjectList(); - } - }, [location.pathname]); - - function getProjectList() { - projectApiService - .getProjectList(filterViewRef.current === PROJECT_FILTER_OPTIONS[0].value) - .then((res) => { - projectListRef.current = res.data; - setProjectList(res.data); - }) - .catch(() => { - console.error("Unable to get project list"); - }); - } + const getListApiCall = ({ initialFilter }) => + projectApiService.getProjectList(initialFilter); - function onSearch(searchText, setSearchList) { - if (!searchText.trim()) { - setSearchList(projectListRef.current); - return; - } - const filteredList = projectListRef.current.filter((item) => - item.workflow_name.toLowerCase().includes(searchText.toLowerCase()) + const addItemApiCall = ({ itemData }) => + projectApiService.editProject( + itemData?.name ?? "", + itemData?.description ?? "" ); - setSearchList(filteredList); - } - - function applyFilter(value) { - filterViewRef.current = value; - projectListRef.current = ""; - setProjectList(""); - getProjectList(); - } - - function editProject(name, description) { - setLoading(true); - projectApiService - .editProject(name, description, editingProject?.id) - .then((res) => { - closeNewProject(); - if (editingProject?.name) { - getProjectList(); - } else { - openProject(res.data); - } - setAlertDetails({ - type: "success", - content: "Workflow updated successfully", - }); - getProjectList(); - }) - .catch((err) => { - handleException(err, "", setBackendErrors); - }) - .finally(() => { - setLoading(false); - }); - } - - function openProject(project) { - updateWorkflow({ projectName: project?.workflow_name }); - navigate(`/${orgName}/workflows/${project.id}`); - } - function showNewProject() { - setEditProject({ name: "", description: "" }); - } - - function updateProject(_event, project) { - toggleModal(true); - setEditProject({ - name: project.workflow_name, - description: project.description || "", - id: project.id, - }); - } - - const canDeleteProject = async (id) => { - let status = false; - await projectApiService.canUpdate(id).then((res) => { - status = res?.data?.can_update || false; - }); - return status; - }; - - const deleteProject = async (_evt, project) => { - const canDelete = await canDeleteProject(project.id); - if (canDelete) { - projectApiService - .deleteProject(project.id) - .then(() => { - getProjectList(); - setAlertDetails({ - type: "success", - content: "Workflow deleted successfully", - }); - }) - .catch(() => { - setAlertDetails({ - type: "error", - content: `Unable to delete workflow ${project.id}`, - }); - }); - } else { - setAlertDetails({ - type: "error", - content: - "Cannot delete this Workflow, since it is used in one or many of the API/ETL/Task pipelines", - }); - } - }; - - function closeNewProject() { - setEditProject(); - } - - const handleNewWorkflowBtnClick = () => { - showNewProject(); - toggleModal(true); - - try { - setPostHogCustomEvent("intent_new_wf_project", { - info: "Clicked on '+ New Workflow' button", - }); - } catch (err) { - // If an error occurs while setting custom posthog event, ignore it and continue - } - }; - - const CustomButtons = () => { - return ( - } - onClick={handleNewWorkflowBtnClick} - > - New Workflow - + const editItemApiCall = ({ itemData, itemId }) => + projectApiService.editProject( + itemData?.name ?? "", + itemData?.description ?? "", + itemId ); - }; + + const deleteItemApiCall = ({ itemId }) => + projectApiService.deleteProject(itemId); + + const useListManagerHook = useListManager({ + getListApiCall, + addItemApiCall, + editItemApiCall, + deleteItemApiCall, + searchProperty: "workflow_name", + itemIdProp: "id", + itemNameProp: "workflow_name", + itemDescriptionProp: "description", + itemType: "Workflow", + initialFilter: "mine", + }); return ( - <> - -
-
- {!projectListRef.current && } - {projectListRef.current && isEmpty(projectListRef?.current) && ( -
- { - showNewProject(); - toggleModal(true); - }} - /> -
- )} - {isEmpty(projectList) && !isEmpty(projectListRef?.current) && ( -
- No results found for this search -
- )} - {!isEmpty(projectList) && ( - - )} - {editingProject && ( - import("../new-workflow/NewWorkflow.jsx")} - componentName={"NewWorkflow"} - name={editingProject.name} - description={editingProject.description} - onDone={editProject} - onClose={closeNewProject} - loading={loading} - toggleModal={toggleModal} - openModal={openModal} - backendErrors={backendErrors} - setBackendErrors={setBackendErrors} - /> - )} -
-
- + } + itemProps={{ + titleProp: "workflow_name", + descriptionProp: "description", + idProp: "id", + type: "Workflow", + }} + setPostHogCustomEvent={setPostHogCustomEvent} + newButtonEventName="intent_new_wf_project" + /> ); } -function User({ name }) { - return name ? ( -
- - - {name} - -
- ) : null; -} - -User.propTypes = { - name: PropTypes.string, -}; - export { Workflows }; diff --git a/frontend/src/hooks/useListManager.js b/frontend/src/hooks/useListManager.js new file mode 100644 index 000000000..80c5ac2f5 --- /dev/null +++ b/frontend/src/hooks/useListManager.js @@ -0,0 +1,132 @@ +import { useState, useEffect } from "react"; +import { useExceptionHandler } from "./useExceptionHandler"; +import { useAlertStore } from "../store/alert-store"; +import { useSessionStore } from "../store/session-store"; +import { useAxiosPrivate } from "./useAxiosPrivate"; + +export function useListManager({ + getListApiCall, + addItemApiCall, + editItemApiCall, + deleteItemApiCall, + searchProperty = "", + itemIdProp = "id", + itemType = "item", + initialFilter, + onAddSuccess, + onEditSuccess, + onDeleteSuccess, + onError, +}) { + const [list, setList] = useState([]); + const [filteredList, setFilteredList] = useState([]); + const [loading, setLoading] = useState(false); + + const handleException = useExceptionHandler(); + const { setAlertDetails } = useAlertStore(); + const sessionDetails = useSessionStore((state) => state?.sessionDetails); + const axiosPrivate = useAxiosPrivate(); + + useEffect(() => { + fetchList(); + }, [initialFilter]); + + const fetchList = () => { + if (!getListApiCall) return; + + setLoading(true); + getListApiCall({ axiosPrivate, sessionDetails, initialFilter }) + .then((res) => { + const data = res?.data || []; + setList(data); + setFilteredList(data); + }) + .catch((err) => { + const errorMsg = handleException( + err, + `Failed to get the list of ${itemType}s` + ); + setAlertDetails(errorMsg); + onError?.(err); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSearch = (searchText = "") => { + if (!searchText.trim()) { + setFilteredList(list); + return; + } + const filtered = list.filter((item) => + item?.[searchProperty]?.toLowerCase()?.includes(searchText.toLowerCase()) + ); + setFilteredList(filtered); + }; + + const handleAddItem = (itemData, isEdit = false, itemId) => { + const apiCall = isEdit ? editItemApiCall : addItemApiCall; + if (!apiCall) return Promise.reject(new Error("API call is not defined")); + + return apiCall({ axiosPrivate, sessionDetails, itemData, itemId }) + .then((res) => { + const updatedItem = res?.data; + let updatedList = []; + + if (isEdit) { + updatedList = list.map((item) => + item?.[itemIdProp] === itemId ? updatedItem : item + ); + onEditSuccess?.(updatedItem); + } else { + updatedList = [...list, updatedItem]; + onAddSuccess?.(updatedItem); + } + + setList(updatedList); + setFilteredList(updatedList); + return updatedItem; + }) + .catch((err) => { + const errorMsg = handleException( + err, + `Failed to ${isEdit ? "edit" : "add"} ${itemType}` + ); + setAlertDetails(errorMsg); + onError?.(err); + throw err; + }); + }; + + const handleDeleteItem = (itemId) => { + if (!deleteItemApiCall) + return Promise.reject(new Error("API call is not defined")); + + return deleteItemApiCall({ axiosPrivate, sessionDetails, itemId }) + .then(() => { + const updatedList = list.filter( + (item) => item?.[itemIdProp] !== itemId + ); + setList(updatedList); + setFilteredList(updatedList); + onDeleteSuccess?.(itemId); + }) + .catch((err) => { + const errorMsg = handleException(err, `Failed to delete ${itemType}`); + setAlertDetails(errorMsg); + onError?.(err); + throw err; + }); + }; + + return { + list, + filteredList, + loading, + fetchList, + handleSearch, + handleAddItem, + handleDeleteItem, + }; +} From 26b476df42efd3b0bf74cb105b78388481c63abb Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Fri, 25 Oct 2024 09:45:12 +0530 Subject: [PATCH 2/7] Sonar issue fixes --- frontend/src/components/widgets/list-view/ListView.jsx | 2 -- frontend/src/components/workflows/workflow/WorkflowModal.jsx | 1 - 2 files changed, 3 deletions(-) diff --git a/frontend/src/components/widgets/list-view/ListView.jsx b/frontend/src/components/widgets/list-view/ListView.jsx index 9a9608fee..271c20526 100644 --- a/frontend/src/components/widgets/list-view/ListView.jsx +++ b/frontend/src/components/widgets/list-view/ListView.jsx @@ -273,7 +273,6 @@ ListView.propTypes = { idProp: PropTypes.string.isRequired, centered: PropTypes.bool, isClickable: PropTypes.bool, - showOwner: PropTypes.bool, type: PropTypes.string, }; @@ -283,7 +282,6 @@ ListView.defaultProps = { iconProp: "", centered: false, isClickable: true, - showOwner: false, type: "", }; diff --git a/frontend/src/components/workflows/workflow/WorkflowModal.jsx b/frontend/src/components/workflows/workflow/WorkflowModal.jsx index b67f1d0e6..7e37babf1 100644 --- a/frontend/src/components/workflows/workflow/WorkflowModal.jsx +++ b/frontend/src/components/workflows/workflow/WorkflowModal.jsx @@ -74,7 +74,6 @@ WorkflowModal.propTypes = { handleAddItem: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, backendErrors: PropTypes.any, - setBackendErrors: PropTypes.func.isRequired, }; export default WorkflowModal; From ef88e85aa30ce2c2fada822bab74b18262f693a6 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Mon, 4 Nov 2024 17:46:06 +0530 Subject: [PATCH 3/7] Bug fixes in adapter settings pages --- .../tool-settings/ToolSettingsModal.jsx | 17 ++++------------- .../src/components/view-projects/ListView.jsx | 3 +++ frontend/src/hooks/useListManager.js | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx index 462e13795..7624a6cf3 100644 --- a/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx +++ b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx @@ -1,7 +1,5 @@ import PropTypes from "prop-types"; import { useCallback } from "react"; -import { useAlertStore } from "../../../store/alert-store"; -import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; import { AddSourceModal } from "../../input-output/add-source-modal/AddSourceModal"; function ToolSettingsModal({ @@ -9,21 +7,13 @@ function ToolSettingsModal({ setOpen, editItem, isEdit, - handleAddItem, type, + updateList, }) { - const { setAlertDetails } = useAlertStore(); - const handleException = useExceptionHandler(); - const handleAddNewItem = useCallback( (itemData) => { - handleAddItem(itemData, isEdit, editItem?.id) - .then(() => { - setOpen(false); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to add/edit adapter")); - }); + updateList(itemData, isEdit, editItem?.id); + setOpen(false); }, [isEdit, editItem?.id] ); @@ -47,6 +37,7 @@ ToolSettingsModal.propTypes = { isEdit: PropTypes.bool.isRequired, handleAddItem: PropTypes.func.isRequired, type: PropTypes.string.isRequired, + updateList: PropTypes.func.isRequired, }; export default ToolSettingsModal; diff --git a/frontend/src/components/view-projects/ListView.jsx b/frontend/src/components/view-projects/ListView.jsx index f0084c92e..e3f703d08 100644 --- a/frontend/src/components/view-projects/ListView.jsx +++ b/frontend/src/components/view-projects/ListView.jsx @@ -27,6 +27,7 @@ function ListView({ handleSearch, handleAddItem, handleDeleteItem: deleteItem, + updateList, } = useListManagerHook; const [openModal, setOpenModal] = useState(false); @@ -114,6 +115,7 @@ function ListView({ editItem={editingItem} isEdit={isEdit} handleAddItem={handleAddItem} + updateList={updateList} type={type} /> )} @@ -131,6 +133,7 @@ ListView.propTypes = { handleSearch: PropTypes.func.isRequired, handleAddItem: PropTypes.func.isRequired, handleDeleteItem: PropTypes.func.isRequired, + updateList: PropTypes.func.isRequired, }).isRequired, CustomModalComponent: PropTypes.oneOfType([ PropTypes.func, diff --git a/frontend/src/hooks/useListManager.js b/frontend/src/hooks/useListManager.js index 80c5ac2f5..45ba32498 100644 --- a/frontend/src/hooks/useListManager.js +++ b/frontend/src/hooks/useListManager.js @@ -54,6 +54,23 @@ export function useListManager({ }); }; + const updateList = (itemData, isEdit = false, itemId) => { + let updatedList = []; + + if (isEdit) { + updatedList = list.map((item) => + item?.[itemIdProp] === itemId ? itemData : item + ); + onEditSuccess?.(itemData); + } else { + updatedList = [itemData, ...list]; + onAddSuccess?.(itemData); + } + + setList(updatedList); + setFilteredList(updatedList); + }; + const handleSearch = (searchText = "") => { if (!searchText.trim()) { setFilteredList(list); @@ -123,6 +140,7 @@ export function useListManager({ return { list, filteredList, + updateList, loading, fetchList, handleSearch, From 0c8c7d3c5628c71fa40a2fe64a6d3c6d045a9e0b Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Mon, 4 Nov 2024 17:58:55 +0530 Subject: [PATCH 4/7] Resolved sonar issues --- .../custom-tools/list-of-tools/ListOfToolsModal.jsx | 2 +- .../tool-settings/tool-settings/ToolSettingsModal.jsx | 3 +-- frontend/src/components/workflows/workflow/WorkflowModal.jsx | 2 +- frontend/src/hooks/useListManager.js | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx b/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx index 8d2ba9560..1c0a3ca3b 100644 --- a/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx +++ b/frontend/src/components/custom-tools/list-of-tools/ListOfToolsModal.jsx @@ -4,7 +4,7 @@ import { AddCustomToolFormModal } from "../add-custom-tool-form-modal/AddCustomT function ListOfToolsModal({ open, setOpen, editItem, isEdit, handleAddItem }) { const handleAddNewTool = useCallback( - (itemData) => handleAddItem(itemData, isEdit, editItem?.tool_id), + (itemData) => handleAddItem(itemData, editItem?.tool_id, isEdit), [handleAddItem, isEdit, editItem?.tool_id] ); diff --git a/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx index 7624a6cf3..49e6868a1 100644 --- a/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx +++ b/frontend/src/components/tool-settings/tool-settings/ToolSettingsModal.jsx @@ -12,7 +12,7 @@ function ToolSettingsModal({ }) { const handleAddNewItem = useCallback( (itemData) => { - updateList(itemData, isEdit, editItem?.id); + updateList(itemData, editItem?.id, isEdit); setOpen(false); }, [isEdit, editItem?.id] @@ -35,7 +35,6 @@ ToolSettingsModal.propTypes = { setOpen: PropTypes.func.isRequired, editItem: PropTypes.object, isEdit: PropTypes.bool.isRequired, - handleAddItem: PropTypes.func.isRequired, type: PropTypes.string.isRequired, updateList: PropTypes.func.isRequired, }; diff --git a/frontend/src/components/workflows/workflow/WorkflowModal.jsx b/frontend/src/components/workflows/workflow/WorkflowModal.jsx index 7e37babf1..543ecaf9b 100644 --- a/frontend/src/components/workflows/workflow/WorkflowModal.jsx +++ b/frontend/src/components/workflows/workflow/WorkflowModal.jsx @@ -25,7 +25,7 @@ function WorkflowModal({ const orgName = sessionDetails?.orgName ?? ""; const handleOnDone = (name, description) => { - handleAddItem({ name, description }, isEdit, editItem?.id) + handleAddItem({ name, description }, editItem?.id, isEdit) .then((project) => { if (!isEdit) { updateWorkflow({ projectName: project?.workflow_name ?? "" }); diff --git a/frontend/src/hooks/useListManager.js b/frontend/src/hooks/useListManager.js index 45ba32498..c06707e90 100644 --- a/frontend/src/hooks/useListManager.js +++ b/frontend/src/hooks/useListManager.js @@ -54,7 +54,7 @@ export function useListManager({ }); }; - const updateList = (itemData, isEdit = false, itemId) => { + const updateList = (itemData, itemId, isEdit = false) => { let updatedList = []; if (isEdit) { @@ -82,7 +82,7 @@ export function useListManager({ setFilteredList(filtered); }; - const handleAddItem = (itemData, isEdit = false, itemId) => { + const handleAddItem = (itemData, itemId, isEdit = false) => { const apiCall = isEdit ? editItemApiCall : addItemApiCall; if (!apiCall) return Promise.reject(new Error("API call is not defined")); From 5ce71b71874592b1344380d6c51e24bed1ade6ec Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Mon, 4 Nov 2024 18:45:18 +0530 Subject: [PATCH 5/7] Moved base url to a separate variable --- .../list-of-tools/ListOfTools.jsx | 19 ++++++++--- .../tool-settings/ToolSettings.jsx | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx b/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx index edb73fdf4..838440405 100644 --- a/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx +++ b/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx @@ -8,10 +8,15 @@ import { ListView } from "../../view-projects/ListView"; function ListOfTools() { const { setPostHogCustomEvent } = usePostHogEvents(); + const getBaseUrl = (sessionDetails) => + `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`; + const getListApiCall = useCallback(({ axiosPrivate, sessionDetails }) => { + const baseUrl = getBaseUrl(sessionDetails); + const requestOptions = { method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`, + url: baseUrl, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, @@ -21,9 +26,11 @@ function ListOfTools() { const addItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemData }) => { + const baseUrl = getBaseUrl(sessionDetails); + const requestOptions = { method: "POST", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/`, + url: baseUrl, headers: { "X-CSRFToken": sessionDetails?.csrfToken, "Content-Type": "application/json", @@ -37,9 +44,11 @@ function ListOfTools() { const editItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemData, itemId }) => { + const baseUrl = getBaseUrl(sessionDetails); + const requestOptions = { method: "PATCH", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${itemId}/`, + url: `${baseUrl}${itemId}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, "Content-Type": "application/json", @@ -53,9 +62,11 @@ function ListOfTools() { const deleteItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemId }) => { + const baseUrl = getBaseUrl(sessionDetails); + const requestOptions = { method: "DELETE", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/${itemId}`, + url: `${baseUrl}${itemId}`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, diff --git a/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx b/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx index 3350e18ba..d56512291 100644 --- a/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx +++ b/frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx @@ -42,24 +42,25 @@ function ToolSettings({ type }) { useState(false); const [isPermissionEdit, setIsPermissionEdit] = useState(false); + const adapterBaseUrl = `/api/v1/unstract/${sessionDetails?.orgId}/adapter`; + const userBaseUrl = `/api/v1/unstract/${sessionDetails?.orgId}/users`; + const getListApiCall = useCallback( ({ axiosPrivate, sessionDetails }) => { const requestOptions = { method: "GET", - url: `/api/v1/unstract/${ - sessionDetails?.orgId - }/adapter?adapter_type=${type.toUpperCase()}`, + url: `${adapterBaseUrl}?adapter_type=${type.toUpperCase()}`, }; return axiosPrivate(requestOptions); }, - [type] + [type, adapterBaseUrl] ); const addItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemData }) => { const requestOptions = { method: "POST", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/`, + url: `${adapterBaseUrl}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, "Content-Type": "application/json", @@ -68,14 +69,14 @@ function ToolSettings({ type }) { }; return axiosPrivate(requestOptions); }, - [] + [adapterBaseUrl] ); const editItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemData, itemId }) => { const requestOptions = { method: "PATCH", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${itemId}/`, + url: `${adapterBaseUrl}/${itemId}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, "Content-Type": "application/json", @@ -84,21 +85,21 @@ function ToolSettings({ type }) { }; return axiosPrivate(requestOptions); }, - [] + [adapterBaseUrl] ); const deleteItemApiCall = useCallback( ({ axiosPrivate, sessionDetails, itemId }) => { const requestOptions = { method: "DELETE", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${itemId}/`, + url: `${adapterBaseUrl}/${itemId}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, }; return axiosPrivate(requestOptions); }, - [] + [adapterBaseUrl] ); const useListManagerHook = useListManager({ @@ -117,7 +118,7 @@ function ToolSettings({ type }) { setIsShareLoading(true); const requestOptions = { method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/users/`, + url: `${userBaseUrl}/`, }; axiosPrivate(requestOptions) @@ -136,14 +137,13 @@ function ToolSettings({ type }) { .finally(() => { setIsShareLoading(false); }); - }, []); + }, [userBaseUrl]); - // Memoized handleShare function const handleShare = useCallback( (adapter, isEdit) => { const requestOptions = { method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/users/${adapter.id}/`, + url: `${adapterBaseUrl}/users/${adapter.id}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, @@ -168,6 +168,7 @@ function ToolSettings({ type }) { setOpenSharePermissionModal, setAdapterDetails, setIsPermissionEdit, + adapterBaseUrl, ] ); @@ -175,7 +176,7 @@ function ToolSettings({ type }) { (userIds, adapter) => { const requestOptions = { method: "PATCH", - url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/${adapter.id}/`, + url: `${adapterBaseUrl}/${adapter.id}/`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, @@ -189,7 +190,7 @@ function ToolSettings({ type }) { setAlertDetails(handleException(err, "Failed to load")); }); }, - [setOpenSharePermissionModal] + [setOpenSharePermissionModal, adapterBaseUrl] ); const itemProps = useMemo( From b0dfc074058b35c566df12cd46e00abeb77b0a31 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Mon, 4 Nov 2024 18:55:08 +0530 Subject: [PATCH 6/7] Moved from defaultProps to default parameters in ViewTools and ListView components --- .../custom-tools/view-tools/ViewTools.jsx | 24 +++++-------------- .../components/widgets/list-view/ListView.jsx | 21 ++++------------ 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/custom-tools/view-tools/ViewTools.jsx b/frontend/src/components/custom-tools/view-tools/ViewTools.jsx index 76a213554..d185c1657 100644 --- a/frontend/src/components/custom-tools/view-tools/ViewTools.jsx +++ b/frontend/src/components/custom-tools/view-tools/ViewTools.jsx @@ -10,19 +10,19 @@ import { EmptyState } from "../../widgets/empty-state/EmptyState"; function ViewTools({ isLoading, isEmpty, - listOfTools, - setOpenAddTool, + listOfTools = [], + setOpenAddTool = () => {}, handleEdit, handleDelete, titleProp, - descriptionProp, - iconProp, + descriptionProp = "", + iconProp = "", idProp, centered = false, isClickable = true, - handleShare, + handleShare = null, showOwner = false, - type, + type = "", }) { const handleEmptyStateClick = useCallback(() => { setOpenAddTool(true); @@ -93,16 +93,4 @@ ViewTools.propTypes = { type: PropTypes.string, }; -ViewTools.defaultProps = { - listOfTools: [], - setOpenAddTool: () => {}, - descriptionProp: "", - iconProp: "", - centered: false, - isClickable: true, - handleShare: null, - showOwner: false, - type: "", -}; - export { ViewTools }; diff --git a/frontend/src/components/widgets/list-view/ListView.jsx b/frontend/src/components/widgets/list-view/ListView.jsx index 271c20526..ff5d4aa09 100644 --- a/frontend/src/components/widgets/list-view/ListView.jsx +++ b/frontend/src/components/widgets/list-view/ListView.jsx @@ -1,5 +1,3 @@ -// ListView.jsx - import { Avatar, Image, @@ -28,14 +26,14 @@ function ListView({ listOfTools, handleEdit, handleDelete, - handleShare, + handleShare = null, titleProp, - descriptionProp, - iconProp, + descriptionProp = "", + iconProp = "", idProp, - centered, + centered = false, isClickable = true, - type, + type = "", }) { const navigate = useNavigate(); const { sessionDetails } = useSessionStore(); @@ -276,13 +274,4 @@ ListView.propTypes = { type: PropTypes.string, }; -ListView.defaultProps = { - handleShare: null, - descriptionProp: "", - iconProp: "", - centered: false, - isClickable: true, - type: "", -}; - export { ListView }; From ca03eed8a5f72e4eab7f23f88f9561df7c6756b1 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 5 Nov 2024 00:13:44 +0530 Subject: [PATCH 7/7] Removed filter param for workflows API --- .../src/components/workflows/workflow/workflow-service.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/workflows/workflow/workflow-service.js b/frontend/src/components/workflows/workflow/workflow-service.js index 494efe4b2..c3c1e12b1 100644 --- a/frontend/src/components/workflows/workflow/workflow-service.js +++ b/frontend/src/components/workflows/workflow/workflow-service.js @@ -24,12 +24,10 @@ function workflowService() { }; return axiosPrivate(options); }, - getProjectList: (myProjects = false) => { - const params = myProjects ? { created_by: sessionDetails?.id } : {}; + getProjectList: () => { options = { url: `${path}/workflow/`, method: "GET", - params, }; return axiosPrivate(options); },