From 5883ac96a697832ee0a1e5bd057abaf58d5d8fa6 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Fri, 29 Aug 2025 16:17:00 +0530 Subject: [PATCH 001/157] Canvas UI POC --- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 8 +++++ .../src/AppBuilder/AppCanvas/Container.jsx | 4 --- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 31 +++++++++++++++++++ frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 4 +++ .../src/AppBuilder/AppCanvas/appCanvas.scss | 12 ++++++- .../_stores/slices/rightSideBarSlice.js | 4 ++- 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 40a8ac16b9..efeee074b3 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -26,6 +26,7 @@ import PagesSidebarNavigation from '../RightSideBar/PageSettingsTab/PageMenu/Pag import { DragGhostWidget, ResizeGhostWidget } from './GhostWidgets'; import AppCanvasBanner from '../../AppBuilder/Header/AppCanvasBanner'; import { debounce } from 'lodash'; +import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => { const { moduleId, isModuleMode, appType } = useModuleContext(); @@ -52,6 +53,7 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => { const editorMarginLeft = useSidebarMargin(canvasContainerRef); const getPageId = useStore((state) => state.getCurrentPageId, shallow); const isRightSidebarOpen = useStore((state) => state.isRightSidebarOpen, shallow); + const draggingComponentId = useStore((state) => state.draggingComponentId, shallow); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); @@ -115,6 +117,12 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => { }; }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned, moduleId, isRightSidebarOpen]); + useEffect(() => { + if (draggingComponentId) { + useStore.getState().setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.COMPONENTS); + } + }, [draggingComponentId]); + const canvasContainerStyles = useMemo(() => { const canvasBgColor = currentMode === 'view' diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index c3dafc657d..9996e250bc 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -72,10 +72,6 @@ const Container = React.memo( const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - const { handleDrop } = useCanvasDropHandler({ - appType, - }); - const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', hover: (item, monitor) => { diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index eee4a31f76..4439ef9e5e 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -32,6 +32,7 @@ import { useGroupedTargetsScrollHandler } from './hooks/useGroupedTargetsScrollH import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { useElementGuidelines } from './hooks/useElementGuidelines'; +import { RIGHT_SIDE_BAR_TAB } from '../../RightSideBar/rightSidebarConstants'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { @@ -86,6 +87,7 @@ export default function Grid({ gridWidth, currentLayout }) { const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); const [isVerticalExpansionRestricted, setIsVerticalExpansionRestricted] = useState(false); const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; + const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const isWidgetResizable = useMemo(() => { if (virtualTarget) { @@ -848,10 +850,12 @@ export default function Grid({ gridWidth, currentLayout }) { if (e.target.id === 'moveable-virtual-ghost-element') { return true; } + // This is to prevent parent component from being dragged and the stop the propagation of the event if (getHoveredComponentForGrid() !== e.target.id) { return false; } + newDragParentId.current = boxList.find((box) => box.id === e.target.id)?.parent; e?.moveable?.controlBox?.removeAttribute('data-off-screen'); @@ -942,6 +946,10 @@ export default function Grid({ gridWidth, currentLayout }) { } try { if (isDraggingRef.current) { + setTimeout(() => { + useStore.getState().setRightSidebarOpen(true); + }, 100); + useStore.getState().setDraggingComponentId(null); isDraggingRef.current = false; } @@ -1007,6 +1015,25 @@ export default function Grid({ gridWidth, currentLayout }) { hideGridLines(); clearActiveTargetClassNamesAfterSnapping(selectedComponents); toggleCanvasUpdater(); + // Move this to common function + const canvas = document.querySelector('.canvas-container'); + const sidebar = document.querySelector('.editor-sidebar'); + const droppedElem = document.getElementById(e.target.id); + + if (!canvas || !sidebar || !droppedElem) return; + + const droppedRect = droppedElem.getBoundingClientRect(); + const sidebarRect = sidebar.getBoundingClientRect(); + + const isOverlapping = droppedRect.right > sidebarRect.left && droppedRect.left < sidebarRect.right; + + if (isOverlapping) { + const overlap = droppedRect.right - sidebarRect.left; + canvas.scrollTo({ + left: canvas.scrollLeft + overlap + 15, + behavior: 'smooth', + }); + } }} onDrag={(e) => { if (e.target.id === 'moveable-virtual-ghost-element') { @@ -1189,6 +1216,10 @@ export default function Grid({ gridWidth, currentLayout }) { } }} snapGridAll={true} + onClick={(e) => { + useStore.getState().setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + useStore.getState().setRightSidebarOpen(true); + }} /> ); diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index 23235fb32a..b380c65f95 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -10,6 +10,7 @@ import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { const { moduleId } = useModuleContext(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); + const setRightSidebarOpen = useStore((state) => state.setRightSidebarOpen); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); const getComponentDefinition = useStore((state) => state.getComponentDefinition); @@ -119,6 +120,9 @@ export const EditorSelecto = () => { } } } + + // setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.COMPONENTS); + // setRightSidebarOpen(true); return false; }, [setSelectedComponents, setActiveRightSideBarTab, getSelectedComponents] diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvas.scss b/frontend/src/AppBuilder/AppCanvas/appCanvas.scss index fea79195c5..0c9e9c6add 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvas.scss +++ b/frontend/src/AppBuilder/AppCanvas/appCanvas.scss @@ -71,4 +71,14 @@ *:focus-visible { outline: none; // Rewriting it to replace outline coming from browser styles - } \ No newline at end of file + } + +// .canvas-container.is-child-being-dragged::before{ +// content: ''; +// position: absolute; +// top: 0; +// left: 0; +// width: 150%; +// height: 100%; +// overflow: hidden; +// } \ No newline at end of file diff --git a/frontend/src/AppBuilder/_stores/slices/rightSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/rightSideBarSlice.js index 5831041dd8..b2977e66a8 100644 --- a/frontend/src/AppBuilder/_stores/slices/rightSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/rightSideBarSlice.js @@ -8,7 +8,9 @@ const initialState = { export const createRightSideBarSlice = (set, get) => ({ ...initialState, - setActiveRightSideBarTab: (tab) => set(() => ({ activeRightSideBarTab: tab }), false, 'setActiveRightSideBarTab'), + setActiveRightSideBarTab: (tab) => { + set(() => ({ activeRightSideBarTab: tab }), false, 'setActiveRightSideBarTab'); + }, toggleRightSidebar: () => set((state) => ({ isRightSidebarOpen: !state.isRightSidebarOpen }), false, 'toggleRightSidebar'), setRightSidebarOpen: (open) => set(() => ({ isRightSidebarOpen: open }), false, 'setRightSidebarOpen'), From 3178b3491ba350d3390dc7797dc51bfe794037bd Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 8 Sep 2025 14:47:56 +0530 Subject: [PATCH 002/157] keep right sidebar closed after dragging --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 6c9e9427f1..53895e90a9 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -949,9 +949,9 @@ export default function Grid({ gridWidth, currentLayout }) { } try { if (isDraggingRef.current) { - setTimeout(() => { - useStore.getState().setRightSidebarOpen(true); - }, 100); + // setTimeout(() => { + // useStore.getState().setRightSidebarOpen(true); + // }, 100); useStore.getState().setDraggingComponentId(null); isDraggingRef.current = false; From bb46e8600473c48445bf1870a23495e271eaad08 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Fri, 3 Oct 2025 10:51:18 +0530 Subject: [PATCH 003/157] updated multi line code editor for test parameters --- .../src/AppBuilder/CodeEditor/CodeHinter.jsx | 2 +- .../CodeEditor/MultiLineCodeEditor.jsx | 33 +++++++++++++------ .../src/AppBuilder/CodeEditor/styles.scss | 16 +++++---- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/src/AppBuilder/CodeEditor/CodeHinter.jsx b/frontend/src/AppBuilder/CodeEditor/CodeHinter.jsx index be37006f61..bedd48ecd0 100644 --- a/frontend/src/AppBuilder/CodeEditor/CodeHinter.jsx +++ b/frontend/src/AppBuilder/CodeEditor/CodeHinter.jsx @@ -98,7 +98,7 @@ const Portal = ({ children, ...restProps }) => { const PopupIcon = ({ callback, icon, tip, position, isMultiEditor = false, isQueryManager = false }) => { const size = 16; const topRef = isNumber(position?.height) ? Math.floor(position?.height) - 30 : 32; - let top = isMultiEditor ? 270 : topRef > 32 ? topRef : 0; + let top = topRef > 32 ? topRef : 0; // for query manager we allow the height of query manager to be dynamic, so we need to render the popup icon at the bottom of code editor const renderAtBottom = isQueryManager && (isMultiEditor || topRef > 32); diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index 6c99aeb05d..fe6d576fb6 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -36,8 +36,8 @@ const langSupport = Object.freeze({ const MultiLineCodeEditor = (props) => { const { - darkMode, - height, + darkMode = 'false', + height = '300', //default height set to 300px initialValue, lang, className, @@ -57,7 +57,6 @@ const MultiLineCodeEditor = (props) => { onInputChange, // Added this prop to immediately handle value changes } = props; const editorRef = useRef(null); - const replaceIdsWithName = useStore((state) => state.replaceIdsWithName, shallow); const wrapperRef = useRef(null); const getSuggestions = useStore((state) => state.getSuggestions, shallow); @@ -133,7 +132,16 @@ const MultiLineCodeEditor = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }; - const heightInPx = typeof height === 'string' && height?.includes('px') ? height : `${height}px`; + // Convert height to proper format + const getHeightValue = () => { + if (!height) return '300px'; // Default height + if (typeof height === 'string') { + return height.includes('px') ? height : `${height}px`; + } + return `${height}px`; + }; + + const heightValue = getHeightValue(); const theme = darkMode ? okaidia : githubLight; const langExtention = langSupport[lang] ?? null; @@ -255,6 +263,7 @@ const MultiLineCodeEditor = (props) => { tip="Pop out code editor into a new window" isMultiEditor={true} isQueryManager={isInsideQueryPane} + position={{ height: height }} /> { componentName={componentName} key={componentName} forceUpdate={forceUpdate} - optionalProps={{ styles: { height: 300 }, cls: '' }} + optionalProps={{ styles: { height: height ? height : '300' }, cls: '' }} darkMode={darkMode} selectors={{ className: 'preview-block-portal' }} dragResizePortal={true} callgpt={null} > -
+
{ basicSetup={setupConfig} style={{ overflowY: 'auto', + height: heightValue, }} className={`codehinter-multi-line-input ${isInsideQueryPane ? 'code-editor-query-panel' : ''}`} indentWithTab={false} readOnly={readOnly} - editable={editable} //for transformations in query manager + editable={editable} onCreateEditor={(view) => { setEditorView(view); if (setCodeEditorView) { diff --git a/frontend/src/AppBuilder/CodeEditor/styles.scss b/frontend/src/AppBuilder/CodeEditor/styles.scss index 7c0fa0c0fa..02021bef9c 100644 --- a/frontend/src/AppBuilder/CodeEditor/styles.scss +++ b/frontend/src/AppBuilder/CodeEditor/styles.scss @@ -299,8 +299,8 @@ height: 100%; .cm-editor { - min-height: 300px; - height: 300px; + // min-height: 300px; + // height: 300px; max-height: fit-content !important; .cm-gutters { @@ -362,11 +362,6 @@ } } - - - - - .cm-scroller { overflow-y: auto !important; overflow-x: hidden !important; @@ -437,9 +432,16 @@ } .editor-container { + .codehinter-container{ + height: 100% !important; + } + .codehinter-multi-line-input { + height: 100% !important; + } .codehinter-input .cm-editor { max-height: auto !important; justify-content: center; + height: 100% !important ; } } From a564a845976b8f981f280b3de2d2275e16f2a597 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Fri, 3 Oct 2025 15:28:16 +0530 Subject: [PATCH 004/157] resolved conflicts --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index e80a07c169..bd5bded7a7 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit e80a07c169dd03c8aaed56ce4e7bac2013963482 +Subproject commit bd5bded7a722713b0bef583b5315809b2eed1431 From fa18d1b815ae38628e85bcc070e5dee48c1ac544 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Fri, 3 Oct 2025 15:36:54 +0530 Subject: [PATCH 005/157] updated submodule --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index e80a07c169..2d736c14bd 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit e80a07c169dd03c8aaed56ce4e7bac2013963482 +Subproject commit 2d736c14bd2286991ed18101639ccc21f6ceb15d From ed1658300b9a25e8adf883d371963086831ba3c3 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Fri, 3 Oct 2025 15:56:38 +0530 Subject: [PATCH 006/157] updated spec --- .../cypress/constants/texts/workflows.js | 2 +- .../cypress/fixtures/exportedApp.json | 384 ++++++++++++++++++ 2 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 cypress-tests/cypress/fixtures/exportedApp.json diff --git a/cypress-tests/cypress/constants/texts/workflows.js b/cypress-tests/cypress/constants/texts/workflows.js index 7db7de9a7e..6c9cd864c5 100644 --- a/cypress-tests/cypress/constants/texts/workflows.js +++ b/cypress-tests/cypress/constants/texts/workflows.js @@ -47,7 +47,7 @@ AND table_type = 'BASE TABLE';`, runjsCodeForWebhooks: 'return "Verifying webhooks response"', runjsExpectedValueForWebhooks: "Verifying webhooks response", - expectedStatusCodeText: 201, + expectedStatusCodeText: 200, exportFixturePath: "cypress/fixtures/exportedApp.json", workflowLabel: "Workflow", }; diff --git a/cypress-tests/cypress/fixtures/exportedApp.json b/cypress-tests/cypress/fixtures/exportedApp.json new file mode 100644 index 0000000000..a424c6c401 --- /dev/null +++ b/cypress-tests/cypress/fixtures/exportedApp.json @@ -0,0 +1,384 @@ +{ + "app": [ + { + "definition": { + "appV2": { + "id": "bf731e7e-09d8-4d8f-a058-0dad247c938e", + "type": "workflow", + "name": "huel", + "slug": "bf731e7e-09d8-4d8f-a058-0dad247c938e", + "isPublic": null, + "isMaintenanceOn": true, + "icon": "server", + "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", + "currentVersionId": null, + "userId": "2b52a870-fc7e-4d2d-a4fd-7cd105568e80", + "workflowApiToken": "7b029a3b-14a9-414e-95d7-74f9aa5410db", + "workflowEnabled": true, + "isInitialisedFromPrompt": false, + "appGeneratedFromPrompt": false, + "appBuilderMode": "visual", + "aiGenerationMetadata": null, + "createdAt": "2025-10-03T10:22:52.118Z", + "creationMode": "DEFAULT", + "updatedAt": "2025-10-03T10:23:09.418Z", + "__loaded": true, + "editingVersion": { + "id": "c7acf317-23ea-47ee-9c14-ae96b69400dd", + "name": "v1", + "definition": { + "nodes": [ + { + "id": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", + "data": { + "nodeType": "start", + "label": "Start trigger" + }, + "position": { + "x": 100, + "y": 250 + }, + "type": "input", + "sourcePosition": "right", + "deletable": false, + "width": 206, + "height": 41, + "selected": false + }, + { + "id": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "type": "query", + "sourcePosition": "right", + "targetPosition": "left", + "draggable": true, + "data": { + "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", + "kind": "runjs", + "options": {} + }, + "position": { + "x": 552.0092353820801, + "y": 252.00923538208008 + }, + "deletable": false, + "width": 206, + "height": 40, + "selected": false + }, + { + "id": "63003194-3056-4107-aa5c-e5745a026b8d", + "data": { + "nodeType": "response", + "label": "Response", + "code": "return runjs1.data", + "statusCode": { + "fxActive": false, + "value": "200" + }, + "nodeName": "response1" + }, + "position": { + "x": 752.0092353820801, + "y": 352.0092353820801 + }, + "type": "output", + "sourcePosition": "right", + "targetPosition": "left", + "deletable": false, + "width": 206, + "height": 40, + "selected": true + } + ], + "edges": [ + { + "id": "7992c4c0-cd40-4e23-b854-cc7c88a5ebd9", + "source": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", + "target": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "sourceHandle": null, + "type": "custom" + }, + { + "id": "18175dab-8e00-4d6e-a4d8-5c506b4d6192", + "source": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "target": "63003194-3056-4107-aa5c-e5745a026b8d", + "sourceHandle": "success", + "type": "custom" + } + ], + "queries": [ + { + "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", + "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c" + } + ], + "webhookParams": [], + "defaultParams": "{\"key\":\"your value\"}", + "dependencies": { + "javascript": { + "dependencies": {} + } + }, + "setupScript": { + "javascript": "// lodash\n// const _ = require('lodash');\n" + } + }, + "globalSettings": { + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "var(--cc-appBackground-surface)", + "backgroundFxQuery": "", + "appMode": "light" + }, + "pageSettings": null, + "showViewerNavigation": true, + "homePageId": "06ea85f4-80be-4d9e-aefc-862548bade41", + "appId": "bf731e7e-09d8-4d8f-a058-0dad247c938e", + "currentEnvironmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", + "promotedFrom": null, + "createdAt": "2025-10-03T10:22:52.302Z", + "updatedAt": "2025-10-03T10:23:06.872Z" + }, + "components": [], + "pages": [ + { + "id": "06ea85f4-80be-4d9e-aefc-862548bade41", + "name": "Home", + "handle": "home", + "index": 1, + "disabled": null, + "hidden": null, + "icon": null, + "createdAt": "2025-10-03T10:22:52.117Z", + "updatedAt": "2025-10-03T10:22:53.408Z", + "autoComputeLayout": true, + "appVersionId": "c7acf317-23ea-47ee-9c14-ae96b69400dd", + "pageGroupIndex": 1, + "pageGroupId": null, + "isPageGroup": false, + "url": null, + "openIn": "new_tab", + "type": "default", + "appId": "" + } + ], + "events": [], + "dataQueries": [ + { + "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c", + "name": "runjs1", + "options": { + "code": "return \"Verifying webhooks response\"", + "parameters": [] + }, + "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", + "appVersionId": "c7acf317-23ea-47ee-9c14-ae96b69400dd", + "createdAt": "2025-10-03T10:22:57.626Z", + "updatedAt": "2025-10-03T10:23:00.640Z" + } + ], + "dataSources": [ + { + "id": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", + "name": "runjsdefault", + "kind": "runjs", + "type": "static", + "pluginId": null, + "appVersionId": null, + "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", + "scope": "global", + "createdAt": "2025-10-03T10:02:19.045Z", + "updatedAt": "2025-10-03T10:02:19.045Z" + } + ], + "appVersions": [ + { + "id": "c7acf317-23ea-47ee-9c14-ae96b69400dd", + "name": "v1", + "definition": { + "nodes": [ + { + "id": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", + "data": { + "nodeType": "start", + "label": "Start trigger" + }, + "position": { + "x": 100, + "y": 250 + }, + "type": "input", + "sourcePosition": "right", + "deletable": false, + "width": 206, + "height": 41, + "selected": false + }, + { + "id": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "type": "query", + "sourcePosition": "right", + "targetPosition": "left", + "draggable": true, + "data": { + "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", + "kind": "runjs", + "options": {} + }, + "position": { + "x": 552.0092353820801, + "y": 252.00923538208008 + }, + "deletable": false, + "width": 206, + "height": 40, + "selected": false + }, + { + "id": "63003194-3056-4107-aa5c-e5745a026b8d", + "data": { + "nodeType": "response", + "label": "Response", + "code": "return runjs1.data", + "statusCode": { + "fxActive": false, + "value": "200" + }, + "nodeName": "response1" + }, + "position": { + "x": 752.0092353820801, + "y": 352.0092353820801 + }, + "type": "output", + "sourcePosition": "right", + "targetPosition": "left", + "deletable": false, + "width": 206, + "height": 40, + "selected": true + } + ], + "edges": [ + { + "id": "7992c4c0-cd40-4e23-b854-cc7c88a5ebd9", + "source": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", + "target": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "sourceHandle": null, + "type": "custom" + }, + { + "id": "18175dab-8e00-4d6e-a4d8-5c506b4d6192", + "source": "5b0155cf-29a5-49e4-873b-de97f61b5968", + "target": "63003194-3056-4107-aa5c-e5745a026b8d", + "sourceHandle": "success", + "type": "custom" + } + ], + "queries": [ + { + "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", + "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c" + } + ], + "webhookParams": [], + "defaultParams": "{\"key\":\"your value\"}", + "dependencies": { + "javascript": { + "dependencies": {} + } + }, + "setupScript": { + "javascript": "// lodash\n// const _ = require('lodash');\n" + } + }, + "globalSettings": { + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "var(--cc-appBackground-surface)", + "backgroundFxQuery": "", + "appMode": "light" + }, + "pageSettings": null, + "showViewerNavigation": true, + "homePageId": "06ea85f4-80be-4d9e-aefc-862548bade41", + "appId": "bf731e7e-09d8-4d8f-a058-0dad247c938e", + "currentEnvironmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", + "promotedFrom": null, + "createdAt": "2025-10-03T10:22:52.302Z", + "updatedAt": "2025-10-03T10:23:06.872Z" + } + ], + "appEnvironments": [ + { + "id": "b21edc90-cd2e-4a3a-9200-d6874615da50", + "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", + "name": "development", + "isDefault": false, + "priority": 1, + "enabled": true, + "createdAt": "2025-10-03T10:02:18.821Z", + "updatedAt": "2025-10-03T10:02:18.821Z" + }, + { + "id": "ef6fc5e8-a95f-412c-8fe3-4e6be00cc6d9", + "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", + "name": "staging", + "isDefault": false, + "priority": 2, + "enabled": true, + "createdAt": "2025-10-03T10:02:18.825Z", + "updatedAt": "2025-10-03T10:02:18.825Z" + }, + { + "id": "0162baf0-a89d-4ebc-9bb2-1666c3d5ccf5", + "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", + "name": "production", + "isDefault": true, + "priority": 3, + "enabled": true, + "createdAt": "2025-10-03T10:02:18.825Z", + "updatedAt": "2025-10-03T10:02:18.825Z" + } + ], + "dataSourceOptions": [ + { + "id": "d8f96951-0e45-4b29-bff0-6cb95a5a82f7", + "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", + "environmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", + "options": null, + "createdAt": "2025-10-03T10:02:19.046Z", + "updatedAt": "2025-10-03T10:02:19.046Z" + }, + { + "id": "e2d43cb2-2bb5-4776-9be7-eca3b68ee716", + "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", + "environmentId": "ef6fc5e8-a95f-412c-8fe3-4e6be00cc6d9", + "options": null, + "createdAt": "2025-10-03T10:02:19.046Z", + "updatedAt": "2025-10-03T10:02:19.046Z" + }, + { + "id": "f560a2d9-cf9d-44c4-bfa0-24e23246e0ac", + "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", + "environmentId": "0162baf0-a89d-4ebc-9bb2-1666c3d5ccf5", + "options": null, + "createdAt": "2025-10-03T10:02:19.046Z", + "updatedAt": "2025-10-03T10:02:19.046Z" + } + ], + "schemaDetails": { + "multiPages": true, + "multiEnv": true, + "globalDataSources": true + } + } + } + } + ], + "tooljet_version": "3.20.13-ee-lts" +} \ No newline at end of file From 0f078b358e7b2961bc779aaf6739a14cd57ee19f Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Sat, 4 Oct 2025 18:34:40 +0530 Subject: [PATCH 007/157] Updated submodule ee-frontend --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 2d736c14bd..fbb348561c 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 2d736c14bd2286991ed18101639ccc21f6ceb15d +Subproject commit fbb348561c7e98e12a215c0de957f2bfc2a73707 From 0694e26f7cb0245a65a7b63237dfa60e3cec35bc Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 9 Oct 2025 14:33:14 +0530 Subject: [PATCH 008/157] fix: adjust height calculation for group widget resizing and update color swatch close event --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 6 +----- .../components/BaseColorSwatches/BaseColorSwatches.jsx | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 00a4da2be6..0011d7ee11 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -775,7 +775,7 @@ export default function Grid({ gridWidth, currentLayout }) { onResizeGroup={({ events }) => { const parentElm = events[0].target.closest('.real-canvas'); const parentWidth = parentElm?.clientWidth; - const parentHeight = parentElm?.clientHeight; + const parentHeight = parentElm?.scrollHeight; handleActivateTargets(parentElm?.id?.replace('canvas-', '')); const { posRight, posLeft, posTop, posBottom } = getPositionForGroupDrag(events, parentWidth, parentHeight); events.forEach((ev) => { @@ -783,7 +783,6 @@ export default function Grid({ gridWidth, currentLayout }) { ev.target.style.height = `${ev.height}px`; ev.target.style.transform = ev.drag.transform; }); - if (!(posLeft < 0 || posTop < 0 || posRight < 0 || posBottom < 0)) { groupResizeDataRef.current = events; } @@ -1088,9 +1087,6 @@ export default function Grid({ gridWidth, currentLayout }) { onDragGroup={(ev) => { const { events } = ev; const parentElm = events[0]?.target?.closest('.real-canvas'); - if (parentElm && !parentElm.classList.contains('show-grid')) { - parentElm?.classList?.add('show-grid'); - } events.forEach((ev) => { const currentWidget = boxList.find(({ id }) => id === ev.target.id); diff --git a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx index 9cea97d7a7..5b9faaa663 100644 --- a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx +++ b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx @@ -182,6 +182,7 @@ const BaseColorSwatches = ({ fallbackPlacements={['top', 'left']} rootClose={true} overlay={eventPopover()} + rootCloseEvent="mousedown" // close picker when mousedown anywhere on screen > {ColorPickerInputBox()} From 2eab0769d744a4134639e835996906f70e8ffe39 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 9 Oct 2025 15:23:31 +0530 Subject: [PATCH 009/157] fix: adjust width calculation for pasted components based on target parent container --- frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index be9d24e8f1..2a06bfae00 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -612,6 +612,12 @@ export function pasteComponents(targetParentId, copiedComponentObj) { // Adjust width if parent changed let width = component.layouts[currentLayout].width; + if (targetParentId !== component.component?.parent) { + const containerWidth = useGridStore.getState().subContainerWidths[targetParentId || 'canvas']; + const oldContainerWidth = useGridStore.getState().subContainerWidths[component?.component?.parent || 'canvas']; + width = Math.round((width * oldContainerWidth) / containerWidth); + } + component.layouts[currentLayout] = { ...component.layouts[currentLayout], width, From 6b57917c78f12bea17ee65fd055d5c5eaece9254 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 9 Oct 2025 15:24:01 +0530 Subject: [PATCH 010/157] update submodule --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index e80a07c169..b4f71af4ae 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit e80a07c169dd03c8aaed56ce4e7bac2013963482 +Subproject commit b4f71af4aefcb7eeb2ca4723542b3a8c140373ff diff --git a/server/ee b/server/ee index 60918c7686..c06e4bd1aa 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 60918c76867e15d2e91d40e6aaade9a343cbdedf +Subproject commit c06e4bd1aa7e405ce5927a96c9e008a1f319ce6a From 7b25936cdc4dd0d5dba8b0d22b1e7721b589d835 Mon Sep 17 00:00:00 2001 From: Nishidh Jain Date: Fri, 10 Oct 2025 15:50:59 +0530 Subject: [PATCH 011/157] feat: Show a delimiter input for CSV file parsing type to handle delimiter other then comma and also add parsing support for .tsv (tab separated value) file --- .../Inspector/Components/FilePicker.jsx | 13 +++++- .../WidgetManager/widgets/filepicker.js | 10 +++++ .../FilePicker/helpers/fileProcessing.js | 45 ++++++++++++++++--- .../Widgets/FilePicker/hooks/useFilePicker.js | 16 +++++-- .../apps/services/widget-config/filepicker.js | 10 +++++ 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/FilePicker.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/FilePicker.jsx index bebebfe88b..fe36c4a0e2 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/FilePicker.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/FilePicker.jsx @@ -82,7 +82,9 @@ const FxSelect = ({ /** Remove minFileCount and maxFileCount validations if multiple file selection is disabled */ const getValidations = (componentMeta, component) => { const validations = Object.keys(componentMeta.validation || {}); - const enableMultipleValue = resolveReferences(component.component.definition.properties.enableMultiple?.value ?? false); + const enableMultipleValue = resolveReferences( + component.component.definition.properties.enableMultiple?.value ?? false + ); const enableMultipleFxActive = component.component.definition.properties.enableMultiple?.fxActive; if (!enableMultipleValue && !enableMultipleFxActive) { @@ -110,10 +112,17 @@ const getPropertiesBySection = (propertiesMeta) => { const getConditionalAccordionItems = (component, renderCustomElement) => { const parseContent = resolveReferences(component.component.definition.properties.parseContent?.value ?? false); + const parseFileType = resolveReferences( + component.component.definition.properties.parseFileType?.value ?? 'auto-detect' + ); + const options = ['parseContent']; let renderOptions = options.map((option) => renderCustomElement(option)); - const conditionalOptions = [{ name: 'parseFileType', condition: parseContent }]; + const conditionalOptions = [ + { name: 'parseFileType', condition: parseContent }, + { name: 'delimiter', condition: parseContent && parseFileType === 'csv' }, + ]; conditionalOptions.forEach(({ name, condition }) => { if (condition) renderOptions.push(renderCustomElement(name)); }); diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/filepicker.js b/frontend/src/AppBuilder/WidgetManager/widgets/filepicker.js index 93d0c4a4c8..afb58a04d0 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/filepicker.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/filepicker.js @@ -102,6 +102,7 @@ export const filepickerConfig = { options: [ { name: 'Autodetect from extension', value: 'auto-detect' }, { name: 'CSV', value: 'csv' }, + { name: 'TSV', value: 'tsv' }, { name: 'Microsoft Excel - xls', value: 'vnd.ms-excel' }, { name: 'Microsoft Excel - xlsx', @@ -115,6 +116,14 @@ export const filepickerConfig = { defaultValue: 'auto-detect', }, }, + delimiter: { + type: 'code', + displayName: 'Delimiter', + validation: { + schema: { type: 'string' }, + defaultValue: ',', + }, + }, loadingState: { type: 'toggle', displayName: 'Show loading state', @@ -310,6 +319,7 @@ export const filepickerConfig = { enableMultiple: { value: '{{false}}', fxActive: false }, parseContent: { value: '{{false}}' }, parseFileType: { value: 'auto-detect' }, + delimiter: { value: ',' }, loadingState: { value: '{{false}}' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, diff --git a/frontend/src/AppBuilder/Widgets/FilePicker/helpers/fileProcessing.js b/frontend/src/AppBuilder/Widgets/FilePicker/helpers/fileProcessing.js index 6a5e169851..274ea262b3 100644 --- a/frontend/src/AppBuilder/Widgets/FilePicker/helpers/fileProcessing.js +++ b/frontend/src/AppBuilder/Widgets/FilePicker/helpers/fileProcessing.js @@ -6,22 +6,35 @@ import JSON5 from 'json5'; // Import JSON5 for more lenient parsing // (Consider moving these to a shared constants file if used elsewhere) export const PARSE_FILE_TYPES = { CSV: 'text/csv', + TSV: 'text/tab-separated-values', + TXT: 'text/plain', XLS: 'application/vnd.ms-excel', XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', JSON: 'application/json', // Added JSON MIME type }; +const lineParser = (line, delimiter) => { + let lineToFormat = line; + + // if delimiter is tab & if line contains '\\t' instead '\t' then just replace it with '\t' + if (delimiter === '\t' && line.includes('\\t')) { + lineToFormat = line.replaceAll('\\t', '\t'); + } + + return lineToFormat.split(delimiter).map((h) => h.trim()); +}; + // Helper functions for processing file content export const processCSV = (str, delimiter = ',') => { try { const lines = str.split(/\r?\n/); const [headerLine, ...rows] = lines; if (!headerLine) return []; - const headers = headerLine.split(delimiter).map((h) => h.trim()); + const headers = lineParser(headerLine, delimiter); return rows .filter((r) => r.trim().length > 0) .map((row) => { - const cols = row.split(delimiter); + const cols = lineParser(row, delimiter); const obj = {}; headers.forEach((h, i) => { obj[h] = cols[i] ?? ''; @@ -66,10 +79,18 @@ export const processJson = (str) => { } }; -export const processFileContent = (fileType, fileContent) => { +export const processFileContent = (fileType, fileContent, options = {}) => { + const { fileParsingDelimiter = ',', fileTypeFromExtension = 'auto-detect' } = options; + switch (fileType) { case PARSE_FILE_TYPES.CSV: - return processCSV(fileContent.readFileAsText); + case PARSE_FILE_TYPES.TXT: + return processCSV( + fileContent.readFileAsText, + fileTypeFromExtension === 'auto-detect' ? ',' : fileParsingDelimiter + ); + case PARSE_FILE_TYPES.TSV: + return processCSV(fileContent.readFileAsText, '\t'); case PARSE_FILE_TYPES.XLS: case PARSE_FILE_TYPES.XLSX: return processXls(fileContent.readFileAsDataURL); // Assuming this contains base64 @@ -87,10 +108,18 @@ const DEPRECATED_processCSV = (str, delimiter = ',') => processCSV(str, delimite const DEPRECATED_processXls = (_str) => ({ Sheet1: [] }); -export const DEPRECATED_processFileContent = (fileType, fileContent) => { +export const DEPRECATED_processFileContent = (fileType, fileContent, options = {}) => { + const { fileParsingDelimiter = ',', fileTypeFromExtension = 'auto-detect' } = options; + switch (fileType) { case 'text/csv': - return DEPRECATED_processCSV(fileContent.readFileAsText); + case PARSE_FILE_TYPES.TXT: + return DEPRECATED_processCSV( + fileContent.readFileAsText, + fileTypeFromExtension === 'auto-detect' ? ',' : fileParsingDelimiter + ); + case PARSE_FILE_TYPES.TSV: + return DEPRECATED_processCSV(fileContent.readFileAsText, '\t'); case 'application/vnd.ms-excel': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': return DEPRECATED_processXls(fileContent.readFileAsDataURL); @@ -113,6 +142,8 @@ export const parseFileContentEnabled = (file, autoDetect = false, parseFileType) // Map friendly name (like 'csv') to mime type if necessary // Assumes parseFileType is like 'CSV', 'XLS', etc. const targetMimeType = PARSE_FILE_TYPES[parseFileType?.toUpperCase()]; - return targetMimeType ? file.type === targetMimeType : false; + return targetMimeType + ? file.type === targetMimeType || (parseFileType === 'csv' && file.type === PARSE_FILE_TYPES.TXT) + : false; } }; diff --git a/frontend/src/AppBuilder/Widgets/FilePicker/hooks/useFilePicker.js b/frontend/src/AppBuilder/Widgets/FilePicker/hooks/useFilePicker.js index 73e77a9018..301efeaa24 100644 --- a/frontend/src/AppBuilder/Widgets/FilePicker/hooks/useFilePicker.js +++ b/frontend/src/AppBuilder/Widgets/FilePicker/hooks/useFilePicker.js @@ -27,6 +27,8 @@ export const useFilePicker = ({ const enableMultiple = properties?.enableMultiple ?? false; const parseContent = properties.parseContent ?? false; const fileTypeFromExtension = properties.parseFileType ?? 'auto-detect'; + const fileParsingDelimiter = properties.delimiter ?? ','; + const labelText = properties.label ?? ''; const initialLoading = properties.loadingState ?? false; @@ -114,8 +116,14 @@ export const useFilePicker = ({ readFileAsText: readFileAsText, readFileAsDataURL: base64Data, }; - parsedValue = processFileContent(file.type, contentForParsing); - parsedData = DEPRECATED_processFileContent(file.type, contentForParsing); + parsedValue = processFileContent(file.type, contentForParsing, { + fileParsingDelimiter, + fileTypeFromExtension, + }); + parsedData = DEPRECATED_processFileContent(file.type, contentForParsing, { + fileParsingDelimiter, + fileTypeFromExtension, + }); } return { @@ -129,7 +137,7 @@ export const useFilePicker = ({ base64Data: base64Data, parsedValue: parsedValue, parsedData: parsedData, - filePath: file.path + filePath: file.path, }; } catch (error) { console.error(`Error reading file ${file.name}:`, error); @@ -142,7 +150,7 @@ export const useFilePicker = ({ throw error; // Re-throw for Promise.allSettled } }, - [getFileData, parseContent, fileTypeFromExtension] + [getFileData, parseContent, fileTypeFromExtension, fileParsingDelimiter] ); // --- Dropzone Setup --- diff --git a/server/src/modules/apps/services/widget-config/filepicker.js b/server/src/modules/apps/services/widget-config/filepicker.js index 1977710c29..275bf334a1 100644 --- a/server/src/modules/apps/services/widget-config/filepicker.js +++ b/server/src/modules/apps/services/widget-config/filepicker.js @@ -103,6 +103,7 @@ export const filepickerConfig = { options: [ { name: 'Autodetect from extension', value: 'auto-detect' }, { name: 'CSV', value: 'csv' }, + { name: 'TSV', value: 'tsv' }, { name: 'Microsoft Excel - xls', value: 'vnd.ms-excel' }, { name: 'Microsoft Excel - xlsx', @@ -116,6 +117,14 @@ export const filepickerConfig = { defaultValue: 'auto-detect', }, }, + delimiter: { + type: 'code', + displayName: 'Delimiter', + validation: { + schema: { type: 'string' }, + defaultValue: ',', + }, + }, loadingState: { type: 'toggle', displayName: 'Show loading state', @@ -311,6 +320,7 @@ export const filepickerConfig = { enableMultiple: { value: '{{false}}', fxActive: false }, parseContent: { value: '{{false}}' }, parseFileType: { value: 'auto-detect' }, + delimiter: { value: ',' }, loadingState: { value: '{{false}}' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, From 9eb80da4e8f348f50be0d5c61467b7c163a285ab Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Fri, 10 Oct 2025 16:59:51 +0530 Subject: [PATCH 012/157] feat: enhance RightSidebarToggle to conditionally disable properties tab and update tooltip based on component selection --- .../src/AppBuilder/RightSideBar/RightSidebarToggle.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx index e4be2abb27..038d42fed0 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx @@ -18,6 +18,7 @@ const RightSidebarToggle = ({ darkMode = false }) => { const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const activeRightSideBarTab = useStore((state) => state.activeRightSideBarTab); const isRightSidebarPinned = useStore((state) => state.isRightSidebarPinned); + const isAnyComponentSelected = useStore((state) => state.selectedComponents.length > 0); const handleToggle = (item) => { setActiveRightSideBarTab(item); if (item === activeRightSideBarTab && !isRightSidebarPinned) { @@ -63,13 +64,15 @@ const RightSidebarToggle = ({ darkMode = false }) => { icon="propertiesstyles" iconOnly iconWidth="14" - tip="Component properties" + tip={isAnyComponentSelected ? 'Component properties' : 'No component selected'} + disabled={!isAnyComponentSelected} > From 592060d69414169cc0a27cac08b0cae29b09ec0f Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Fri, 10 Oct 2025 18:26:36 +0530 Subject: [PATCH 013/157] fix --- frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx index 038d42fed0..9588d96c23 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx @@ -71,9 +71,9 @@ const RightSidebarToggle = ({ darkMode = false }) => { width="16" height="16" className={`${ - (activeRightSideBarTab === RIGHT_SIDE_BAR_TAB.CONFIGURATION ? 'tw-text-icon-accent' : 'tw-text-icon-strong', - !isAnyComponentSelected && 'tw-text-text-disabled') + activeRightSideBarTab === RIGHT_SIDE_BAR_TAB.CONFIGURATION ? 'tw-text-icon-accent' : 'tw-text-icon-strong' }`} + style={{ color: !isAnyComponentSelected ? 'var(--text-disabled)' : undefined }} /> {appType !== 'module' && ( From 968d95bb517ad526dbe78c36875c771681039451 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Mon, 13 Oct 2025 10:09:32 +0530 Subject: [PATCH 014/157] fixed real events --- cypress-tests/cypress/support/e2e.js | 1 + package-lock.json | 1602 ++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 1605 insertions(+), 1 deletion(-) diff --git a/cypress-tests/cypress/support/e2e.js b/cypress-tests/cypress/support/e2e.js index c6eeb537c4..c09c1c9ed5 100644 --- a/cypress-tests/cypress/support/e2e.js +++ b/cypress-tests/cypress/support/e2e.js @@ -14,6 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: +import "cypress-real-events/support"; import "../commands/commands"; import "../commands/apiCommands"; import '../commands/workflowCommands'; diff --git a/package-lock.json b/package-lock.json index 7338c9d91f..91f9c98820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@tooljet/cli": "^0.0.13", + "cypress-real-events": "^1.15.0", "eslint": "^9.28.0", "eslint-plugin-jest": "^28.13.3", "eslint-plugin-prettier": "^5.4.1", @@ -37,6 +38,60 @@ "node": ">=12" } }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -716,6 +771,22 @@ "undici-types": "~7.8.0" } }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/through": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", @@ -726,6 +797,26 @@ "@types/node": "*" } }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.34.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", @@ -984,6 +1075,32 @@ "node": ">=0.4.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aggregate-error/node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1058,6 +1175,28 @@ "dev": true, "license": "MIT" }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1085,6 +1224,28 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1102,6 +1263,14 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -1112,6 +1281,25 @@ "node": ">= 4.0.0" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1139,6 +1327,17 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1151,6 +1350,22 @@ "readable-stream": "^3.4.0" } }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1197,6 +1412,61 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1231,6 +1501,14 @@ "cdl": "bin/cdl.js" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1293,6 +1571,23 @@ "dev": true, "license": "MIT" }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/clean-stack": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", @@ -1348,6 +1643,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -1464,6 +1776,32 @@ "dev": true, "license": "MIT" }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", @@ -1474,6 +1812,17 @@ "node": ">=20" } }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1501,6 +1850,14 @@ "node": ">= 0.6" } }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1522,6 +1879,302 @@ "node": ">= 8" } }, + "node_modules/cypress": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.4.0.tgz", + "integrity": "sha512-+GC/Y/LXAcaMCzfuM7vRx5okRmonceZbr0ORUAoOrZt/5n2eGK8yh04bok1bWSjZ32wRHrZESqkswQ6biArN5w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cypress/request": "^3.0.9", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "@types/tmp": "^0.2.3", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "ci-info": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-table3": "0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "hasha": "5.2.2", + "is-installed-globally": "~0.4.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.7.1", + "supports-color": "^8.1.1", + "systeminformation": "5.27.7", + "tmp": "~0.2.4", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^20.1.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/cypress-real-events": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.15.0.tgz", + "integrity": "sha512-in98xxTnnM9Z7lZBvvVozm99PBT2eEOjXRG5LKWyYvQnj9mGWXMiPNpfw7e7WiraBFh7XlXIxnE9Cu5o+52kQQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x || ^14.x || ^15.x" + } + }, + "node_modules/cypress/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/cypress/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/cypress/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/cypress/node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cypress/node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -1571,6 +2224,17 @@ "node": ">=8.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1604,6 +2268,34 @@ "no-case": "^2.2.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -1627,6 +2319,17 @@ "dev": true, "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -1664,6 +2367,59 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1964,6 +2720,14 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -1995,6 +2759,39 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -2010,6 +2807,56 @@ "node": ">=4" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "peer": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2072,6 +2919,17 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -2180,6 +3038,35 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "license": "ISC" }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/front-matter": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", @@ -2213,6 +3100,17 @@ "dev": true, "license": "ISC" }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-east-asian-width": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", @@ -2226,6 +3124,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2236,6 +3160,21 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2249,6 +3188,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2307,6 +3257,23 @@ "node": "*" } }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -2350,6 +3317,20 @@ "node": ">= 4" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2372,6 +3353,80 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/header-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", @@ -2401,6 +3456,22 @@ "node": ">=8.0.0" } }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2629,6 +3700,17 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -2731,6 +3813,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -2760,6 +3860,17 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -2783,6 +3894,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2825,6 +3944,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -2882,6 +4009,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2895,6 +4030,14 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)", + "peer": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2907,6 +4050,14 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2920,6 +4071,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3154,6 +4322,14 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3411,6 +4587,17 @@ "dev": true, "license": "ISC" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3440,6 +4627,31 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -3478,6 +4690,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3543,6 +4766,20 @@ "node": ">=8" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-treeify": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", @@ -3630,6 +4867,14 @@ "node": ">=0.10.0" } }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3660,6 +4905,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -3766,6 +5028,22 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3840,6 +5118,51 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3849,6 +5172,23 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3894,6 +5234,17 @@ "esprima": "~4.0.0" } }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4066,6 +5417,86 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4130,6 +5561,33 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4290,6 +5748,45 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/systeminformation": { + "version": "5.27.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", + "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "peer": true, + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4308,6 +5805,28 @@ "upper-case": "^1.0.3" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -4333,6 +5852,31 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4409,6 +5953,14 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense", + "peer": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4465,6 +6017,17 @@ "node": ">= 10.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", @@ -4498,6 +6061,17 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -4505,6 +6079,22 @@ "dev": true, "license": "MIT" }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -4622,6 +6212,18 @@ "node": ">=4.0.0" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 18920d5f2a..2b3308968d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@tooljet/cli": "^0.0.13", + "cypress-real-events": "^1.15.0", "eslint": "^9.28.0", "eslint-plugin-jest": "^28.13.3", "eslint-plugin-prettier": "^5.4.1", @@ -68,4 +69,4 @@ "@typescript-eslint/eslint-plugin": "^8.34.0", "eslint-config-prettier": "^10.1.5" } -} \ No newline at end of file +} From 08b57766bcb61f8d2333f45ff4da6836a20548c9 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 13 Oct 2025 12:00:11 +0530 Subject: [PATCH 015/157] Update disabled state to opacity 0.5 --- frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx index 9588d96c23..49333ad500 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSidebarToggle.jsx @@ -73,7 +73,7 @@ const RightSidebarToggle = ({ darkMode = false }) => { className={`${ activeRightSideBarTab === RIGHT_SIDE_BAR_TAB.CONFIGURATION ? 'tw-text-icon-accent' : 'tw-text-icon-strong' }`} - style={{ color: !isAnyComponentSelected ? 'var(--text-disabled)' : undefined }} + style={{ opacity: !isAnyComponentSelected ? '0.5' : undefined }} /> {appType !== 'module' && ( From e4664f3199ac196c1beb749246857570dd3c897c Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 13 Oct 2025 12:00:47 +0530 Subject: [PATCH 016/157] update submodule --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index b4f71af4ae..ac825461cd 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit b4f71af4aefcb7eeb2ca4723542b3a8c140373ff +Subproject commit ac825461cda3b2ca75aacd882e62d66c7044bd70 diff --git a/server/ee b/server/ee index c06e4bd1aa..592ee28bfa 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit c06e4bd1aa7e405ce5927a96c9e008a1f319ce6a +Subproject commit 592ee28bfac6e610c6273c0475e5aef2681316a7 From 68810a0c514e15f67b213cb2eeef41222336f779 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 13 Oct 2025 12:15:12 +0530 Subject: [PATCH 017/157] update submodules --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index b4f71af4ae..ac825461cd 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit b4f71af4aefcb7eeb2ca4723542b3a8c140373ff +Subproject commit ac825461cda3b2ca75aacd882e62d66c7044bd70 diff --git a/server/ee b/server/ee index c06e4bd1aa..592ee28bfa 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit c06e4bd1aa7e405ce5927a96c9e008a1f319ce6a +Subproject commit 592ee28bfac6e610c6273c0475e5aef2681316a7 From aa3ff9537ff96ddcb655cab10d7bed38695e1398 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Mon, 13 Oct 2025 12:40:55 +0530 Subject: [PATCH 018/157] Fix: Code editor popup UI --- frontend/src/AppBuilder/CodeEditor/styles.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/AppBuilder/CodeEditor/styles.scss b/frontend/src/AppBuilder/CodeEditor/styles.scss index 02021bef9c..31b89674fa 100644 --- a/frontend/src/AppBuilder/CodeEditor/styles.scss +++ b/frontend/src/AppBuilder/CodeEditor/styles.scss @@ -385,6 +385,13 @@ } } +.codehinter-popup{ + .cm-editor{ + border-radius: 0 0 4px 4px !important; + //box-shadow: none !important; + } +} + .rest-api-tab-content { .fields-container { .rest-api-codehinter-key-field { From 8d3e5a99d07bb10baad18b25b4b992e5fb426629 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Tue, 14 Oct 2025 03:25:42 +0530 Subject: [PATCH 019/157] fix: updated code editor UI for runjs and loop nodes --- frontend/ee | 2 +- frontend/src/AppBuilder/CodeEditor/styles.scss | 10 +++++----- .../QueryManager/QueryEditors/Runjs/Runjs.jsx | 2 +- frontend/src/Editor/CodeEditor/styles.scss | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/ee b/frontend/ee index a66e03846e..a5adf37bb2 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit a66e03846e43704dcd80d32642e1a11f5b6bde1d +Subproject commit a5adf37bb28f5a6e927b12493de0ae0bbc4e5bf1 diff --git a/frontend/src/AppBuilder/CodeEditor/styles.scss b/frontend/src/AppBuilder/CodeEditor/styles.scss index 31b89674fa..a2b65962b2 100644 --- a/frontend/src/AppBuilder/CodeEditor/styles.scss +++ b/frontend/src/AppBuilder/CodeEditor/styles.scss @@ -415,9 +415,9 @@ } } -.runjs-editor .cm-editor { - border: none !important; -} +//.runjs-editor .cm-editor { +// border: none !important; +//} .preview-alert-banner { height: fit-content; @@ -506,7 +506,7 @@ width: 42px !important; background-color: var(--interactive-default) !important; color: var(--text-disabled) !important; - border-right: 1px solid var(--borders-disabled-on-white) !important; + border-right: 1px solid var(--slate5) !important; align-self: stretch !important; height: auto !important; } @@ -516,7 +516,7 @@ .cm-editor { border-bottom-right-radius: 4px !important; border-bottom-left-radius: 4px !important; - border-color: var(--borders-disabled-on-white); + border-color: var(--slate5); border-top-right-radius: 0px !important; border-top-left-radius: 0px !important; justify-content: center; diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx index dc5630a167..dccf1549db 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx @@ -13,7 +13,7 @@ const Runjs = (props) => { }, [props.options]); return ( - + Date: Tue, 14 Oct 2025 09:11:30 +0530 Subject: [PATCH 020/157] feat: enhance Label component to conditionally set htmlFor based on viewer mode --- frontend/src/_ui/Label.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/_ui/Label.jsx b/frontend/src/_ui/Label.jsx index e4a8c7d3f4..8c9a15583e 100644 --- a/frontend/src/_ui/Label.jsx +++ b/frontend/src/_ui/Label.jsx @@ -1,4 +1,7 @@ import React from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { shallow } from 'zustand/shallow'; function Label({ label, width, @@ -14,6 +17,8 @@ function Label({ inputId, id, }) { + const { moduleId } = useModuleContext(); + const isViewerMode = useStore((state) => state.modeStore.modules[moduleId].currentMode === 'view', shallow); return ( <> {label && (width > 0 || auto) && ( @@ -28,7 +33,7 @@ function Label({ fontSize: '12px', height: defaultAlignment === 'top' && '20px', }} - htmlFor={inputId} + htmlFor={isViewerMode ? inputId : undefined} // To avoid focus on label in edit mode which prevents copy/paste id={id} >

Date: Tue, 14 Oct 2025 09:54:00 +0530 Subject: [PATCH 021/157] fix --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 0011d7ee11..a2980d2891 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -465,7 +465,8 @@ export default function Grid({ gridWidth, currentLayout }) { const handleDragGroupEnd = (e) => { try { hideGridLines(); - // setIsGroupDragging(false); + handleDeactivateTargets(); + clearActiveTargetClassNamesAfterSnapping(selectedComponents); const { events, clientX, clientY } = e; const initialParent = events[0].target.closest('.real-canvas'); // Get potential new parent using same logic as onDragEnd @@ -1108,8 +1109,6 @@ export default function Grid({ gridWidth, currentLayout }) { }} onDragGroupEnd={(e) => { handleDragGroupEnd(e); - handleDeactivateTargets(); - clearActiveTargetClassNamesAfterSnapping(selectedComponents); toggleCanvasUpdater(); }} onClickGroup={(e) => { From 470f5ae479e95c47416c5143edd0ad64137deb65 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Tue, 14 Oct 2025 12:31:24 +0530 Subject: [PATCH 022/157] fix: code editor for loop nodes --- .../src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx | 4 ++-- frontend/src/Editor/QueryManager/QueryEditors/Runjs/Runjs.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx index dccf1549db..419b8c40a5 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Runjs/Runjs.jsx @@ -13,7 +13,7 @@ const Runjs = (props) => { }, [props.options]); return ( - +

{ cyLabel={`runjs`} delayOnChange={false} /> - +
); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/Runjs/Runjs.jsx b/frontend/src/Editor/QueryManager/QueryEditors/Runjs/Runjs.jsx index 1a38a7cbad..3599287bd7 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/Runjs/Runjs.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/Runjs/Runjs.jsx @@ -13,7 +13,7 @@ const Runjs = (props) => { }, [props.options]); return ( - +
{ cyLabel={`runjs`} delayOnChange={false} /> - +
); }; From 0c0e3d4b4f84731fd41bee2beff2f65c9be672b4 Mon Sep 17 00:00:00 2001 From: medhansh-alt Date: Tue, 14 Oct 2025 12:50:40 +0530 Subject: [PATCH 023/157] merge lts-3.16 --- .version | 2 +- frontend/.version | 2 +- frontend/ee | 2 +- frontend/src/App/App.jsx | 13 ------- frontend/src/HomePage/HomePage.jsx | 1 + frontend/src/_styles/license.scss | 3 +- frontend/src/_styles/theme.scss | 1 + plugins/packages/elasticsearch/lib/index.ts | 22 ++++++++---- plugins/packages/minio/lib/operations.ts | 12 ++++++- server/.version | 2 +- server/src/modules/auth/service.ts | 36 ++++++++++--------- .../modules/licensing/configs/LicenseBase.ts | 2 +- .../modules/organization-users/controller.ts | 2 +- .../src/modules/organization-users/service.ts | 32 ++++++++++++++++- 14 files changed, 86 insertions(+), 46 deletions(-) diff --git a/.version b/.version index 268c5b0414..99caa545a4 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.20.19-lts +3.20.20-lts diff --git a/frontend/.version b/frontend/.version index 268c5b0414..99caa545a4 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.20.19-lts +3.20.20-lts diff --git a/frontend/ee b/frontend/ee index a5adf37bb2..37cb1ce8cc 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit a5adf37bb28f5a6e927b12493de0ae0bbc4e5bf1 +Subproject commit 37cb1ce8cc203e6b0f3ddb6813413600d0d89383 diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 2900e7b6d6..b721aa4f34 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -132,7 +132,6 @@ class AppComponent extends React.Component { } setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1); this.updateMargin(); // Set initial margin - this.updateColorScheme(); let counter = 0; let interval; @@ -169,15 +168,11 @@ class AppComponent extends React.Component { // Update margin when showBanner changes this.updateMargin(); // Update color scheme if darkMode changed - if (prevProps.darkMode !== this.props.darkMode) { - this.updateColorScheme(); - } } switchDarkMode = (newMode) => { this.props.updateIsTJDarkMode(newMode); localStorage.setItem('darkMode', newMode); - this.updateColorScheme(newMode); }; isEditorOrViewerFromPath = () => { @@ -193,14 +188,6 @@ class AppComponent extends React.Component { closeBasicPlanMigrationBanner = () => { this.setState({ showBanner: false }); }; - updateColorScheme = (darkModeValue) => { - const isDark = darkModeValue !== undefined ? darkModeValue : this.props.darkMode; - if (isDark) { - document.documentElement.style.setProperty('color-scheme', 'dark'); - } else { - document.documentElement.style.removeProperty('color-scheme'); - } - }; render() { const { updateAvailable, isEditorOrViewer, showBanner } = this.state; const { darkMode } = this.props; diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 2b985becca..5be3db9c9b 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -1475,6 +1475,7 @@ class HomePageComponent extends React.Component { handleConfirm={this.importGitApp} confirmBtnProps={{ title: 'Import app', + tooltipMessage: '', isLoading: importingApp, disabled: importingApp || !selectedAppRepo || importingGitAppOperations?.message, }} diff --git a/frontend/src/_styles/license.scss b/frontend/src/_styles/license.scss index 66993e8f4e..6b5572b793 100644 --- a/frontend/src/_styles/license.scss +++ b/frontend/src/_styles/license.scss @@ -1162,6 +1162,7 @@ gap: 68px; align-self: stretch; padding: 40px; + padding-left: 20px; .inner-container{ display: flex; @@ -1186,7 +1187,7 @@ font-size: 12px; box-shadow: 0 0 1px 0 var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0 1px 1px 0 var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); } - .top-up-button-container.disabled { + .top-up-button-container.disabledBtn { border-color: var(--disabled-border, #D3D3D3); /* Light gray fallback */ cursor: not-allowed; } diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index a101ce5671..6b7b545401 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -2231,6 +2231,7 @@ tr:focus { body { color: #3e525b; + overflow: hidden; } .RichEditor-root { diff --git a/plugins/packages/elasticsearch/lib/index.ts b/plugins/packages/elasticsearch/lib/index.ts index d7f9226d74..0cf4fac5e9 100644 --- a/plugins/packages/elasticsearch/lib/index.ts +++ b/plugins/packages/elasticsearch/lib/index.ts @@ -79,12 +79,17 @@ export default class ElasticsearchService implements QueryService { } async testConnection(sourceOptions: SourceOptions): Promise { - const client = await this.getConnection(sourceOptions); - await client.info(); - - return { - status: 'ok', - }; + try { + const client = await this.getConnection(sourceOptions); + await client.info(); + return { + status: 'ok', + message: 'Connection successful', + }; + } catch (err: any) { + const errorMessage = err || 'Unknown error'; + throw new QueryError(errorMessage, err, {}); + } } determineProtocol(sourceOptions: SourceOptions) { @@ -116,7 +121,10 @@ export default class ElasticsearchService implements QueryService { url = `${protocol}://${host}:${port}`; } - const options: ClientOptions = { node: url }; + const options: ClientOptions = { + node: url, + requestTimeout: 10000, + }; if (sslEnabled) { if (sslCertificate === 'ca_certificate') { diff --git a/plugins/packages/minio/lib/operations.ts b/plugins/packages/minio/lib/operations.ts index 6a5325d1e4..de013d9c1d 100644 --- a/plugins/packages/minio/lib/operations.ts +++ b/plugins/packages/minio/lib/operations.ts @@ -45,14 +45,24 @@ export async function getObject(minioClient: MinioClient, queryOptions: object): } export async function uploadObject(minioClient: MinioClient, queryOptions: object): Promise { + let data = queryOptions['data']; + if(isBase64(data)){ + data = Buffer.from(data, 'base64'); + } + return await minioClient.putObject( queryOptions['bucket'], queryOptions['objectName'], - queryOptions['data'], + data, queryOptions['contentType'] && { contentType: queryOptions['contentType'] } ); } +const isBase64 = (str: string) => { + const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/; + return str.length % 4 === 0 && base64Regex.test(str); +} + export async function signedUrlForPut(minioClient: MinioClient, queryOptions: object): Promise { const defaultExpiry = +queryOptions['expiresIn'] || 86400; const url = await minioClient.presignedPutObject(queryOptions['bucket'], queryOptions['objectName'], defaultExpiry); diff --git a/server/.version b/server/.version index 268c5b0414..99caa545a4 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.20.19-lts +3.20.20-lts diff --git a/server/src/modules/auth/service.ts b/server/src/modules/auth/service.ts index cd7d7912c6..cfbee23c58 100644 --- a/server/src/modules/auth/service.ts +++ b/server/src/modules/auth/service.ts @@ -1,6 +1,5 @@ import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; import { User } from '@entities/user.entity'; -import { decamelizeKeys } from 'humps'; import { Organization } from 'src/entities/organization.entity'; import { SSOConfigs } from 'src/entities/sso_config.entity'; import { EntityManager } from 'typeorm'; @@ -22,6 +21,7 @@ import { IAuthService } from './interfaces/IService'; import { SetupOrganizationsUtilService } from '@modules/setup-organization/util.service'; import { RequestContext } from '@modules/request-context/service'; import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; +import { decamelizeKeysExcept } from 'src/helpers/utils.helper'; @Injectable() export class AuthService implements IAuthService { @@ -144,7 +144,6 @@ export class AuthService implements IAuthService { }); } - //TODO:this function is not used now async authorizeOrganization(user: User) { return await dbTransactionWrap(async (manager: EntityManager) => { if (user.defaultOrganizationId !== user.organizationId) @@ -154,22 +153,25 @@ export class AuthService implements IAuthService { const permissionData = await this.sessionUtilService.getPermissionDataToAuthorize(user, manager); - return decamelizeKeys({ - currentOrganizationId: user.organizationId, - currentOrganizationSlug: organization.slug, - currentOrganizationName: organization.name, - currentUser: { - id: user.id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - avatarId: user.avatarId, - ssoUserInfo: permissionData.ssoUserInfo, - metadata: permissionData.metadata, - createdAt: user.createdAt, + return decamelizeKeysExcept( + { + currentOrganizationId: user.organizationId, + currentOrganizationSlug: organization.slug, + currentOrganizationName: organization.name, + currentUser: { + id: user.id, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + avatarId: user.avatarId, + ssoUserInfo: permissionData.ssoUserInfo, + metadata: permissionData.metadata, + createdAt: user.createdAt, + }, + ...permissionData, }, - ...permissionData, - }); + ['metadata'] + ); }); } diff --git a/server/src/modules/licensing/configs/LicenseBase.ts b/server/src/modules/licensing/configs/LicenseBase.ts index 3415039298..65357d3330 100644 --- a/server/src/modules/licensing/configs/LicenseBase.ts +++ b/server/src/modules/licensing/configs/LicenseBase.ts @@ -121,7 +121,7 @@ export default class LicenseBase { this._isComments = this.getFeatureValue('comments'); this._isGitSync = this.getFeatureValue('gitSync'); this._isAi = this.getFeatureValue('ai'); - this._isExternalApis = this.getFeatureValue('externalApis'); + this._isExternalApis = this.getFeatureValue('externalApi'); } private getFeatureValue(key: string) { diff --git a/server/src/modules/organization-users/controller.ts b/server/src/modules/organization-users/controller.ts index 43d6c78af9..1de98ce9e3 100644 --- a/server/src/modules/organization-users/controller.ts +++ b/server/src/modules/organization-users/controller.ts @@ -120,6 +120,6 @@ export class OrganizationUsersController implements IOrganizationUsersController @Get() async getUsers(@User() user, @Query() query) { const response = await this.organizationUsersService.getUsers(user, query); - return decamelizeKeys(response); + return response; } } diff --git a/server/src/modules/organization-users/service.ts b/server/src/modules/organization-users/service.ts index 04c6931b12..a49fdc273c 100644 --- a/server/src/modules/organization-users/service.ts +++ b/server/src/modules/organization-users/service.ts @@ -27,6 +27,7 @@ import { UpdateOrgUserDto } from './dto'; import { RequestContext } from '@modules/request-context/service'; import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; import { Organization } from '@entities/organization.entity'; +import { decamelizeKeys } from 'humps'; @Injectable() export class OrganizationUsersService implements IOrganizationUsersService { constructor( @@ -446,6 +447,35 @@ export class OrganizationUsersService implements IOrganizationUsersService { current_page: parseInt(page || 1), }; - return { meta, users }; + return this.decamelizeUsersResponse(meta, users); + } + + async decamelizeUsersResponse( + meta: { + total_pages: number; + total_count: number; + current_page: number; + }, + users: any[] + ): Promise<{ + meta: { + total_pages: number; + total_count: number; + current_page: number; + }; + users: any[]; + }> { + const decamelizedUsers = users.map((user) => { + const { userMetadata, ...restUser } = user; + return { + ...decamelizeKeys(restUser), + user_metadata: userMetadata, // keep nested metadata untouched + }; + }); + + return { + meta, + users: decamelizedUsers, + }; } } From c43e5f75fc73d0e05a825405c4ae4d4ddb4d0e7c Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 14 Oct 2025 15:00:42 +0530 Subject: [PATCH 024/157] update submodules --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index ac825461cd..3e9d349fc9 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit ac825461cda3b2ca75aacd882e62d66c7044bd70 +Subproject commit 3e9d349fc94d9ab33884fc4a517b04013a7ddfa2 diff --git a/server/ee b/server/ee index 592ee28bfa..d47298f779 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 592ee28bfac6e610c6273c0475e5aef2681316a7 +Subproject commit d47298f779d6d848f9be9bc6560a478009b3c3d1 From 724e62340dda29947a04da21c04ebec4b34d8833 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Tue, 14 Oct 2025 18:09:55 +0530 Subject: [PATCH 025/157] updated spec with banner fixes --- cypress-tests/cypress/commands/apiCommands.js | 43 +++++++++++++++++++ .../cypress/constants/texts/workflows.js | 4 +- .../workflows/eeTestcases/workflowInApp.cy.js | 15 ++++--- .../eeTestcases/workflowWIthDatasource.cy.js | 21 ++++++--- .../workflowWithExportImport.cy.js | 11 +++-- .../eeTestcases/workflowWithWebhooks.cy.js | 4 +- .../eeTestcases/workflowfeatures.cy.js | 14 +++--- 7 files changed, 89 insertions(+), 23 deletions(-) diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 873e6e1d13..2f2348f62e 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -159,6 +159,49 @@ Cypress.Commands.add("apiDeleteApp", (appId = Cypress.env("appId")) => { }); }); +Cypress.Commands.add("apiDeleteWorkflow", (workflowName) => { + cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { + Cypress.env("authToken", `tj_auth_token=${cookie.value}`); + + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: Cypress.env("authToken"), + }, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: Cypress.env("authToken"), + }, + }, { log: false }).then((deleteResponse) => { + expect(deleteResponse.status).to.equal(200); + Cypress.log({ + name: "Workflow Delete", + displayName: "WORKFLOW DELETED", + message: `: ${workflowName}`, + }); + }); + } else { + Cypress.log({ + name: "Workflow Not Found", + displayName: "WORKFLOW NOT FOUND", + message: `: ${workflowName}`, + }); + } + }); + }); +}); + Cypress.Commands.add( "openApp", ( diff --git a/cypress-tests/cypress/constants/texts/workflows.js b/cypress-tests/cypress/constants/texts/workflows.js index 6c9cd864c5..17f8dd2b88 100644 --- a/cypress-tests/cypress/constants/texts/workflows.js +++ b/cypress-tests/cypress/constants/texts/workflows.js @@ -33,8 +33,8 @@ FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';`, postgresResponseNodeQuery: "return postgresql1.data", - postgresExpectedValue: "employees", - + postgresExpectedValue: "server_side_pagination", + restApiUrl: "http://9.234.17.31:8000/delay/10s", restApiResponseNodeQuery: "return restapi1.data", restApiExpectedValue: "", diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js index ca014ffb69..9de59541f8 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js @@ -2,15 +2,15 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteDatasource } from "Support/utils/dataSource"; +import { deleteDatasource} from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { workflowsText } from "Texts/workflows"; import { workflowSelector } from "Selectors/workflows"; - import { enterJsonInputInStartNode, - deleteAppandWorkflowAfterExecution, + verifyPreviewOutputText, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard, } from "Support/utils/workFlows"; const data = {}; @@ -61,8 +61,9 @@ describe("Workflows in apps", () => { // commonSelectors.toastMessage, // `Query (${data.dsName}) completed.` // ); - - deleteAppandWorkflowAfterExecution(data.wfName, data.appName); + cy.backToApps(); + cy.apiDeleteApp(); + cy.apiDeleteWorkflow(data.wfName); }); it("Creating workflows with postgres and validating execution in apps", () => { @@ -137,7 +138,9 @@ describe("Workflows in apps", () => { // commonSelectors.toastMessage, // `Query (${data.dsName}) completed.` // ); - deleteAppandWorkflowAfterExecution(data.wfName, data.appName); + cy.backToApps(); + cy.apiDeleteApp(); + cy.apiDeleteWorkflow(data.wfName); deleteDatasource(`cypress-${data.dataSourceName}-manual-pgsql`); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js index 24fe84ebc9..c29df1e079 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js @@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteWorkflowAndDS } from "Support/utils/dataSource"; +import { deleteWorkflowAndDS, deleteDatasource } from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { harperDbText } from "Texts/harperDb"; import { workflowsText } from "Texts/workflows"; @@ -14,6 +14,7 @@ import { import { enterJsonInputInStartNode, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard, } from "Support/utils/workFlows"; const data = {}; @@ -50,7 +51,8 @@ describe("Workflows with Datasource", () => { ); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.deleteWorkflow(data.wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); }); it("Postgres workflow - execute and validate", () => { @@ -112,7 +114,10 @@ describe("Workflows with Datasource", () => { ); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); - deleteWorkflowAndDS(data.wfName, dsName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); + + deleteDatasource(dsName); }); it("REST API workflow - execute and validate", () => { @@ -174,7 +179,10 @@ describe("Workflows with Datasource", () => { ); cy.verifyTextInResponseOutput(workflowsText.restApiExpectedValue); - deleteWorkflowAndDS(data.wfName, dsName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); + + deleteDatasource(dsName); }); it("HarperDB workflow - execute and validate", () => { @@ -257,6 +265,9 @@ describe("Workflows with Datasource", () => { ); cy.verifyTextInResponseOutput(workflowsText.harperDbExpectedValue); - deleteWorkflowAndDS(data.wfName, dsName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); + + deleteDatasource(dsName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js index 7294f97c63..edf93b1394 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js @@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake"; import { commonSelectors } from "Selectors/common"; import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; -import { deleteWorkflowAndDS } from "Support/utils/dataSource"; +import { deleteWorkflowAndDS,deleteDatasource } from "Support/utils/dataSource"; import { dataSourceSelector } from "Selectors/dataSource"; import { workflowsText } from "Texts/workflows"; import { workflowSelector } from "Selectors/workflows"; @@ -11,6 +11,7 @@ import { enterJsonInputInStartNode, importWorkflowApp, verifyTextInResponseOutputLimited, + navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; const data = {}; @@ -53,8 +54,8 @@ describe("Workflows Export/Import Sanity", () => { importWorkflowApp(wfName, workflowsText.exportFixturePath); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - - cy.deleteWorkflow(wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(wfName); cy.task("deleteFile", workflowsText.exportFixturePath); }); @@ -124,8 +125,10 @@ describe("Workflows Export/Import Sanity", () => { importWorkflowApp(wfName, workflowsText.exportFixturePath); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(wfName); - deleteWorkflowAndDS(wfName, dsName); + deleteDatasource(dsName); cy.task("deleteFile", workflowsText.exportFixturePath); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js index d6fda50df5..2c47fc4e16 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js @@ -5,6 +5,7 @@ import { workflowSelector } from "Selectors/workflows"; import { enterJsonInputInStartNode, revealWorkflowToken, + navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; const data = {}; @@ -64,6 +65,7 @@ describe("Workflows with Webhooks", () => { }); }); }); - cy.deleteWorkflow(data.wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js index 4aba89e079..eb39b438f4 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js @@ -45,7 +45,8 @@ describe("Workflows features", () => { workflowsText.responseNodeQuery ); cy.verifyTextInResponseOutput(workflowsText.longStringJsonText); - cy.deleteWorkflow(data.wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); }); it("Creating workflow with Node Preview Validation and execution", () => { @@ -71,7 +72,8 @@ describe("Workflows features", () => { workflowsText.responseNodeQuery ); cy.verifyTextInResponseOutput(workflowsText.jsonValuePlaceholder); - cy.deleteWorkflow(data.wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); }); // Need to run after bug fixes @@ -122,8 +124,9 @@ describe("Workflows features", () => { workflowsText.workflowResponseNodeQuery ); // cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.deleteWorkflow(data.childWFName); - cy.deleteWorkflowfromDashboard(data.parentWFName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.childWFName); + cy.apiDeleteWorkflow(data.parentWFName); }); it("Creating workflow with large datasets and validating execution", () => { @@ -153,6 +156,7 @@ describe("Workflows features", () => { workflowsText.responseNodeExpectedValueTextForLargeDataset ); - cy.deleteWorkflow(data.wfName); + navigateBackToWorkflowsDashboard(); + cy.apiDeleteWorkflow(data.wfName); }); }); From ef38815bda626b70b90f19fc8baebd68b79b3bbe Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Wed, 15 Oct 2025 12:11:28 +0530 Subject: [PATCH 026/157] added api commands --- cypress-tests/cypress/commands/apiCommands.js | 183 ++++++++++++++---- .../cypress/commands/workflowCommands.js | 13 +- .../workflows/eeTestcases/workflowInApp.cy.js | 3 +- .../eeTestcases/workflowWIthDatasource.cy.js | 9 +- .../workflowWithExportImport.cy.js | 5 +- .../eeTestcases/workflowfeatures.cy.js | 3 +- 6 files changed, 162 insertions(+), 54 deletions(-) diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 2f2348f62e..7f26ceb31d 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -159,49 +159,6 @@ Cypress.Commands.add("apiDeleteApp", (appId = Cypress.env("appId")) => { }); }); -Cypress.Commands.add("apiDeleteWorkflow", (workflowName) => { - cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { - Cypress.env("authToken", `tj_auth_token=${cookie.value}`); - - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: Cypress.env("authToken"), - }, - }, { log: false }).then((response) => { - const workflow = response.body.apps?.find( - app => app.name === workflowName || app.slug === workflowName - ); - - if (workflow) { - cy.request({ - method: "DELETE", - url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: Cypress.env("authToken"), - }, - }, { log: false }).then((deleteResponse) => { - expect(deleteResponse.status).to.equal(200); - Cypress.log({ - name: "Workflow Delete", - displayName: "WORKFLOW DELETED", - message: `: ${workflowName}`, - }); - }); - } else { - Cypress.log({ - name: "Workflow Not Found", - displayName: "WORKFLOW NOT FOUND", - message: `: ${workflowName}`, - }); - } - }); - }); -}); - Cypress.Commands.add( "openApp", ( @@ -764,3 +721,143 @@ Cypress.Commands.add( }); } ); + +Cypress.Commands.add("apiCreateWorkflow", (workflowName) => { + + cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { + Cypress.env("authToken", `tj_auth_token=${cookie.value}`); + + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/apps`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }, + body: { + icon: "sentfast", + name: workflowName, + type: "workflow", + }, + }).then((response) => { + expect(response.status).to.equal(201); + + const workflowId = response.body?.id || response.allRequestResponses?.[0]?.["Response Body"]?.id; + const userId = response.body?.user_id || response.allRequestResponses?.[0]?.["Response Body"]?.user_id; + + Cypress.env("workflowId", workflowId); + Cypress.env("user_id", userId); + + Cypress.log({ + name: "Workflow create", + displayName: "WORKFLOW CREATED", + message: `: ${response.body.name}`, + }); + }); + }); +}); + +Cypress.Commands.add( + "openWorkflow", + ( + slug = "", + workspaceId = Cypress.env("workspaceId"), + workflowId = Cypress.env("workflowId"), + ) => { + cy.intercept("GET", "/api/apps/*").as("getWorkflowData"); + cy.window({ log: false }).then((win) => { + win.localStorage.setItem("walkthroughCompleted", "true"); + }); + cy.visit(`/${workspaceId}/apps/${workflowId}/${slug}`); + + cy.wait("@getWorkflowData").then((interception) => { + const responseData = interception.response.body; + + Cypress.env("editingVersionId", responseData.editing_version.id); + Cypress.env("environmentId", responseData.editorEnvironment.id); + Cypress.env("workflowId", responseData.id); + }); + } +); + +Cypress.Commands.add( + "openWorkflowByName", + ( + workflowName, + workspaceId = Cypress.env("workspaceId"), + componentSelector = "[data-cy='workflow-canvas']" + ) => { + cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { + Cypress.env("authToken", `tj_auth_token=${cookie.value}`); + + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: { + "Tj-Workspace-Id": workspaceId, + Cookie: Cypress.env("authToken"), + }, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + Cypress.env("workflowId", workflow.id); + cy.openWorkflow(workflow.slug, workspaceId, workflow.id, componentSelector); + + Cypress.log({ + name: "Workflow Open", + displayName: "WORKFLOW OPENED", + message: `: ${workflowName}`, + }); + } else { + throw new Error(`Workflow "${workflowName}" not found`); + } + }); + }); + } +); + +Cypress.Commands.add("apiDeleteWorkflow", (workflowName) => { + cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { + Cypress.env("authToken", `tj_auth_token=${cookie.value}`); + + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: Cypress.env("authToken"), + }, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: Cypress.env("authToken"), + }, + }, { log: false }).then((deleteResponse) => { + expect(deleteResponse.status).to.equal(200); + Cypress.log({ + name: "Workflow Delete", + displayName: "WORKFLOW DELETED", + message: `: ${workflowName}`, + }); + }); + } else { + Cypress.log({ + name: "Workflow Not Found", + displayName: "WORKFLOW NOT FOUND", + message: `: ${workflowName}`, + }); + } + }); + }); +}); \ No newline at end of file diff --git a/cypress-tests/cypress/commands/workflowCommands.js b/cypress-tests/cypress/commands/workflowCommands.js index 592bba9365..7caca0fae1 100644 --- a/cypress-tests/cypress/commands/workflowCommands.js +++ b/cypress-tests/cypress/commands/workflowCommands.js @@ -145,10 +145,14 @@ Cypress.Commands.add( (wfName, fixtureFile = "cypress/fixtures/exportedApp.json") => { navigateBackToWorkflowsDashboard(); - selectAppCardOption( - wfName, - commonSelectors.appCardOptions(workflowsText.exportWFOption) - ); + cy.get(`[data-cy="${wfName}-card"]`) + .trigger('mouseover') + .find('[data-cy="app-card-menu-icon"]') + .click({ force: true }); + + cy.get(commonSelectors.appCardOptions(workflowsText.exportWFOption)) + .click(); + cy.wait(2000); cy.exec("ls -t ./cypress/downloads/ | head -1").then((result) => { @@ -158,6 +162,7 @@ Cypress.Commands.add( cy.writeFile(fixtureFile, json); }); }); + cy.deleteWorkflowfromDashboard(wfName); } ); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js index 9de59541f8..ff2f92c70b 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js @@ -104,7 +104,8 @@ describe("Workflows in apps", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(dsName); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js index c29df1e079..e0866f0bf7 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js @@ -93,7 +93,8 @@ describe("Workflows with Datasource", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(dsName); @@ -157,7 +158,8 @@ describe("Workflows with Datasource", () => { ] ); cy.reload(); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(dsName); @@ -236,7 +238,8 @@ describe("Workflows with Datasource", () => { postgreSqlText.toastDSSaved ); - cy.createWorkflowApp(data.wfName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(dsName); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js index edf93b1394..7112fc2bdc 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js @@ -26,7 +26,7 @@ describe("Workflows Export/Import Sanity", () => { .replaceAll("[^A-Za-z]", ""); }); - it("RunJS workflow - execute, export/import, re-execute", () => { + it.only("RunJS workflow - execute, export/import, re-execute", () => { const wfName = `${data.wfName}-runjs`; cy.createWorkflowApp(wfName); @@ -99,7 +99,8 @@ describe("Workflows Export/Import Sanity", () => { cy.reload(); - cy.createWorkflowApp(wfName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(dsName); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js index eb39b438f4..62ebdb8acf 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js @@ -101,7 +101,8 @@ describe("Workflows features", () => { navigateBackToWorkflowsDashboard(); - cy.createWorkflowApp(data.parentWFName); + cy.apiCreateWorkflow(data.wfName) + cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.workflowNodeLabel); From 91b5b3979fc364d5c40da0af38b9d0b57dd4304d Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Wed, 15 Oct 2025 12:15:32 +0530 Subject: [PATCH 027/157] removed imported file --- .../cypress/fixtures/exportedApp.json | 384 ------------------ 1 file changed, 384 deletions(-) delete mode 100644 cypress-tests/cypress/fixtures/exportedApp.json diff --git a/cypress-tests/cypress/fixtures/exportedApp.json b/cypress-tests/cypress/fixtures/exportedApp.json deleted file mode 100644 index a424c6c401..0000000000 --- a/cypress-tests/cypress/fixtures/exportedApp.json +++ /dev/null @@ -1,384 +0,0 @@ -{ - "app": [ - { - "definition": { - "appV2": { - "id": "bf731e7e-09d8-4d8f-a058-0dad247c938e", - "type": "workflow", - "name": "huel", - "slug": "bf731e7e-09d8-4d8f-a058-0dad247c938e", - "isPublic": null, - "isMaintenanceOn": true, - "icon": "server", - "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", - "currentVersionId": null, - "userId": "2b52a870-fc7e-4d2d-a4fd-7cd105568e80", - "workflowApiToken": "7b029a3b-14a9-414e-95d7-74f9aa5410db", - "workflowEnabled": true, - "isInitialisedFromPrompt": false, - "appGeneratedFromPrompt": false, - "appBuilderMode": "visual", - "aiGenerationMetadata": null, - "createdAt": "2025-10-03T10:22:52.118Z", - "creationMode": "DEFAULT", - "updatedAt": "2025-10-03T10:23:09.418Z", - "__loaded": true, - "editingVersion": { - "id": "c7acf317-23ea-47ee-9c14-ae96b69400dd", - "name": "v1", - "definition": { - "nodes": [ - { - "id": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", - "data": { - "nodeType": "start", - "label": "Start trigger" - }, - "position": { - "x": 100, - "y": 250 - }, - "type": "input", - "sourcePosition": "right", - "deletable": false, - "width": 206, - "height": 41, - "selected": false - }, - { - "id": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "type": "query", - "sourcePosition": "right", - "targetPosition": "left", - "draggable": true, - "data": { - "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", - "kind": "runjs", - "options": {} - }, - "position": { - "x": 552.0092353820801, - "y": 252.00923538208008 - }, - "deletable": false, - "width": 206, - "height": 40, - "selected": false - }, - { - "id": "63003194-3056-4107-aa5c-e5745a026b8d", - "data": { - "nodeType": "response", - "label": "Response", - "code": "return runjs1.data", - "statusCode": { - "fxActive": false, - "value": "200" - }, - "nodeName": "response1" - }, - "position": { - "x": 752.0092353820801, - "y": 352.0092353820801 - }, - "type": "output", - "sourcePosition": "right", - "targetPosition": "left", - "deletable": false, - "width": 206, - "height": 40, - "selected": true - } - ], - "edges": [ - { - "id": "7992c4c0-cd40-4e23-b854-cc7c88a5ebd9", - "source": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", - "target": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "sourceHandle": null, - "type": "custom" - }, - { - "id": "18175dab-8e00-4d6e-a4d8-5c506b4d6192", - "source": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "target": "63003194-3056-4107-aa5c-e5745a026b8d", - "sourceHandle": "success", - "type": "custom" - } - ], - "queries": [ - { - "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", - "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c" - } - ], - "webhookParams": [], - "defaultParams": "{\"key\":\"your value\"}", - "dependencies": { - "javascript": { - "dependencies": {} - } - }, - "setupScript": { - "javascript": "// lodash\n// const _ = require('lodash');\n" - } - }, - "globalSettings": { - "appInMaintenance": false, - "canvasMaxWidth": 100, - "canvasMaxWidthType": "%", - "canvasMaxHeight": 2400, - "canvasBackgroundColor": "var(--cc-appBackground-surface)", - "backgroundFxQuery": "", - "appMode": "light" - }, - "pageSettings": null, - "showViewerNavigation": true, - "homePageId": "06ea85f4-80be-4d9e-aefc-862548bade41", - "appId": "bf731e7e-09d8-4d8f-a058-0dad247c938e", - "currentEnvironmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", - "promotedFrom": null, - "createdAt": "2025-10-03T10:22:52.302Z", - "updatedAt": "2025-10-03T10:23:06.872Z" - }, - "components": [], - "pages": [ - { - "id": "06ea85f4-80be-4d9e-aefc-862548bade41", - "name": "Home", - "handle": "home", - "index": 1, - "disabled": null, - "hidden": null, - "icon": null, - "createdAt": "2025-10-03T10:22:52.117Z", - "updatedAt": "2025-10-03T10:22:53.408Z", - "autoComputeLayout": true, - "appVersionId": "c7acf317-23ea-47ee-9c14-ae96b69400dd", - "pageGroupIndex": 1, - "pageGroupId": null, - "isPageGroup": false, - "url": null, - "openIn": "new_tab", - "type": "default", - "appId": "" - } - ], - "events": [], - "dataQueries": [ - { - "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c", - "name": "runjs1", - "options": { - "code": "return \"Verifying webhooks response\"", - "parameters": [] - }, - "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", - "appVersionId": "c7acf317-23ea-47ee-9c14-ae96b69400dd", - "createdAt": "2025-10-03T10:22:57.626Z", - "updatedAt": "2025-10-03T10:23:00.640Z" - } - ], - "dataSources": [ - { - "id": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", - "name": "runjsdefault", - "kind": "runjs", - "type": "static", - "pluginId": null, - "appVersionId": null, - "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", - "scope": "global", - "createdAt": "2025-10-03T10:02:19.045Z", - "updatedAt": "2025-10-03T10:02:19.045Z" - } - ], - "appVersions": [ - { - "id": "c7acf317-23ea-47ee-9c14-ae96b69400dd", - "name": "v1", - "definition": { - "nodes": [ - { - "id": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", - "data": { - "nodeType": "start", - "label": "Start trigger" - }, - "position": { - "x": 100, - "y": 250 - }, - "type": "input", - "sourcePosition": "right", - "deletable": false, - "width": 206, - "height": 41, - "selected": false - }, - { - "id": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "type": "query", - "sourcePosition": "right", - "targetPosition": "left", - "draggable": true, - "data": { - "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", - "kind": "runjs", - "options": {} - }, - "position": { - "x": 552.0092353820801, - "y": 252.00923538208008 - }, - "deletable": false, - "width": 206, - "height": 40, - "selected": false - }, - { - "id": "63003194-3056-4107-aa5c-e5745a026b8d", - "data": { - "nodeType": "response", - "label": "Response", - "code": "return runjs1.data", - "statusCode": { - "fxActive": false, - "value": "200" - }, - "nodeName": "response1" - }, - "position": { - "x": 752.0092353820801, - "y": 352.0092353820801 - }, - "type": "output", - "sourcePosition": "right", - "targetPosition": "left", - "deletable": false, - "width": 206, - "height": 40, - "selected": true - } - ], - "edges": [ - { - "id": "7992c4c0-cd40-4e23-b854-cc7c88a5ebd9", - "source": "1ce09ce1-2bf8-4b0e-9144-7aabc2cbbc03", - "target": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "sourceHandle": null, - "type": "custom" - }, - { - "id": "18175dab-8e00-4d6e-a4d8-5c506b4d6192", - "source": "5b0155cf-29a5-49e4-873b-de97f61b5968", - "target": "63003194-3056-4107-aa5c-e5745a026b8d", - "sourceHandle": "success", - "type": "custom" - } - ], - "queries": [ - { - "idOnDefinition": "e7f15471-53c0-49e5-b978-5f1b64fd8ad8", - "id": "886943b9-6cbe-4d00-a4fc-d5567529d15c" - } - ], - "webhookParams": [], - "defaultParams": "{\"key\":\"your value\"}", - "dependencies": { - "javascript": { - "dependencies": {} - } - }, - "setupScript": { - "javascript": "// lodash\n// const _ = require('lodash');\n" - } - }, - "globalSettings": { - "appInMaintenance": false, - "canvasMaxWidth": 100, - "canvasMaxWidthType": "%", - "canvasMaxHeight": 2400, - "canvasBackgroundColor": "var(--cc-appBackground-surface)", - "backgroundFxQuery": "", - "appMode": "light" - }, - "pageSettings": null, - "showViewerNavigation": true, - "homePageId": "06ea85f4-80be-4d9e-aefc-862548bade41", - "appId": "bf731e7e-09d8-4d8f-a058-0dad247c938e", - "currentEnvironmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", - "promotedFrom": null, - "createdAt": "2025-10-03T10:22:52.302Z", - "updatedAt": "2025-10-03T10:23:06.872Z" - } - ], - "appEnvironments": [ - { - "id": "b21edc90-cd2e-4a3a-9200-d6874615da50", - "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", - "name": "development", - "isDefault": false, - "priority": 1, - "enabled": true, - "createdAt": "2025-10-03T10:02:18.821Z", - "updatedAt": "2025-10-03T10:02:18.821Z" - }, - { - "id": "ef6fc5e8-a95f-412c-8fe3-4e6be00cc6d9", - "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", - "name": "staging", - "isDefault": false, - "priority": 2, - "enabled": true, - "createdAt": "2025-10-03T10:02:18.825Z", - "updatedAt": "2025-10-03T10:02:18.825Z" - }, - { - "id": "0162baf0-a89d-4ebc-9bb2-1666c3d5ccf5", - "organizationId": "36d06256-56db-4236-a9cd-c1732000d618", - "name": "production", - "isDefault": true, - "priority": 3, - "enabled": true, - "createdAt": "2025-10-03T10:02:18.825Z", - "updatedAt": "2025-10-03T10:02:18.825Z" - } - ], - "dataSourceOptions": [ - { - "id": "d8f96951-0e45-4b29-bff0-6cb95a5a82f7", - "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", - "environmentId": "b21edc90-cd2e-4a3a-9200-d6874615da50", - "options": null, - "createdAt": "2025-10-03T10:02:19.046Z", - "updatedAt": "2025-10-03T10:02:19.046Z" - }, - { - "id": "e2d43cb2-2bb5-4776-9be7-eca3b68ee716", - "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", - "environmentId": "ef6fc5e8-a95f-412c-8fe3-4e6be00cc6d9", - "options": null, - "createdAt": "2025-10-03T10:02:19.046Z", - "updatedAt": "2025-10-03T10:02:19.046Z" - }, - { - "id": "f560a2d9-cf9d-44c4-bfa0-24e23246e0ac", - "dataSourceId": "5f5f68b4-64d3-4eae-8269-5362e3ace99d", - "environmentId": "0162baf0-a89d-4ebc-9bb2-1666c3d5ccf5", - "options": null, - "createdAt": "2025-10-03T10:02:19.046Z", - "updatedAt": "2025-10-03T10:02:19.046Z" - } - ], - "schemaDetails": { - "multiPages": true, - "multiEnv": true, - "globalDataSources": true - } - } - } - } - ], - "tooljet_version": "3.20.13-ee-lts" -} \ No newline at end of file From 3f11a03d3b9d7a11f3e1d942978da228d417fac9 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Wed, 15 Oct 2025 13:15:43 +0530 Subject: [PATCH 028/157] updated spec --- .../e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js index 62ebdb8acf..cfaf5bf85b 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js @@ -101,7 +101,7 @@ describe("Workflows features", () => { navigateBackToWorkflowsDashboard(); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.parentWFName) cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.workflowNodeLabel); From 956f6be885d479c97affe03d1390390c944d3233 Mon Sep 17 00:00:00 2001 From: Srimanitejas123 Date: Wed, 15 Oct 2025 14:33:22 +0530 Subject: [PATCH 029/157] updated spec with review comments --- cypress-tests/cypress/commands/apiCommands.js | 146 +----------------- cypress-tests/cypress/commands/commands.js | 25 ++- .../cypress/commands/workflowCommands.js | 26 ++-- .../cypress/commands/workflowsApiCommands.js | 102 ++++++++++++ .../workflows/eeTestcases/workflowInApp.cy.js | 28 ++-- .../eeTestcases/workflowWIthDatasource.cy.js | 42 ++--- .../workflowWithExportImport.cy.js | 32 ++-- .../eeTestcases/workflowWithWebhooks.cy.js | 6 +- .../eeTestcases/workflowfeatures.cy.js | 32 ++-- cypress-tests/cypress/support/e2e.js | 1 + .../cypress/support/utils/workFlows.js | 8 +- 11 files changed, 217 insertions(+), 231 deletions(-) create mode 100644 cypress-tests/cypress/commands/workflowsApiCommands.js diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 7f26ceb31d..884d8b6509 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -233,7 +233,7 @@ Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => { Cypress.Commands.add( "apiAddQueryToApp", - ({ queryName, options, dsName, dsKind }) => { + ({ queryName, options, dataSourceName, dsKind }) => { cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { const authToken = cookie?.value; const workspaceId = Cypress.env("workspaceId"); @@ -259,10 +259,10 @@ Cypress.Commands.add( headers: commonHeaders, }).then((dsResponse) => { const dataSource = dsResponse.body.data_sources.find( - (ds) => ds.name === dsName + (ds) => ds.name === dataSourceName ); const dataSourceID = dataSource.id; - Cypress.env(`${dsName}`, dataSourceID); + Cypress.env(`${dataSourceName}`, dataSourceID); cy.request({ method: "POST", @@ -721,143 +721,3 @@ Cypress.Commands.add( }); } ); - -Cypress.Commands.add("apiCreateWorkflow", (workflowName) => { - - cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { - Cypress.env("authToken", `tj_auth_token=${cookie.value}`); - - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/apps`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }, - body: { - icon: "sentfast", - name: workflowName, - type: "workflow", - }, - }).then((response) => { - expect(response.status).to.equal(201); - - const workflowId = response.body?.id || response.allRequestResponses?.[0]?.["Response Body"]?.id; - const userId = response.body?.user_id || response.allRequestResponses?.[0]?.["Response Body"]?.user_id; - - Cypress.env("workflowId", workflowId); - Cypress.env("user_id", userId); - - Cypress.log({ - name: "Workflow create", - displayName: "WORKFLOW CREATED", - message: `: ${response.body.name}`, - }); - }); - }); -}); - -Cypress.Commands.add( - "openWorkflow", - ( - slug = "", - workspaceId = Cypress.env("workspaceId"), - workflowId = Cypress.env("workflowId"), - ) => { - cy.intercept("GET", "/api/apps/*").as("getWorkflowData"); - cy.window({ log: false }).then((win) => { - win.localStorage.setItem("walkthroughCompleted", "true"); - }); - cy.visit(`/${workspaceId}/apps/${workflowId}/${slug}`); - - cy.wait("@getWorkflowData").then((interception) => { - const responseData = interception.response.body; - - Cypress.env("editingVersionId", responseData.editing_version.id); - Cypress.env("environmentId", responseData.editorEnvironment.id); - Cypress.env("workflowId", responseData.id); - }); - } -); - -Cypress.Commands.add( - "openWorkflowByName", - ( - workflowName, - workspaceId = Cypress.env("workspaceId"), - componentSelector = "[data-cy='workflow-canvas']" - ) => { - cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { - Cypress.env("authToken", `tj_auth_token=${cookie.value}`); - - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, - headers: { - "Tj-Workspace-Id": workspaceId, - Cookie: Cypress.env("authToken"), - }, - }, { log: false }).then((response) => { - const workflow = response.body.apps?.find( - app => app.name === workflowName || app.slug === workflowName - ); - - if (workflow) { - Cypress.env("workflowId", workflow.id); - cy.openWorkflow(workflow.slug, workspaceId, workflow.id, componentSelector); - - Cypress.log({ - name: "Workflow Open", - displayName: "WORKFLOW OPENED", - message: `: ${workflowName}`, - }); - } else { - throw new Error(`Workflow "${workflowName}" not found`); - } - }); - }); - } -); - -Cypress.Commands.add("apiDeleteWorkflow", (workflowName) => { - cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { - Cypress.env("authToken", `tj_auth_token=${cookie.value}`); - - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: Cypress.env("authToken"), - }, - }, { log: false }).then((response) => { - const workflow = response.body.apps?.find( - app => app.name === workflowName || app.slug === workflowName - ); - - if (workflow) { - cy.request({ - method: "DELETE", - url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: Cypress.env("authToken"), - }, - }, { log: false }).then((deleteResponse) => { - expect(deleteResponse.status).to.equal(200); - Cypress.log({ - name: "Workflow Delete", - displayName: "WORKFLOW DELETED", - message: `: ${workflowName}`, - }); - }); - } else { - Cypress.log({ - name: "Workflow Not Found", - displayName: "WORKFLOW NOT FOUND", - message: `: ${workflowName}`, - }); - } - }); - }); -}); \ No newline at end of file diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index ceeb223f50..9333e7a644 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -659,4 +659,27 @@ Cypress.Commands.add("runSqlQuery", (query, db = Cypress.env("app_db")) => { dbconfig: db, sql: query, }); -}); \ No newline at end of file +}); + +Cypress.Commands.add( + "openWorkflow", + ( + slug = "", + workspaceId = Cypress.env("workspaceId"), + workflowId = Cypress.env("workflowId"), + ) => { + cy.intercept("GET", "/api/apps/*").as("getWorkflowData"); + cy.window({ log: false }).then((win) => { + win.localStorage.setItem("walkthroughCompleted", "true"); + }); + cy.visit(`/${workspaceId}/apps/${workflowId}/${slug}`); + + cy.wait("@getWorkflowData").then((interception) => { + const responseData = interception.response.body; + + Cypress.env("editingVersionId", responseData.editing_version.id); + Cypress.env("environmentId", responseData.editorEnvironment.id); + Cypress.env("workflowId", responseData.id); + }); + } +); diff --git a/cypress-tests/cypress/commands/workflowCommands.js b/cypress-tests/cypress/commands/workflowCommands.js index 7caca0fae1..9288b924f4 100644 --- a/cypress-tests/cypress/commands/workflowCommands.js +++ b/cypress-tests/cypress/commands/workflowCommands.js @@ -6,10 +6,10 @@ import { commonText } from "Texts/common"; import { selectAppCardOption } from "Support/utils/common"; import { navigateBackToWorkflowsDashboard } from "Support/utils/workFlows"; -Cypress.Commands.add("createWorkflowApp", (wfName) => { +Cypress.Commands.add("createWorkflowApp", (workflowName) => { cy.get(workflowSelector.globalWorkFlowsIcon).click(); cy.get(workflowSelector.workflowsCreateButton).click(); - cy.get(workflowSelector.workFlowNameInputField).type(wfName); + cy.get(workflowSelector.workFlowNameInputField).type(workflowName); cy.get(workflowSelector.createWorkFlowsButton).click(); }); @@ -115,10 +115,10 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("deleteWorkflow", (wfName) => { +Cypress.Commands.add("deleteWorkflow", (workflowName) => { cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); navigateBackToWorkflowsDashboard(); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() @@ -128,9 +128,9 @@ Cypress.Commands.add("deleteWorkflow", (wfName) => { cy.wait("@appDeleted"); }); -Cypress.Commands.add("deleteWorkflowfromDashboard", (wfName) => { +Cypress.Commands.add("deleteWorkflowfromDashboard", (workflowName) => { cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() @@ -142,10 +142,10 @@ Cypress.Commands.add("deleteWorkflowfromDashboard", (wfName) => { Cypress.Commands.add( "exportWorkflowApp", - (wfName, fixtureFile = "cypress/fixtures/exportedApp.json") => { + (workflowName, fixtureFile = "cypress/fixtures/exportedApp.json") => { navigateBackToWorkflowsDashboard(); - cy.get(`[data-cy="${wfName}-card"]`) + cy.get(`[data-cy="${workflowName}-card"]`) .trigger('mouseover') .find('[data-cy="app-card-menu-icon"]') .click({ force: true }); @@ -163,20 +163,20 @@ Cypress.Commands.add( }); }); - cy.deleteWorkflowfromDashboard(wfName); + cy.deleteWorkflowfromDashboard(workflowName); } ); -Cypress.Commands.add("addWorkflowInApp", (wfName) => { +Cypress.Commands.add("addWorkflowInApp", (workflowName) => { cy.get(workflowSelector.showDSPopoverButton).click(); cy.get(workflowSelector.workflowSearchInput).type( workflowsText.workflowLabel ); cy.contains(`[id*="react-select-"]`, workflowsText.workflowLabel).click(); - cy.get(workflowSelector.queryRenameInput).clear().type(wfName); + cy.get(workflowSelector.queryRenameInput).clear().type(workflowName); cy.get(workflowSelector.workflowDropdown).parent() .find('.react-select__control') .click(); - cy.get(workflowSelector.workflowSelectInput).realType(wfName); - cy.get(workflowSelector.workflowSelectOption).contains(wfName).click(); + cy.get(workflowSelector.workflowSelectInput).realType(workflowName); + cy.get(workflowSelector.workflowSelectOption).contains(workflowName).click(); }); diff --git a/cypress-tests/cypress/commands/workflowsApiCommands.js b/cypress-tests/cypress/commands/workflowsApiCommands.js new file mode 100644 index 0000000000..5ad11ef171 --- /dev/null +++ b/cypress-tests/cypress/commands/workflowsApiCommands.js @@ -0,0 +1,102 @@ +const envVar = Cypress.env("environment"); + + Cypress.Commands.add("apiCreateWorkflow", (workflowName, reuseSession = false) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/apps`, + headers: headers, + body: { + icon: "sentfast", + name: workflowName, + type: "workflow", + }, + }).then((response) => { + expect(response.status).to.equal(201); + + const workflowId = response.body?.id || response.allRequestResponses?.[0]?.["Response Body"]?.id; + const userId = response.body?.user_id || response.allRequestResponses?.[0]?.["Response Body"]?.user_id; + + Cypress.env("workflowId", workflowId); + Cypress.env("user_id", userId); + + Cypress.log({ + name: "Workflow create", + displayName: "WORKFLOW CREATED", + message: `: ${response.body.name}`, + }); + }); + }); + }); + + + Cypress.Commands.add( + "openWorkflowByName", + ( + workflowName, + workspaceId = Cypress.env("workspaceId"), + componentSelector = "[data-cy='workflow-canvas']", + reuseSession = false + ) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: headers, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + Cypress.env("workflowId", workflow.id); + cy.openWorkflow(workflow.slug, workspaceId, workflow.id, componentSelector); + + Cypress.log({ + name: "Workflow Open", + displayName: "WORKFLOW OPENED", + message: `: ${workflowName}`, + }); + } else { + throw new Error(`Workflow "${workflowName}" not found`); + } + }); + }); + } + ); + + Cypress.Commands.add("apiDeleteWorkflow", (workflowName, reuseSession = false) => { + cy.getAuthHeaders(reuseSession).then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/apps?page=1&type=workflow&searchKey=${workflowName}`, + headers: headers, + }, { log: false }).then((response) => { + const workflow = response.body.apps?.find( + app => app.name === workflowName || app.slug === workflowName + ); + + if (workflow) { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/apps/${workflow.id}`, + headers: headers, + }, { log: false }).then((deleteResponse) => { + expect(deleteResponse.status).to.equal(200); + Cypress.log({ + name: "Workflow Delete", + displayName: "WORKFLOW DELETED", + message: `: ${workflowName}`, + }); + }); + } else { + Cypress.log({ + name: "Workflow Not Found", + displayName: "WORKFLOW NOT FOUND", + message: `: ${workflowName}`, + }); + } + }); + }); + }); + diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js index ff2f92c70b..195161bcac 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowInApp.cy.js @@ -19,15 +19,15 @@ describe("Workflows in apps", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.appName = `${data.wfName}-wf-app`; + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.appName = `${data.workflowName}-wf-app`; data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflows with runjs and validating execution in apps", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -51,7 +51,7 @@ describe("Workflows in apps", () => { cy.apiCreateApp(data.appName); cy.openApp(); - cy.addWorkflowInApp(data.wfName); + cy.addWorkflowInApp(data.workflowName); cy.get(dataSourceSelector.queryPreviewButton).click(); @@ -59,20 +59,20 @@ describe("Workflows in apps", () => { // cy.verifyToastMessage( // commonSelectors.toastMessage, - // `Query (${data.dsName}) completed.` + // `Query (${data.dataSourceName}) completed.` // ); cy.backToApps(); cy.apiDeleteApp(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); it("Creating workflows with postgres and validating execution in apps", () => { - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -95,7 +95,7 @@ describe("Workflows in apps", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); @@ -104,10 +104,10 @@ describe("Workflows in apps", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.workflowName) cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -129,7 +129,7 @@ describe("Workflows in apps", () => { cy.apiCreateApp(data.appName); cy.openApp(); - cy.addWorkflowInApp(data.wfName); + cy.addWorkflowInApp(data.workflowName); cy.get(dataSourceSelector.queryPreviewButton).click(); @@ -137,11 +137,11 @@ describe("Workflows in apps", () => { // cy.verifyToastMessage( // commonSelectors.toastMessage, - // `Query (${data.dsName}) completed.` + // `Query (${data.dataSourceName}) completed.` // ); cy.backToApps(); cy.apiDeleteApp(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); deleteDatasource(`cypress-${data.dataSourceName}-manual-pgsql`); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js index e0866f0bf7..71f1ea1463 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWIthDatasource.cy.js @@ -23,14 +23,14 @@ describe("Workflows with Datasource", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("RunJS workflow - execute and validate", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -52,16 +52,16 @@ describe("Workflows with Datasource", () => { cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); it("Postgres workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -84,7 +84,7 @@ describe("Workflows with Datasource", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); @@ -93,10 +93,10 @@ describe("Workflows with Datasource", () => { }).should("have.text", postgreSqlText.labelConnectionVerified); cy.reload(); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.workflowName) cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -116,17 +116,17 @@ describe("Workflows with Datasource", () => { verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); - deleteDatasource(dsName); + deleteDatasource(dataSourceName); }); it("REST API workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-restapi`; + const dataSourceName = `cypress-${data.dataSourceName}-restapi`; cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "restapi", [ { key: "url", value: "https://jsonplaceholder.typicode.com" }, @@ -158,10 +158,10 @@ describe("Workflows with Datasource", () => { ] ); cy.reload(); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.workflowName) cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.restapiNodeName)).click({ force: true, @@ -182,13 +182,13 @@ describe("Workflows with Datasource", () => { cy.verifyTextInResponseOutput(workflowsText.restApiExpectedValue); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); - deleteDatasource(dsName); + deleteDatasource(dataSourceName); }); it("HarperDB workflow - execute and validate", () => { - const dsName = `cypress-${data.dataSourceName}-harperdb`; + const dataSourceName = `cypress-${data.dataSourceName}-harperdb`; const Host = Cypress.env("harperdb_host"); const Port = Cypress.env("harperdb_port"); const Username = Cypress.env("harperdb_username"); @@ -238,10 +238,10 @@ describe("Workflows with Datasource", () => { postgreSqlText.toastDSSaved ); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.workflowName) cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.harperdbNodeName)).click({ force: true, @@ -269,8 +269,8 @@ describe("Workflows with Datasource", () => { cy.verifyTextInResponseOutput(workflowsText.harperDbExpectedValue); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); - deleteDatasource(dsName); + deleteDatasource(dataSourceName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js index 7112fc2bdc..934b2b2069 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithExportImport.cy.js @@ -20,16 +20,16 @@ describe("Workflows Export/Import Sanity", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it.only("RunJS workflow - execute, export/import, re-execute", () => { - const wfName = `${data.wfName}-runjs`; + const workflowName = `${data.workflowName}-runjs`; - cy.createWorkflowApp(wfName); + cy.createWorkflowApp(workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -50,23 +50,23 @@ describe("Workflows Export/Import Sanity", () => { ); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); - cy.exportWorkflowApp(wfName); + cy.exportWorkflowApp(workflowName); - importWorkflowApp(wfName, workflowsText.exportFixturePath); + importWorkflowApp(workflowName, workflowsText.exportFixturePath); cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(wfName); + cy.apiDeleteWorkflow(workflowName); cy.task("deleteFile", workflowsText.exportFixturePath); }); it("Postgres workflow - execute, export/import, re-execute", () => { - const wfName = `${data.wfName}-pg`; - const dsName = `cypress-${data.dataSourceName}-manual-pgsql`; + const workflowName = `${data.workflowName}-pg`; + const dataSourceName = `cypress-${data.dataSourceName}-manual-pgsql`; cy.get(commonSelectors.globalDataSourceIcon).click(); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, - dsName, + dataSourceName, "postgresql", [ { key: "connection_type", value: "manual", encrypted: false }, @@ -88,7 +88,7 @@ describe("Workflows Export/Import Sanity", () => { ] ); - cy.get(dataSourceSelector.dataSourceNameButton(dsName)) + cy.get(dataSourceSelector.dataSourceNameButton(dataSourceName)) .should("be.visible") .click(); @@ -99,10 +99,10 @@ describe("Workflows Export/Import Sanity", () => { cy.reload(); - cy.apiCreateWorkflow(data.wfName) + cy.apiCreateWorkflow(data.workflowName) cy.openWorkflow(); enterJsonInputInStartNode(); - cy.connectDataSourceNode(dsName); + cy.connectDataSourceNode(dataSourceName); cy.get(workflowSelector.nodeName(workflowsText.postgresqlNodeName)).click({ force: true, @@ -122,14 +122,14 @@ describe("Workflows Export/Import Sanity", () => { ); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); - cy.exportWorkflowApp(wfName); + cy.exportWorkflowApp(workflowName); - importWorkflowApp(wfName, workflowsText.exportFixturePath); + importWorkflowApp(workflowName, workflowsText.exportFixturePath); verifyTextInResponseOutputLimited(workflowsText.postgresExpectedValue); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(wfName); + cy.apiDeleteWorkflow(workflowName); - deleteDatasource(dsName); + deleteDatasource(dataSourceName); cy.task("deleteFile", workflowsText.exportFixturePath); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js index 2c47fc4e16..239f73e8ed 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowWithWebhooks.cy.js @@ -14,14 +14,14 @@ describe("Workflows with Webhooks", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflows with runjs, triggering via webhook, and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -66,6 +66,6 @@ describe("Workflows with Webhooks", () => { }); }); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js index cfaf5bf85b..72cb753b76 100644 --- a/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/workflows/eeTestcases/workflowfeatures.cy.js @@ -15,17 +15,17 @@ describe("Workflows features", () => { beforeEach(() => { cy.apiLogin(); cy.visit("/"); - data.wfName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.appName = `${data.wfName}-wf-app`; - data.childWFName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); - data.parentWFName = `${data.wfName}-wf-app`; + data.workflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.appName = `${data.workflowName}-wf-app`; + data.childWorkflowName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.parentWorkflowName = `${data.workflowName}-wf-app`; data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); it("Creating workflow with long string input and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(workflowsText.longStringJsonText); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -46,11 +46,11 @@ describe("Workflows features", () => { ); cy.verifyTextInResponseOutput(workflowsText.longStringJsonText); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); it("Creating workflow with Node Preview Validation and execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -73,12 +73,12 @@ describe("Workflows features", () => { ); cy.verifyTextInResponseOutput(workflowsText.jsonValuePlaceholder); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); // Need to run after bug fixes it("Creating workflow inside Workflow and validating execution", () => { - cy.createWorkflowApp(data.childWFName); + cy.createWorkflowApp(data.childWorkflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -101,7 +101,7 @@ describe("Workflows features", () => { navigateBackToWorkflowsDashboard(); - cy.apiCreateWorkflow(data.parentWFName) + cy.apiCreateWorkflow(data.parentWorkflowName) cy.openWorkflow(); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.workflowNodeLabel); @@ -112,9 +112,9 @@ describe("Workflows features", () => { cy.get('input[id^="react-select-"]') .eq(1) - .type(data.childWFName, { force: true }); + .type(data.childWorkflowName, { force: true }); cy.get(".react-select__option") - .contains(data.childWFName) + .contains(data.childWorkflowName) .click({ force: true }); cy.get("body").click(50, 50); @@ -126,12 +126,12 @@ describe("Workflows features", () => { ); // cy.verifyTextInResponseOutput(workflowsText.responseNodeExpectedValueText); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.childWFName); - cy.apiDeleteWorkflow(data.parentWFName); + cy.apiDeleteWorkflow(data.childWorkflowName); + cy.apiDeleteWorkflow(data.parentWorkflowName); }); it("Creating workflow with large datasets and validating execution", () => { - cy.createWorkflowApp(data.wfName); + cy.createWorkflowApp(data.workflowName); enterJsonInputInStartNode(); cy.connectDataSourceNode(workflowsText.runjsNodeLabel); @@ -158,6 +158,6 @@ describe("Workflows features", () => { ); navigateBackToWorkflowsDashboard(); - cy.apiDeleteWorkflow(data.wfName); + cy.apiDeleteWorkflow(data.workflowName); }); }); diff --git a/cypress-tests/cypress/support/e2e.js b/cypress-tests/cypress/support/e2e.js index 410c60f646..2929869182 100644 --- a/cypress-tests/cypress/support/e2e.js +++ b/cypress-tests/cypress/support/e2e.js @@ -17,6 +17,7 @@ import "cypress-real-events/support"; import "../commands/commands"; import "../commands/apiCommands"; +import "../commands/workflowsApiCommands"; import '../commands/workflowCommands'; import '../commands/platform/platformApiCommands'; import "@cypress/code-coverage/support"; diff --git a/cypress-tests/cypress/support/utils/workFlows.js b/cypress-tests/cypress/support/utils/workFlows.js index e415e48e84..711a19da46 100644 --- a/cypress-tests/cypress/support/utils/workFlows.js +++ b/cypress-tests/cypress/support/utils/workFlows.js @@ -40,23 +40,23 @@ export const revealWorkflowToken = (selectors) => { }; export const importWorkflowApp = ( - wfName, + workflowName, fixturePath = "cypress/fixtures/exportedApp.json" ) => { cy.get(workflowSelector.importWorkFlowsOption).click(); cy.get(workflowSelector.importWorkFlowsLabel).click(); cy.get('input[type="file"]').first().selectFile(fixturePath, { force: true }); cy.wait(2000); - cy.get(workflowSelector.workFlowNameInputField).clear().type(wfName); + cy.get(workflowSelector.workFlowNameInputField).clear().type(workflowName); cy.get(workflowSelector.importWorkFlowsButton).click(); }; -export const deleteAppandWorkflowAfterExecution = (wfName, appName) => { +export const deleteAppandWorkflowAfterExecution = (workflowName, appName) => { cy.backToApps(); cy.deleteApp(appName); cy.get(workflowSelector.globalWorkFlowsIcon).click(); cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); - cy.get(commonSelectors.appCard(wfName)) + cy.get(commonSelectors.appCard(workflowName)) .realHover() .find(commonSelectors.appCardOptionsButton) .realHover() From 1a9761bc06f46694fb10b50175d62454f0150946 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 15 Oct 2025 14:59:24 +0530 Subject: [PATCH 030/157] update submodule --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 4c9743f485..ea751ada29 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 4c9743f485d77aa186c29729abd743e708536e79 +Subproject commit ea751ada293469f8fdf5e3cb62b39b60c60071ef From a3b0a33ce7998287182b713b0ef00a8aa63701ff Mon Sep 17 00:00:00 2001 From: emidhun Date: Wed, 15 Oct 2025 15:26:34 +0530 Subject: [PATCH 031/157] Fix-import spec --- cypress-tests/cypress/commands/apiCommands.js | 89 ++---------- cypress-tests/cypress/commands/commands.js | 18 +-- .../commands/platform/platformApiCommands.js | 78 ++++++++++ .../platform/ceTestcases/apps/appImport.cy.js | 133 ++++++++---------- .../utils/editor/editorHeaderOperations.js | 38 +++++ 5 files changed, 194 insertions(+), 162 deletions(-) create mode 100644 cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 873e6e1d13..bcd0cce5ab 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -1,35 +1,5 @@ const envVar = Cypress.env("environment"); -Cypress.Commands.add( - "apiLogin", - ( - userEmail = "dev@tooljet.io", - userPassword = "password", - workspaceId = "", - redirection = "/" - ) => { - cy.request({ - url: `${Cypress.env("server_host")}/api/authenticate/${workspaceId}`, - method: "POST", - body: { - email: userEmail, - password: userPassword, - redirectTo: redirection, - }, - }) - .its("body") - .then((res) => { - Cypress.env("workspaceId", res.current_organization_id); - - Cypress.log({ - name: "Api login", - displayName: "LOGIN: ", - message: `: Success`, - }); - }); - } -); - Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => { cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -64,7 +34,7 @@ Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => { }); }); -Cypress.Commands.add("apiFetchDataSourcesId", () => { +Cypress.Commands.add("apiFetchDataSourcesIdFromApp", () => { cy.getAuthHeaders().then((headers) => { cy.request({ method: "GET", @@ -183,23 +153,6 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("apiLogout", () => { - cy.getCookie("tj_auth_token").then((cookie) => { - cy.request( - { - method: "GET", - url: `${Cypress.env("server_host")}/api/session/logout`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }, - }, - { log: false } - ).then((response) => { - expect(response.status).to.equal(200); - }); - }); -}); Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => { cy.getCookie("tj_auth_token").then((cookie) => { @@ -378,19 +331,6 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("apiGetEnvironments", () => { - cy.getAuthHeaders().then((headers) => { - cy.request({ - method: "GET", - url: `${Cypress.env("server_host")}/api/app-environments`, - headers: headers, - }).then((response) => { - expect(response.status).to.equal(200); - return response.body.environments; - }); - }); -}); - Cypress.Commands.add("apiMakeAppPublic", (appId = Cypress.env("appId")) => { cy.getAuthHeaders().then((headers) => { cy.request({ @@ -495,30 +435,17 @@ Cypress.Commands.add("apiGetDataSourceIdByName", (dataSourceName) => { headers: headers, }).then((response) => { expect(response.status).to.equal(200); - const dataSource = response.body.data_sources.find( - (ds) => ds.name === dataSourceName - ); - return dataSource.id; + const id = response.body.data_sources.find(ds => ds.name === dataSourceName)?.id; + Cypress.log({ + name: "apiGetDataSourceIdByName", + displayName: "Data Source ID", + message: `Data source ID for '${dataSourceName}': ${id}`, + }); + return id; }); }); }); -Cypress.Commands.add("getAuthHeaders", (returnCached = false) => { - let headers = {}; - if (returnCached) { - return returnCached; - } else { - cy.getCookie("tj_auth_token").then((cookie) => { - headers = { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }; - Cypress.env("authHeaders", headers); - return headers; - }); - } -}); - Cypress.Commands.add( "apiUpdateDataSource", (dataSourceName, envName, updateData) => { diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index ceeb223f50..fa46f242b1 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -219,14 +219,14 @@ Cypress.Commands.add("createAppFromTemplate", (appName) => { cy.get('[data-cy="app-name-label"]').should("have.text", "App Name"); }); -Cypress.Commands.add("renameApp", (appName) => { - cy.get(commonSelectors.appNameInput).type( - `{selectAll}{backspace}${appName}`, - { force: true } - ); - cy.forceClickOnCanvas(); - cy.waitForAutoSave(); -}); +// Cypress.Commands.add("renameApp", (appName) => { +// cy.get(commonSelectors.appNameInput).type( +// `{selectAll}{backspace}${appName}`, +// { force: true } +// ); +// cy.forceClickOnCanvas(); +// cy.waitForAutoSave(); +// }); Cypress.Commands.add( "clearCodeMirror", @@ -557,7 +557,7 @@ Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { } }); - function installPlugin (pluginName) { + function installPlugin(pluginName) { cy.get('[data-cy="-list-item"]').eq(1).click(); cy.wait(1000); diff --git a/cypress-tests/cypress/commands/platform/platformApiCommands.js b/cypress-tests/cypress/commands/platform/platformApiCommands.js index f17bc6ade0..d2cd90371b 100644 --- a/cypress-tests/cypress/commands/platform/platformApiCommands.js +++ b/cypress-tests/cypress/commands/platform/platformApiCommands.js @@ -1,5 +1,66 @@ const envVar = Cypress.env("environment"); +Cypress.Commands.add( + "apiLogin", + ( + userEmail = "dev@tooljet.io", + userPassword = "password", + workspaceId = "", + redirection = "/" + ) => { + cy.request({ + url: `${Cypress.env("server_host")}/api/authenticate/${workspaceId}`, + method: "POST", + body: { + email: userEmail, + password: userPassword, + redirectTo: redirection, + }, + }) + .its("body") + .then((res) => { + Cypress.env("workspaceId", res.current_organization_id); + + Cypress.log({ + name: "Api login", + displayName: "LOGIN: ", + message: `: Success`, + }); + }); + } +); + +Cypress.Commands.add("apiLogout", () => { + cy.getCookie("tj_auth_token").then((cookie) => { + cy.request( + { + method: "GET", + url: `${Cypress.env("server_host")}/api/session/logout`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }, + }, + { log: false } + ).then((response) => { + expect(response.status).to.equal(200); + }); + }); +}); + +Cypress.Commands.add("apiGetEnvironments", () => { + cy.getAuthHeaders().then((headers) => { + cy.request({ + method: "GET", + url: `${Cypress.env("server_host")}/api/app-environments`, + headers: headers, + }).then((response) => { + expect(response.status).to.equal(200); + return response.body.environments; + }); + }); +}); + Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => { cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -567,3 +628,20 @@ Cypress.Commands.add( }); } ); + +Cypress.Commands.add("getAuthHeaders", (returnCached = false) => { + let headers = {}; + if (returnCached) { + return returnCached; + } else { + cy.getCookie("tj_auth_token").then((cookie) => { + headers = { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }; + Cypress.env("authHeaders", headers); + Cypress.log({ name: "getAuthHeaders", message: `Auth headers: ${JSON.stringify(headers)}` }); + return headers; + }); + } +}); \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js index cfe0c426d1..5ea812c6b4 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js @@ -7,8 +7,9 @@ import { buttonText } from "Texts/button"; import { importText } from "Texts/exportImport"; import { importAndVerifyApp } from "Support/utils/exportImport"; import { switchVersionAndVerify } from "Support/utils/version"; +import { renameApp, verifyAppName, verifyCurrentEnvironment, verifyCurrentVersion, addNewVersion, promoteEnv } from 'Support/utils/editor/editorHeaderOperations'; -describe("App Import Functionality", () => { +describe("App Import", () => { const TEST_DATA = { toolJetImage: "cypress/fixtures/Image/tooljet.png", invalidApp: "cypress/fixtures/templates/invalid_app.json", @@ -32,16 +33,20 @@ describe("App Import Functionality", () => { }; cy.apiLogin(); - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug).then((workspace) => { + Cypress.env("workspaceId", workspace.body.organization_id); + }); cy.skipWalkthrough(); - }); - - it("should verify app import functionality", () => { - cy.apiLogin(); cy.visit(`${data.workspaceSlug}`); + }); + it("should verify invalid import files", () => { + + cy.get(importSelectors.dropDownMenu).should("be.visible").click(); + cy.get(importSelectors.importOptionLabel).verifyVisibleElement( + "have.text", + importText.importOption + ); - // Test invalid file import cy.get(dashboardSelector.importAppButton).click(); importAndVerifyApp( TEST_DATA.toolJetImage, @@ -55,14 +60,10 @@ describe("App Import Functionality", () => { "Could not import: SyntaxError: Expected ',' or '}' after property value in JSON at position 246 (line 11 column 13)" ); - cy.wait(500); - // Test valid app import - cy.get(importSelectors.dropDownMenu).should("be.visible").click(); - cy.get(importSelectors.importOptionLabel).verifyVisibleElement( - "have.text", - importText.importOption - ); + }); + + it("should verify app with multiple version", () => { cy.intercept("POST", "/api/v2/resources/import").as("importApp"); cy.get(importSelectors.importOptionInput) @@ -103,26 +104,19 @@ describe("App Import Functionality", () => { // Verify imported app cy.get(commonSelectors.toastCloseButton).click(); cy.wait(500); - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", + cy.get('[data-cy="edit-app-name-button"]').verifyVisibleElement( + "have.text", "three-versions" ); cy.get(appVersionSelectors.currentVersionField("v3")).should("be.visible"); - // Configure app - cy.skipEditorPopover(); - cy.dragAndDropWidget(buttonText.defaultWidgetText); - cy.get(appVersionSelectors.appVersionLabel).should("be.visible"); - cy.get(commonWidgetSelector.draggableWidget("button1")).should( - "be.visible" - ); + //App editing is pending - cy.renameApp(data.appName); - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", + renameApp(data.appName); + cy.get('[data-cy="edit-app-name-button"]').verifyVisibleElement( + "have.text", data.appName ); - cy.waitForAutoSave(); // Verify initial widget states @@ -152,52 +146,24 @@ describe("App Import Functionality", () => { cy.visit(`${data.workspaceSlug}/data-sources`); cy.get('[data-cy="postgresql-button"]').should("be.visible"); - cy.ifEnv("Community", () => { - cy.apiUpdateDataSource("postgresql", "production", { - options: [ - { - key: "password", - value: `${Cypress.env("pg_password")}`, - encrypted: true, - }, - ], + const edition = Cypress.env("environment"); + if (edition === "Community" || edition === "Enterprise") { + const dsEnv = edition === "Community" ? "production" : "development"; + cy.apiUpdateDataSource("postgresql", dsEnv, { + options: [{ + key: "password", + value: Cypress.env("pg_password"), + encrypted: true, + }], }); - }); - cy.ifEnv("Enterprise", () => { - cy.apiUpdateDataSource("postgresql", "development", { - options: [ - { - key: "password", - value: `${Cypress.env("pg_password")}`, - encrypted: true, - }, - ], - }); - }); - - cy.ifEnv("Community", () => { - cy.apiCreateWorkspaceConstant( - "pageHeader", - "Import and Export", - ["Global"], - ["production"] - ); - cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], ["production"]); - }); - - cy.ifEnv("Enterprise", () => { - cy.apiCreateWorkspaceConstant( - "pageHeader", - "Import and Export", - ["Global"], - ["development"] - ); - cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], ["development"]); - }); + cy.apiCreateWorkspaceConstant("pageHeader", "Import and Export", ["Global"], [dsEnv]); + cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], [dsEnv]); + } // Verify app after setup cy.wait("@importApp").then((interception) => { const appId = interception.response.body.imports.app[0].id; + cy.log(`Imported app id: ${appId}`); cy.openApp( "", Cypress.env("workspaceId"), @@ -227,14 +193,37 @@ describe("App Import Functionality", () => { cy.backToApps(); // Test single version import + + }); + + it("should verify app with single version", () => { + cy.get(importSelectors.dropDownMenu).click(); + const edition = Cypress.env("environment"); + let dsEnv + if (edition === "Community" || edition === "Enterprise") { + dsEnv = edition === "Community" ? "production" : "development"; + cy.apiCreateWorkspaceConstant("pageHeader", "Import and Export", ["Global"], [dsEnv]); + cy.apiCreateWorkspaceConstant("db_name", "persons", ["Secret"], [dsEnv]); + } + importAndVerifyApp(TEST_DATA.appFiles.singleVersion); - // Verify final state - cy.get(commonSelectors.appNameInput).verifyVisibleElement( - "contain.value", + cy.get('[data-cy="edit-app-name-button"]').verifyVisibleElement( + "have.text", "one_version" ); + cy.apiUpdateDataSource("postgresql", dsEnv, { + options: [{ + key: "password", + value: Cypress.env("pg_password"), + encrypted: true, + }], + }); + + cy.reload() + + verifyCommonData({ text2: "Import and Export", diff --git a/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js b/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js new file mode 100644 index 0000000000..c8ee8fc7f0 --- /dev/null +++ b/cypress-tests/cypress/support/utils/editor/editorHeaderOperations.js @@ -0,0 +1,38 @@ +export const renameApp = (name) => { + cy.get('[data-cy="edit-app-name-button"]').click(); + cy.get("[data-cy='app-name-input']").type(`{selectAll}{backspace}${name}`, { force: true }); + cy.get("[data-cy='rename-app']").click(); +}; + +export const verifyAppName = (name) => { + cy.get('[data-cy="edit-app-name-button"]').should("have.text", name); +} + +export const verifyCurrentEnvironment = (envName) => { + cy.get('[data-cy="list-current-env-name"]').should("have.text", envName); +} + +export const verifyCurrentVersion = (version) => { + cy.get('[data-cy*="-current-version-text"]').should("have.text", version); +} + +export const addNewVersion = (newVersion, fromVersion) => { + cy.get('[data-cy*="-current-version-text"]').click(); + cy.get('[data-cy="create-new-version-button"]').click(); + if (fromVersion) { + cy.get('[data-cy="create-version-from-input-field"]').click(); + cy.contains('[id*="react-select"]', fromVersion).click(); + } + cy.get('[data-cy="version-name-input-field"]').type(newVersion, { force: true }); + cy.get('[data-cy="create-new-version-button"]').click(); + cy.verifyToastMessage("Version Created"); +}; + +export const promoteEnv = () => { + cy.get('[data-cy="promote-button"]').first().click(); + cy.get('[data-cy="promote-button"]').last().click(); +} + + + +// import { renameApp, verifyAppName, verifyCurrentEnvironment, verifyCurrentVersion, addNewVersion, promoteEnv } from 'cypress-tests/cypress/support/utils/editor/editorHeaderOperations'; \ No newline at end of file From 4359621be8955d196191c54039a3d0c67223ed2e Mon Sep 17 00:00:00 2001 From: emidhun Date: Wed, 15 Oct 2025 15:45:21 +0530 Subject: [PATCH 032/157] Minor fixes --- cypress-tests/cypress.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 4c2e971648..53b86c4c53 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -114,7 +114,7 @@ module.exports = defineConfig({ baseUrl: "http://localhost:8082", specPattern: "cypress/e2e/happyPath/**/*.cy.js", downloadsFolder: "cypress/downloads", - numTestsKeptInMemory: 0, + numTestsKeptInMemory: 1, redirectionLimit: 5, trashAssetsBeforeRuns: true, experimentalMemoryManagement: true, From 70be5e62bb33ae24d03a816c19b869a312f66880 Mon Sep 17 00:00:00 2001 From: emidhun Date: Wed, 15 Oct 2025 15:47:37 +0530 Subject: [PATCH 033/157] Minor fixes --- .../commonTestcases/newSuits/queries/chainingOfQueries.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js index 9d9dad964d..939363cff2 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js @@ -14,7 +14,7 @@ describe("Chaining of queries", () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-chaining-App`); cy.openApp(); - cy.apiFetchDataSourcesId(); + cy.apiFetchDataSourcesIdFromApp(); cy.viewport(1800, 1800); cy.dragAndDropWidget("Button"); resizeQueryPanel("80"); From 0fd25e11326a978707cec24ba8786598a3931faa Mon Sep 17 00:00:00 2001 From: emidhun Date: Wed, 15 Oct 2025 15:48:31 +0530 Subject: [PATCH 034/157] Add data-cy --- frontend/src/AppBuilder/Header/EditAppName.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 8f5477b647..9ba4bcd464 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -45,6 +45,7 @@ function EditAppName() { + {showOrSeparator && ( +
+
+ {orText} +
+
+ )} +
+ + + ); +} + +LoginForm.propTypes = { + className: PropTypes.string, + + // Header and sign up section + signinHeader: PropTypes.string, + signUpText: PropTypes.string, + signUpUrl: PropTypes.string, + signUpCTA: PropTypes.string, + + // Form fields + emailLabel: PropTypes.string, + emailPlaceholder: PropTypes.string, + passwordLabel: PropTypes.string, + + // Forgot password + showForgotPassword: PropTypes.bool, + forgotPasswordUrl: PropTypes.string, + forgotPasswordText: PropTypes.string, + + // Button and separator + signinButtonText: PropTypes.string, + orText: PropTypes.string, + showOrSeparator: PropTypes.bool, + + // Form functionality + onSubmit: PropTypes.func, + emailValue: PropTypes.string, + passwordValue: PropTypes.string, + onEmailChange: PropTypes.func, + onPasswordChange: PropTypes.func, + + // Validation and state + emailError: PropTypes.string, + passwordError: PropTypes.string, + isLoading: PropTypes.bool, + disabled: PropTypes.bool, +}; + +LoginForm.defaultProps = { + className: '', + + // Header and sign up section + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + + // Form fields + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + + // Forgot password + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + + // Button and separator + signinButtonText: 'Sign in', + orText: 'OR', + showOrSeparator: true, + + // Form functionality + onSubmit: undefined, + emailValue: undefined, + passwordValue: undefined, + onEmailChange: undefined, + onPasswordChange: undefined, + + // Validation and state + emailError: undefined, + passwordError: undefined, + isLoading: false, + disabled: false, +}; diff --git a/frontend/src/components/Auth/PasswordInput.jsx b/frontend/src/components/Auth/PasswordInput.jsx new file mode 100644 index 0000000000..261b671b61 --- /dev/null +++ b/frontend/src/components/Auth/PasswordInput.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Input } from '@/components/ui/Input/Input.jsx'; + +import { cn } from '@/lib/utils'; +import { HelperMessage, ValidationMessage } from '@/components/ui/Input/InputUtils/InputUtils'; +import { usePasswordInput } from '@/hooks/usePasswordInput'; + +export const PasswordInput = ({ + placeholder = 'Create a password', + value, + onChange, + error, + name = 'password', + dataCy = 'password', + minLength = 5, + hint = `Password must be at least ${minLength} characters`, + disabled = false, + className, + validation, + isValidatedMessages, + required = true, + ...props +}) => { + const { isValid, message, handleChange } = usePasswordInput({ + onChange, + validation, + isValidatedMessages, + }); + + return ( +
+
+ +
+ + {hint && !error && ( + + )} + + {error && } + + {(isValid === true || isValid === false) && !disabled && message !== '' && ( + + )} +
+ ); +}; + +PasswordInput.displayName = 'PasswordInput'; + +PasswordInput.propTypes = { + placeholder: PropTypes.string, + value: PropTypes.string, + onChange: PropTypes.func, + error: PropTypes.string, + name: PropTypes.string, + dataCy: PropTypes.string, + minLength: PropTypes.number, + hint: PropTypes.string, + disabled: PropTypes.bool, + className: PropTypes.string, + validation: PropTypes.func, + isValidatedMessages: PropTypes.shape({ + valid: PropTypes.bool, + message: PropTypes.string, + }), + required: PropTypes.bool, +}; + +PasswordInput.defaultProps = { + placeholder: 'Create a password', + value: undefined, + onChange: undefined, + error: undefined, + name: 'password', + dataCy: 'password', + minLength: 5, + hint: undefined, + disabled: false, + className: undefined, + validation: undefined, + isValidatedMessages: undefined, + required: true, +}; + +export default PasswordInput; diff --git a/frontend/src/components/Auth/Stories/LoginForm.stories.jsx b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx new file mode 100644 index 0000000000..fc74965362 --- /dev/null +++ b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx @@ -0,0 +1,232 @@ +import { LoginForm } from '../LoginForm'; + +export default { + title: 'Auth/LoginForm', + component: LoginForm, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + signinHeader: { + control: 'text', + description: 'The main heading text for the login form', + }, + signUpText: { + control: 'text', + description: 'Text before the sign up link', + }, + signUpUrl: { + control: 'text', + description: 'URL for the sign up link', + }, + signUpCTA: { + control: 'text', + description: 'Call-to-action text for the sign up link', + }, + emailLabel: { + control: 'text', + description: 'Label for the email input field', + }, + emailPlaceholder: { + control: 'text', + description: 'Placeholder text for the email input', + }, + passwordLabel: { + control: 'text', + description: 'Label for the password input field', + }, + showForgotPassword: { + control: 'boolean', + description: 'Show or hide the forgot password link', + }, + forgotPasswordUrl: { + control: 'text', + description: 'URL for the forgot password link', + }, + forgotPasswordText: { + control: 'text', + description: 'Text for the forgot password link', + }, + signinButtonText: { + control: 'text', + description: 'Text for the submit button', + }, + orText: { + control: 'text', + description: 'Text for the OR separator', + }, + showOrSeparator: { + control: 'boolean', + description: 'Show or hide the OR separator', + }, + emailValue: { + control: 'text', + description: 'Controlled value for email input', + }, + passwordValue: { + control: 'text', + description: 'Controlled value for password input', + }, + emailError: { + control: 'text', + description: 'Error message for email field', + }, + passwordError: { + control: 'text', + description: 'Error message for password field', + }, + isLoading: { + control: 'boolean', + description: 'Loading state for the form', + }, + disabled: { + control: 'boolean', + description: 'Disabled state for the form', + }, + onSubmit: { + action: 'submitted', + description: 'Form submission handler', + }, + onEmailChange: { + action: 'email changed', + description: 'Email input change handler', + }, + onPasswordChange: { + action: 'password changed', + description: 'Password input change handler', + }, + }, +}; + +export const Default = { + args: { + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', + showOrSeparator: true, + }, +}; + +export const CustomHeader = { + args: { + signinHeader: 'Welcome Back', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', + showOrSeparator: true, + }, +}; + +export const WithErrors = { + args: { + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', + showOrSeparator: true, + emailValue: 'invalid-email', + passwordValue: '123', + emailError: 'Please enter a valid email address', + passwordError: 'Password must be at least 8 characters', + }, +}; + +export const Loading = { + args: { + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Signing in...', + orText: 'OR', + showOrSeparator: true, + emailValue: 'user@example.com', + passwordValue: 'password123', + isLoading: true, + disabled: true, + }, +}; + +export const CustomSeparator = { + args: { + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR CONTINUE WITH', + showOrSeparator: true, + }, +}; + +export const NoSeparator = { + args: { + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + showForgotPassword: true, + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', + showOrSeparator: false, + }, +}; + +export const Minimal = { + args: { + signinHeader: 'Login', + signUpText: '', + signUpUrl: '#', + signUpCTA: '', + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + passwordLabel: 'Password', + showForgotPassword: false, + signinButtonText: 'Login', + orText: 'OR', + showOrSeparator: false, + }, +}; diff --git a/frontend/src/components/Auth/Stories/PasswordInput.stories.jsx b/frontend/src/components/Auth/Stories/PasswordInput.stories.jsx new file mode 100644 index 0000000000..9696ca0446 --- /dev/null +++ b/frontend/src/components/Auth/Stories/PasswordInput.stories.jsx @@ -0,0 +1,145 @@ +import React from 'react'; +import PasswordInput from '../PasswordInput'; + +export default { + title: 'Auth/PasswordInput', + component: PasswordInput, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + label: { + control: 'text', + description: 'Label text for the password input', + }, + placeholder: { + control: 'text', + description: 'Placeholder text for the input field', + }, + value: { + control: 'text', + description: 'Current value of the password input', + }, + onChange: { + action: 'changed', + description: 'Callback function when input value changes', + }, + error: { + control: 'text', + description: 'Error message to display below the input', + }, + name: { + control: 'text', + description: 'Name attribute for the input field', + }, + dataCy: { + control: 'text', + description: 'Data-cy attribute for testing', + }, + minLength: { + control: 'number', + description: 'Minimum length requirement for the password', + }, + hint: { + control: 'text', + description: 'Hint text to display below the input', + }, + disabled: { + control: 'boolean', + description: 'Whether the input is disabled', + }, + showForgotPassword: { + control: 'boolean', + description: 'Whether to show the forgot password link', + }, + className: { + control: 'text', + description: 'Additional CSS classes', + }, + }, +}; + +const Template = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + label: 'Password', + placeholder: 'Enter your password', + value: '', + name: 'password', + dataCy: 'password', + minLength: 8, + hint: 'Password must be at least 8 characters', + disabled: false, + showForgotPassword: false, +}; + +export const WithForgotPassword = Template.bind({}); +WithForgotPassword.args = { + ...Default.args, + showForgotPassword: true, +}; + +export const WithError = Template.bind({}); +WithError.args = { + ...Default.args, + value: '123', + error: 'Password must be at least 8 characters long', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + ...Default.args, + value: 'mypassword123', + disabled: true, +}; + +export const CustomHint = Template.bind({}); +CustomHint.args = { + ...Default.args, + hint: 'Use a strong password with letters, numbers, and symbols', +}; + +export const CustomLabel = Template.bind({}); +CustomLabel.args = { + ...Default.args, + label: 'New Password', + placeholder: 'Create a new password', + hint: 'Choose a secure password for your account', +}; + +export const WithValue = Template.bind({}); +WithValue.args = { + ...Default.args, + value: 'mySecurePassword123!', +}; + +export const ShortPassword = Template.bind({}); +ShortPassword.args = { + ...Default.args, + minLength: 12, + hint: 'Password must be at least 12 characters', + value: 'short', + error: 'Password is too short', +}; + +export const LoginFormStyle = Template.bind({}); +LoginFormStyle.args = { + ...Default.args, + label: 'Password', + placeholder: 'Enter your password', + showForgotPassword: true, + hint: 'Enter your account password', +}; + +export const SignupFormStyle = Template.bind({}); +SignupFormStyle.args = { + ...Default.args, + label: 'Create Password', + placeholder: 'Create a strong password', + hint: 'Password must be at least 8 characters with letters and numbers', +}; + + + diff --git a/frontend/src/components/login-form.jsx b/frontend/src/components/login-form.jsx new file mode 100644 index 0000000000..002e142ce7 --- /dev/null +++ b/frontend/src/components/login-form.jsx @@ -0,0 +1,53 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import Logo from '@/modules/common/resources/images/Logo'; + +export function LoginForm({ className, ...props }) { + return ( +
+
+

Login to your account

+

+ Enter your email below to login to your account +

+
+
+
+ + +
+
+ + +
+ +
+ + Or continue with + +
+ +
+
+ Don't have an account?{' '} + + Sign up + +
+
+ ); +} diff --git a/frontend/src/components/ui/Input/CommonInput/Index.jsx b/frontend/src/components/ui/Input/CommonInput/Index.jsx index c992db5aaa..5f6f657029 100644 --- a/frontend/src/components/ui/Input/CommonInput/Index.jsx +++ b/frontend/src/components/ui/Input/CommonInput/Index.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import NumberInput from './NumberInput'; import TextInput from './TextInput'; import { HelperMessage, InputLabel, ValidationMessage } from '../InputUtils/InputUtils'; -import { ButtonSolid } from '../../../../_components/AppButton'; +import { Button } from '@/components/ui/button'; import { generateCypressDataCy } from '../../../../modules/common/helpers/cypressHelpers.js'; const CommonInput = ({ label, helperText, disabled, required, onChange: change, ...restProps }) => { @@ -62,8 +62,7 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, {type === 'password' && (
- {isEditing ? 'Cancel' : 'Edit'} - +
diff --git a/frontend/src/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 7e0eda7465..0c6b1b27ca 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -1,11 +1,13 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; import { inputVariants } from './InputUtils/Variants'; -import SolidIcon from '../../../_ui/Icon/SolidIcons'; +import { Eye, EyeOff } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + import { useEffect } from 'react'; -const Input = React.forwardRef( - ({ className, size, type, multiline, response, isWorkspaceConstant, rows = 3, ...props }, ref) => { +export const Input = React.forwardRef( + ({ className, size, type, multiline, response, isValid, isWorkspaceConstant, rows = 3, ...props }, ref) => { const [isPasswordVisible, setIsPasswordVisible] = React.useState(false); const isPasswordField = type === 'password'; @@ -23,6 +25,12 @@ const Input = React.forwardRef( const validationClass = response === true ? 'valid-textarea' : response === false ? 'invalid-textarea' : ''; + const inputStyle = ` ${ + props.disabled ? 'placeholder:tw-text-text-placeholder' : 'placeholder:tw-text-text-default' + } ${ + isValid === true ? '!tw-border-border-success-strong' : isValid === false ? '!tw-border-border-danger-strong' : '' + }`; + return (
{multiline ? ( @@ -37,23 +45,30 @@ const Input = React.forwardRef( {...props} /> ) : ( - - )} - {isPasswordField && !multiline && ( -
- {isPasswordVisible ? ( - - ) : ( - +
+ + {isPasswordField && ( + )}
)} @@ -62,5 +77,3 @@ const Input = React.forwardRef( } ); Input.displayName = 'Input'; - -export { Input }; diff --git a/frontend/src/components/ui/Label/Label.jsx b/frontend/src/components/ui/Label/Label.jsx index b691f8fb58..907bec21cd 100644 --- a/frontend/src/components/ui/Label/Label.jsx +++ b/frontend/src/components/ui/Label/Label.jsx @@ -5,11 +5,11 @@ import * as LabelPrimitive from '@radix-ui/react-label'; import { cva } from 'class-variance-authority'; import { cn } from '@/lib/utils'; -const labelVariants = cva('peer-disabled:tw-cursor-not-allowed peer-disabled:tw-opacity-70', { +const labelVariants = cva('tw-font-medium peer-disabled:tw-cursor-not-allowed peer-disabled:tw-opacity-70', { variants: { type: { - label: `tw-text-text-default`, - helper: `tw-text-text-placeholder`, + label: 'tw-text-text-default', + helper: 'tw-text-text-placeholder', }, }, compoundVariants: [ @@ -36,7 +36,7 @@ const labelVariants = cva('peer-disabled:tw-cursor-not-allowed peer-disabled:tw- ], }); -const Label = React.forwardRef(({ className, size, type, ...restProps }, ref) => ( +const Label = React.forwardRef(({ className, size = 'default', type = 'label', ...restProps }, ref) => ( )); Label.displayName = LabelPrimitive.Root.displayName; diff --git a/frontend/src/components/ui/button.jsx b/frontend/src/components/ui/button.jsx new file mode 100644 index 0000000000..a6fffcd550 --- /dev/null +++ b/frontend/src/components/ui/button.jsx @@ -0,0 +1,3 @@ +import { Button, buttonVariants } from './Button/Button'; + +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/card.jsx b/frontend/src/components/ui/card.jsx new file mode 100644 index 0000000000..40c320166b --- /dev/null +++ b/frontend/src/components/ui/card.jsx @@ -0,0 +1,3 @@ +import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './Card/Index'; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/frontend/src/components/ui/input.jsx b/frontend/src/components/ui/input.jsx new file mode 100644 index 0000000000..ac963997f0 --- /dev/null +++ b/frontend/src/components/ui/input.jsx @@ -0,0 +1,3 @@ +import InputComponent from './Input/Index'; + +export { InputComponent as Input }; diff --git a/frontend/src/components/ui/label.jsx b/frontend/src/components/ui/label.jsx new file mode 100644 index 0000000000..25def6d8a2 --- /dev/null +++ b/frontend/src/components/ui/label.jsx @@ -0,0 +1,3 @@ +import { Label } from './Label/Label'; + +export { Label }; diff --git a/frontend/src/hooks/usePasswordInput.js b/frontend/src/hooks/usePasswordInput.js new file mode 100644 index 0000000000..ede924ff1d --- /dev/null +++ b/frontend/src/hooks/usePasswordInput.js @@ -0,0 +1,47 @@ +import { useState, useEffect } from 'react'; + +/** + * Custom hook for password input functionality + * @param {Object} options - Configuration options + * @param {Function} options.onChange - Change handler function + * @param {Function} options.validation - Validation function + * @param {Object} options.isValidatedMessages - External validation messages + * @param {boolean} options.disabled - Whether input is disabled + * @returns {Object} Password input state and handlers + */ +export const usePasswordInput = ({ onChange, validation, isValidatedMessages }) => { + const [isValid, setIsValid] = useState(null); + const [message, setMessage] = useState(''); + + /** + * Handle input change with validation + * @param {Event} e - Input change event + */ + const handleChange = (e) => { + if (validation) { + const validateObj = validation(e); + setIsValid(validateObj.valid); + setMessage(validateObj.message); + onChange(e, validateObj); + } else { + onChange(e); + } + }; + + // Update validation state when external validation messages change + useEffect(() => { + if (isValidatedMessages) { + setIsValid(isValidatedMessages.valid); + setMessage(isValidatedMessages.message); + } + }, [isValidatedMessages]); + + return { + // State + isValid, + message, + + // Handlers + handleChange, + }; +}; diff --git a/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx b/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx index 770bb4cbe1..35129447ae 100644 --- a/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx +++ b/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx @@ -2,6 +2,9 @@ import React from 'react'; import './resources/styles/background.styles.scss'; import { defaultWhiteLabellingSettings } from '@white-label/whiteLabelling'; import { useWhiteLabellingStore } from '@/_stores/whiteLabellingStore'; +import { GalleryVerticalEnd } from 'lucide-react'; + +import { LoginForm } from '@/components/login-form'; import WhiteLabellingBackgroundWrapper from '@/modules/onboarding/components/WhiteLabellingBackgroundWrapper'; const OnboardingBackgroundWrapper = ({ LeftSideComponent, @@ -19,27 +22,27 @@ const OnboardingBackgroundWrapper = ({ return } />; } return ( -
-
- {MiddleComponent ? ( -
-
- +
+
+ +
+
+
- ) : ( -
-
- -
-
- -
-
- )} +
+
+
+
); }; -export default OnboardingBackgroundWrapper; +export default OnboardingBackgroundWrapper; \ No newline at end of file diff --git a/frontend/src/stories/Configure.mdx b/frontend/src/stories/Configure.mdx index a50fcea3ed..77cbbcd0fe 100644 --- a/frontend/src/stories/Configure.mdx +++ b/frontend/src/stories/Configure.mdx @@ -1,4 +1,4 @@ -import { Meta } from "@storybook/blocks"; +import { Meta } from "@storybook/addon-docs/blocks"; import Github from "./assets/github.svg"; import Discord from "./assets/discord.svg"; From be552c8f23eecda3328c86c86dbb4fe56a1f423f Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:53:41 +0530 Subject: [PATCH 050/157] Add GitHub and Google sign-in buttons, along with corresponding icons and stories - Introduced `GitHubSigninButton` and `GoogleSigninButton` components for authentication. - Added `GitHub` and `Google` icons to the solid icons collection. - Updated the `SolidIcons` index to include new icons. - Created Storybook stories for both sign-in buttons to demonstrate usage and variations. - Refactored `LoginForm` to support external validation and improved input handling. --- frontend/src/_ui/Icon/solidIcons/GitHub.jsx | 26 ++ frontend/src/_ui/Icon/solidIcons/Google.jsx | 58 +++ frontend/src/_ui/Icon/solidIcons/index.js | 6 + .../components/Auth/GitHubSigninButton.jsx | 21 + .../components/Auth/GoogleSigninButton.jsx | 21 + frontend/src/components/Auth/LoginForm.jsx | 76 ++-- .../src/components/Auth/PasswordInput.jsx | 103 ----- .../Stories/GitHubSigninButton.stories.jsx | 62 +++ .../Stories/GoogleSigninButton.stories.jsx | 62 +++ .../Auth/Stories/LoginForm.stories.jsx | 390 ++++++++++++------ .../Auth/Stories/PasswordInput.stories.jsx | 145 ------- .../components/ui/Input/CommonInput/Index.jsx | 5 +- 12 files changed, 562 insertions(+), 413 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/GitHub.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/Google.jsx create mode 100644 frontend/src/components/Auth/GitHubSigninButton.jsx create mode 100644 frontend/src/components/Auth/GoogleSigninButton.jsx delete mode 100644 frontend/src/components/Auth/PasswordInput.jsx create mode 100644 frontend/src/components/Auth/Stories/GitHubSigninButton.stories.jsx create mode 100644 frontend/src/components/Auth/Stories/GoogleSigninButton.stories.jsx delete mode 100644 frontend/src/components/Auth/Stories/PasswordInput.stories.jsx diff --git a/frontend/src/_ui/Icon/solidIcons/GitHub.jsx b/frontend/src/_ui/Icon/solidIcons/GitHub.jsx new file mode 100644 index 0000000000..5fcb4037e9 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/GitHub.jsx @@ -0,0 +1,26 @@ +import React from 'react'; + +const GitHub = (props) => { + const { width = 24, height = 24, ...restProps } = props; + + return ( + + GitHub + + + ); +}; + +export default GitHub; diff --git a/frontend/src/_ui/Icon/solidIcons/Google.jsx b/frontend/src/_ui/Icon/solidIcons/Google.jsx new file mode 100644 index 0000000000..0dff57d02b --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Google.jsx @@ -0,0 +1,58 @@ +import React from "react"; + +const Google = (props) => { + const { width = 24, height = 24, ...restProps } = props; + + return ( + + Google + + + + + + + + + + + + + + + + + + ); +}; + +export default Google; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 240bed8876..4efd3f65e6 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -287,6 +287,8 @@ import AITopupWhite from './AITopupWhite.jsx'; import AITopupGrey from './AITopupGrey.jsx'; import MobileEmptyStateIcon from './MobileEmptyStateIcon'; import MobileEmptyStateIconDark from './MobileEmptyStateIconDark'; +import Google from './Google.jsx'; +import GitHub from './GitHub.jsx'; const Icon = (props) => { switch (props.name) { @@ -867,6 +869,10 @@ const Icon = (props) => { return ; case 'ai-topup-grey': return ; + case 'google': + return ; + case 'github': + return ; default: return ; } diff --git a/frontend/src/components/Auth/GitHubSigninButton.jsx b/frontend/src/components/Auth/GitHubSigninButton.jsx new file mode 100644 index 0000000000..cd4b75c32e --- /dev/null +++ b/frontend/src/components/Auth/GitHubSigninButton.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; + +export const GitHubSigninButton = ({ onClick, icon, text, dataCy }) => { + return ( + + ); +}; diff --git a/frontend/src/components/Auth/GoogleSigninButton.jsx b/frontend/src/components/Auth/GoogleSigninButton.jsx new file mode 100644 index 0000000000..4246f24ef2 --- /dev/null +++ b/frontend/src/components/Auth/GoogleSigninButton.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; + +export const GoogleSigninButton = ({ onClick, icon, text, dataCy }) => { + return ( + + ); +}; diff --git a/frontend/src/components/Auth/LoginForm.jsx b/frontend/src/components/Auth/LoginForm.jsx index 8e84d20466..f6fb490304 100644 --- a/frontend/src/components/Auth/LoginForm.jsx +++ b/frontend/src/components/Auth/LoginForm.jsx @@ -5,33 +5,33 @@ import { CornerDownLeft } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/Input/Input.jsx'; +import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { FormWrapper } from '@/components/Auth/FormWrapper'; -import { PasswordInput } from '@/components/Auth/PasswordInput'; export function LoginForm({ className, ...props }) { const { // Header and sign up section - signinHeader = 'Sign in', - signUpText = 'New to ToolJet?', - signUpUrl = '#', - signUpCTA = 'Create an account', + signinHeader, + signUpText, + signUpUrl, + signUpCTA, // Form fields - emailLabel = 'Email', - emailPlaceholder = 'm@example.com', - passwordLabel = 'Password', + emailLabel, + emailPlaceholder, + passwordLabel, + passwordPlaceholder, // Forgot password - showForgotPassword = true, - forgotPasswordUrl = '/forgot-password', - forgotPasswordText = 'Forgot?', + showForgotPassword, + forgotPasswordUrl, + forgotPasswordText, // Button and separator - signinButtonText = 'Sign in', - orText = 'OR', - showOrSeparator = true, + signinButtonText, + orText, + showOrSeparator, // Form functionality onSubmit, @@ -41,15 +41,17 @@ export function LoginForm({ className, ...props }) { onPasswordChange, // Validation and state - emailError, - passwordError, - isLoading = false, - disabled = false, + emailValidation, + passwordValidation, + emailValidationMessage, + passwordValidationMessage, + isLoading, + disabled, } = props; return (
-
+

{signinHeader}

@@ -71,18 +73,18 @@ export function LoginForm({ className, ...props }) {
- - {emailError &&

{emailError}

}
@@ -98,17 +100,18 @@ export function LoginForm({ className, ...props }) { )}
- - {passwordError &&

{passwordError}

}
)} - {type === 'password' && ( + {showEncryption && type === 'password' && (
@@ -144,7 +160,8 @@ LoginForm.propTypes = { signUpText: PropTypes.string, signUpUrl: PropTypes.string, signUpCTA: PropTypes.string, - + showSignup: PropTypes.bool, + organizationName: PropTypes.string, // Form fields emailLabel: PropTypes.string, emailPlaceholder: PropTypes.string, @@ -184,7 +201,8 @@ LoginForm.defaultProps = { signUpText: 'New to ToolJet?', signUpUrl: '#', signUpCTA: 'Create an account', - + showSignup: true, + organizationName: '', // Form fields emailLabel: 'Email', emailPlaceholder: 'Enter your work email', diff --git a/frontend/src/components/Auth/Stories/LoginForm.stories.jsx b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx index 065e939501..de18ae765f 100644 --- a/frontend/src/components/Auth/Stories/LoginForm.stories.jsx +++ b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx @@ -24,6 +24,14 @@ export default { control: "text", description: "Call-to-action text for the sign up link", }, + showSignup: { + control: "boolean", + description: "Show or hide the signup section", + }, + organizationName: { + control: "text", + description: "Organization name to display in the header", + }, emailLabel: { control: "text", description: "Label for the email input field", @@ -36,6 +44,10 @@ export default { control: "text", description: "Label for the password input field", }, + passwordPlaceholder: { + control: "text", + description: "Placeholder text for the password input", + }, showForgotPassword: { control: "boolean", description: "Show or hide the forgot password link", @@ -68,14 +80,6 @@ export default { control: "text", description: "Controlled value for password input", }, - emailError: { - control: "text", - description: "Error message for email field", - }, - passwordError: { - control: "text", - description: "Error message for password field", - }, isLoading: { control: "boolean", description: "Loading state for the form", @@ -121,8 +125,12 @@ export const Default = { signUpText: "New to ToolJet?", signUpUrl: "#", signUpCTA: "Create an account", + showSignup: true, + organizationName: "", emailLabel: "Email", + emailPlaceholder: "Enter your work email", passwordLabel: "Password", + passwordPlaceholder: "Enter password", showForgotPassword: true, forgotPasswordUrl: "/forgot-password", forgotPasswordText: "Forgot?", @@ -138,9 +146,138 @@ export const CustomHeader = { signUpText: "New to ToolJet?", signUpUrl: "#", signUpCTA: "Create an account", + showSignup: true, + organizationName: "", emailLabel: "Email", emailPlaceholder: "m@example.com", passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const WithOrganization = { + args: { + signinHeader: "Sign in", + signUpText: "New to ToolJet?", + signUpUrl: "#", + signUpCTA: "Create an account", + showSignup: true, + organizationName: "Acme Corporation", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const OrganizationOnly = { + args: { + signinHeader: "Sign in", + signUpText: "", + signUpUrl: "#", + signUpCTA: "", + showSignup: false, + organizationName: "TechCorp Inc.", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const LongOrganizationName = { + args: { + signinHeader: "Sign in", + signUpText: "New to ToolJet?", + signUpUrl: "#", + signUpCTA: "Create an account", + showSignup: true, + organizationName: "Very Long Organization Name That Might Wrap", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const OrganizationWithSpecialChars = { + args: { + signinHeader: "Sign in", + signUpText: "New to ToolJet?", + signUpUrl: "#", + signUpCTA: "Create an account", + showSignup: true, + organizationName: "Acme & Co. (Ltd.)", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const EmptyOrganizationName = { + args: { + signinHeader: "Sign in", + signUpText: "New to ToolJet?", + signUpUrl: "#", + signUpCTA: "Create an account", + showSignup: true, + organizationName: "", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", + showForgotPassword: true, + forgotPasswordUrl: "/forgot-password", + forgotPasswordText: "Forgot?", + signinButtonText: "Sign in", + orText: "OR", + showOrSeparator: true, + }, +}; + +export const NoOrganizationNoSignup = { + args: { + signinHeader: "Sign in", + signUpText: "", + signUpUrl: "#", + signUpCTA: "", + showSignup: false, + organizationName: "", + emailLabel: "Email", + emailPlaceholder: "Enter your work email", + passwordLabel: "Password", + passwordPlaceholder: "Enter password", showForgotPassword: true, forgotPasswordUrl: "/forgot-password", forgotPasswordText: "Forgot?", @@ -242,9 +379,12 @@ export const Minimal = { signUpText: "", signUpUrl: "#", signUpCTA: "", + showSignup: false, + organizationName: "", emailLabel: "Email", emailPlaceholder: "Enter your email", passwordLabel: "Password", + passwordPlaceholder: "Enter password", showForgotPassword: false, signinButtonText: "Login", orText: "OR", diff --git a/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx b/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx index 35129447ae..770bb4cbe1 100644 --- a/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx +++ b/frontend/src/modules/onboarding/components/OnboardingBackgroundWrapper/OnboardingBackgroundWrapper.jsx @@ -2,9 +2,6 @@ import React from 'react'; import './resources/styles/background.styles.scss'; import { defaultWhiteLabellingSettings } from '@white-label/whiteLabelling'; import { useWhiteLabellingStore } from '@/_stores/whiteLabellingStore'; -import { GalleryVerticalEnd } from 'lucide-react'; - -import { LoginForm } from '@/components/login-form'; import WhiteLabellingBackgroundWrapper from '@/modules/onboarding/components/WhiteLabellingBackgroundWrapper'; const OnboardingBackgroundWrapper = ({ LeftSideComponent, @@ -22,27 +19,27 @@ const OnboardingBackgroundWrapper = ({ return } />; } return ( -
-
-
- -
- +
+ -
- + ) : ( +
+
+ +
+
+ +
+
+ )}
); }; -export default OnboardingBackgroundWrapper; \ No newline at end of file +export default OnboardingBackgroundWrapper; diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 6f96d3fca3..63f39da8d2 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -124,6 +124,7 @@ module.exports = { fallback: { process: require.resolve('process/browser.js'), path: require.resolve('path-browserify'), + util: require.resolve('util/'), '@ee/modules': emptyModulePath, '@cloud/modules': emptyModulePath, }, @@ -209,7 +210,15 @@ module.exports = { options: { plugins: [ isDevEnv && require.resolve('react-refresh/babel'), - ['import', { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false }, 'lodash'], + [ + 'import', + { + libraryName: 'lodash', + libraryDirectory: '', + camel2DashComponentName: false, + }, + 'lodash', + ], ].filter(Boolean), }, }, From 01df4b8098f87640a15ee7e8f7da5d744b6c9efd Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:18:35 +0530 Subject: [PATCH 052/157] Add Forgot Password components and enhance authentication flow - Introduced `ForgotPasswordForm` and `ForgotPasswordInfoScreen` components for improved password recovery functionality. - Implemented form validation and state management for the `ForgotPasswordForm`. - Created Storybook stories for both components to demonstrate various use cases and configurations. - Updated `LoginForm` styles for consistency with new components. - Enhanced overall user experience in the authentication flow. --- .../components/Auth/ForgotPasswordForm.jsx | 200 +++++++ .../Auth/ForgotPasswordInfoScreen.jsx | 123 ++++ frontend/src/components/Auth/LoginForm.jsx | 2 +- .../Stories/ForgotPasswordForm.stories.jsx | 270 +++++++++ .../ForgotPasswordInfoScreen.stories.jsx | 167 ++++++ .../Auth/Stories/LoginForm.stories.jsx | 538 +++++++++--------- 6 files changed, 1030 insertions(+), 270 deletions(-) create mode 100644 frontend/src/components/Auth/ForgotPasswordForm.jsx create mode 100644 frontend/src/components/Auth/ForgotPasswordInfoScreen.jsx create mode 100644 frontend/src/components/Auth/Stories/ForgotPasswordForm.stories.jsx create mode 100644 frontend/src/components/Auth/Stories/ForgotPasswordInfoScreen.stories.jsx diff --git a/frontend/src/components/Auth/ForgotPasswordForm.jsx b/frontend/src/components/Auth/ForgotPasswordForm.jsx new file mode 100644 index 0000000000..0ecf2ebdef --- /dev/null +++ b/frontend/src/components/Auth/ForgotPasswordForm.jsx @@ -0,0 +1,200 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import { CornerDownLeft } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { FormWrapper } from '@/components/Auth/FormWrapper'; + +export function ForgotPasswordForm({ className, ...props }) { + const { + // Header and sign up section + headerText, + signupText, + signupUrl, + signupCTA, + showSignup, + + // Form fields + emailLabel, + emailPlaceholder, + + // Button + buttonText, + + // Admin banner + adminContactText, + showAdminBanner, + showSeparator, + + // Form functionality + onSubmit, + emailValue, + onEmailChange, + + // Validation and state + emailValidation, + emailValidationMessage, + isLoading, + disabled, + } = props; + + return ( + + +
+

+ {headerText} +

+ + {showSignup && ( +

+ {signupText} + + + {signupCTA} + + +

+ )} +
+ +
+
+ +
+ + + + {showSeparator && ( +
+
+ OR +
+
+ )} + + {showAdminBanner && ( +
+
+ + Information + + +
+

+ {adminContactText} +

+
+ )} +
+ + + ); +} + +ForgotPasswordForm.propTypes = { + className: PropTypes.string, + + // Header and sign up section + headerText: PropTypes.string, + signupText: PropTypes.string, + signupUrl: PropTypes.string, + signupCTA: PropTypes.string, + showSignup: PropTypes.bool, + + // Form fields + emailLabel: PropTypes.string, + emailPlaceholder: PropTypes.string, + + // Button + buttonText: PropTypes.string, + + // Admin banner + adminContactText: PropTypes.string, + showAdminBanner: PropTypes.bool, + showSeparator: PropTypes.bool, + + // Form functionality + onSubmit: PropTypes.func, + emailValue: PropTypes.string, + onEmailChange: PropTypes.func, + + // Validation and state + emailValidation: PropTypes.func, + emailValidationMessage: PropTypes.object, + isLoading: PropTypes.bool, + disabled: PropTypes.bool, +}; + +ForgotPasswordForm.defaultProps = { + className: '', + + // Header and sign up section + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + + // Form fields + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + + // Button + buttonText: 'Send a reset link', + + // Admin banner + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + showSeparator: true, + + // Form functionality + onSubmit: undefined, + emailValue: undefined, + onEmailChange: undefined, + + // Validation and state + emailValidation: undefined, + emailValidationMessage: undefined, + isLoading: false, + disabled: false, +}; diff --git a/frontend/src/components/Auth/ForgotPasswordInfoScreen.jsx b/frontend/src/components/Auth/ForgotPasswordInfoScreen.jsx new file mode 100644 index 0000000000..9b1607aacf --- /dev/null +++ b/frontend/src/components/Auth/ForgotPasswordInfoScreen.jsx @@ -0,0 +1,123 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { ArrowLeft } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { FormWrapper } from '@/components/Auth/FormWrapper'; + +export function ForgotPasswordInfoScreen({ className, ...props }) { + const { + // Header + headerText, + + // Message content + messageText, + email, + + // Info text + infoText, + showInfo, + + // Button + buttonText, + onBackToLogin, + + // Separator + showSeparator, + } = props; + + return ( + +
+
+

+ {headerText} +

+
+ +
+

+ {messageText} + {email && ( + <> + {' '} + {email} + + )} + . +

+ + {showInfo && ( +

+ {infoText} +

+ )} + + {showSeparator && ( +
+
+ OR +
+
+ )} + + +
+
+ + ); +} + +ForgotPasswordInfoScreen.propTypes = { + className: PropTypes.string, + + // Header + headerText: PropTypes.string, + + // Message content + messageText: PropTypes.string, + email: PropTypes.string, + + // Info text + infoText: PropTypes.string, + showInfo: PropTypes.bool, + + // Button + buttonText: PropTypes.string, + onBackToLogin: PropTypes.func, + + // Separator + showSeparator: PropTypes.bool, +}; + +ForgotPasswordInfoScreen.defaultProps = { + className: '', + + // Header + headerText: 'Check your mail', + + // Message content + messageText: "We've sent a password reset link to", + email: '', + + // Info text + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + + // Button + buttonText: 'Back to login', + onBackToLogin: undefined, + + // Separator + showSeparator: true, +}; diff --git a/frontend/src/components/Auth/LoginForm.jsx b/frontend/src/components/Auth/LoginForm.jsx index c65b3e33c0..cf0207e28e 100644 --- a/frontend/src/components/Auth/LoginForm.jsx +++ b/frontend/src/components/Auth/LoginForm.jsx @@ -54,7 +54,7 @@ export function LoginForm({ className, ...props }) {
-

+

{signinHeader}

diff --git a/frontend/src/components/Auth/Stories/ForgotPasswordForm.stories.jsx b/frontend/src/components/Auth/Stories/ForgotPasswordForm.stories.jsx new file mode 100644 index 0000000000..5821647fcb --- /dev/null +++ b/frontend/src/components/Auth/Stories/ForgotPasswordForm.stories.jsx @@ -0,0 +1,270 @@ +import React from 'react'; +import { ForgotPasswordForm } from '../ForgotPasswordForm'; + +export default { + title: 'Auth/ForgotPasswordForm', + component: ForgotPasswordForm, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + headerText: { + control: 'text', + description: 'The main heading text for the forgot password form', + }, + signupText: { + control: 'text', + description: 'Text before the sign up link', + }, + signupUrl: { + control: 'text', + description: 'URL for the sign up link', + }, + signupCTA: { + control: 'text', + description: 'Call-to-action text for the sign up link', + }, + showSignup: { + control: 'boolean', + description: 'Show or hide the signup section', + }, + emailLabel: { + control: 'text', + description: 'Label for the email input field', + }, + emailPlaceholder: { + control: 'text', + description: 'Placeholder text for the email input', + }, + buttonText: { + control: 'text', + description: 'Text for the submit button', + }, + adminContactText: { + control: 'text', + description: 'Text for the admin contact banner', + }, + showAdminBanner: { + control: 'boolean', + description: 'Show or hide the admin contact banner', + }, + emailValue: { + control: 'text', + description: 'Controlled value for email input', + }, + isLoading: { + control: 'boolean', + description: 'Loading state for the form', + }, + disabled: { + control: 'boolean', + description: 'Disabled state for the form', + }, + onSubmit: { + action: 'submitted', + description: 'Form submission handler', + }, + onEmailChange: { + action: 'email changed', + description: 'Email input change handler', + }, + emailValidation: { + control: false, + description: 'Email validation function', + }, + emailValidationMessage: { + control: 'object', + description: 'External email validation message object', + }, + }, +}; + +export const Default = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + }, +}; + +export const CustomHeader = { + args: { + headerText: 'Reset Your Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter your work email', + buttonText: 'Send reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + }, +}; + +export const NoSignup = { + args: { + headerText: 'Forgot Password', + signupText: '', + signupUrl: '#', + signupCTA: '', + showSignup: false, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + }, +}; + +export const NoAdminBanner = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: false, + }, +}; + +export const WithEmailValue = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + emailValue: 'user@example.com', + }, +}; + +export const WithValidationError = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + emailValue: 'invalid-email', + emailValidationMessage: { + valid: false, + message: 'Please enter a valid email address', + }, + }, +}; + +export const Loading = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Sending...', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + emailValue: 'user@example.com', + isLoading: true, + disabled: true, + }, +}; + +export const Disabled = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + disabled: true, + }, +}; + +export const WithValidation = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact admin to reset your password', + showAdminBanner: true, + emailValidation: (e) => { + const value = e.target.value; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!value) { + return { valid: false, message: 'Email is required' }; + } + + if (!emailRegex.test(value)) { + return { valid: false, message: 'Please enter a valid email address' }; + } + + return { valid: true, message: 'Email looks good!' }; + }, + }, +}; + +export const Minimal = { + args: { + headerText: 'Reset Password', + signupText: '', + signupUrl: '#', + signupCTA: '', + showSignup: false, + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + buttonText: 'Reset', + adminContactText: '', + showAdminBanner: false, + }, +}; + +export const CustomAdminText = { + args: { + headerText: 'Forgot Password', + signupText: 'New to ToolJet?', + signupUrl: '#', + signupCTA: 'Create an account', + showSignup: true, + emailLabel: 'Email address', + emailPlaceholder: 'Enter email address', + buttonText: 'Send a reset link', + adminContactText: 'Contact your system administrator for password reset assistance', + showAdminBanner: true, + }, +}; diff --git a/frontend/src/components/Auth/Stories/ForgotPasswordInfoScreen.stories.jsx b/frontend/src/components/Auth/Stories/ForgotPasswordInfoScreen.stories.jsx new file mode 100644 index 0000000000..a7c9d145f9 --- /dev/null +++ b/frontend/src/components/Auth/Stories/ForgotPasswordInfoScreen.stories.jsx @@ -0,0 +1,167 @@ +import React from 'react'; +import { ForgotPasswordInfoScreen } from '../ForgotPasswordInfoScreen'; + +export default { + title: 'Auth/ForgotPasswordInfoScreen', + component: ForgotPasswordInfoScreen, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + headerText: { + control: 'text', + description: 'The main heading text for the info screen', + }, + messageText: { + control: 'text', + description: 'Main message text', + }, + email: { + control: 'text', + description: 'Email address to display in the message', + }, + infoText: { + control: 'text', + description: 'Additional info text', + }, + showInfo: { + control: 'boolean', + description: 'Show or hide the info text', + }, + buttonText: { + control: 'text', + description: 'Text for the back to login button', + }, + onBackToLogin: { + action: 'back to login clicked', + description: 'Back to login button handler', + }, + showSeparator: { + control: 'boolean', + description: 'Show or hide the separator', + }, + }, +}; + +export const Default = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to", + email: 'user@example.com', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Back to login', + showSeparator: true, + }, +}; + +export const CustomHeader = { + args: { + headerText: 'Password Reset Sent', + messageText: "We've sent a password reset link to", + email: 'user@example.com', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Back to login', + showSeparator: true, + }, +}; + +export const LongEmail = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to", + email: 'very.long.email.address@verylongdomainname.com', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Back to login', + showSeparator: true, + }, +}; + +export const NoEmail = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to your registered email address", + email: '', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Back to login', + showSeparator: true, + }, +}; + +export const NoInfo = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to", + email: 'user@example.com', + infoText: '', + showInfo: false, + buttonText: 'Back to login', + showSeparator: true, + }, +}; + +export const NoSeparator = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to", + email: 'user@example.com', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Back to login', + showSeparator: false, + }, +}; + +export const CustomMessage = { + args: { + headerText: 'Check your mail', + messageText: 'A password reset link has been sent to your email address', + email: 'user@example.com', + infoText: 'Please check your inbox and follow the instructions to reset your password', + showInfo: true, + buttonText: 'Return to login', + showSeparator: true, + }, +}; + +export const Minimal = { + args: { + headerText: 'Email Sent', + messageText: 'Check your email for reset instructions', + email: '', + infoText: '', + showInfo: false, + buttonText: 'Back', + showSeparator: false, + }, +}; + +export const CustomButtonText = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a password reset link to", + email: 'user@example.com', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + buttonText: 'Return to sign in', + showSeparator: true, + }, +}; + +export const LongMessage = { + args: { + headerText: 'Check your mail', + messageText: + "We've sent a password reset link to your registered email address. Please check your inbox and click the link to reset your password", + email: 'user@example.com', + infoText: + 'Did not receive an email? Please check your spam folder or contact support if you continue to have issues', + showInfo: true, + buttonText: 'Back to login', + showSeparator: true, + }, +}; diff --git a/frontend/src/components/Auth/Stories/LoginForm.stories.jsx b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx index de18ae765f..46f15bfe0f 100644 --- a/frontend/src/components/Auth/Stories/LoginForm.stories.jsx +++ b/frontend/src/components/Auth/Stories/LoginForm.stories.jsx @@ -1,337 +1,337 @@ -import { LoginForm } from "../LoginForm"; +import { LoginForm } from '../LoginForm'; export default { - title: "Auth/LoginForm", + title: 'Auth/LoginForm', component: LoginForm, parameters: { - layout: "centered", + layout: 'centered', }, - tags: ["autodocs"], + tags: ['autodocs'], argTypes: { signinHeader: { - control: "text", - description: "The main heading text for the login form", + control: 'text', + description: 'The main heading text for the login form', }, signUpText: { - control: "text", - description: "Text before the sign up link", + control: 'text', + description: 'Text before the sign up link', }, signUpUrl: { - control: "text", - description: "URL for the sign up link", + control: 'text', + description: 'URL for the sign up link', }, signUpCTA: { - control: "text", - description: "Call-to-action text for the sign up link", + control: 'text', + description: 'Call-to-action text for the sign up link', }, showSignup: { - control: "boolean", - description: "Show or hide the signup section", + control: 'boolean', + description: 'Show or hide the signup section', }, organizationName: { - control: "text", - description: "Organization name to display in the header", + control: 'text', + description: 'Organization name to display in the header', }, emailLabel: { - control: "text", - description: "Label for the email input field", + control: 'text', + description: 'Label for the email input field', }, emailPlaceholder: { - control: "text", - description: "Placeholder text for the email input", + control: 'text', + description: 'Placeholder text for the email input', }, passwordLabel: { - control: "text", - description: "Label for the password input field", + control: 'text', + description: 'Label for the password input field', }, passwordPlaceholder: { - control: "text", - description: "Placeholder text for the password input", + control: 'text', + description: 'Placeholder text for the password input', }, showForgotPassword: { - control: "boolean", - description: "Show or hide the forgot password link", + control: 'boolean', + description: 'Show or hide the forgot password link', }, forgotPasswordUrl: { - control: "text", - description: "URL for the forgot password link", + control: 'text', + description: 'URL for the forgot password link', }, forgotPasswordText: { - control: "text", - description: "Text for the forgot password link", + control: 'text', + description: 'Text for the forgot password link', }, signinButtonText: { - control: "text", - description: "Text for the submit button", + control: 'text', + description: 'Text for the submit button', }, orText: { - control: "text", - description: "Text for the OR separator", + control: 'text', + description: 'Text for the OR separator', }, showOrSeparator: { - control: "boolean", - description: "Show or hide the OR separator", + control: 'boolean', + description: 'Show or hide the OR separator', }, emailValue: { - control: "text", - description: "Controlled value for email input", + control: 'text', + description: 'Controlled value for email input', }, passwordValue: { - control: "text", - description: "Controlled value for password input", + control: 'text', + description: 'Controlled value for password input', }, isLoading: { - control: "boolean", - description: "Loading state for the form", + control: 'boolean', + description: 'Loading state for the form', }, disabled: { - control: "boolean", - description: "Disabled state for the form", + control: 'boolean', + description: 'Disabled state for the form', }, onSubmit: { - action: "submitted", - description: "Form submission handler", + action: 'submitted', + description: 'Form submission handler', }, onEmailChange: { - action: "email changed", - description: "Email input change handler", + action: 'email changed', + description: 'Email input change handler', }, onPasswordChange: { - action: "password changed", - description: "Password input change handler", + action: 'password changed', + description: 'Password input change handler', }, emailValidation: { control: false, - description: "Email validation function", + description: 'Email validation function', }, passwordValidation: { control: false, - description: "Password validation function", + description: 'Password validation function', }, emailValidationMessage: { - control: "object", - description: "External email validation message object", + control: 'object', + description: 'External email validation message object', }, passwordValidationMessage: { - control: "object", - description: "External password validation message object", + control: 'object', + description: 'External password validation message object', }, }, }; export const Default = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: '', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const CustomHeader = { args: { - signinHeader: "Welcome Back", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Welcome Back', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "", - emailLabel: "Email", - emailPlaceholder: "m@example.com", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: '', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const WithOrganization = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "Acme Corporation", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: 'Acme Corporation', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const OrganizationOnly = { args: { - signinHeader: "Sign in", - signUpText: "", - signUpUrl: "#", - signUpCTA: "", + signinHeader: 'Sign in', + signUpText: '', + signUpUrl: '#', + signUpCTA: '', showSignup: false, - organizationName: "TechCorp Inc.", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: 'TechCorp Inc.', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const LongOrganizationName = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "Very Long Organization Name That Might Wrap", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: 'Very Long Organization Name That Might Wrap', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const OrganizationWithSpecialChars = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "Acme & Co. (Ltd.)", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: 'Acme & Co. (Ltd.)', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const EmptyOrganizationName = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', showSignup: true, - organizationName: "", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: '', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const NoOrganizationNoSignup = { args: { - signinHeader: "Sign in", - signUpText: "", - signUpUrl: "#", - signUpCTA: "", + signinHeader: 'Sign in', + signUpText: '', + signUpUrl: '#', + signUpCTA: '', showSignup: false, - organizationName: "", - emailLabel: "Email", - emailPlaceholder: "Enter your work email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: '', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, }, }; export const WithErrors = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "m@example.com", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, - emailValue: "invalid-email", - passwordValue: "123", + emailValue: 'invalid-email', + passwordValue: '123', emailValidationMessage: { valid: false, - message: "Please enter a valid email address", + message: 'Please enter a valid email address', }, passwordValidationMessage: { valid: false, - message: "Password must be at least 8 characters", + message: 'Password must be at least 8 characters', }, }, }; export const Loading = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "m@example.com", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Signing in...", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Signing in...', + orText: 'OR', showOrSeparator: true, - emailValue: "user@example.com", - passwordValue: "password123", + emailValue: 'user@example.com', + passwordValue: 'password123', isLoading: true, disabled: true, }, @@ -339,166 +339,166 @@ export const Loading = { export const CustomSeparator = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "m@example.com", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR CONTINUE WITH", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR CONTINUE WITH', showOrSeparator: true, }, }; export const NoSeparator = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "m@example.com", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'm@example.com', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: false, }, }; export const Minimal = { args: { - signinHeader: "Login", - signUpText: "", - signUpUrl: "#", - signUpCTA: "", + signinHeader: 'Login', + signUpText: '', + signUpUrl: '#', + signUpCTA: '', showSignup: false, - organizationName: "", - emailLabel: "Email", - emailPlaceholder: "Enter your email", - passwordLabel: "Password", - passwordPlaceholder: "Enter password", + organizationName: '', + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', showForgotPassword: false, - signinButtonText: "Login", - orText: "OR", + signinButtonText: 'Login', + orText: 'OR', showOrSeparator: false, }, }; export const WithValidation = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "Enter your email", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, emailValidation: (e) => { const value = e.target.value; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!value) { - return { valid: false, message: "Email is required" }; + return { valid: false, message: 'Email is required' }; } if (!emailRegex.test(value)) { - return { valid: false, message: "Please enter a valid email address" }; + return { valid: false, message: 'Please enter a valid email address' }; } - return { valid: true, message: "Email looks good!" }; + return { valid: true, message: 'Email looks good!' }; }, passwordValidation: (e) => { const value = e.target.value; if (!value) { - return { valid: false, message: "Password is required" }; + return { valid: false, message: 'Password is required' }; } if (value.length < 8) { return { valid: false, - message: "Password must be at least 8 characters", + message: 'Password must be at least 8 characters', }; } if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { return { valid: false, - message: "Password must contain uppercase, lowercase, and number", + message: 'Password must contain uppercase, lowercase, and number', }; } - return { valid: true, message: "Password is strong!" }; + return { valid: true, message: 'Password is strong!' }; }, }, }; export const WithExternalValidation = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "Enter your email", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, - emailValue: "user@example.com", - passwordValue: "password123", + emailValue: 'user@example.com', + passwordValue: 'password123', emailValidationMessage: { valid: true, - message: "Email is valid and available", + message: 'Email is valid and available', }, passwordValidationMessage: { valid: true, - message: "Password meets all requirements", + message: 'Password meets all requirements', }, }, }; export const WithValidationErrors = { args: { - signinHeader: "Sign in", - signUpText: "New to ToolJet?", - signUpUrl: "#", - signUpCTA: "Create an account", - emailLabel: "Email", - emailPlaceholder: "Enter your email", - passwordLabel: "Password", + signinHeader: 'Sign in', + signUpText: 'New to ToolJet?', + signUpUrl: '#', + signUpCTA: 'Create an account', + emailLabel: 'Email', + emailPlaceholder: 'Enter your email', + passwordLabel: 'Password', showForgotPassword: true, - forgotPasswordUrl: "/forgot-password", - forgotPasswordText: "Forgot?", - signinButtonText: "Sign in", - orText: "OR", + forgotPasswordUrl: '/forgot-password', + forgotPasswordText: 'Forgot?', + signinButtonText: 'Sign in', + orText: 'OR', showOrSeparator: true, - emailValue: "invalid-email", - passwordValue: "123", + emailValue: 'invalid-email', + passwordValue: '123', emailValidationMessage: { valid: false, - message: "Please enter a valid email address", + message: 'Please enter a valid email address', }, passwordValidationMessage: { valid: false, - message: "Password must be at least 8 characters", + message: 'Password must be at least 8 characters', }, }, }; From f27a3a2aaf6b6ac4abb4f4a620c5a8834ac38462 Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:23:17 +0530 Subject: [PATCH 053/157] feat: add GitHub Actions workflow for rendering Storybook previews --- .github/workflows/story-book.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index cfbb7814ff..b1791c99d2 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -29,11 +29,15 @@ jobs: "type": "static_site", "autoDeploy": "yes", "branch": "${{ env.BRANCH_NAME }}", - "name": "storybook PR #${{ env.PR_NUMBER }}", + "name": "ToolJet PR #${{ env.PR_NUMBER }}", "ownerId": "tea-caeo4bj19n072h3dddc0", "repo": "${{ github.event.pull_request.head.repo.git_url }}", "rootDir": "frontend", "envVars": [ + { + "key": "NODE_ENV", + "value": "production" + }, { "key": "NODE_VERSION", "value": "v22.15.1" @@ -41,13 +45,17 @@ jobs: { "key": "NPM_VERSION", "value": "10.9.2" + }, + { + "key": "GTM", + "value": "dummy" } ], "serviceDetails": { "pullRequestPreviewsEnabled": "no", "buildCommand": "npm i && npx storybook build", - "publishPath": "storybook-static/", - "url": "https://storybook-pr-${{ env.PR_NUMBER }}.onrender.com" + "publishPath": "frontend/storybook-static/", + "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" } }') @@ -64,7 +72,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Deployment: https://storybook-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/static/${{ env.SERVICE_ID }}' + body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/static/${{ env.SERVICE_ID }}' }) - uses: actions/github-script@v6 @@ -96,7 +104,7 @@ jobs: - name: Delete service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -150,7 +158,7 @@ jobs: - name: Suspend service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -182,7 +190,7 @@ jobs: - name: Resume service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') From 5466216ea1236f555a58b1c6c8a5f8da87ed28ea Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:40:39 +0530 Subject: [PATCH 054/157] Add SetupAdminForm component and corresponding Storybook stories - Introduced `SetupAdminForm` component for admin account setup, featuring fields for name, email, and password with validation. - Implemented customizable props for labels, placeholders, and button text to enhance usability. - Created comprehensive Storybook stories to showcase various configurations and states of the `SetupAdminForm`, including validation and loading scenarios. - Ensured accessibility and user-friendly design in the form layout. --- .../src/components/Auth/SetupAdminForm.jsx | 208 +++++++++++ .../Auth/Stories/SetupAdminForm.stories.jsx | 328 ++++++++++++++++++ 2 files changed, 536 insertions(+) create mode 100644 frontend/src/components/Auth/SetupAdminForm.jsx create mode 100644 frontend/src/components/Auth/Stories/SetupAdminForm.stories.jsx diff --git a/frontend/src/components/Auth/SetupAdminForm.jsx b/frontend/src/components/Auth/SetupAdminForm.jsx new file mode 100644 index 0000000000..724af2e7bd --- /dev/null +++ b/frontend/src/components/Auth/SetupAdminForm.jsx @@ -0,0 +1,208 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { CornerDownLeft } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { FormWrapper } from '@/components/Auth/FormWrapper'; + +export function SetupAdminForm({ className, ...props }) { + const { + // Header + headerText, + + // Form fields + nameLabel, + namePlaceholder, + emailLabel, + emailPlaceholder, + passwordLabel, + passwordPlaceholder, + + // Button + buttonText, + + // Terms and privacy + termsText, + showTerms, + + // Form functionality + onSubmit, + nameValue, + emailValue, + passwordValue, + onNameChange, + onEmailChange, + onPasswordChange, + + // Validation and state + nameValidation, + emailValidation, + passwordValidation, + nameValidationMessage, + emailValidationMessage, + passwordValidationMessage, + isLoading, + disabled, + } = props; + + return ( + + +
+

+ {headerText} +

+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ + + + {showTerms && ( +

+ {termsText} +

+ )} +
+ +
+ ); +} + +SetupAdminForm.propTypes = { + className: PropTypes.string, + + // Header + headerText: PropTypes.string, + + // Form fields + nameLabel: PropTypes.string, + namePlaceholder: PropTypes.string, + emailLabel: PropTypes.string, + emailPlaceholder: PropTypes.string, + passwordLabel: PropTypes.string, + passwordPlaceholder: PropTypes.string, + + // Button + buttonText: PropTypes.string, + + // Terms and privacy + termsText: PropTypes.string, + showTerms: PropTypes.bool, + + // Form functionality + onSubmit: PropTypes.func, + nameValue: PropTypes.string, + emailValue: PropTypes.string, + passwordValue: PropTypes.string, + onNameChange: PropTypes.func, + onEmailChange: PropTypes.func, + onPasswordChange: PropTypes.func, + + // Validation and state + nameValidation: PropTypes.func, + emailValidation: PropTypes.func, + passwordValidation: PropTypes.func, + nameValidationMessage: PropTypes.object, + emailValidationMessage: PropTypes.object, + passwordValidationMessage: PropTypes.object, + isLoading: PropTypes.bool, + disabled: PropTypes.bool, +}; + +SetupAdminForm.defaultProps = { + className: '', + + // Header + headerText: 'Set up your admin account', + + // Form fields + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + + // Button + buttonText: 'Sign up', + + // Terms and privacy + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + + // Form functionality + onSubmit: undefined, + nameValue: undefined, + emailValue: undefined, + passwordValue: undefined, + onNameChange: undefined, + onEmailChange: undefined, + onPasswordChange: undefined, + + // Validation and state + nameValidation: undefined, + emailValidation: undefined, + passwordValidation: undefined, + nameValidationMessage: undefined, + emailValidationMessage: undefined, + passwordValidationMessage: undefined, + isLoading: false, + disabled: false, +}; diff --git a/frontend/src/components/Auth/Stories/SetupAdminForm.stories.jsx b/frontend/src/components/Auth/Stories/SetupAdminForm.stories.jsx new file mode 100644 index 0000000000..818eb38c5b --- /dev/null +++ b/frontend/src/components/Auth/Stories/SetupAdminForm.stories.jsx @@ -0,0 +1,328 @@ +import React from 'react'; +import { SetupAdminForm } from '../SetupAdminForm'; + +export default { + title: 'Auth/SetupAdminForm', + component: SetupAdminForm, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + headerText: { + control: 'text', + description: 'The main heading text for the setup admin form', + }, + nameLabel: { + control: 'text', + description: 'Label for the name input field', + }, + namePlaceholder: { + control: 'text', + description: 'Placeholder text for the name input', + }, + emailLabel: { + control: 'text', + description: 'Label for the email input field', + }, + emailPlaceholder: { + control: 'text', + description: 'Placeholder text for the email input', + }, + passwordLabel: { + control: 'text', + description: 'Label for the password input field', + }, + passwordPlaceholder: { + control: 'text', + description: 'Placeholder text for the password input', + }, + buttonText: { + control: 'text', + description: 'Text for the submit button', + }, + termsText: { + control: 'text', + description: 'Text for the terms and privacy notice', + }, + showTerms: { + control: 'boolean', + description: 'Show or hide the terms and privacy notice', + }, + nameValue: { + control: 'text', + description: 'Controlled value for name input', + }, + emailValue: { + control: 'text', + description: 'Controlled value for email input', + }, + passwordValue: { + control: 'text', + description: 'Controlled value for password input', + }, + isLoading: { + control: 'boolean', + description: 'Loading state for the form', + }, + disabled: { + control: 'boolean', + description: 'Disabled state for the form', + }, + onSubmit: { + action: 'submitted', + description: 'Form submission handler', + }, + onNameChange: { + action: 'name changed', + description: 'Name input change handler', + }, + onEmailChange: { + action: 'email changed', + description: 'Email input change handler', + }, + onPasswordChange: { + action: 'password changed', + description: 'Password input change handler', + }, + nameValidation: { + control: false, + description: 'Name validation function', + }, + emailValidation: { + control: false, + description: 'Email validation function', + }, + passwordValidation: { + control: false, + description: 'Password validation function', + }, + nameValidationMessage: { + control: 'object', + description: 'External name validation message object', + }, + emailValidationMessage: { + control: 'object', + description: 'External email validation message object', + }, + passwordValidationMessage: { + control: 'object', + description: 'External password validation message object', + }, + }, +}; + +export const Default = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + }, +}; + +export const CustomHeader = { + args: { + headerText: 'Create Admin Account', + nameLabel: 'Full Name', + namePlaceholder: 'Enter your full name', + emailLabel: 'Work Email', + emailPlaceholder: 'Enter your work email address', + passwordLabel: 'Password', + passwordPlaceholder: 'Create a secure password', + buttonText: 'Create Account', + termsText: 'By creating an account, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + }, +}; + +export const WithValues = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + nameValue: 'John Doe', + emailValue: 'john.doe@company.com', + passwordValue: 'SecurePassword123!', + }, +}; + +export const WithValidationErrors = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + nameValue: '', + emailValue: 'invalid-email', + passwordValue: '123', + nameValidationMessage: { + valid: false, + message: 'Name is required', + }, + emailValidationMessage: { + valid: false, + message: 'Please enter a valid email address', + }, + passwordValidationMessage: { + valid: false, + message: 'Password must be at least 8 characters', + }, + }, +}; + +export const Loading = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Creating Account...', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + nameValue: 'John Doe', + emailValue: 'john.doe@company.com', + passwordValue: 'SecurePassword123!', + isLoading: true, + disabled: true, + }, +}; + +export const Disabled = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + disabled: true, + }, +}; + +export const NoTerms = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: '', + showTerms: false, + }, +}; + +export const WithValidation = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: 'By signing up, you agree to our Terms of Service and Privacy Policy', + showTerms: true, + nameValidation: (e) => { + const value = e.target.value; + if (!value.trim()) { + return { valid: false, message: 'Name is required' }; + } + if (value.length < 2) { + return { valid: false, message: 'Name must be at least 2 characters' }; + } + return { valid: true, message: 'Name looks good!' }; + }, + emailValidation: (e) => { + const value = e.target.value; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!value) { + return { valid: false, message: 'Email is required' }; + } + if (!emailRegex.test(value)) { + return { valid: false, message: 'Please enter a valid email address' }; + } + return { valid: true, message: 'Email looks good!' }; + }, + passwordValidation: (e) => { + const value = e.target.value; + if (!value) { + return { valid: false, message: 'Password is required' }; + } + if (value.length < 8) { + return { + valid: false, + message: 'Password must be at least 8 characters', + }; + } + if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { + return { + valid: false, + message: 'Password must contain uppercase, lowercase, and number', + }; + } + return { valid: true, message: 'Password is strong!' }; + }, + }, +}; + +export const Minimal = { + args: { + headerText: 'Admin Setup', + nameLabel: 'Name', + namePlaceholder: 'Enter name', + emailLabel: 'Email', + emailPlaceholder: 'Enter email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Setup', + termsText: '', + showTerms: false, + }, +}; + +export const CustomTerms = { + args: { + headerText: 'Set up your admin account', + nameLabel: 'Name', + namePlaceholder: 'Enter your name', + emailLabel: 'Email', + emailPlaceholder: 'Enter your work email', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter password', + buttonText: 'Sign up', + termsText: + 'By creating an admin account, you acknowledge that you have read and agree to our Terms of Service, Privacy Policy, and Data Processing Agreement.', + showTerms: true, + }, +}; From 6c05894add55dcb3457c1bce12d86e0cac63eaa1 Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:24:09 +0530 Subject: [PATCH 055/157] chore: add dummy PR comment to Storybook workflow --- .github/workflows/story-book.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index b1791c99d2..48684efc69 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -220,3 +220,5 @@ jobs: } catch (e) { console.log(e) } + +# dummy PR From f74b12ef02b454099ca09810e0b5d53845a7ec39 Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:32:07 +0530 Subject: [PATCH 056/157] chore: comment out environment variables in Storybook workflow --- .github/workflows/story-book.yml | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index 48684efc69..69c4e95b11 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -33,24 +33,24 @@ jobs: "ownerId": "tea-caeo4bj19n072h3dddc0", "repo": "${{ github.event.pull_request.head.repo.git_url }}", "rootDir": "frontend", - "envVars": [ - { - "key": "NODE_ENV", - "value": "production" - }, - { - "key": "NODE_VERSION", - "value": "v22.15.1" - }, - { - "key": "NPM_VERSION", - "value": "10.9.2" - }, - { - "key": "GTM", - "value": "dummy" - } - ], + # "envVars": [ + # { + # "key": "NODE_ENV", + # "value": "production" + # }, + # { + # "key": "NODE_VERSION", + # "value": "18.18.2" + # }, + # { + # "key": "NPM_VERSION", + # "value": "9.8.1" + # }, + # { + # "key": "GTM", + # "value": "dummy" + # } + # ], "serviceDetails": { "pullRequestPreviewsEnabled": "no", "buildCommand": "npm i && npx storybook build", From f6f0a9a5d5f2902674c7184af3b5b4c2a62ddb68 Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:40:49 +0530 Subject: [PATCH 057/157] fix: update repository URL and remove commented environment variables in Storybook workflow --- .github/workflows/story-book.yml | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index 69c4e95b11..ef2177f430 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -31,26 +31,8 @@ jobs: "branch": "${{ env.BRANCH_NAME }}", "name": "ToolJet PR #${{ env.PR_NUMBER }}", "ownerId": "tea-caeo4bj19n072h3dddc0", - "repo": "${{ github.event.pull_request.head.repo.git_url }}", - "rootDir": "frontend", - # "envVars": [ - # { - # "key": "NODE_ENV", - # "value": "production" - # }, - # { - # "key": "NODE_VERSION", - # "value": "18.18.2" - # }, - # { - # "key": "NPM_VERSION", - # "value": "9.8.1" - # }, - # { - # "key": "GTM", - # "value": "dummy" - # } - # ], + "repo": https://github.com/ToolJet/ToolJet, + "rootDir": "frontend/", "serviceDetails": { "pullRequestPreviewsEnabled": "no", "buildCommand": "npm i && npx storybook build", From 4cd37ded0ee36e8ea5aab9a494d9eaf74473e559 Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:48:08 +0530 Subject: [PATCH 058/157] fix: update repository URL and add environment variables for Node and NPM versions in Storybook workflow --- .github/workflows/story-book.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index ef2177f430..ee2d24eea7 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -31,12 +31,22 @@ jobs: "branch": "${{ env.BRANCH_NAME }}", "name": "ToolJet PR #${{ env.PR_NUMBER }}", "ownerId": "tea-caeo4bj19n072h3dddc0", - "repo": https://github.com/ToolJet/ToolJet, - "rootDir": "frontend/", + "repo": "${{ github.event.pull_request.head.repo.git_url }}", + "rootDir": "frontend", + "envVars": [ + { + "key": "NODE_VERSION", + "value": "v22.15.1" + }, + { + "key": "NPM_VERSION", + "value": "10.9.2" + } + ], "serviceDetails": { "pullRequestPreviewsEnabled": "no", "buildCommand": "npm i && npx storybook build", - "publishPath": "frontend/storybook-static/", + "publishPath": "storybook-static/", "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" } }') From 3186859a441bd353053f948d83a8563223392abe Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 18:52:03 +0530 Subject: [PATCH 059/157] chore: update dummy PR comment in Storybook workflow --- .github/workflows/story-book.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index ee2d24eea7..297fa2767f 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -213,4 +213,4 @@ jobs: console.log(e) } -# dummy PR +# dummy PR 1 From d3e5ab514f618799059b947eac673acab6099814 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:04:30 +0530 Subject: [PATCH 060/157] Add SignupSuccessInfo component and corresponding Storybook stories - Introduced `SignupSuccessInfo` component to display a success message after user signup, including options for email, name, and additional info. - Implemented customizable props for header text, message content, resend button, and back button functionality. - Created comprehensive Storybook stories to showcase various configurations and states of the `SignupSuccessInfo` component, enhancing usability and testing. - Ensured accessibility and user-friendly design in the component layout. --- .../src/components/Auth/SignupSuccessInfo.jsx | 165 ++++++++++ .../Stories/SignupSuccessInfo.stories.jsx | 305 ++++++++++++++++++ 2 files changed, 470 insertions(+) create mode 100644 frontend/src/components/Auth/SignupSuccessInfo.jsx create mode 100644 frontend/src/components/Auth/Stories/SignupSuccessInfo.stories.jsx diff --git a/frontend/src/components/Auth/SignupSuccessInfo.jsx b/frontend/src/components/Auth/SignupSuccessInfo.jsx new file mode 100644 index 0000000000..c78e0f3ae9 --- /dev/null +++ b/frontend/src/components/Auth/SignupSuccessInfo.jsx @@ -0,0 +1,165 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { ArrowLeft, Mail } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { FormWrapper } from '@/components/Auth/FormWrapper'; + +export function SignupSuccessInfo({ className, ...props }) { + const { + // Header + headerText, + + // Message content + messageText, + email, + name, + + // Info text + infoText, + showInfo, + + // Resend email + resendButtonText, + resendCountdownText, + showResendButton, + resendDisabled, + resendCountdown, + + // Back button + backButtonText, + onBackToSignup, + + // Separator + showSeparator, + } = props; + + return ( + +
+
+

+ {headerText} +

+
+ +
+

+ {messageText} + {email && ( + <> + {' '} + {email} + + )} + .{' '} + {name && ( + <> + Welcome, {name}! + + )} +

+ + {showInfo && ( +

+ {infoText} +

+ )} + + {showSeparator && ( +
+
+ OR +
+
+ )} + + {showResendButton && ( + + )} + + +
+
+ + ); +} + +SignupSuccessInfo.propTypes = { + className: PropTypes.string, + + // Header + headerText: PropTypes.string, + + // Message content + messageText: PropTypes.string, + email: PropTypes.string, + name: PropTypes.string, + + // Info text + infoText: PropTypes.string, + showInfo: PropTypes.bool, + + // Resend email + resendButtonText: PropTypes.string, + resendCountdownText: PropTypes.string, + showResendButton: PropTypes.bool, + resendDisabled: PropTypes.bool, + resendCountdown: PropTypes.number, + + // Back button + backButtonText: PropTypes.string, + onBackToSignup: PropTypes.func, + + // Separator + showSeparator: PropTypes.bool, +}; + +SignupSuccessInfo.defaultProps = { + className: '', + + // Header + headerText: 'Check your mail', + + // Message content + messageText: "We've sent a verification email to", + email: '', + name: '', + + // Info text + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + + // Resend email + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + + // Back button + backButtonText: 'Back to sign up', + onBackToSignup: undefined, + + // Separator + showSeparator: true, +}; diff --git a/frontend/src/components/Auth/Stories/SignupSuccessInfo.stories.jsx b/frontend/src/components/Auth/Stories/SignupSuccessInfo.stories.jsx new file mode 100644 index 0000000000..239b5b11d3 --- /dev/null +++ b/frontend/src/components/Auth/Stories/SignupSuccessInfo.stories.jsx @@ -0,0 +1,305 @@ +import React from 'react'; +import { SignupSuccessInfo } from '../SignupSuccessInfo'; + +export default { + title: 'Auth/SignupSuccessInfo', + component: SignupSuccessInfo, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + headerText: { + control: 'text', + description: 'The main heading text for the signup success screen', + }, + messageText: { + control: 'text', + description: 'Main message text', + }, + email: { + control: 'text', + description: 'Email address to display in the message', + }, + name: { + control: 'text', + description: 'User name to display in the message', + }, + infoText: { + control: 'text', + description: 'Additional info text', + }, + showInfo: { + control: 'boolean', + description: 'Show or hide the info text', + }, + resendButtonText: { + control: 'text', + description: 'Text for the resend verification email button', + }, + resendCountdownText: { + control: 'text', + description: 'Text for the resend countdown', + }, + showResendButton: { + control: 'boolean', + description: 'Show or hide the resend button', + }, + resendDisabled: { + control: 'boolean', + description: 'Disabled state for the resend button', + }, + resendCountdown: { + control: 'number', + description: 'Countdown value for resend button', + }, + backButtonText: { + control: 'text', + description: 'Text for the back to signup button', + }, + onBackToSignup: { + action: 'back to signup clicked', + description: 'Back to signup button handler', + }, + showSeparator: { + control: 'boolean', + description: 'Show or hide the separator', + }, + }, +}; + +export const Default = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const CustomHeader = { + args: { + headerText: 'Account Created Successfully', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const WithEmailOnly = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: '', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const WithNameOnly = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to your registered email address", + email: '', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const LongEmail = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'very.long.email.address@verylongdomainname.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const NoInfo = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: '', + showInfo: false, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const NoResendButton = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: false, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const ResendDisabled = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: true, + resendCountdown: 15, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; + +export const NoSeparator = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: false, + }, +}; + +export const CustomMessage = { + args: { + headerText: 'Account Created', + messageText: 'A verification email has been sent to your email address', + email: 'user@example.com', + name: 'John Doe', + infoText: 'Please check your inbox and follow the instructions to verify your account', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Return to signup', + showSeparator: true, + }, +}; + +export const Minimal = { + args: { + headerText: 'Email Sent', + messageText: 'Check your email for verification instructions', + email: '', + name: '', + infoText: '', + showInfo: false, + resendButtonText: 'Resend', + resendCountdownText: 'Resend in', + showResendButton: false, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back', + showSeparator: false, + }, +}; + +export const CustomButtonTexts = { + args: { + headerText: 'Check your mail', + messageText: "We've sent a verification email to", + email: 'user@example.com', + name: 'John Doe', + infoText: 'Did not receive an email? Check your spam folder!', + showInfo: true, + resendButtonText: 'Send another verification email', + resendCountdownText: 'Send another email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Return to registration', + showSeparator: true, + }, +}; + +export const LongMessage = { + args: { + headerText: 'Check your mail', + messageText: + "We've sent a verification email to your registered email address. Please check your inbox and click the link to verify your account and continue with the setup process", + email: 'user@example.com', + name: 'John Doe', + infoText: + 'Did not receive an email? Please check your spam folder or contact support if you continue to have issues with email delivery', + showInfo: true, + resendButtonText: 'Resend verification email', + resendCountdownText: 'Resend verification email in', + showResendButton: true, + resendDisabled: false, + resendCountdown: 0, + backButtonText: 'Back to sign up', + showSeparator: true, + }, +}; From 5dca28ec2ebdba9f49aa338bdedd0ae3991c2e4a Mon Sep 17 00:00:00 2001 From: adishM98 Bot Date: Wed, 17 Sep 2025 19:09:39 +0530 Subject: [PATCH 061/157] fix: update deployment URLs and names to use 'storybook' prefix in Storybook workflow --- .github/workflows/story-book.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/story-book.yml b/.github/workflows/story-book.yml index 297fa2767f..cfbb7814ff 100644 --- a/.github/workflows/story-book.yml +++ b/.github/workflows/story-book.yml @@ -29,7 +29,7 @@ jobs: "type": "static_site", "autoDeploy": "yes", "branch": "${{ env.BRANCH_NAME }}", - "name": "ToolJet PR #${{ env.PR_NUMBER }}", + "name": "storybook PR #${{ env.PR_NUMBER }}", "ownerId": "tea-caeo4bj19n072h3dddc0", "repo": "${{ github.event.pull_request.head.repo.git_url }}", "rootDir": "frontend", @@ -47,7 +47,7 @@ jobs: "pullRequestPreviewsEnabled": "no", "buildCommand": "npm i && npx storybook build", "publishPath": "storybook-static/", - "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + "url": "https://storybook-pr-${{ env.PR_NUMBER }}.onrender.com" } }') @@ -64,7 +64,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/static/${{ env.SERVICE_ID }}' + body: 'Deployment: https://storybook-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/static/${{ env.SERVICE_ID }}' }) - uses: actions/github-script@v6 @@ -96,7 +96,7 @@ jobs: - name: Delete service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -150,7 +150,7 @@ jobs: - name: Suspend service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -182,7 +182,7 @@ jobs: - name: Resume service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=storybook%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -212,5 +212,3 @@ jobs: } catch (e) { console.log(e) } - -# dummy PR 1 From bf6ee2d63c4dc6bebf342604fbf6a7a83a28ae8d Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:26:34 +0530 Subject: [PATCH 062/157] Enhance form components with Label integration and Button updates - Added `Label` components to `ForgotPasswordForm`, `LoginForm`, and `SetupAdminForm` for improved accessibility and user experience. - Updated `Button` component to support Lucide icons with dynamic loading, enhancing visual consistency and flexibility. - Refactored utility functions in `ButtonUtils` to accommodate new icon handling logic. - Expanded Storybook stories for `Button` to demonstrate new Lucide icon capabilities and various button configurations. - Improved `Input` component styling to support dynamic text sizes based on input size prop. --- .../components/Auth/ForgotPasswordForm.jsx | 5 +- frontend/src/components/Auth/LoginForm.jsx | 8 +- .../src/components/Auth/SetupAdminForm.jsx | 13 +- frontend/src/components/ui/Button/Button.jsx | 41 ++- .../components/ui/Button/Button.stories.jsx | 299 +++++++++++++++++- .../src/components/ui/Button/ButtonUtils.jsx | 36 ++- frontend/src/components/ui/Input/Input.jsx | 21 +- .../src/components/ui/Input/Input.stories.jsx | 188 ++++++++--- 8 files changed, 542 insertions(+), 69 deletions(-) diff --git a/frontend/src/components/Auth/ForgotPasswordForm.jsx b/frontend/src/components/Auth/ForgotPasswordForm.jsx index 0ecf2ebdef..ca7b4f59fe 100644 --- a/frontend/src/components/Auth/ForgotPasswordForm.jsx +++ b/frontend/src/components/Auth/ForgotPasswordForm.jsx @@ -6,6 +6,7 @@ import { CornerDownLeft } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { FormWrapper } from '@/components/Auth/FormWrapper'; export function ForgotPasswordForm({ className, ...props }) { @@ -68,8 +69,10 @@ export function ForgotPasswordForm({ className, ...props }) {
+
+
- + {showForgotPassword && (
+
+
+ { - const iconFillColor = !defaultButtonFillColour.includes(fill) && fill ? fill : getDefaultIconFillColor(variant); + const iconFillColor = + !defaultButtonFillColour.includes(fill) && fill ? fill : getDefaultIconFillColor(variant, iconOnly); + const lucideIconClassName = getLucideIconClassName(variant, iconOnly); const Comp = asChild ? Slot : 'button'; + const iconSize = isLucid ? getLucideIconSize(size) : getIconSize(size); + const leadingIconElement = leadingIcon && ( -
- +
+ {isLucid ? ( + + ) : ( + + )}
); const trailingIconElement = trailingIcon && ( -
- +
+ {isLucid ? ( + + ) : ( + + )}
); @@ -212,6 +227,7 @@ Button.propTypes = { fill: PropTypes.string, leadingIcon: PropTypes.string, trailingIcon: PropTypes.string, + isLucid: PropTypes.bool, }; Button.defaultProps = { @@ -225,6 +241,7 @@ Button.defaultProps = { fill: '', leadingIcon: '', trailingIcon: '', + isLucid: false, }; export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/Button/Button.stories.jsx b/frontend/src/components/ui/Button/Button.stories.jsx index 1cc142de8d..9dc6e7266e 100644 --- a/frontend/src/components/ui/Button/Button.stories.jsx +++ b/frontend/src/components/ui/Button/Button.stories.jsx @@ -2,7 +2,7 @@ import { Button } from './Button'; import * as React from 'react'; // Function to determine default icon fill color -const getDefaultIconFillColor = (variant, customFill = '') => { +const getDefaultIconFillColor = (variant, customFill = '', iconOnly = false) => { if (customFill) { return customFill; } @@ -15,8 +15,28 @@ const getDefaultIconFillColor = (variant, customFill = '') => { return 'var(--icon-brand)'; case 'outline': case 'ghost': - return 'var(--icon-strong)'; + return iconOnly ? 'var(--icon-strong)' : 'var(--icon-default)'; case 'dangerSecondary': + return 'var(--icon-danger)'; + default: + return ''; + } +}; + +// Function to determine Lucide icon className +const getLucideIconClassName = (variant, iconOnly = false) => { + switch (variant) { + case 'primary': + case 'dangerPrimary': + return 'tw-text-icon-on-solid'; + case 'secondary': + case 'ghostBrand': + return 'tw-text-icon-brand'; + case 'outline': + case 'ghost': + return iconOnly ? 'tw-text-icon-strong' : 'tw-text-icon-default'; + case 'dangerSecondary': + return 'tw-text-icon-danger'; default: return ''; } @@ -58,7 +78,7 @@ RocketButton.args = { export const RocketButtonWithIcon = (args) => { const variant = args.variant || 'primary'; const fill = ''; // If fill is provided by user, it will be used; otherwise, it falls back to defaults - const color = getDefaultIconFillColor(variant, fill); + const color = getDefaultIconFillColor(variant, fill, false); return + ))} +
+ +
+

Variants & Sizes Demonstrated:

+
+
+
Variants:
+
    +
  • • primary
  • +
  • • secondary
  • +
  • • outline
  • +
  • • ghost
  • +
  • • dangerPrimary
  • +
+
+
+
Sizes:
+
    +
  • • large (40px)
  • +
  • • default (32px)
  • +
  • • medium (28px)
  • +
  • • small (20px)
  • +
+
+
+
+
Icon Color Logic:
+
    +
  • • outline/ghost + iconOnly=true: tw-text-icon-strong (darker for visibility)
  • +
  • • outline/ghost + iconOnly=false: tw-text-icon-default (lighter for text buttons)
  • +
  • • Other variants: standard Tailwind classes (tw-text-icon-on-solid, tw-text-icon-brand, etc.)
  • +
+

+ Lucide icons use Tailwind CSS classes for colors. Compare the outline/ghost buttons with text vs icon-only + to see the color difference. +

+
+
+
+ ); +}; +LucideIconShowcase.parameters = { + docs: { + description: { + story: + 'Comprehensive showcase of Lucide icons with mixed variants and sizes. Demonstrates the flexibility of the Button component with different visual styles and dimensions.', + }, + }, +}; diff --git a/frontend/src/components/ui/Button/ButtonUtils.jsx b/frontend/src/components/ui/Button/ButtonUtils.jsx index 5d0d6d3c7f..b9275ca7e2 100644 --- a/frontend/src/components/ui/Button/ButtonUtils.jsx +++ b/frontend/src/components/ui/Button/ButtonUtils.jsx @@ -1,4 +1,4 @@ -export const getDefaultIconFillColor = (variant) => { +export const getDefaultIconFillColor = (variant, iconOnly = false) => { switch (variant) { case 'primary': case 'dangerPrimary': @@ -8,7 +8,7 @@ export const getDefaultIconFillColor = (variant) => { return 'var(--icon-brand)'; case 'outline': case 'ghost': - return 'var(--icon-strong)'; + return iconOnly ? 'var(--icon-strong)' : 'var(--icon-default)'; case 'dangerSecondary': case 'dangerGhost': return 'var(--icon-danger)'; @@ -16,6 +16,25 @@ export const getDefaultIconFillColor = (variant) => { return ''; } }; + +export const getLucideIconClassName = (variant, iconOnly = false) => { + switch (variant) { + case 'primary': + case 'dangerPrimary': + return 'tw-text-icon-on-solid'; + case 'secondary': + case 'ghostBrand': + return 'tw-text-icon-brand'; + case 'outline': + case 'ghost': + return iconOnly ? 'tw-text-icon-strong' : 'tw-text-icon-default'; + case 'dangerSecondary': + case 'dangerGhost': + return 'tw-text-icon-danger'; + default: + return ''; + } +}; export const defaultButtonFillColour = ['#FFFFFF', '#4368E3', '#ACB2B9', '#D72D39']; // all default fill colors export const getIconSize = (size) => { @@ -30,3 +49,16 @@ export const getIconSize = (size) => { return '12px'; } }; + +export const getLucideIconSize = (size) => { + switch (size) { + case 'large': + return 20; + case 'default': + return 16; + case 'medium': + return 14; + case 'small': + return 12; + } +}; diff --git a/frontend/src/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 0c6b1b27ca..e8196e0e2c 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -25,6 +25,19 @@ export const Input = React.forwardRef( const validationClass = response === true ? 'valid-textarea' : response === false ? 'invalid-textarea' : ''; + const getTextSize = () => { + switch (size) { + case 'small': + return 'tw-text-[12px]/[18px]'; + case 'medium': + return 'tw-text-[12px]/[18px]'; + case 'large': + return 'tw-text-[14px]/[20px]'; + default: + return 'tw-text-[12px]/[18px]'; + } + }; + const inputStyle = ` ${ props.disabled ? 'placeholder:tw-text-text-placeholder' : 'placeholder:tw-text-text-default' } ${ @@ -36,7 +49,9 @@ export const Input = React.forwardRef( {multiline ? (