From 28f7e2096e40f25e40b7db4720e32f852de53cf8 Mon Sep 17 00:00:00 2001 From: Siddhiq <113336661+mohamed-siddhiq@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:00:08 +0530 Subject: [PATCH] Fix/validation in add new prompt studio (#60) * Add validation in add new tool from prompt studio * Handle error validation for update API deployment --- frontend/package-lock.json | 58 +++- frontend/package.json | 2 + .../agency/cards-list/CardsList.jsx | 10 +- .../agency/side-panel/SidePanel.jsx | 15 +- .../components/agency/tool-icon/ToolIcon.jsx | 55 +++- .../agency/tool-info-card/ToolInfoCard.jsx | 2 +- .../AddCustomToolFormModal.css | 7 +- .../AddCustomToolFormModal.jsx | 268 ++++++++++-------- .../list-of-tools/ListOfTools.jsx | 27 +- .../CreateApiDeploymentModal.jsx | 5 +- .../deployments/display-code/DisplayCode.jsx | 26 +- frontend/src/helpers/GetStaticData.js | 14 +- 12 files changed, 308 insertions(+), 181 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 91399eb6c..a57e18432 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,8 @@ "antd": "^5.5.1", "axios": "^1.4.0", "cron-validator": "^1.3.1", + "emoji-picker-react": "^4.8.0", + "emoji-regex": "^10.3.0", "handlebars": "^4.7.8", "http-proxy-middleware": "^2.0.6", "js-yaml": "^4.1.0", @@ -7891,10 +7893,24 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, + "node_modules/emoji-picker-react": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.8.0.tgz", + "integrity": "sha512-gKv+NOM2FjNAokXqlhhmDJMLp1hBTx4X+OoYrPAK1qpTVBseoeybtqPBR1NXixTow7Vbxv1WOuI3SSTiEB089A==", + "dependencies": { + "flairup": "0.0.38" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -8498,6 +8514,11 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, "node_modules/eslint-plugin-prettier": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", @@ -9110,6 +9131,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flairup": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-0.0.38.tgz", + "integrity": "sha512-W9QA5TM7eYNlGoBYwfVn/o6v4yWBCxfq4+EJ5w774oFeyWvVWnYq6Dgt4CJltjG9y/lPwbOqz3jSSr8K66ToGg==" + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -26028,10 +26054,18 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==" }, + "emoji-picker-react": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.8.0.tgz", + "integrity": "sha512-gKv+NOM2FjNAokXqlhhmDJMLp1hBTx4X+OoYrPAK1qpTVBseoeybtqPBR1NXixTow7Vbxv1WOuI3SSTiEB089A==", + "requires": { + "flairup": "0.0.38" + } + }, "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" }, "emojis-list": { "version": "3.0.0", @@ -26498,6 +26532,13 @@ "object.entries": "^1.1.6", "object.fromentries": "^2.0.6", "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + } } }, "eslint-plugin-prettier": { @@ -26940,6 +26981,11 @@ "path-exists": "^4.0.0" } }, + "flairup": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-0.0.38.tgz", + "integrity": "sha512-W9QA5TM7eYNlGoBYwfVn/o6v4yWBCxfq4+EJ5w774oFeyWvVWnYq6Dgt4CJltjG9y/lPwbOqz3jSSr8K66ToGg==" + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 939df4ff2..fdc22fe68 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,8 @@ "antd": "^5.5.1", "axios": "^1.4.0", "cron-validator": "^1.3.1", + "emoji-picker-react": "^4.8.0", + "emoji-regex": "^10.3.0", "handlebars": "^4.7.8", "http-proxy-middleware": "^2.0.6", "js-yaml": "^4.1.0", diff --git a/frontend/src/components/agency/cards-list/CardsList.jsx b/frontend/src/components/agency/cards-list/CardsList.jsx index 939ec578c..ceae2ab8e 100644 --- a/frontend/src/components/agency/cards-list/CardsList.jsx +++ b/frontend/src/components/agency/cards-list/CardsList.jsx @@ -1,5 +1,5 @@ import { CloseOutlined } from "@ant-design/icons"; -import { Card, Col, Image, Progress, Row, Typography } from "antd"; +import { Card, Col, Progress, Row, Typography } from "antd"; import PropTypes from "prop-types"; import { useRef } from "react"; import { useDrag, useDrop } from "react-dnd"; @@ -13,6 +13,7 @@ import { useWorkflowStore } from "../../../store/workflow-store"; import { ConfirmModal } from "../../widgets/confirm-modal/ConfirmModal"; import "../step-card/StepCard.css"; import "./CardList.css"; +import { ToolIcon } from "../tool-icon/ToolIcon"; const CardsList = ({ step, index, activeTool, moveItem }) => { const ref = useRef(null); @@ -143,12 +144,7 @@ const CardsList = ({ step, index, activeTool, moveItem }) => {
- +
diff --git a/frontend/src/components/agency/side-panel/SidePanel.jsx b/frontend/src/components/agency/side-panel/SidePanel.jsx index b7b85e5e9..d64296c24 100644 --- a/frontend/src/components/agency/side-panel/SidePanel.jsx +++ b/frontend/src/components/agency/side-panel/SidePanel.jsx @@ -40,6 +40,10 @@ function SidePanel() { setActiveTabKey(key.toString()); }; + const isObjectEmpty = (obj) => { + return Object.keys(obj).length === 0; + }; + useEffect(() => { const toolSettingsId = toolSettings?.tool_id; if (!toolSettingsId) { @@ -65,8 +69,15 @@ function SidePanel() { setSpecLoading(true); axiosPrivate(requestOptions) .then((res) => { - setToolId(toolSettingsId); - setSpec(res?.data); + if (isObjectEmpty(res?.data?.properties)) { + // Disable tool settings & switch to Tools tab - when custom tool is selected + setSpec({}); + setToolId(""); + setActiveTabKey("1"); + } else { + setToolId(toolSettingsId); + setSpec(res?.data); + } }) .catch((err) => {}) .finally(() => { diff --git a/frontend/src/components/agency/tool-icon/ToolIcon.jsx b/frontend/src/components/agency/tool-icon/ToolIcon.jsx index e1f6924d4..bbed9bd60 100644 --- a/frontend/src/components/agency/tool-icon/ToolIcon.jsx +++ b/frontend/src/components/agency/tool-icon/ToolIcon.jsx @@ -1,29 +1,54 @@ import { Image } from "antd"; +import emojiRegex from "emoji-regex"; import PropTypes from "prop-types"; import "./ToolIcon.css"; -import { useEffect, useState } from "react"; -function ToolIcon({ iconSrc }) { - const [svgDataUri, setSvgDataUri] = useState(""); - useEffect(() => { - if (!iconSrc) { - setSvgDataUri(""); - return; - } +function ToolIcon({ iconSrc, showBorder }) { + const emojiregex = emojiRegex(); + const isSVG = + iconSrc && typeof iconSrc === "string" && iconSrc.includes(" - -
- ); + return ( +
+ +
+ ); + } else if (typeof iconSrc === "string" && emojiregex.test(iconSrc)) { + // If it's a string, render it using Typography + return ( +
+ {iconSrc} +
+ ); + } else { + // Handle other cases if needed + return ( +
+ {"🧰"} +
+ ); + } } ToolIcon.propTypes = { iconSrc: PropTypes.string, + showBorder: PropTypes.bool, }; export { ToolIcon }; diff --git a/frontend/src/components/agency/tool-info-card/ToolInfoCard.jsx b/frontend/src/components/agency/tool-info-card/ToolInfoCard.jsx index 2c2033d99..784a7b827 100644 --- a/frontend/src/components/agency/tool-info-card/ToolInfoCard.jsx +++ b/frontend/src/components/agency/tool-info-card/ToolInfoCard.jsx @@ -15,7 +15,7 @@ function ToolInfoCard({ toolInfo }) { - +
diff --git a/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.css b/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.css index 12f940646..14f8a1562 100644 --- a/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.css +++ b/frontend/src/components/custom-tools/add-custom-tool-form-modal/AddCustomToolFormModal.css @@ -7,4 +7,9 @@ .add-cus-tool-gap { margin-bottom: 16px; -} \ No newline at end of file +} + +.emoji-modal .ant-modal-content { + padding: 0; + width: fit-content; + } 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 01b8c462d..509559755 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 @@ -1,161 +1,181 @@ -import { Input, Modal, Space, Typography } from "antd"; +import { useState } from "react"; +import { Form, Input, Modal, Popover, Button } from "antd"; import PropTypes from "prop-types"; +import EmojiPicker from "emoji-picker-react"; -import { CustomButton } from "../../widgets/custom-button/CustomButton"; -import SpaceWrapper from "../../widgets/space-wrapper/SpaceWrapper"; +import { + handleException, + getBackendErrorDetail, +} from "../../../helpers/GetStaticData"; +import { useAlertStore } from "../../../store/alert-store"; import "./AddCustomToolFormModal.css"; -import { useEffect, useState } from "react"; - -import { handleException } from "../../../helpers/GetStaticData"; -import { useAlertStore } from "../../../store/alert-store"; +const defaultFromDetails = { + tool_name: "", + author: "", + description: "", + icon: "", +}; function AddCustomToolFormModal({ open, setOpen, editItem, - setEditItem, + isEdit, handleAddNewTool, }) { - const [title, setTitle] = useState(""); - const [toolName, setToolName] = useState(""); - const [author, setAuthor] = useState(""); - const [description, setDescription] = useState(""); - const [icon, setIcon] = useState(""); - const [isEdit, setIsEdit] = useState(false); + const [form] = Form.useForm(); + const [isLoading, setIsLoading] = useState(false); const { setAlertDetails } = useAlertStore(); + const [showEmojiPicker, setShowEmojiPicker] = useState(false); + const [formDetails, setFormDetails] = useState( + isEdit ? { ...editItem } : { ...defaultFromDetails } + ); + const [icon, setIcon] = useState(isEdit ? formDetails.icon : ""); + const [backendErrors, setBackendErrors] = useState(null); - useEffect(() => { - setIsEdit(editItem && Object.keys(editItem)?.length > 0); - }, [editItem]); - - useEffect(() => { - if (!open) { - clearForm(); - } - }, [open]); + const updateIcon = (emoji) => { + setIcon(emoji); + setFormDetails((prevState) => ({ + ...prevState, + icon: emoji, + })); + }; - useEffect(() => { - if (isEdit) { - setTitle("Edit Tool Information"); - setToolName(editItem?.tool_name || ""); - setAuthor(editItem?.author || ""); - setDescription(editItem?.description || ""); - setIcon(editItem?.icon || ""); - return; - } - setTitle("Add Tool Information"); - }, [isEdit]); + const handleInputChange = (changedValues, allValues) => { + setFormDetails({ ...formDetails, ...allValues }); + const changedFieldName = Object.keys(changedValues)[0]; + form.setFields([ + { + name: changedFieldName, + errors: [], + }, + ]); + setBackendErrors((prevErrors) => { + if (prevErrors) { + const updatedErrors = prevErrors.errors.filter( + (error) => error.attr !== changedFieldName + ); + return { ...prevErrors, errors: updatedErrors }; + } + return null; + }); + }; const handleSubmit = (event) => { - event.preventDefault(); - if (!toolName.trim() || !author.trim() || !description.trim()) { - setAlertDetails({ - type: "error", - content: "Please add valid values in input fields", - }); - return; - } - - const body = { - tool_name: toolName, - author: author, - description: description, - icon: icon, - }; + const body = formDetails; + setIsLoading(true); handleAddNewTool(body) .then((success) => { setAlertDetails({ type: "success", content: `${isEdit ? "Updated" : "Added"} Successfully`, }); - clearForm(); + setOpen(false); + clearFormDetails(); }) .catch((err) => { - const msg = `Failed to ${isEdit ? "update" : "add"}`; - setAlertDetails(handleException(err, msg)); + handleException(err, "", setBackendErrors); + }) + .finally(() => { + setIsLoading(false); }); }; - const clearForm = () => { - setToolName(""); - setAuthor(""); - setDescription(""); - setIcon(""); - setEditItem({}); - setIsEdit(false); + const clearFormDetails = () => { + setFormDetails({ ...defaultFromDetails }); }; return ( setOpen(false)} - footer={null} + onCancel={() => { + setOpen(false); + setShowEmojiPicker(false); + }} centered maskClosable={false} + onOk={handleSubmit} + okText={isEdit ? "Update" : "Save"} + okButtonProps={{ + loading: isLoading, + }} + destroyOnClose > -
-
-
- - {title} - -
-
- - Tool Name - setToolName(e.target.value)} - required - /> - -
- - Author/Org Name - setAuthor(e.target.value)} - required - /> - -
- - Description - setDescription(e.target.value)} - required - /> - -
- - Icon - setIcon(e.target.value)} /> - - Enter the name of the icon from{" "} - - Google Fonts - - - -
-
- - setOpen(false)}>Cancel - - Save - - -
- +
+ + + + + + + + + + + + + + { + updateIcon(emoji.emoji); + setShowEmojiPicker(false); + }} + /> + } + > + + + +
); } @@ -164,7 +184,7 @@ AddCustomToolFormModal.propTypes = { open: PropTypes.bool.isRequired, setOpen: PropTypes.func.isRequired, editItem: PropTypes.object.isRequired, - setEditItem: PropTypes.func.isRequired, + isEdit: PropTypes.bool.isRequired, handleAddNewTool: PropTypes.func.isRequired, }; 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 84f3fb1c5..b41cac96f 100644 --- a/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx +++ b/frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx @@ -14,7 +14,6 @@ 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 { handleException } from "../../../helpers/GetStaticData"; import "./ListOfTools.css"; @@ -42,6 +41,7 @@ function ListOfTools() { const [listOfTools, setListOfTools] = useState([]); const [filteredListOfTools, setFilteredListOfTools] = useState([]); const [search, setSearch] = useState(""); + const [isEdit, setIsEdit] = useState(false); useEffect(() => { getListOfTools(); @@ -124,6 +124,7 @@ function ListOfTools() { if (!editToolData) { return; } + setIsEdit(true); setEditItem(editToolData); setOpenAddTool(true); }; @@ -175,6 +176,12 @@ function ListOfTools() { [] ); + const showAddTool = () => { + setEditItem(null); + setIsEdit(false); + setOpenAddTool(true); + }; + return ( <>
@@ -199,7 +206,7 @@ function ListOfTools() { } - onClick={() => setOpenAddTool(true)} + onClick={showAddTool} > New Tool @@ -220,13 +227,15 @@ function ListOfTools() {
- + {openAddTool && ( + + )} ); } diff --git a/frontend/src/components/deployments/create-api-deployment-modal/CreateApiDeploymentModal.jsx b/frontend/src/components/deployments/create-api-deployment-modal/CreateApiDeploymentModal.jsx index 9214923c6..ef3433c53 100644 --- a/frontend/src/components/deployments/create-api-deployment-modal/CreateApiDeploymentModal.jsx +++ b/frontend/src/components/deployments/create-api-deployment-modal/CreateApiDeploymentModal.jsx @@ -116,7 +116,7 @@ const CreateApiDeploymentModal = ({ }); }) .catch((err) => { - setAlertDetails(handleException(err)); + handleException(err, "", setBackendErrors); }) .finally(() => { setIsLoading(false); @@ -139,7 +139,7 @@ const CreateApiDeploymentModal = ({ }); }) .catch((err) => { - setAlertDetails(handleException(err)); + handleException(err, "", setBackendErrors); }) .finally(() => { setIsLoading(false); @@ -183,7 +183,6 @@ const CreateApiDeploymentModal = ({ { const generatePythonCode = () => { let code = `import requests - api_url = '{{url}}' - headers = { - 'Authorization': 'Bearer REPLACE_WITH_API_KEY' - } {{#if isPost}} + api_url = '{{url}}' + headers = { + 'Authorization': 'Bearer REPLACE_WITH_API_KEY' + } payload = {'timeout': '80'} filepath = '/path/to/file' - files=[('file',('file',open(filepath,'rb'),'application/octet-stream'))] + files=[('files',('file',open(filepath,'rb'),'application/octet-stream'))] response = requests.request("POST", api_url, headers=headers, data=payload, files=files) {{else}} + api_url = '{{url}}?execution_id=REPLACE_WITH_EXECUTION_ID' + headers = { + 'Authorization': 'Bearer REPLACE_WITH_API_KEY' + } response = requests.request("GET", api_url, headers=headers, data=payload) {{/if}} print('Response:', response.json()) @@ -63,9 +67,14 @@ const DisplayCode = ({ isDialogOpen, setDialogOpen, url }) => { }; const generateCurlCode = () => { - let code = `curl --location '{{url}}' + let code = `{{#if isPost}} + curl --request POST --location '{{url}}' + --header 'Authorization: Bearer REPLACE_WITH_API_KEY' + --form 'files=@"{{pathToFile}}"' --form 'timeout="80"' + {{else}} + curl --location '{{url}}?execution_id=REPLACE_WITH_EXECUTION_ID' --header 'Authorization: Bearer REPLACE_WITH_API_KEY' - {{#if isPost}}--form 'files=@"{{pathToFile}}"' --form 'timeout="80"'{{/if}} + {{/if}} `; code = trimIndent(code); const template = Handlebars.compile(code); @@ -85,10 +94,11 @@ const DisplayCode = ({ isDialogOpen, setDialogOpen, url }) => { formdata.append("files", fileInput.files[0], "file"); formdata.append("timeout", "80"); var requestOptions = { method: 'POST', body: formdata, redirect: 'follow', headers: myHeaders }; + fetch("{{url}}", requestOptions) {{else}} var requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders}; + fetch("{{url}}?execution_id=REPLACE_WITH_EXECUTION_ID", requestOptions) {{/if}} - fetch("{{url}}", requestOptions) .then(response => response.text()) .then(result => console.log(result)) .catch(error => console.log('error', error)); diff --git a/frontend/src/helpers/GetStaticData.js b/frontend/src/helpers/GetStaticData.js index 72c4b0353..6b2701d85 100644 --- a/frontend/src/helpers/GetStaticData.js +++ b/frontend/src/helpers/GetStaticData.js @@ -244,13 +244,17 @@ const getTimeForLogs = () => { return formattedDate; }; -const handleException = (err, errMessage) => { +const handleException = (err, errMessage, setBackendErrors = undefined) => { if (err?.response?.data?.type === "validation_error") { // Handle validation errors - return { - type: "error", - content: errMessage || "Something went wrong", - }; + if (setBackendErrors) { + setBackendErrors(err?.response?.data); + } else { + return { + type: "error", + content: errMessage || "Something went wrong", + }; + } } if (["client_error", "server_error"].includes(err?.response?.data?.type)) {