From b2a6490e6b13c330be25b500a7b42955f0efbb33 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 3 Apr 2025 18:10:49 +0530 Subject: [PATCH 01/88] init: modules and added a tab on homepage --- frontend/src/App/App.jsx | 9 + frontend/src/HomePage/Folders.jsx | 23 +- frontend/src/HomePage/Footer.jsx | 4 +- frontend/src/HomePage/HomePage.jsx | 99 ++-- frontend/src/_components/AppModal.jsx | 9 +- frontend/src/_styles/modules.scss | 39 ++ frontend/src/_styles/theme.scss | 494 ++++++++++-------- .../components/AppTypeTab/AppTypeTab.jsx | 7 + .../dashboard/components/AppTypeTab/index.js | 1 + .../src/modules/dashboard/components/index.js | 2 + 10 files changed, 423 insertions(+), 264 deletions(-) create mode 100644 frontend/src/_styles/modules.scss create mode 100644 frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx create mode 100644 frontend/src/modules/dashboard/components/AppTypeTab/index.js diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 0c06b4c513..06cc061e0c 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -254,6 +254,15 @@ class AppComponent extends React.Component { }> }> }> + + + + } + /> {getAuditLogsRoutes(this.props)} { if (_.isEmpty(currentFolder)) { - updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : 'apps'}`); + updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : appType === 'module' ? 'modules' : 'apps'}`); setActiveFolder({}); } else { updateSidebarNAV(currentFolder.name); @@ -104,7 +104,9 @@ export const Folders = function Folders({ setActiveFolder(folder); } folderChanged(folder); - updateSidebarNAV(folder?.name ?? 'All apps'); + updateSidebarNAV( + folder?.name ?? `All ${appType === 'front-end' ? 'apps' : appType === 'module' ? 'modules' : 'workflows'}` + ); //update the url query parameter with folder name updateFolderQuery(folder?.name); } @@ -112,7 +114,12 @@ export const Folders = function Folders({ function updateFolderQuery(name) { const search = `${name ? `?folder=${name}` : ''}`; navigate( - { pathname: `/${getWorkspaceId()}${appType === 'workflow' ? '/workflows' : ''}`, search }, + { + pathname: `/${getWorkspaceId()}${ + appType === 'workflow' ? '/workflows' : appType === 'module' ? '/modules' : '' + }`, + search, + }, { replace: true } ); } @@ -286,10 +293,12 @@ export const Folders = function Folders({ onClick={() => handleFolderChange({})} data-cy="all-applications-link" > - {t( - `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`, - 'All apps' - )} + {appType === 'module' + ? 'All modules' + : t( + `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`, + 'All apps' + )} )} diff --git a/frontend/src/HomePage/Footer.jsx b/frontend/src/HomePage/Footer.jsx index d5520aea77..a95f450e1b 100644 --- a/frontend/src/HomePage/Footer.jsx +++ b/frontend/src/HomePage/Footer.jsx @@ -2,7 +2,7 @@ import React, { useState, useMemo } from 'react'; import Pagination from '@/_ui/Pagination'; import Skeleton from 'react-loading-skeleton'; -const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 }) => { +const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9, appType }) => { const [pageCount, setPageCount] = useState(1); const totalPages = useMemo(() => { return Math.floor((count - 1) / itemsPerPage) + 1; @@ -60,7 +60,7 @@ const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 }) ) : ( - {pageRange} of {count} apps + {pageRange} of {count} {appType === 'module' ? 'modules' : 'apps'} )} diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index ab50cbb05d..a94b322252 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -44,6 +44,7 @@ import { OrganizationList, UserGroupMigrationBanner, ConsultationBanner, + AppTypeTab, } from '@/modules/dashboard/components'; import CreateAppWithPrompt from '@/modules/AiBuilder/components/CreateAppWithPrompt'; @@ -236,10 +237,18 @@ class HomePageComponent extends React.Component { let _self = this; _self.setState({ creatingApp: true }); try { - const data = await appsService.createApp({ icon: sample(iconList), name: appName, type: this.props.appType }); + const data = await appsService.createApp({ + icon: sample(iconList), + name: appName, + type: type ?? this.props.appType, + }); const workspaceId = getWorkspaceId(); _self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } }); - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} created successfully!`); + toast.success( + `${ + this.props.appType === 'workflow' ? 'Workflow' : this.props.appType === 'module' ? 'Module' : 'App' + } created successfully!` + ); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { @@ -839,6 +848,10 @@ class HomePageComponent extends React.Component { dependentPluginsForTemplate, dependentPluginsDetail, } = this.state; + + const invalidLicense = featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid; + // const invalidLicense = false; + const modalConfigs = { create: { modalType: 'create', @@ -1136,6 +1149,7 @@ class HomePageComponent extends React.Component { )}
+ {this.canCreateApp() && (
= 100} disabled={ - this.props.appType === 'front-end' - ? appsLimit?.percentage >= 100 + this.props.appType === 'front-end' || this.props.appType === 'module' + ? appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense) : workflowInstanceLevelLimit.percentage >= 100 || workflowWorkspaceLevelLimit.percentage >= 100 } className={`create-new-app-button col-11 ${creatingApp ? 'btn-loading' : ''}`} - onClick={() => this.setState({ showCreateAppModal: true })} + onClick={() => + this.setState({ + [this.props.appType === 'module' ? 'showCreateModuleModal' : 'showCreateAppModal']: true, + }) + } data-cy="create-new-app-button" > {isImportingApp && ( )} - {this.props.t( - `${ - this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' - }.header.createNewApplication`, - 'Create new app' - )} + {this.props.appType === 'module' + ? 'Create new module' + : this.props.t( + `${ + this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' + }.header.createNewApplication`, + 'Create new app' + )} {this.props.appType !== 'workflow' && ( = 100} + disabled={ + appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense) + } split className="d-inline" data-cy="import-dropdown-menu" @@ -1273,6 +1295,7 @@ class HomePageComponent extends React.Component { onSearchSubmit={this.onSearchSubmit} darkMode={this.props.darkMode} appType={this.props.appType} + disabled={this.props.appType === 'module' && invalidLicense} />
@@ -1300,24 +1323,37 @@ class HomePageComponent extends React.Component {
)} - {!isLoading && featuresLoaded && meta?.total_count === 0 && !currentFolder.id && !appSearchKey && ( - - )} + {!isLoading && + featuresLoaded && + meta?.total_count === 0 && + !currentFolder.id && + !appSearchKey && + (['front-end', 'modules'].includes(this.props.appType) ? ( + + ) : ( +

+ You have not created any modules.  + + Create a module  + + to start using it within your apps. +

+ ))} {!isLoading && apps?.length === 0 && appSearchKey && (
@@ -1341,7 +1377,7 @@ class HomePageComponent extends React.Component { appActionModal={this.appActionModal} removeAppFromFolder={this.removeAppFromFolder} appType={this.props.appType} - basicPlan={featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid} + basicPlan={invalidLicense} appSearchKey={this.state.appSearchKey} /> )} @@ -1355,6 +1391,7 @@ class HomePageComponent extends React.Component { pageChanged={this.pageChanged} darkMode={this.props.darkMode} dataLoading={isLoading} + appType={this.props.appType} /> )} {/* need to review the mobile view */} diff --git a/frontend/src/_components/AppModal.jsx b/frontend/src/_components/AppModal.jsx index 7d45e4c36e..318d7f5280 100644 --- a/frontend/src/_components/AppModal.jsx +++ b/frontend/src/_components/AppModal.jsx @@ -10,6 +10,7 @@ import { PluginsListForAppModal } from './PluginsListForAppModal'; const APP_TYPE = { WORKFLOW: 'workflow', APP: 'app', + MODULE: 'module', }; export function AppModal({ @@ -52,6 +53,8 @@ export function AppModal({ const [isNameChanged, setIsNameChanged] = useState(false); const inputRef = useRef(null); + const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : APP_TYPE.MODULE == appType ? 'Module' : 'App'; + useEffect(() => { setIsNameChanged(newAppName?.trim() !== selectedAppName); }, [newAppName, selectedAppName]); @@ -85,7 +88,7 @@ export function AppModal({ success = await processApp(trimmedAppName); } if (success === false) { - setErrorText(`${appType == APP_TYPE.WORKFLOW ? 'Workflow' : 'App'} name already exists`); + setErrorText(`${appTypeName} name already exists`); setInfoText(''); } else { setErrorText(''); @@ -127,8 +130,6 @@ export function AppModal({ (actionButton === 'Rename app' && (!isNameChanged || newAppName.trim().length === 0 || newAppName.length > 50)) || // For rename case (actionButton !== 'Rename app' && (newAppName.length > 50 || newAppName.trim().length === 0)); - const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : 'App'; - return ( )} - {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && ( + {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && appType != APP_TYPE.MODULE && (
'); - /* Add a custom arrow (you can use your own SVG) */ - background-repeat: no-repeat; - background-position: right center; - border: none; - /* Remove the default border */ - padding: 8px; - /* Adjust padding as needed */ - cursor: pointer; - /* Add pointer cursor for better usability */ - background: none; - padding: 0px; - height: 24px; - text-align: center; - color: var(--text-primary); - font-weight: 500; - width:auto; +.tj-daterangepicker-widget-month-selector, +.tj-daterangepicker-widget-year-selector { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + padding-right: 4px; + /* Add some padding on the right to create space for custom arrow */ + background-image: url('data:image/svg+xml;utf8,'); + /* Add a custom arrow (you can use your own SVG) */ + background-repeat: no-repeat; + background-position: right center; + border: none; + /* Remove the default border */ + padding: 8px; + /* Adjust padding as needed */ + cursor: pointer; + /* Add pointer cursor for better usability */ + background: none; + padding: 0px; + height: 24px; + text-align: center; + color: var(--text-primary); + font-weight: 500; + width: auto; } .datepicker-widget { - .react-datepicker-wrapper{ - width:100% !important; + .react-datepicker-wrapper { + width: 100% !important; } } @@ -17185,26 +17223,29 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-month-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__month-container { box-shadow: none !important; } - + .react-datepicker__month-text { - height:26px !important; + height: 26px !important; margin: 0px; - width:100% !important; + width: 100% !important; } - .react-datepicker__month-text--in-selecting-range, .react-datepicker__month-text--in-range { - border-radius:0px; + .react-datepicker__month-text--in-selecting-range, + .react-datepicker__month-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17217,45 +17258,49 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__month-text--selecting-range-start, .react-datepicker__month-text--selected, .react-datepicker__month-text--range-end { - border-radius:8px !important; + .react-datepicker__month-text--selecting-range-start, + .react-datepicker__month-text--selected, + .react-datepicker__month-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range, .react-datepicker__month-text--selecting-range-start + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--selecting-range-start+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range { + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - .react-datepicker__month-wrapper{ - gap:0px !important; - .react-datepicker__month-text--in-range:first-of-type, - .react-datepicker__month-text--in-selecting-range:first-of-type, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-range, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-wrapper { + gap: 0px !important; + + .react-datepicker__month-text--in-range:first-of-type, + .react-datepicker__month-text--in-selecting-range:first-of-type, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--in-range:last-of-type, - .react-datepicker__month-text--in-selecting-range:last-of-type, - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), - .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text){ + .react-datepicker__month-text--in-range:last-of-type, + .react-datepicker__month-text--in-selecting-range:last-of-type, + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17275,44 +17320,47 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-year-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__year-container { box-shadow: none !important; } - .react-datepicker__year-wrapper{ - gap:0px !important; + .react-datepicker__year-wrapper { + gap: 0px !important; - .react-datepicker__year-text--in-range:first-of-type, - .react-datepicker__year-text--in-selecting-range:first-of-type{ + .react-datepicker__year-text--in-range:first-of-type, + .react-datepicker__year-text--in-selecting-range:first-of-type { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--in-range:last-of-type, - .react-datepicker__year-text--in-selecting-range:last-of-type{ + .react-datepicker__year-text--in-range:last-of-type, + .react-datepicker__year-text--in-selecting-range:last-of-type { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } } - + .react-datepicker__year-text { - height:26px !important; + height: 26px !important; margin-top: 5px !important; - margin-bottom:5px !important; + margin-bottom: 5px !important; margin: 0px; - width:62px !important; + width: 62px !important; } - .react-datepicker__year-text--in-selecting-range, .react-datepicker__year-text--in-range { - border-radius:0px; + .react-datepicker__year-text--in-selecting-range, + .react-datepicker__year-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17325,31 +17373,35 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__year-text--selecting-range-start, .react-datepicker__year-text--selected, .react-datepicker__year-text--range-end { - border-radius:8px !important; + .react-datepicker__year-text--selecting-range-start, + .react-datepicker__year-text--selected, + .react-datepicker__year-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), + .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range, .react-datepicker__year-text--selecting-range-start + .react-datepicker__year-text--in-selecting-range{ + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range, + .react-datepicker__year-text--selecting-range-start+.react-datepicker__year-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range { + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - + + } .dark-theme { @@ -18610,6 +18662,7 @@ section.ai-message-prompt-input-wrapper { flex-grow: 1; } } + .workspace-constant-value { position: relative; @@ -18653,8 +18706,9 @@ section.ai-message-prompt-input-wrapper { font-style: normal; font-weight: 400; line-height: 18px; + &.dark { background: #FFFAEB !important; } } -} +} \ No newline at end of file diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx new file mode 100644 index 0000000000..293d325c59 --- /dev/null +++ b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const AppTypeTab = () => { + return <>; +}; +export default withEditionSpecificComponent(AppTypeTab, 'Dashboard'); diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/index.js b/frontend/src/modules/dashboard/components/AppTypeTab/index.js new file mode 100644 index 0000000000..ae18e0c4c3 --- /dev/null +++ b/frontend/src/modules/dashboard/components/AppTypeTab/index.js @@ -0,0 +1 @@ +export { default } from './AppTypeTab'; diff --git a/frontend/src/modules/dashboard/components/index.js b/frontend/src/modules/dashboard/components/index.js index ab540b5feb..d4ffff00c4 100644 --- a/frontend/src/modules/dashboard/components/index.js +++ b/frontend/src/modules/dashboard/components/index.js @@ -6,6 +6,7 @@ import SettingsMenu from './SettingsMenu'; import WorkspaceActions from './WorkspaceActions'; import ConsultationBanner from './ConsultationBanner'; import UserGroupMigrationBanner from './UserGroupMigrationBanner'; +import AppTypeTab from './AppTypeTab'; export { ImportAppMenu, @@ -16,4 +17,5 @@ export { WorkspaceActions, ConsultationBanner, UserGroupMigrationBanner, + AppTypeTab, }; From 61dfb9299c81004152c90f80bed3c5b02e78427f Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Fri, 4 Apr 2025 17:40:21 +0530 Subject: [PATCH 02/88] Fixed bug on creating a app with correct type --- frontend/src/HomePage/HomePage.jsx | 44 +++++++++---------- .../BaseAppActionModal/BaseAppActionModal.jsx | 2 +- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index a94b322252..0b9a69aa71 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -100,7 +100,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations: {}, featuresLoaded: false, showCreateAppModal: false, - showCreateModuleModal: false, showCreateAppFromTemplateModal: false, showImportAppModal: false, showCloneAppModal: false, @@ -233,22 +232,23 @@ class HomePageComponent extends React.Component { this.fetchFolders(); }; - createApp = async (appName, type) => { + getAppType = () => { + return this.props.appType === 'module' ? 'Module' : this.props.appType === 'workflow' ? 'Workflow' : 'App'; + }; + + createApp = async (appName) => { let _self = this; _self.setState({ creatingApp: true }); + try { const data = await appsService.createApp({ icon: sample(iconList), name: appName, - type: type ?? this.props.appType, + type: this.props.appType, }); const workspaceId = getWorkspaceId(); _self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } }); - toast.success( - `${ - this.props.appType === 'workflow' ? 'Workflow' : this.props.appType === 'module' ? 'Module' : 'App' - } created successfully!` - ); + toast.success(`${this.getAppType()} created successfully!`); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { @@ -267,7 +267,7 @@ class HomePageComponent extends React.Component { try { await appsService.saveApp(appId, { name: newAppName }); await this.fetchApps(this.state.currentPage, this.state.currentFolder.id); - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} name has been updated!`); + toast.success(`${this.getAppType()} name has been updated!`); _self.setState({ renamingApp: false }); return true; } catch (errorResponse) { @@ -483,7 +483,7 @@ class HomePageComponent extends React.Component { .deleteApp(this.state.appToBeDeleted.id) // eslint-disable-next-line no-unused-vars .then((data) => { - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} deleted successfully.`); + toast.success(`${this.getAppType()} deleted successfully.`); this.fetchApps( this.state.currentPage ? this.state.apps?.length === 1 @@ -766,11 +766,11 @@ class HomePageComponent extends React.Component { }; openCreateAppModal = () => { - this.setState({ showCreateAppModal: true, showCreateModuleModal: true }); + this.setState({ showCreateAppModal: true }); }; closeCreateAppModal = () => { - this.setState({ showCreateAppModal: false, showCreateModuleModal: false }); + this.setState({ showCreateAppModal: false }); }; isWithinSevenDaysOfSignUp = (date) => { const currentDate = new Date().toISOString(); @@ -835,7 +835,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations, featuresLoaded, showCreateAppModal, - showCreateModuleModal, showImportAppModal, fileContent, fileName, @@ -856,10 +855,10 @@ class HomePageComponent extends React.Component { create: { modalType: 'create', closeModal: this.closeCreateAppModal, - processApp: (name) => this.createApp(name, showCreateAppModal ? 'front-end' : 'module'), + processApp: (name) => this.createApp(name), show: this.openCreateAppModal, - title: this.props.appType === 'workflow' ? 'Create workflow' : 'Create app', - actionButton: this.props.appType === 'workflow' ? '+ Create workflow' : '+ Create app', + title: `Create ${this.getAppType().toLocaleLowerCase()}`, + actionButton: `+ Create ${this.getAppType().toLocaleLowerCase()}`, actionLoadingButton: 'Creating', appType: this.props.appType, }, @@ -904,7 +903,6 @@ class HomePageComponent extends React.Component { @@ -1171,7 +1169,7 @@ class HomePageComponent extends React.Component { className={`create-new-app-button col-11 ${creatingApp ? 'btn-loading' : ''}`} onClick={() => this.setState({ - [this.props.appType === 'module' ? 'showCreateModuleModal' : 'showCreateAppModal']: true, + showCreateAppModal: true, }) } data-cy="create-new-app-button" @@ -1189,7 +1187,7 @@ class HomePageComponent extends React.Component { )} - {this.props.appType !== 'workflow' && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && ( = 100 || (this.props.appType === 'module' && invalidLicense) @@ -1283,7 +1281,7 @@ class HomePageComponent extends React.Component { !appSearchKey && )} - {this.props.appType !== 'workflow' && this.canCreateApp() && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && this.canCreateApp() && ( )} @@ -1328,7 +1326,7 @@ class HomePageComponent extends React.Component { meta?.total_count === 0 && !currentFolder.id && !appSearchKey && - (['front-end', 'modules'].includes(this.props.appType) ? ( + (['front-end', 'workflow'].includes(this.props.appType) ? ( { const getActiveConfig = () => { switch (true) { - case modalStates.showCreateAppModal || modalStates.showCreateModuleModal: + case modalStates.showCreateAppModal: return configs.create; case modalStates.showCloneAppModal: return configs.clone; From 3ba4affe6d7ef630a429eff25c0e9ac9d3f286c5 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 16 Apr 2025 17:37:24 +0530 Subject: [PATCH 03/88] Left sidebar inspector base --- frontend/package-lock.json | 12 +++ frontend/package.json | 3 +- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 80 +++++++++++++++ .../LeftSidebarInspector.jsx | 99 +++++++++---------- .../LeftSidebar/LeftSidebarInspector/utils.js | 78 +++++++++++++++ frontend/src/_styles/theme.scss | 55 +++++++++++ 6 files changed, 276 insertions(+), 51 deletions(-) create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2db11e6493..56c9e6c5c1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -89,6 +89,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -41074,6 +41075,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-accessible-treeview": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-accessible-treeview/-/react-accessible-treeview-2.11.1.tgz", + "integrity": "sha512-lFegHjFJp2OvtoHMtbIqjby7N3MGDRASlbJsMLqElxQHwZ97xIYho2S4QvXKK7l3/nII0IKDQFJXZNBj6ecG3g==", + "peerDependencies": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-base16-styling": { "version": "0.9.1", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index 45d94532f5..789fdae8df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -84,6 +84,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -263,4 +264,4 @@ "jsx" ] } -} \ No newline at end of file +} diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx new file mode 100644 index 0000000000..87194f3ca2 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import TreeView, { flattenTree } from 'react-accessible-treeview'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ToolTip } from '@/_components/ToolTip'; +import { extractComponentName } from './utils'; + +const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, callbackActions = [] }) => { + const flattendedData = flattenTree(data); + + const renderNodeIcons = (node) => { + const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; + if (icon && icon?.iconPath) { + return ( + + ); + } + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx()}
+
+ ); + } + return icon.jsx(); + } + }; + + return ( + { + const { element, getNodeProps, level, handleSelect, handleExpand, isExpanded, isDisabled, isBranch } = props; + const nodeIcon = renderNodeIcons(element.name); + const metadata = element.metadata || {}; + const { type } = metadata; + + const actions = callbackActions.filter((action) => [type, 'all'].includes(action.for)); + + return ( +
+ {(isBranch || level === 1) && ( +
+ {isExpanded ? ( + + ) : ( + + )} +
+ )} + {nodeIcon &&
{nodeIcon}
} +
+ {element.name} +
+
+ ); + }} + /> + ); +}; + +export default JSONTreeViewerV2; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 3adca4be98..6cbd53ed8f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -3,19 +3,12 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { HeaderSection } from '@/_ui/LeftSidebar'; import JSONTreeViewer from '@/_ui/JSONTreeViewer'; +import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; import useCallbackActions from './useCallbackActions'; - -const sortAndReduce = (obj) => { - return Object.entries(obj) - .sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })) - .reduce((acc, [name, value]) => { - acc[name] = value; - return acc; - }, {}); -}; +import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); @@ -33,59 +26,59 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { exposedQueries, }); const callbackActions = useCallbackActions(); + console.log('callbackActions', callbackActions); const sortedComponents = useMemo(() => { - return Object.entries(componentIdNameMapping) - .map(([key, name]) => ({ - key, - name: name || key, - value: exposedComponentsVariables[key] ?? { id: key }, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { key, name, value }) => { - acc[name] = { ...value, id: key }; - return acc; - }, {}); + return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables); }, [exposedComponentsVariables, componentIdNameMapping]); const sortedQueries = useMemo(() => { - // Create a reverse mapping for faster lookups - const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); - - const _sortedQueries = Object.entries(exposedQueries) - .map(([key, value]) => ({ - key, - name: reverseMapping[key] || key, - value, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { name, value }) => { - acc[name] = value; - return acc; - }, {}); - return _sortedQueries; + return formatInspectorQueryData(queryNameIdMapping, exposedQueries); }, [exposedQueries, queryNameIdMapping]); - const sortedVariables = useMemo(() => sortAndReduce(exposedVariables), [exposedVariables]); + const sortedVariables = useMemo(() => formatInspectorDataMisc(exposedVariables), [exposedVariables]); - const sortedConstants = useMemo(() => sortAndReduce(exposedConstants), [exposedConstants]); + const sortedConstants = useMemo(() => formatInspectorDataMisc(exposedConstants), [exposedConstants]); - const sortedPageVariables = useMemo(() => sortAndReduce(exposedPageVariables), [exposedPageVariables]); + const sortedPageVariables = useMemo(() => formatInspectorDataMisc(exposedPageVariables), [exposedPageVariables]); - const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]); + const sortedGlobalVariables = useMemo( + () => formatInspectorDataMisc(exposedGlobalVariables), + [exposedGlobalVariables] + ); const memoizedJSONData = React.useMemo(() => { - const jsontreeData = {}; - - jsontreeData['queries'] = sortedQueries; - jsontreeData['components'] = sortedComponents; - jsontreeData['globals'] = sortedGlobalVariables; - jsontreeData['variables'] = sortedVariables; - jsontreeData['page'] = sortedPageVariables; - jsontreeData['constants'] = sortedConstants; + const jsontreeData = { + name: '', + children: [ + { + name: 'Queries', + children: sortedQueries, + }, + { + name: 'Components', + children: sortedComponents, + }, + { + name: 'Globals', + children: sortedGlobalVariables, + }, + { + name: 'Variables', + children: sortedVariables, + }, + { + name: 'Page', + children: sortedPageVariables, + }, + { + name: 'Constants', + children: sortedConstants, + }, + ], + }; return jsontreeData; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); const handleNodeExpansion = (path, data, currentNode) => { @@ -128,7 +121,13 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
- + {/* { // selectedComponent={selectedComponent} treeType="inspector" darkMode={darkMode} - /> + /> */}
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js new file mode 100644 index 0000000000..5fc6176bf4 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -0,0 +1,78 @@ +export const formatInspectorDataMisc = (obj) => { + if (typeof obj !== 'object' || obj === null) return []; + const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data.reduce((acc, [name, value]) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'misc' } }]; + }, []); + }; + + return reduceData(data); +}; + +export const formatInspectorComponentData = (componentIdNameMapping, exposedComponentsVariables) => { + const data = Object.entries(componentIdNameMapping) + .map(([key, name]) => ({ + key, + name: name || key, + value: exposedComponentsVariables[key] ?? { id: key }, + })) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data + .filter((item) => item.name) + .reduce((acc, { key, name, value }) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'components' } }]; + }, []); + }; + + return reduceData(data); +}; + +export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => { + const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); + const _sortedQueries = Object.entries(exposedQueries) + .map(([key, value]) => ({ + key, + name: reverseMapping[key] || key, + value, + })) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data + .filter((item) => item.name) + .reduce((acc, { id, name, value }) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'queries' } }]; + }, []); + }; + + return reduceData(_sortedQueries); +}; + +export const extractComponentName = (path) => { + // Match the last part of the URL before ".svg" using a regular expression + const match = path.match(/\/([^/]+)\.svg$/); + + if (match && match[1]) { + return match[1]; // Return the matched component name + } else { + return null; // Return null if the pattern doesn't match + } +}; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 4fd1083987..0bc2abcdd3 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18875,4 +18875,59 @@ section.ai-message-prompt-input-wrapper { .cm-editor { max-height: 100px !important; } +} + + +.basic.tree { + list-style: none; + margin: 0; + padding: 23.4px; +} +.basic .tree-node, +.basic .tree-node-group { + list-style: none; + margin: 0; + padding: 0; +} + +.basic .tree-branch-wrapper, +.basic .tree-node__leaf { + outline: none; +} + +// .basic .tree-node--focused { +// outline-color: rgb(77, 144, 254); +// outline-style: auto; +// outline-width: 2px; +// display: block; +// } + +.basic .tree-node__branch { + display: block; +} + +.basic .tree-node { + cursor: pointer; +} + +.node-expansion-icon { + width:20px; + height:20px; + margin-right: 2px; + display: flex; + align-items: center; + justify-content: center; +} + +.node-icon { + display: flex; + align-items: center; + justify-content: center; + margin-right: 6px; +} + +.node-label { + display: flex; + align-items: center; + font-size:12px; } \ No newline at end of file From 12428260ab9ba648fbbd23af0bb593ee706630b8 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Sat, 19 Apr 2025 16:22:26 +0530 Subject: [PATCH 04/88] Added modules container --- .../assets/images/icons/module-editor.svg | 3 + .../modules/blank-module-list-icon-1.svg | 3 + .../modules/blank-module-list-icon-2.svg | 3 + .../assets/images/modules/blank-module.svg | 3 + frontend/ee | 2 +- frontend/src/AppBuilder/AppBuilder.jsx | 13 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 72 ++++-- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 31 ++- .../src/AppBuilder/AppCanvas/Container.jsx | 71 +++++- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 8 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 2 + .../AppBuilder/AppCanvas/appCanvasUtils.js | 36 ++- .../AppBuilder/CodeBuilder/Elements/Query.jsx | 100 ++++++++ .../src/AppBuilder/CodeBuilder/TypeMapping.js | 1 + .../CodeEditor/DynamicFxTypeRenderer.jsx | 2 + .../CodeEditor/SingleLineCodeEditor.jsx | 49 ++-- frontend/src/AppBuilder/CodeEditor/utils.js | 3 + .../src/AppBuilder/Header/EditorHeader.jsx | 24 +- .../RightTopHeaderButtons.jsx | 4 +- .../LeftSidebar/GlobalSettings/index.jsx | 19 +- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 137 +++++----- .../LeftSidebarInspector.jsx | 15 +- .../ComponentConfigurationTab.jsx | 3 +- .../ComponentModuleTab/ComponentModuleTab.jsx | 32 +++ .../ComponentModuleTab/index.js | 1 + .../ComponentModuleTab/styles.scss | 19 ++ .../ComponentsManagerTab.jsx | 24 +- .../DragLayer.jsx | 13 +- .../constants.js | 0 .../RightSideBar/ComponentManagerTab/index.js | 1 + .../ComponentsManagerTab/index.js | 1 - .../Inspector/Components/DefaultComponent.jsx | 2 +- .../RightSideBar/Inspector/Elements/Code.jsx | 17 +- .../RightSideBar/Inspector/Inspector.jsx | 140 +++++----- .../RightSideBar/Inspector/Utils.js | 14 +- .../AppBuilder/RightSideBar/RightSideBar.jsx | 8 +- frontend/src/AppBuilder/Viewer/Viewer.jsx | 97 ++++--- .../WidgetManager/configs/widgetConfig.js | 4 + .../AppBuilder/WidgetManager/widgets/index.js | 4 + .../WidgetManager/widgets/moduleContainer.js | 36 +++ .../WidgetManager/widgets/moduleViewer.js | 30 +++ .../AppBuilder/_contexts/ModuleContext.jsx | 23 +- .../src/AppBuilder/_helpers/editorHelpers.js | 5 + frontend/src/AppBuilder/_hooks/useAppData.js | 62 ++++- frontend/src/AppBuilder/_stores/ast.js | 9 +- .../_stores/slices/DependencyClass.js | 2 +- .../src/AppBuilder/_stores/slices/appSlice.js | 1 - .../_stores/slices/componentsSlice.js | 11 +- .../AppBuilder/_stores/slices/eventsSlice.js | 1 - .../AppBuilder/_stores/slices/moduleSlice.js | 5 + .../_stores/slices/resolvedSlice.js | 45 +++- frontend/src/AppBuilder/_stores/store.js | 2 + frontend/src/AppBuilder/_stores/utils.js | 4 +- frontend/src/AppLoader/AppLoader.jsx | 10 +- .../Editor/ControlledComponentToRender.jsx | 4 +- frontend/src/_helpers/editorHelpers.js | 239 +++++++++--------- frontend/src/_services/apps.service.js | 24 +- frontend/src/_styles/theme.scss | 19 +- .../_ui/Icon/solidIcons/EmptyStateModules.jsx | 25 ++ frontend/src/_ui/Icon/solidIcons/index.js | 3 + .../ModuleContainer/ModuleContainer.jsx | 8 + .../components/ModuleContainer/index.js | 1 + .../ModuleContainerBlank.jsx | 8 + .../components/ModuleContainerBlank/index.js | 1 + .../ModuleContainerInspector.jsx | 8 + .../ModuleContainerInspector/index.js | 1 + .../ModuleEditorBanner/ModuleEditorBanner.jsx | 8 + .../components/ModuleEditorBanner/index.js | 1 + .../ModuleManager/ModuleManager.jsx | 8 + .../Modules/components/ModuleManager/index.js | 1 + .../components/ModuleViewer/ModuleViewer.jsx | 8 + .../Modules/components/ModuleViewer/index.js | 1 + .../ModuleViewerInspector.jsx | 8 + .../components/ModuleViewerInspector/index.js | 1 + .../ModuleWidgetBox/ModuleWidgetBox.jsx | 8 + .../components/ModuleWidgetBox/index.js | 1 + .../src/modules/Modules/components/index.js | 19 ++ frontend/src/modules/Modules/index.js | 2 + frontend/src/modules/index.js | 2 + server/ee | 2 +- server/src/modules/app/constants/modules.ts | 1 + server/src/modules/app/module.ts | 2 + .../apps/services/component.service.ts | 4 +- .../apps/services/widget-config/index.js | 4 + .../services/widget-config/moduleContainer.js | 36 +++ .../services/widget-config/moduleViewer.js | 31 +++ .../src/modules/modules/IModulesController.ts | 6 + server/src/modules/modules/constants/index.ts | 4 + server/src/modules/modules/module.ts | 52 ++++ .../src/modules/modules/modules.controller.ts | 20 ++ 90 files changed, 1352 insertions(+), 446 deletions(-) create mode 100644 frontend/assets/images/icons/module-editor.svg create mode 100644 frontend/assets/images/modules/blank-module-list-icon-1.svg create mode 100644 frontend/assets/images/modules/blank-module-list-icon-2.svg create mode 100644 frontend/assets/images/modules/blank-module.svg create mode 100644 frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/ComponentsManagerTab.jsx (90%) rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/DragLayer.jsx (80%) rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/constants.js (100%) create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js delete mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js create mode 100644 frontend/src/AppBuilder/_stores/slices/moduleSlice.js create mode 100644 frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainer/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainerBlank/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainerInspector/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleEditorBanner/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleManager/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleViewer/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleViewerInspector/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleWidgetBox/index.js create mode 100644 frontend/src/modules/Modules/components/index.js create mode 100644 frontend/src/modules/Modules/index.js create mode 100644 server/src/modules/apps/services/widget-config/moduleContainer.js create mode 100644 server/src/modules/apps/services/widget-config/moduleViewer.js create mode 100644 server/src/modules/modules/IModulesController.ts create mode 100644 server/src/modules/modules/constants/index.ts create mode 100644 server/src/modules/modules/module.ts create mode 100644 server/src/modules/modules/modules.controller.ts diff --git a/frontend/assets/images/icons/module-editor.svg b/frontend/assets/images/icons/module-editor.svg new file mode 100644 index 0000000000..e0b55223c3 --- /dev/null +++ b/frontend/assets/images/icons/module-editor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-1.svg b/frontend/assets/images/modules/blank-module-list-icon-1.svg new file mode 100644 index 0000000000..1f9c188e27 --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-1.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-2.svg b/frontend/assets/images/modules/blank-module-list-icon-2.svg new file mode 100644 index 0000000000..4ca21df25a --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-2.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module.svg b/frontend/assets/images/modules/blank-module.svg new file mode 100644 index 0000000000..35207b8ca1 --- /dev/null +++ b/frontend/assets/images/modules/blank-module.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/ee b/frontend/ee index 715a830c7a..45c8130ae5 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69 +Subproject commit 45c8130ae5ee82abdfe1843c25dbdf694aac3433 diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 30b0d6d97b..48b280aa7c 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -22,10 +22,11 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; // const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel')); // TODO: split Loader into separate component and remove editor loading state from Editor -export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode }) => { +export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType }) => { useAppData(appId, moduleId, darkMode); const isEditorLoading = useStore((state) => state.isEditorLoading); const currentMode = useStore((state) => state.currentMode); + const isModuleEditor = appType === 'module'; const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode); @@ -46,15 +47,15 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
Loading...
}> - - + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - + + - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 365c3bbc59..e8183cc535 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Container } from './Container'; import Grid from './Grid'; import { EditorSelecto } from './Selecto'; -import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleId, useIsModuleMode } from '@/AppBuilder/_contexts/ModuleContext'; import { HotkeyProvider } from './HotkeyProvider'; import './appCanvas.scss'; import useStore from '@/AppBuilder/_stores/store'; @@ -18,7 +18,7 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); const canvasHeight = useStore((state) => state.canvasHeight); @@ -41,6 +41,10 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); + const moduleId = useModuleId(); + const isModuleMode = useIsModuleMode(); + + const hideSidebar = isModuleMode || isPagesSidebarHidden; useEffect(() => { // Need to remove this if we shift setExposedVariable Logic outside of components @@ -60,6 +64,45 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { return () => window.removeEventListener('resize', handleResize); }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]); + const styles = useMemo(() => { + const canvasBgColor = + currentMode === 'view' + ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) + : !isAppDarkMode + ? '#EBEBEF' + : '#2F3C4C'; + + if (isModuleMode) { + return { + borderLeft: 'none', + height: '100%', + background: canvasBgColor, + }; + } + + return { + borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', + height: currentMode === 'edit' ? canvasContainerHeight : '100%', + background: canvasBgColor, + marginLeft: + isViewerSidebarPinned && !hideSidebar && currentLayout !== 'mobile' && currentMode !== 'edit' + ? pageSidebarStyle === 'icon' + ? '65px' + : '210px' + : 'auto', + }; + }, [ + currentMode, + isAppDarkMode, + isModuleMode, + editorMarginLeft, + canvasContainerHeight, + isViewerSidebarPinned, + hideSidebar, + currentLayout, + pageSidebarStyle, + ]); + return (
{ { 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned }, { 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' } )} - style={{ - // transform: `scale(1)`, - borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', - height: currentMode === 'edit' ? canvasContainerHeight : '100%', - background: - currentMode === 'view' - ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) - : !isAppDarkMode - ? '#EBEBEF' - : '#2F3C4C', - marginLeft: - isViewerSidebarPinned && !isPagesSidebarHidden && currentLayout !== 'mobile' && currentMode !== 'edit' - ? pageSidebarStyle === 'icon' - ? '65px' - : '210px' - : 'auto', - }} + style={styles} >
{ {environmentLoadingState !== 'loading' && (
{ canvasMaxWidth={canvasMaxWidth} isViewerSidebarPinned={isViewerSidebarPinned} pageSidebarStyle={pageSidebarStyle} + appType={appType} />
)} {currentMode === 'view' || (currentLayout === 'mobile' && isAutoMobileLayout) ? null : ( - + )}
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 4c558f3cc2..ad972f7e90 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -18,6 +18,7 @@ export const ConfigHandle = ({ showHandle, componentType, visibility, + hideDelete, }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow); @@ -123,20 +124,22 @@ export const ConfigHandle = ({ data-cy={`${componentName.toLowerCase()}-inspect-button`} className="config-handle-inspect" /> - { - deleteComponents([id]); - }} - data-cy={`${componentName.toLowerCase()}-delete-button`} - > - - + {!hideDelete && ( + { + deleteComponents([id]); + }} + data-cy={`${componentName.toLowerCase()}-delete-button`} + > + + + )}
)} diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index e622e1a2cd..6a5afd83e2 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -20,7 +20,7 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; - +import { ModuleContainerBlank } from '@/modules/Modules/components'; //TODO: Revisit the logic of height (dropRef) /* @@ -44,6 +44,7 @@ export const Container = React.memo( isViewerSidebarPinned, pageSidebarStyle, componentType, + appType, }) => { const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); @@ -63,13 +64,13 @@ export const Container = React.memo( }, [componentType, index, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ - accept: 'box', + accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', hover: (item) => { item.canvasRef = realCanvasRef?.current; item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); }, - drop: async ({ componentType }, monitor) => { + drop: async ({ componentType, component }, monitor) => { const didDrop = monitor.didDrop(); if (didDrop) return; if (componentType === 'PDF' && !isPDFSupported()) { @@ -78,15 +79,40 @@ export const Container = React.memo( ); return; } + + const moduleInfo = component?.moduleId + ? { + moduleId: component.moduleId, + versionId: component.versionId, + environmentId: component.environmentId, + moduleName: component.displayName, + moduleContainer: component.moduleContainer, + } + : undefined; + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { - const parentComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const parentComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); const newComponents = [parentComponent, ...childComponents]; await addComponentToCurrentPage(newComponents); // setSelectedComponents([parentComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } else { - const newComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const newComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); await addComponentToCurrentPage([newComponent]); // setSelectedComponents([newComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); @@ -97,7 +123,11 @@ export const Container = React.memo( }), }); - const showEmptyContainer = currentMode === 'edit' && id === 'canvas' && components.length === 0 && !isOverCurrent; + const showEmptyContainer = + currentMode === 'edit' && + (id === 'canvas' || componentType === 'ModuleContainer') && + components.length === 0 && + !isOverCurrent; function getContainerCanvasWidth() { if (canvasWidth !== undefined) { @@ -146,6 +176,24 @@ export const Container = React.memo( [setLastCanvasClickPosition] ); + /* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. + Hence display is set to none on dragover and removed only when the component is added */ + + const renderEmptyContainer = () => { + if (components && components?.length !== 0) return; + + const styles = { + display: showEmptyContainer ? 'block' : 'none', + ...(componentType === 'ModuleContainer' ? { height: '100%', width: '100%' } : {}), + }; + + return ( +
+ {componentType === 'ModuleContainer' ? : } +
+ ); + }; + return (
{ @@ -213,14 +263,7 @@ export const Container = React.memo( /> ))}
- - {/* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. - Hence display is set to none on dragover and removed only when the component is added */} - {(!components || components?.length === 0) && ( -
- -
- )} + {renderEmptyContainer()}
); } diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 44f148a000..abddc4681f 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -37,7 +37,7 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout }) { +export default function Grid({ gridWidth, currentLayout, appType }) { const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); @@ -329,6 +329,11 @@ export default function Grid({ gridWidth, currentLayout }) { }; const isComponentVisible = (id) => { + // Return TRUE if it is a module container + if (appType === 'module') { + return true; + } + const component = getResolvedComponent(id); let visibility; if (isArray(component)) { @@ -641,6 +646,7 @@ export default function Grid({ gridWidth, currentLayout }) { }} onResizeEnd={(e) => { try { + console.log('end--- e.target.id', e.target.id); useGridStore.getState().actions.setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 0f1cb3359f..ac60a5790f 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -91,6 +91,8 @@ const WidgetWrapper = memo( showHandle={isWidgetActive} componentType={componentType} visibility={visibility} + customClassName={componentType === 'ModuleContainer' ? 'module-container' : ''} + hideDelete={componentType === 'ModuleContainer'} /> )} { +export const addNewWidgetToTheEditor = ( + componentType, + eventMonitorObject, + currentLayout, + realCanvasRef, + parentId, + moduleInfo = undefined +) => { const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); @@ -42,6 +49,25 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas']; const width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth); + let customLayouts = undefined; + + if (moduleInfo) { + componentData.definition.properties.moduleAppId = { value: moduleInfo.moduleId }; + componentData.definition.properties.moduleVersionId = { value: moduleInfo.versionId }; + componentData.definition.properties.moduleEnvironmentId = { value: moduleInfo.environmentId }; + customLayouts = moduleInfo.moduleContainer.layouts; + + const inputItems = Object.values( + moduleInfo.moduleContainer.component.definition.properties?.input_items?.value ?? {} + ); + + console.log('inputItems--- ', inputItems); + + for (const { name, default_value } of inputItems) { + componentData.definition.properties[name] = { value: default_value }; + } + } + if (currentLayout === 'mobile') { componentData.definition.others.showOnDesktop.value = `{{false}}`; componentData.definition.others.showOnMobile.value = `{{true}}`; @@ -59,14 +85,14 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre [currentLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[currentLayout].width : width, + height: customLayouts ? customLayouts[currentLayout].height : defaultHeight, }, [nonActiveLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[nonActiveLayout].width : width, + height: customLayouts ? customLayouts[nonActiveLayout].height : defaultHeight, }, }, withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component), diff --git a/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx new file mode 100644 index 0000000000..f182a5cbb9 --- /dev/null +++ b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx @@ -0,0 +1,100 @@ +import React, { useCallback, useMemo } from 'react'; +import SelectComponent from '@/_ui/Select'; +import { components } from 'react-select'; +import Check from '@/_ui/Icon/solidIcons/Check'; +import useStore from '@/AppBuilder/_stores/store'; + +const Option = (props) => { + return ( + +
+ {props.label} + {props.isSelected && ( + + + + )} +
+
+ ); +}; + +const selectCustomStyles = { + control: (base, state) => { + return { + ...base, + border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc', + boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none', + backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)', + '&:hover': { + border: '1px solid #3E63DD !important', + boxShadow: '0px 0px 6px #3E63DD', + }, + borderRadius: '6px', + width: '144px', + minHeight: '32px', + }; + }, + + dropdownIndicator: (base) => ({ + ...base, + padding: '4px', + }), + menuList: (base) => ({ + ...base, + padding: '4px', + }), + option: (base, state) => ({ + ...base, + backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white', + color: '#11181C', + borderRadius: '6px', + }), +}; + +export const Query = ({ value, onChange, meta }) => { + const dataQueries = useStore((state) => state.dataQuery.getCurrentModuleQueries('canvas')); + const options = dataQueries + .filter((query) => !(meta?.skipKinds ?? []).includes(query.kind)) + .map((query) => ({ name: query.name, value: query.id })); + + const onValueChange = useCallback( + (value) => { + console.log('value--- ', value, options); + onChange(value); + }, + [onChange, options] + ); + + // const cleanedValue = useMemo(() => { + // if (initialValue) { + // return initialValue.replace('{{queries.', '').replace('}}', ''); + // } + // return ''; + // }, [initialValue]); + + return ( +
+
e.stopPropagation()}> + null, + Option, + }} + /> +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js index eb01df1241..a249f52676 100644 --- a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js +++ b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js @@ -20,4 +20,5 @@ export const TypeMapping = { visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }; diff --git a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx index 20b196476d..ca639e4643 100644 --- a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx +++ b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx @@ -18,6 +18,7 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput'; import { Datepicker } from '../CodeBuilder/Elements/Datepicker'; import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput'; import { TimePicker } from '../CodeBuilder/Elements/TimePicker'; +import { Query } from '../CodeBuilder/Elements/Query'; const AllElements = { Color, @@ -38,6 +39,7 @@ const AllElements = { TableRowHeightInput, Datepicker, TimePicker, + Query, }; export const DynamicFxTypeRenderer = ({ paramType, ...restProps }) => { diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 5506a94fed..c1c0e03aa5 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -442,6 +442,33 @@ const DynamicEditorBridge = (props) => { setForceCodeBox(fxActive); }, [component, fxActive]); + const renderFx = () => { + if (paramType === 'query' || (paramLabel !== 'Type' && isFxNotRequired === undefined)) { + return null; + } + return ( +
+ { + if (codeShow) { + setForceCodeBox(false); + onFxPress(false); + } else { + setForceCodeBox(true); + onFxPress(true); + } + }} + dataCy={cyLabel} + /> +
+ ); + }; + const fxClass = isEventManagerParam ? 'justify-content-start' : 'justify-content-end'; return (
@@ -459,27 +486,7 @@ const DynamicEditorBridge = (props) => { )}
- {paramLabel !== 'Type' && isFxNotRequired === undefined && ( -
- { - if (codeShow) { - setForceCodeBox(false); - onFxPress(false); - } else { - setForceCodeBox(true); - onFxPress(true); - } - }} - dataCy={cyLabel} - /> -
- )} + {renderFx()}
{!codeShow && ( diff --git a/frontend/src/AppBuilder/CodeEditor/utils.js b/frontend/src/AppBuilder/CodeEditor/utils.js index 03473a629f..04f92b352e 100644 --- a/frontend/src/AppBuilder/CodeEditor/utils.js +++ b/frontend/src/AppBuilder/CodeEditor/utils.js @@ -143,6 +143,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor 'queries', 'globals', 'page', + 'input', 'constants', 'moment', '_', @@ -157,6 +158,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor isJsCode ? state?.queries : undefined, isJsCode ? state?.globals : undefined, isJsCode ? state?.page : undefined, + isJsCode ? state?.input : undefined, state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly. moment, _, @@ -353,6 +355,7 @@ export const FxParamTypeMapping = Object.freeze({ visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }); export function computeCoercion(oldValue, newValue) { diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index fb779c6269..c5420c98bf 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -12,8 +12,8 @@ import RightTopHeaderButtons from './RightTopHeaderButtons/RightTopHeaderButtons import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; - -export const EditorHeader = ({ darkMode }) => { +import { ModuleEditorBanner } from '@/modules/Modules/components'; +export const EditorHeader = ({ darkMode, isModuleEditor }) => { const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ isSaving: state.app.isSaving, @@ -72,7 +72,10 @@ export const EditorHeader = ({ darkMode }) => { }} >
- +
+ {isModuleEditor && } + +
@@ -96,20 +99,21 @@ export const EditorHeader = ({ darkMode }) => { {shouldEnableMultiplayer && }
-
- {/*
*/} + {!isModuleEditor &&
}
- {/* {editingVersion && ( */} - - {/* )} */} - + {!isModuleEditor && ( + <> + + + + )}
- +
diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index d22a2978c8..021887c824 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -9,12 +9,12 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; -const RightTopHeaderButtons = () => { +const RightTopHeaderButtons = ({ isModuleEditor }) => { return (
- + {!isModuleEditor && }
); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx index 869677928c..1e649961d8 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx @@ -8,9 +8,26 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import AppModeToggle from './AppModeToggle'; -const GlobalSettings = ({ darkMode }) => { +const GlobalSettings = ({ darkMode, isModuleEditor }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); + if (isModuleEditor) { + return ( +
+
+ + + +
+
+ +
+
+
+
+ ); + } + return ( <>
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 4146c4a28e..4a3507c59e 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -22,6 +22,7 @@ export const BaseLeftSidebar = ({ switchDarkMode, renderAISideBarTrigger = () => null, renderAIChat = () => null, + isModuleEditor = false, }) => { const [ pinned, @@ -142,6 +143,7 @@ export const BaseLeftSidebar = ({ // globalSettingsChanged={globalSettingsChanged} // globalSettings={appDefinition.globalSettings} darkMode={darkMode} + isModuleEditor={isModuleEditor} // toggleAppMaintenance={toggleAppMaintenance} // isMaintenanceOn={isMaintenanceOn} // app={app} @@ -156,72 +158,79 @@ export const BaseLeftSidebar = ({ return null; } + const renderCommonItems = () => { + return ( + <> + handleSelectedSidebarItem('inspect')} + darkMode={darkMode} + icon="inspect" + className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} + tip="Inspector" + ref={setSideBarBtnRefs('inspect')} + /> + + handleSelectedSidebarItem('debugger')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + count={unreadErrorCount} + tip="Debugger" + ref={setSideBarBtnRefs('debugger')} + /> + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> + + ); + }; + + const renderLeftSidebarItems = () => { + if (isModuleEditor) { + return renderCommonItems(); + } + return ( + <> + {renderAISideBarTrigger({ + selectedSidebarItem: selectedSidebarItem, + onClick: () => handleSelectedSidebarItem('tooljetai'), + darkMode: darkMode, + icon: 'tooljetai', + className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, + tip: 'Build with AI', + ref: setSideBarBtnRefs('tooljetai'), + })} + handleSelectedSidebarItem('page')} + darkMode={darkMode} + icon="page" + className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} + tip="Pages" + ref={setSideBarBtnRefs('page')} + /> + {renderCommonItems()} + + ); + }; + return (
- {renderAISideBarTrigger({ - selectedSidebarItem: selectedSidebarItem, - onClick: () => handleSelectedSidebarItem('tooljetai'), - darkMode: darkMode, - icon: 'tooljetai', - className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, - tip: 'Build with AI', - ref: setSideBarBtnRefs('tooljetai'), - })} - handleSelectedSidebarItem('page')} - darkMode={darkMode} - icon="page" - className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} - tip="Pages" - ref={setSideBarBtnRefs('page')} - /> - - handleSelectedSidebarItem('inspect')} - darkMode={darkMode} - icon="inspect" - className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} - tip="Inspector" - ref={setSideBarBtnRefs('inspect')} - /> - - handleSelectedSidebarItem('debugger')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - count={unreadErrorCount} - tip="Debugger" - ref={setSideBarBtnRefs('debugger')} - /> - handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - /> - - {/* {dataSources?.length > 0 && ( - handleSelectedSidebarItem('datasource')} - icon="datasource" - className={`left-sidebar-item left-sidebar-layout sidebar-datasources`} - tip="Sources" - ref={setSideBarBtnRefs('datasource')} - /> - )} */} - + {renderLeftSidebarItems()} { // if tooljetai is open don't close diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 3adca4be98..a1e483d8e1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -24,6 +24,8 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow); const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); + const exposedModuleInputs = useStore((state) => state.getAllExposedValues().input || {}, shallow); + const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); const pathToBeInspected = useStore((state) => state.pathToBeInspected); @@ -74,6 +76,8 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]); + const sortedModuleInputs = useMemo(() => sortAndReduce(exposedModuleInputs), [exposedModuleInputs]); + const memoizedJSONData = React.useMemo(() => { const jsontreeData = {}; @@ -83,10 +87,19 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { jsontreeData['variables'] = sortedVariables; jsontreeData['page'] = sortedPageVariables; jsontreeData['constants'] = sortedConstants; + if (sortedModuleInputs) jsontreeData['input'] = sortedModuleInputs; return jsontreeData; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); + }, [ + sortedComponents, + sortedQueries, + sortedVariables, + sortedConstants, + sortedPageVariables, + sortedGlobalVariables, + sortedModuleInputs, + ]); const handleNodeExpansion = (path, data, currentNode) => { if (pathToBeInspected && path?.length > 0) { diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx index e0ddb28922..2f4b785b6a 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx @@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; -export const ComponentConfigurationTab = ({ darkMode }) => { +export const ComponentConfigurationTab = ({ darkMode, isModuleEditor }) => { const selectedComponentId = useStore((state) => state.selectedComponents?.[0], shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); if (!selectedComponentId) { @@ -17,6 +17,7 @@ export const ComponentConfigurationTab = ({ darkMode }) => { darkMode={darkMode} selectedComponentId={selectedComponentId} pages={[]} + isModuleEditor={isModuleEditor} /> ); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx new file mode 100644 index 0000000000..f1126ab4d5 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import './styles.scss'; + +export const ComponentModuleTab = ({ onChangeTab }) => { + const [activeTab, setActiveTab] = useState(1); + + const handleChangeTab = (tab) => { + setActiveTab(tab); + onChangeTab(tab); + }; + + return ( +
+
+ + +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js new file mode 100644 index 0000000000..a9bf63add0 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js @@ -0,0 +1 @@ +export { ComponentModuleTab as default } from './ComponentModuleTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss new file mode 100644 index 0000000000..51fdce0d61 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss @@ -0,0 +1,19 @@ +.tj-tabs-container-outer { + padding-top: 0px; + gap: 10px; + height: 36px; + margin-bottom: 8px; + margin-top: 16px; +} + +.tj-tabs-container { + padding: 2px; + gap: 2px; + display: flex; + + width: 100%; + height: 100%; + background: var(--slate4); + border-radius: 6px; + +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx similarity index 90% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx index 2ad7977496..ca98316a73 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx @@ -7,18 +7,21 @@ import Fuse from 'fuse.js'; import { SearchBox } from '@/_components'; import { DragLayer } from './DragLayer'; import useStore from '@/AppBuilder/_stores/store'; +import ComponentModuleTab from './ComponentModuleTab'; +import { ModuleManager } from '@/modules/Modules/components'; // TODO: Hardcode all the component-section mapping in a constant file and just loop over it // TODO: styling // TODO: scrolling // TODO: searching -export const ComponentsManagerTab = ({ darkMode }) => { +export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const componentList = useMemo(() => { return componentTypes.map((component) => component.component); }, [componentTypes]); const [filteredComponents, setFilteredComponents] = useState(componentList); + const [activeTab, setActiveTab] = useState(1); const _shouldFreeze = useStore((state) => state.getShouldFreeze()); const isAutoMobileLayout = useStore((state) => state.currentLayout === 'mobile' && state.getIsAutoMobileLayout()); const shouldFreeze = _shouldFreeze || isAutoMobileLayout; @@ -157,9 +160,24 @@ export const ComponentsManagerTab = ({ darkMode }) => { } } + const handleChangeTab = (tab) => { + setActiveTab(tab); + }; + + const renderSection = () => { + if (activeTab === 1) { + return
{segregateSections()}
; + } + return ; + }; + return (
-

Components

+ {isModuleEditor ? ( +

Components

+ ) : ( + + )}
{ width={266} />
-
{segregateSections()}
+ {renderSection()}
); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx similarity index 80% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 77274cd658..cae0c00624 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -1,11 +1,13 @@ import React, { useEffect } from 'react'; import { WidgetBox } from '../WidgetBox'; +import { ModuleWidgetBox } from '@/modules/Modules/components'; import { useDrag, useDragLayer } from 'react-dnd'; import { getEmptyImage } from 'react-dnd-html5-backend'; import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants'; -export const DragLayer = ({ index, component }) => { +export const DragLayer = ({ index, component, isModuleTab = false }) => { + // const currentLayout = useStore((state) => state.currentLayout, shallow); const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', @@ -19,12 +21,17 @@ export const DragLayer = ({ index, component }) => { preview(getEmptyImage(), { captureDraggingState: true }); }, []); + // const size = isModuleTab + // ? component.module_container.layouts[currentLayout] + // : component.defaultSize || { width: 30, height: 40 }; + const size = component.defaultSize || { width: 30, height: 40 }; + return ( <> {isDragging && } -
- +
+ {isModuleTab ? : }
); diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js similarity index 100% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js new file mode 100644 index 0000000000..6f24ff7948 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js @@ -0,0 +1 @@ +export { ComponentsManagerTab as default } from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js deleted file mode 100644 index ddb70f8220..0000000000 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index d680f21d1e..f131e2ea81 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx @@ -6,7 +6,7 @@ import { renderElement } from '../Utils'; import i18next from 'i18next'; import { resolveReferences } from '@/_helpers/utils'; // import { AllComponents } from '@/Editor/Box'; -import { AllComponents } from '@/_helpers/editorHelpers'; +import { AllComponents } from '@/AppBuilder/_helpers/editorHelpers'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx index c74e023c0b..f2b0ff7594 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx @@ -20,14 +20,23 @@ export const Code = ({ placeholder, validationFn, isHidden = false, + customMeta, }) => { const currentState = useCurrentState(); - let initialValue = !_.isEmpty(definition) - ? definition.value - : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + function getInitialValue() { + if (customMeta && customMeta.defaultValue) { + return customMeta.defaultValue; + } + return !_.isEmpty(definition) + ? definition.value + : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + } - const paramMeta = accordian ? componentMeta[paramType]?.[param.name] : componentMeta[paramType][param.name]; + let initialValue = getInitialValue(); + const paramMeta = accordian + ? customMeta ?? componentMeta[paramType]?.[param.name] + : customMeta ?? componentMeta[paramType][param.name]; const displayName = paramMeta.displayName || param.name; function handleCodeChanged(value) { diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index 6a3465403d..a69cd94470 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -39,6 +39,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes'; import { copyComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils.js'; import DatetimePickerV2 from './Components/DatetimePickerV2.jsx'; +import { ModuleContainerInspector, ModuleViewerInspector, ModuleEditorBanner } from '@/modules/Modules/components'; const INSPECTOR_HEADER_OPTIONS = [ { @@ -105,6 +106,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte definition: allComponents?.[selectedComponentId]?.component.definition, }; + const isModuleContainer = componentMeta.component === 'ModuleContainer'; + const component = { id: selectedComponentId, component: JSON.parse(JSON.stringify(componentMeta)), @@ -443,8 +446,39 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ showHeaderActionsMenu })]); + const renderAppNameInput = () => { + if (isModuleContainer) { + return ; + } + + return ( +
+ setNewComponentName(e.target.value)} + type="text" + onBlur={() => handleComponentNameChange(newComponentName)} + className="w-100 inspector-edit-widget-name" + value={newComponentName} + ref={inputRef} + data-cy="edit-widget-name" + /> +
+ ); + }; + + const renderTabs = () => ( + + ); + return ( -
+
clearSelectedComponents()}> @@ -456,68 +490,50 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
-
-
- setNewComponentName(e.target.value)} - type="text" - onBlur={() => handleComponentNameChange(newComponentName)} - className="w-100 inspector-edit-widget-name" - value={newComponentName} - ref={inputRef} - data-cy="edit-widget-name" - /> -
-
-
- - - {INSPECTOR_HEADER_OPTIONS.map((option) => ( -
{ - e.stopPropagation(); - handleInspectorHeaderActions(option.value); - }} - > -
{option.icon}
+
{renderAppNameInput()}
+ {!isModuleContainer && ( +
+ + + {INSPECTOR_HEADER_OPTIONS.map((option) => (
{ + e.stopPropagation(); + handleInspectorHeaderActions(option.value); + }} > - {option?.label} +
{option.icon}
+
+ {option?.label} +
-
- ))} - - - } - > - setShowHeaderActionsMenu(true)}> - - - -
-
-
- - - {propertiesTab} - - - {stylesTab} - - + ))} + + + } + > + setShowHeaderActionsMenu(true)}> + + + +
+ )}
+ +
{renderTabs()}
@@ -731,6 +747,12 @@ const GetAccordion = React.memo( case 'TimePicker': return ; + case 'ModuleContainer': + return ; + + case 'ModuleViewer': + return ; + default: { return ; } diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js index 62ee032172..141b6a6261 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js @@ -28,13 +28,14 @@ export function renderCustomStyles( components = {}, accordian, darkMode = false, - placeholder = '' + placeholder = '', + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; const paramTypeDefinition = componentDefinition[paramType] || {}; const definition = paramTypeDefinition[param] || {}; - const meta = componentMeta[paramType]?.[accordian]?.[param]; + const meta = customMeta ?? componentMeta[paramType]?.[accordian]?.[param]; if ( componentConfig.component == 'DropDown' || @@ -92,7 +93,7 @@ export function renderCustomStyles( return ( <> ); @@ -125,7 +127,8 @@ export function renderElement( components = {}, darkMode = false, placeholder = '', - validationFn + validationFn, + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; @@ -155,7 +158,7 @@ export function renderElement( return ( ); } diff --git a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx index 88ea0f477a..5efc153286 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx @@ -1,11 +1,11 @@ import React from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { ComponentConfigurationTab } from './ComponentConfigurationTab'; -import { ComponentsManagerTab } from './ComponentsManagerTab'; +import ComponentsManagerTab from './ComponentManagerTab'; import cx from 'classnames'; import { PageSettings } from './PageSettingsTab'; -export const RightSideBar = ({ darkMode }) => { +export const RightSideBar = ({ darkMode, isModuleEditor }) => { const activeTab = useStore((state) => state.activeRightSideBarTab); const pageSettingSelected = useStore((state) => state.pageSettingSelected); @@ -15,9 +15,9 @@ export const RightSideBar = ({ darkMode }) => {
{pageSettingSelected && } {activeTab === 'components' ? ( - + ) : ( - + )}
diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index af820fca13..abf0ef6646 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -16,11 +16,20 @@ import { shallow } from 'zustand/shallow'; import Popups from '../Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; -export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, environmentId, versionId } = {}) => { +export const Viewer = ({ + id: appId, + darkMode, + moduleId = 'canvas', + switchDarkMode, + environmentId, + versionId, + moduleMode = false, + appType = 'front-end', +} = {}) => { const DEFAULT_CANVAS_WIDTH = 1292; const { t } = useTranslation(); const [isSidebarPinned, setIsSidebarPinned] = useState(localStorage.getItem('isPagesSidebarPinned') !== 'false'); - useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }); + useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); const { isEditorLoading, @@ -69,18 +78,20 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow); const deviceWindowWidth = window.screen.width - 5; + const hideSidebar = moduleMode || isPagesSidebarHidden; + const computeCanvasMaxWidth = useCallback(() => { if (globalSettings?.maxCanvasWidth) { return globalSettings.maxCanvasWidth; } if (globalSettings?.canvasMaxWidthType === 'px') { - return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!isPagesSidebarHidden ? 200 : 0); + return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!hideSidebar ? 200 : 0); } if (globalSettings?.canvasMaxWidthType === '%') { return +globalSettings?.canvasMaxWidth + '%'; } return DEFAULT_CANVAS_WIDTH; - }, [globalSettings, isPagesSidebarHidden]); + }, [globalSettings, hideSidebar]); const toggleSidebarPinned = useCallback(() => { const newValue = !isSidebarPinned; @@ -119,6 +130,45 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod }; }, []); + const renderHeader = () => { + if (moduleMode) { + return null; + } + + if (currentLayout !== 'mobile') { + return ( + + ); + } + + return ( + <> + {currentLayout === 'mobile' && !isMobilePreviewMode && ( + + )} + + ); + }; + if (isEditorLoading) { return (
@@ -144,32 +194,8 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod className={cx('viewer wrapper', { 'mobile-layout': currentLayout, 'theme-dark dark-theme': darkMode })} > - - {currentLayout !== 'mobile' && ( - - )} - {currentLayout === 'mobile' && !isMobilePreviewMode && ( - - )} + + {renderHeader()}
- {currentLayout !== 'mobile' && !isPagesSidebarHidden && ( + {currentLayout !== 'mobile' && !hideSidebar && !moduleMode && (
- {currentLayout === 'mobile' && isMobilePreviewMode && ( + {currentLayout === 'mobile' && isMobilePreviewMode && !moduleMode && ( )} - +
{/* {!licenseValid && isAppLoaded && } */} {isMobilePreviewMode &&
} diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js index be2855c476..1b32dc7e13 100644 --- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js +++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js @@ -58,6 +58,8 @@ import { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, } from '../widgets'; export const widgets = [ @@ -120,4 +122,6 @@ export const widgets = [ linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, ]; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js index ce0e73fdf5..65eda8c036 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/index.js @@ -58,6 +58,8 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { moduleContainerConfig } from './moduleContainer'; +import { moduleViewerConfig } from './moduleViewer'; export { buttonConfig, @@ -120,4 +122,6 @@ export { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js new file mode 100644 index 0000000000..bf488095a6 --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js @@ -0,0 +1,36 @@ +export const moduleContainerConfig = { + name: 'ModuleContainer', + displayName: 'Module Container', + description: 'Module Container', + component: 'ModuleContainer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + inputItems: { type: 'array', displayName: 'Input' }, + outputItems: { type: 'array', displayName: 'Output' }, + }, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + }, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js new file mode 100644 index 0000000000..b5d2962cde --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js @@ -0,0 +1,30 @@ +export const moduleViewerConfig = { + name: 'ModuleViewer', + displayName: 'Module', + description: 'Module', + component: 'ModuleViewer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: {}, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: {}, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index 1c8e64a843..f07e30ad65 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -2,16 +2,31 @@ import React, { createContext, useContext } from 'react'; export const ModuleContext = createContext(); -export const ModuleProvider = ({ moduleId, children }) => { - return {children}; +export const ModuleProvider = ({ moduleId, isModuleMode, appType, children }) => { + return {children}; }; export const useModuleId = () => { const context = useContext(ModuleContext); - if (!context) { throw new Error('useModuleId must be used within a ModuleProvider'); } - return context; + return context.moduleId; +}; + +export const useIsModuleMode = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useIsModuleMode must be used within a ModuleProvider'); + } + return context.isModuleMode; +}; + +export const useAppType = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useAppType must be used within a ModuleProvider'); + } + return context.appType; }; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 5e817c7f7e..a920328b66 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -67,6 +67,9 @@ import { Form } from '@/AppBuilder/Widgets/Form/Form'; import { Modal } from '@/AppBuilder/Widgets/Modal'; import { ModalV2 } from '@/AppBuilder/Widgets/ModalV2/ModalV2'; import { Calendar } from '@/AppBuilder/Widgets/Calendar/Calendar'; + +import { ModuleContainer, ModuleViewer } from '@/modules/Modules/components'; + // import './requestIdleCallbackPolyfill'; export function memoizeFunction(func) { @@ -145,6 +148,8 @@ export const AllComponents = { Form, BoundedBox, ToggleSwitchV2, + ModuleContainer, + ModuleViewer, }; if (isPDFSupported()) { AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 8f1c350f99..3caabf5db2 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { appEnvironmentService, appService, + appsService, appVersionService, dataqueryService, datasourceService, @@ -53,7 +54,14 @@ const normalizeQueryTransformationOptions = (query) => { return query; }; -const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, versionId } = {}) => { +const useAppData = ( + appId, + moduleId, + darkMode, + mode = 'edit', + { environmentId, versionId } = {}, + moduleMode = false +) => { const { state } = useLocation(); const [currentSession, setCurrentSession] = useState(); const setEditorLoading = useStore((state) => state.setEditorLoading); @@ -76,8 +84,8 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v // const fetchDataSources = useStore((state) => state.fetchDataSources); const fetchGlobalDataSources = useStore((state) => state.fetchGlobalDataSources); const previousVersion = usePrevious(currentVersionId); - const events = useStore((state) => state.eventsSlice.module[moduleId].events); - const pages = useStore((state) => state.modules[moduleId].pages); + const events = useStore((state) => state.eventsSlice.module[moduleId]?.events || []); + const pages = useStore((state) => state.modules[moduleId]?.pages || []); const currentPageId = useStore((state) => state.currentPageId); const setResolvedConstants = useStore((state) => state.setResolvedConstants); const setSecrets = useStore((state) => state.setSecrets); @@ -108,6 +116,10 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const selectedVersion = useStore((state) => state.selectedVersion); const setIsPublicAccess = useStore((state) => state.setIsPublicAccess); + const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow); + const setModulesList = useStore((state) => state.setModulesList, shallow); + const initModules = useStore((state) => state.initModules, shallow); + const setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); const setConversationZeroState = useStore((state) => state.ai?.setConversationZeroState); @@ -148,16 +160,22 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v }; useEffect(() => { - if (pageSwitchInProgress) { + if (!moduleMode) return; + initModules(moduleId); + }, [initModules, moduleId, moduleMode]); + + useEffect(() => { + if (pageSwitchInProgress && !moduleMode) { const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId); setPageSwitchInProgress(false); setTimeout(() => { handleEvent('onPageLoad', currentPageEvents, {}); }, 0); } - }, [pageSwitchInProgress, currentPageId]); + }, [pageSwitchInProgress, currentPageId, moduleMode]); useEffect(() => { + if (moduleMode) return; const subscription = authenticationService.currentSession .pipe( distinctUntilChanged((prev, curr) => { @@ -185,16 +203,16 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v return () => { subscription && subscription.unsubscribe(); }; - }, []); + }, [moduleMode]); useEffect(() => { const exposedTheme = appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; - setResolvedGlobals('theme', { name: exposedTheme }); - }, [appMode, darkMode]); + setResolvedGlobals('theme', { name: exposedTheme }, moduleId); + }, [appMode, darkMode, moduleId]); useEffect(() => { - if (!currentSession) { + if (!currentSession || moduleMode) { return; } const queryParams = getPreviewQueryParams(); @@ -415,9 +433,10 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v document.title = retrieveWhiteLabelText(); }; }); - }, [setApp, setEditorLoading, currentSession]); + }, [setApp, setEditorLoading, currentSession, moduleMode]); useEffect(() => { + if (moduleMode) return; if (isComponentLayoutReady) { runOnLoadQueries().then(() => { let startingPage = pages.find((page) => page.id === currentPageId); @@ -427,13 +446,15 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v handleEvent('onPageLoad', currentPageEvents, {}); }); } - }, [isComponentLayoutReady]); + }, [isComponentLayoutReady, moduleMode]); useEffect(() => { + if (moduleMode) return; fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: app.appName }); - }, [app.appName]); + }, [app.appName, moduleMode]); useEffect(() => { + if (moduleMode) return; const exposedTheme = appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; const isEnvChanged = @@ -540,7 +561,22 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v setEditorLoading(false); }); } - }, [selectedEnvironment?.id, currentVersionId]); + }, [selectedEnvironment?.id, currentVersionId, moduleMode]); + + useEffect(() => { + if (moduleMode) return; + if (mode === 'edit') { + requestIdleCallback( + () => { + appsService.getAll(0, '', '', 'module').then((data) => { + setModulesIsLoading(false); + setModulesList(data.apps); + }); + }, + { timeout: 2000 } + ); // Adding a timeout of 2 seconds as fallback + } + }, [setModulesIsLoading, setModulesList, mode, moduleMode]); }; export default useAppData; diff --git a/frontend/src/AppBuilder/_stores/ast.js b/frontend/src/AppBuilder/_stores/ast.js index 106298588f..8412c3b7b2 100644 --- a/frontend/src/AppBuilder/_stores/ast.js +++ b/frontend/src/AppBuilder/_stores/ast.js @@ -32,7 +32,7 @@ function findExpression(input) { export function extractAndReplaceReferencesFromString(input, componentIdNameMapping = {}, queryIdNameMapping = {}) { // Quick check for relevant keywords const regexForQuickCheck = - /\b(components|queries|globals|variables|page|parameters|secrets|constants)(?:\[\S*|\.\S*|\?\.\S*)/; + /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)(?:\[\S*|\.\S*|\?\.\S*)/; if (!regexForQuickCheck.test(input)) { return { allRefs: [], @@ -41,7 +41,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp }; } - const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants)\b/; + const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)\b/; const expressionRegex = /{{(.*?)}}/gs; const results = []; let lastIndex = 0; @@ -312,6 +312,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping, variables: true, globals: true, page: true, + input: true, }; walk.simple(ast, { @@ -359,7 +360,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping, if ( (rootObject && (rootObject === 'queries' || rootObject === 'components') && path.length >= 3) || - ((rootObject === 'variables' || rootObject === 'globals') && path.length === 2) || + ((rootObject === 'variables' || rootObject === 'globals' || rootObject === 'input') && path.length === 2) || (rootObject === 'page' && path.length === 3) ) { return createReferenceObject(rootObject, path, uuidMappings, componentIdNameMapping, queryIdNameMapping); @@ -386,7 +387,7 @@ function createReferenceObject(entityType, path, uuidMappings, componentIdNameMa const mapping = entityType === 'components' ? componentIdNameMapping : queryIdNameMapping; entityNameOrId = mapping[entityNameOrId] || entityNameOrId; } - } else if (entityType === 'variables' || entityType === 'globals') { + } else if (entityType === 'variables' || entityType === 'globals' || entityType === 'input') { entityKey = path[1]; } else if (entityType === 'page') { entityNameOrId = path[1]; diff --git a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js index 730cb41e12..e263532310 100644 --- a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js +++ b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js @@ -14,7 +14,7 @@ class DependencyGraph { } addDependency(fromPath, toPath, nodeData = '') { - if (fromPath.startsWith('variables.')) { + if (fromPath.startsWith('variables.') || fromPath.startsWith('input.')) { if (!this.hasNode(fromPath)) { const parts = fromPath.split('.'); fromPath = parts.slice(0, 2).join('.'); diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index 39cc7a4986..9f2501f444 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -1,4 +1,3 @@ -import { updateCanvasBackground } from '@/_helpers/editorHelpers'; import { appsService, appVersionService } from '@/_services'; import { decimalToHex } from '@/Editor/editorConstants'; import toast from 'react-hot-toast'; diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index c4448a3bbf..c90f12e746 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -50,6 +50,16 @@ const initialState = { export const createComponentsSlice = (set, get) => ({ ...initialState, + initializeComponentsSlice: (moduleId) => { + set( + (state) => { + state.modules[moduleId] = { ...state.modules[moduleId], ...initialState.modules.canvas }; + }, + false, + 'initializeComponentsSlice' + ); + }, + setPages: (pages = [], moduleId = 'canvas') => { set( (state) => { @@ -643,7 +653,6 @@ export const createComponentsSlice = (set, get) => ({ moduleId ); updatedPropertyValue[index] = updatedValue; - console.log('updatedPropertyValue', updatedPropertyValue); if (allRefs.length) { generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); } diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index 948ac39b39..60ac1b6fe7 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -6,7 +6,6 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { dfs } from '@/_stores/handleReferenceTransactions'; import { isQueryRunnable, isValidUUID, serializeNestedObjectToQueryParams } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; -import { handleLowPriorityWork } from '@/AppBuilder/_helpers/editorHelpers'; import _ from 'lodash'; import { logoutAction } from '@/AppBuilder/_utils/auth'; import { copyToClipboard } from '@/_helpers/appUtils'; diff --git a/frontend/src/AppBuilder/_stores/slices/moduleSlice.js b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js new file mode 100644 index 0000000000..5c97f74cf1 --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js @@ -0,0 +1,5 @@ +import { getEditionSpecificSlice } from '../../../modules/common/helpers/getEditionSpecificSlice'; + +const createModuleSlice = getEditionSpecificSlice('createModuleSlice'); + +export { createModuleSlice }; diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index ebbd524fd1..94c7433e90 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -3,15 +3,6 @@ import { resolveDynamicValues } from '../utils'; import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast'; import { componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager'; import _ from 'lodash'; -import { - reservedKeyword, - resolveString, - removeNestedDoubleCurlyBraces, - getDynamicVariables, - resolveCode, -} from '@/_helpers/utils'; - -import { validateMultilineCode } from '@/_helpers/utility'; const initialState = { resolvedStore: { @@ -25,7 +16,7 @@ const initialState = { secrets: {}, customResolvables: {}, exposedValues: { - queries: {}, + queries: {} /* IMPORTANT: Query is subscribed by the moduleContainer component */, components: {}, variables: {}, constants: {}, @@ -50,6 +41,18 @@ export const DEFAULT_COMPONENT_STRUCTURE = { export const createResolvedSlice = (set, get) => ({ ...initialState, + initializeResolvedSlice: (moduleId) => { + set( + (state) => { + state.resolvedStore.modules[moduleId] = { + ...state.resolvedStore.modules[moduleId], + ...initialState.resolvedStore.modules.canvas, + }; + }, + false, + 'initializeResolvedSlice' + ); + }, setResolvedGlobals: (objKey, values, moduleId = 'canvas') => { set( (state) => { @@ -469,6 +472,9 @@ export const createResolvedSlice = (set, get) => ({ state.resolvedStore.modules[moduleId].exposedValues.components = {}; state.resolvedStore.modules[moduleId].exposedValues.variables = {}; state.resolvedStore.modules[moduleId].exposedValues.globals = {}; + if (state.resolvedStore.modules[moduleId].exposedValues.input) { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + } if (state.resolvedStore.modules[moduleId].exposedValues.page?.variables) { state.resolvedStore.modules[moduleId].exposedValues.page.variables = {}; } @@ -581,4 +587,23 @@ export const createResolvedSlice = (set, get) => ({ } } }, + + setModuleInputs: (key, value, moduleId = 'canvas') => { + set( + (state) => { + if (!state.resolvedStore.modules[moduleId].exposedValues.input) { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + } + state.resolvedStore.modules[moduleId].exposedValues.input[key] = value; + }, + false, + 'setModuleInputs' + ); + get().updateDependencyValues(`input.${key}`); + }, + clearModuleInputs: (moduleId = 'canvas') => { + set((state) => { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + }); + }, }); diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 4d1392fc7c..92b982f6fd 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -28,6 +28,7 @@ import { createDebuggerSlice } from './slices/debuggerSlice'; import { createGitSyncSlice } from './slices/gitSyncSlice'; import { createAiSlice } from './slices/aiSlice'; import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice'; +import { createModuleSlice } from './slices/moduleSlice'; export default create( zustandDevTools( @@ -60,6 +61,7 @@ export default create( ...createGitSyncSlice(...state), ...createAiSlice(...state), ...createWhiteLabellingSlice(...state), + ...createModuleSlice(...state), })), { name: 'App Builder Store', anonymousActionType: 'unknown' } ) diff --git a/frontend/src/AppBuilder/_stores/utils.js b/frontend/src/AppBuilder/_stores/utils.js index bb08c620da..2bc297ed47 100644 --- a/frontend/src/AppBuilder/_stores/utils.js +++ b/frontend/src/AppBuilder/_stores/utils.js @@ -6,7 +6,7 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { dfs } from '@/_stores/handleReferenceTransactions'; import { extractAndReplaceReferencesFromString as extractAndReplaceReferencesFromStringAst } from '@/AppBuilder/_stores/ast'; -import _ from 'lodash'; +var _ = require('lodash'); const resetters = []; @@ -148,6 +148,7 @@ export const resolveCode = ( 'queries', 'globals', 'page', + 'input', 'client', 'server', 'constants', @@ -165,6 +166,7 @@ export const resolveCode = ( isJsCode ? state?.queries : undefined, isJsCode ? state?.globals : undefined, isJsCode ? state?.page : undefined, + isJsCode ? state?.input : undefined, isJsCode ? undefined : state?.client, isJsCode ? undefined : state?.server, state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly. diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index dba9883d14..2808a04545 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -12,8 +12,14 @@ const AppLoader = (props) => { resetAllStores(); }, []); - if (appType === 'front-end') return ; - else if (appType === 'workflow') return ; + switch (appType) { + case 'front-end': + return ; + case 'workflow': + return ; + case 'module': + return ; + } }; export default withTranslation()(AppLoader); diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 54a451188b..120b47f864 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,5 +1,5 @@ import React, { useState, useCallback } from 'react'; -import { getComponentToRender } from '@/_helpers/editorHelpers'; +// import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; import { getComponentsToRenders, flushComponentsToRender } from '@/_stores/editorStore'; @@ -58,7 +58,7 @@ const ComponentWrapper = React.memo(({ componentName, ...props }) => { setKey(Math.random()); }, []); - const ComponentToRender = getComponentToRender(componentName); + const ComponentToRender = <>; // getComponentToRender(componentName); if (ComponentToRender === null) return; if (componentName === 'Form') { diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 1f97cc42ee..2a0ed161db 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -1,59 +1,60 @@ -import { Button } from '@/Editor/Components/Button'; -import { Image } from '@/Editor/Components/Image/Image'; -import { Text } from '@/Editor/Components/Text'; -import { Table } from '@/Editor/Components/Table/Table'; -import { TextInput } from '@/Editor/Components/TextInput'; -import { NumberInput } from '@/Editor/Components/NumberInput'; -import { TextArea } from '@/Editor/Components/TextArea'; -import { Container } from '@/Editor/Components/Container'; -import { Tabs } from '@/Editor/Components/Tabs'; -import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; -import { DropDown } from '@/Editor/Components/DropDown'; -import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; -import { Checkbox } from '@/Editor/Components/Checkbox'; -import { Datepicker } from '@/Editor/Components/Datepicker'; -import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; -import { Multiselect } from '@/Editor/Components/Multiselect'; -import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; -import { Modal } from '@/Editor/Components/Modal'; -import { Chart } from '@/Editor/Components/Chart'; -import { Map as MapComponent } from '@/Editor/Components/Map/Map'; -import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; -import { ToggleSwitch } from '@/Editor/Components/Toggle'; -import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; +// import { Button } from '@/Editor/Components/Button'; +// import { Image } from '@/Editor/Components/Image/Image'; +// import { Text } from '@/Editor/Components/Text'; +// import { Table } from '@/Editor/Components/Table/Table'; +// import { TextInput } from '@/Editor/Components/TextInput'; +// import { NumberInput } from '@/Editor/Components/NumberInput'; +// import { TextArea } from '@/Editor/Components/TextArea'; -import { RadioButton } from '@/Editor/Components/RadioButton'; -import { StarRating } from '@/Editor/Components/StarRating'; -import { Divider } from '@/Editor/Components/Divider'; -import { FilePicker } from '@/Editor/Components/FilePicker'; -import { PasswordInput } from '@/Editor/Components/PasswordInput'; -import { Calendar } from '@/Editor/Components/Calendar'; -import { Listview } from '@/Editor/Components/Listview'; -import { IFrame } from '@/Editor/Components/IFrame'; -import { CodeEditor } from '@/Editor/Components/CodeEditor'; -import { Timer } from '@/Editor/Components/Timer'; -import { Statistics } from '@/Editor/Components/Statistics'; -import { Pagination } from '@/Editor/Components/Pagination'; -import { Tags } from '@/Editor/Components/Tags'; -import { Spinner } from '@/Editor/Components/Spinner'; -import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; -import { RangeSlider } from '@/Editor/Components/RangeSlider'; -import { Timeline } from '@/Editor/Components/Timeline'; -import { SvgImage } from '@/Editor/Components/SvgImage'; -import { Html } from '@/Editor/Components/Html'; -import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; -import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; -import { VerticalDivider } from '@/Editor/Components/verticalDivider'; -import { ColorPicker } from '@/Editor/Components/ColorPicker'; -import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; -import { Kanban } from '@/Editor/Components/Kanban/Kanban'; -import { Steps } from '@/Editor/Components/Steps'; -import { TreeSelect } from '@/Editor/Components/TreeSelect'; -import { Icon } from '@/Editor/Components/Icon'; -import { Link } from '@/Editor/Components/Link'; -import { Form } from '@/Editor/Components/Form/Form'; -import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; -import { isPDFSupported } from '@/_helpers/appUtils'; +// import { Container } from '@/Editor/Components/Container'; +// import { Tabs } from '@/Editor/Components/Tabs'; +// import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; +// import { DropDown } from '@/Editor/Components/DropDown'; +// import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; +// import { Checkbox } from '@/Editor/Components/Checkbox'; +// import { Datepicker } from '@/Editor/Components/Datepicker'; +// import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; +// import { Multiselect } from '@/Editor/Components/Multiselect'; +// import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; +// import { Modal } from '@/Editor/Components/Modal'; +// import { Chart } from '@/Editor/Components/Chart'; +// import { Map as MapComponent } from '@/Editor/Components/Map/Map'; +// import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; +// import { ToggleSwitch } from '@/Editor/Components/Toggle'; +// import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; + +// import { RadioButton } from '@/Editor/Components/RadioButton'; +// import { StarRating } from '@/Editor/Components/StarRating'; +// import { Divider } from '@/Editor/Components/Divider'; +// import { FilePicker } from '@/Editor/Components/FilePicker'; +// import { PasswordInput } from '@/Editor/Components/PasswordInput'; +// import { Calendar } from '@/Editor/Components/Calendar'; +// import { Listview } from '@/Editor/Components/Listview'; +// import { IFrame } from '@/Editor/Components/IFrame'; +// import { CodeEditor } from '@/Editor/Components/CodeEditor'; +// import { Timer } from '@/Editor/Components/Timer'; +// import { Statistics } from '@/Editor/Components/Statistics'; +// import { Pagination } from '@/Editor/Components/Pagination'; +// import { Tags } from '@/Editor/Components/Tags'; +// import { Spinner } from '@/Editor/Components/Spinner'; +// import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; +// import { RangeSlider } from '@/Editor/Components/RangeSlider'; +// import { Timeline } from '@/Editor/Components/Timeline'; +// import { SvgImage } from '@/Editor/Components/SvgImage'; +// import { Html } from '@/Editor/Components/Html'; +// import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; +// import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; +// import { VerticalDivider } from '@/Editor/Components/verticalDivider'; +// import { ColorPicker } from '@/Editor/Components/ColorPicker'; +// import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; +// import { Kanban } from '@/Editor/Components/Kanban/Kanban'; +// import { Steps } from '@/Editor/Components/Steps'; +// import { TreeSelect } from '@/Editor/Components/TreeSelect'; +// import { Icon } from '@/Editor/Components/Icon'; +// import { Link } from '@/Editor/Components/Link'; +// import { Form } from '@/Editor/Components/Form/Form'; +// import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; +// import { isPDFSupported } from '@/_helpers/appUtils'; import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { useEditorStore } from '@/_stores/editorStore'; import './requestIdleCallbackPolyfill'; @@ -74,71 +75,71 @@ export function memoizeFunction(func) { }; } -export const AllComponents = { - Button, - Image, - Text, - TextInput, - NumberInput, - Table, - TextArea, - Container, - Tabs, - RichTextEditor, - DropDown, - DropdownV2, - Checkbox, - Datepicker, - DaterangePicker, - Multiselect, - MultiselectV2, - Modal, - Chart, - Map: MapComponent, - QrScanner, - ToggleSwitch, - RadioButton, - StarRating, - Divider, - FilePicker, - PasswordInput, - Calendar, - IFrame, - CodeEditor, - Listview, - Timer, - Statistics, - Pagination, - Tags, - Spinner, - CircularProgressBar, - RangeSlider, - Timeline, - SvgImage, - Html, - ButtonGroup, - CustomComponent, - VerticalDivider, - ColorPicker, - KanbanBoard, - Kanban, - Steps, - TreeSelect, - Link, - Icon, - Form, - BoundedBox, - ToggleSwitchV2, -}; -if (isPDFSupported()) { - AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); -} +// export const AllComponents = { +// Button, +// Image, +// Text, +// TextInput, +// NumberInput, +// Table, +// TextArea, +// Container, +// Tabs, +// RichTextEditor, +// DropDown, +// DropdownV2, +// Checkbox, +// Datepicker, +// DaterangePicker, +// Multiselect, +// MultiselectV2, +// Modal, +// Chart, +// Map: MapComponent, +// QrScanner, +// ToggleSwitch, +// RadioButton, +// StarRating, +// Divider, +// FilePicker, +// PasswordInput, +// Calendar, +// IFrame, +// CodeEditor, +// Listview, +// Timer, +// Statistics, +// Pagination, +// Tags, +// Spinner, +// CircularProgressBar, +// RangeSlider, +// Timeline, +// SvgImage, +// Html, +// ButtonGroup, +// CustomComponent, +// VerticalDivider, +// ColorPicker, +// KanbanBoard, +// Kanban, +// Steps, +// TreeSelect, +// Link, +// Icon, +// Form, +// BoundedBox, +// ToggleSwitchV2, +// }; +// if (isPDFSupported()) { +// AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); +// } -export const getComponentToRender = (componentName) => { - const shouldHideWidget = componentName === 'PDF' && !isPDFSupported(); - if (shouldHideWidget) return null; - return AllComponents[componentName]; -}; +// export const getComponentToRender = (componentName) => { +// const shouldHideWidget = componentName === 'PDF' && !isPDFSupported(); +// if (shouldHideWidget) return null; +// return AllComponents[componentName]; +// }; export function isOnlyLayoutUpdate(diffState) { const componentDiff = Object.keys(diffState).filter((key) => diffState[key]?.layouts && !diffState[key]?.component); diff --git a/frontend/src/_services/apps.service.js b/frontend/src/_services/apps.service.js index 82cf4312e9..c29dea481a 100644 --- a/frontend/src/_services/apps.service.js +++ b/frontend/src/_services/apps.service.js @@ -67,18 +67,30 @@ function getAll(page, folder, searchKey, type = 'front-end') { } function createApp(body = {}) { - if (body.type === 'workflow') { - return createWorkflow(body); + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(body), + }; + switch (body.type) { + case 'workflow': + return createWorkflow(requestOptions); + case 'module': + return createModule(requestOptions); + default: + return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse); } - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; - return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse); } -function createWorkflow(body = {}) { - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; +function createWorkflow(requestOptions) { return fetch(`${config.apiUrl}/workflows`, requestOptions).then(handleResponse); } +function createModule(requestOptions) { + return fetch(`${config.apiUrl}/modules`, requestOptions).then(handleResponse); +} + function cloneApp(id, name) { const requestOptions = { method: 'POST', diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 9445ed810d..ea548eea8e 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -807,6 +807,15 @@ button { } } + .editor .viewer .main { + height: auto !important; + + .canvas-container { + top: 0; + right: 0; + } + } + @media screen and (max-height: 450px) { .sidebar { padding-top: 15px; @@ -1553,6 +1562,13 @@ button { border-top: 1px solid var(--slate5) !important; } + &.module-editor-inspector { + .tab-content { + border-top: none !important; + } + } + + /* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */ .tab-content::-webkit-scrollbar { display: none; @@ -9119,7 +9135,8 @@ tbody { } .global-settings-app-wrapper { - max-width: 190px; + max-width: 350px; + margin-right: 10px; } .version-manager-container { diff --git a/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx new file mode 100644 index 0000000000..963c4b7bef --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const EmptyStateModules = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24' }) => ( + + + + + +); + +export default EmptyStateModules; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 34a2410e1d..97a65e82f4 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -229,6 +229,7 @@ import CalendarSmall from './CalendarSmall.jsx'; import UserGroupsGrey from './UserGroupsGrey.jsx'; import AppLimitSvg from './AppLimitSvg.jsx'; import NewTabSmall from './NewTabSmall.jsx'; +import EmptyStateModules from './EmptyStateModules.jsx'; const Icon = (props) => { switch (props.name) { @@ -692,6 +693,8 @@ const Icon = (props) => { return ; case 'ai-crown': return ; + case 'empty-state-modules': + return ; default: return ; } diff --git a/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx new file mode 100644 index 0000000000..8c18f534f6 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainer = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainer, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainer/index.js b/frontend/src/modules/Modules/components/ModuleContainer/index.js new file mode 100644 index 0000000000..f5495b7be9 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainer/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainer'; diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx new file mode 100644 index 0000000000..2ddb28f0d2 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainerBlank = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainerBlank, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js new file mode 100644 index 0000000000..b8954c974c --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainerBlank'; diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx new file mode 100644 index 0000000000..8782ae9ac7 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainerInspector = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainerInspector, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js new file mode 100644 index 0000000000..6417a2160e --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainerInspector'; diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx new file mode 100644 index 0000000000..3bf0d5b4e8 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleEditorBanner = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleEditorBanner, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js new file mode 100644 index 0000000000..c157c1e0b9 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js @@ -0,0 +1 @@ +export { default } from './ModuleEditorBanner'; diff --git a/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx new file mode 100644 index 0000000000..ee4d9a6a47 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleManager = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleManager, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleManager/index.js b/frontend/src/modules/Modules/components/ModuleManager/index.js new file mode 100644 index 0000000000..5a89ccc37e --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleManager/index.js @@ -0,0 +1 @@ +export { default } from './ModuleManager'; diff --git a/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx new file mode 100644 index 0000000000..2297735672 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleViewer = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleViewer, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleViewer/index.js b/frontend/src/modules/Modules/components/ModuleViewer/index.js new file mode 100644 index 0000000000..df8725725a --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewer/index.js @@ -0,0 +1 @@ +export { default } from './ModuleViewer'; diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx new file mode 100644 index 0000000000..b5043e170a --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleViewerInspector = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleViewerInspector, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js new file mode 100644 index 0000000000..c95c5a7a46 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js @@ -0,0 +1 @@ +export { default } from './ModuleViewerInspector'; diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx new file mode 100644 index 0000000000..7a43aa67e3 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleWidgetBox = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleWidgetBox, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js new file mode 100644 index 0000000000..1a903dadf7 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js @@ -0,0 +1 @@ +export { default } from './ModuleWidgetBox'; diff --git a/frontend/src/modules/Modules/components/index.js b/frontend/src/modules/Modules/components/index.js new file mode 100644 index 0000000000..0cd5f2301e --- /dev/null +++ b/frontend/src/modules/Modules/components/index.js @@ -0,0 +1,19 @@ +import ModuleContainer from './ModuleContainer'; +import ModuleViewer from './ModuleViewer'; +import ModuleContainerInspector from './ModuleContainerInspector'; +import ModuleViewerInspector from './ModuleViewerInspector'; +import ModuleWidgetBox from './ModuleWidgetBox'; +import ModuleManager from './ModuleManager'; +import ModuleEditorBanner from './ModuleEditorBanner'; +import ModuleContainerBlank from './ModuleContainerBlank'; + +export { + ModuleContainer, + ModuleViewer, + ModuleContainerInspector, + ModuleViewerInspector, + ModuleWidgetBox, + ModuleManager, + ModuleEditorBanner, + ModuleContainerBlank, +}; diff --git a/frontend/src/modules/Modules/index.js b/frontend/src/modules/Modules/index.js new file mode 100644 index 0000000000..04dab890ed --- /dev/null +++ b/frontend/src/modules/Modules/index.js @@ -0,0 +1,2 @@ +const Modules = (props) => []; +export default Modules; diff --git a/frontend/src/modules/index.js b/frontend/src/modules/index.js index 69786dee07..f6feaf45c0 100644 --- a/frontend/src/modules/index.js +++ b/frontend/src/modules/index.js @@ -13,6 +13,7 @@ import Settings from './Settings'; import Workflows from './workflows'; import WorkspaceSettings from './WorkspaceSettings'; import RenderWorkflow from './RenderWorkflow'; +import Modules from './Modules'; export { onboarding, @@ -27,4 +28,5 @@ export { getAuditLogsRoutes, RenderWorkflow, AiBuilder, + Modules, }; diff --git a/server/ee b/server/ee index 0eefbb71a1..683647f83d 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 0eefbb71a1d5288f49641af5efaaab25970f27d1 +Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 diff --git a/server/src/modules/app/constants/modules.ts b/server/src/modules/app/constants/modules.ts index d3a04367ab..6eb8993fc4 100644 --- a/server/src/modules/app/constants/modules.ts +++ b/server/src/modules/app/constants/modules.ts @@ -36,4 +36,5 @@ export enum MODULES { IMPORT_EXPORT_RESOURCES = 'ImportExportResources', TEMPLATES = 'Templates', AI = 'ai', + MODULES = 'modules', } diff --git a/server/src/modules/app/module.ts b/server/src/modules/app/module.ts index c0ca97e4be..d297877243 100644 --- a/server/src/modules/app/module.ts +++ b/server/src/modules/app/module.ts @@ -39,6 +39,7 @@ import { TemplatesModule } from '@modules/templates/module'; import { ImportExportResourcesModule } from '@modules/import-export-resources/module'; import { TooljetDbModule } from '@modules/tooljet-db/module'; import { WorkflowsModule } from '@modules/workflows/module'; +import { ModulesModule } from '@modules/modules/module'; import { AiModule } from '@modules/ai/module'; import { CustomStylesModule } from '@modules/custom-styles/module'; @@ -92,6 +93,7 @@ export class AppModule implements OnModuleInit { await TemplatesModule.register(configs), await TooljetDbModule.register(configs), await WorkflowsModule.register(configs), + await ModulesModule.register(configs), await AiModule.register(configs), await CustomStylesModule.register(configs), ]; diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts index a7538f5f40..82ed31fd8b 100644 --- a/server/src/modules/apps/services/component.service.ts +++ b/server/src/modules/apps/services/component.service.ts @@ -95,7 +95,9 @@ export class ComponentsService implements IComponentsService { if (componentData.type === 'Table' && _.isArray(objValue)) { return srcValue; } else if ( - (componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') && + (componentData.type === 'DropdownV2' || + componentData.type === 'MultiselectV2' || + componentData.type === 'ModuleContainer') && _.isArray(objValue) ) { return _.isArray(srcValue) ? srcValue : Object.values(srcValue); diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index bef9a2ea99..b434d02269 100644 --- a/server/src/modules/apps/services/widget-config/index.js +++ b/server/src/modules/apps/services/widget-config/index.js @@ -58,6 +58,8 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { moduleContainerConfig } from './moduleContainer'; +import { moduleViewerConfig } from './moduleViewer'; const widgets = { buttonConfig, @@ -120,6 +122,8 @@ const widgets = { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig }; const universalProps = { diff --git a/server/src/modules/apps/services/widget-config/moduleContainer.js b/server/src/modules/apps/services/widget-config/moduleContainer.js new file mode 100644 index 0000000000..af0f77c823 --- /dev/null +++ b/server/src/modules/apps/services/widget-config/moduleContainer.js @@ -0,0 +1,36 @@ +export const moduleContainerConfig = { + name: 'ModuleContainer', + displayName: 'Module Container', + description: 'Module Container', + component: 'ModuleContainer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + inputItems: { type: 'array', displayName: 'Input' }, + outputItems: { type: 'array', displayName: 'Output' }, + }, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + }, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/server/src/modules/apps/services/widget-config/moduleViewer.js b/server/src/modules/apps/services/widget-config/moduleViewer.js new file mode 100644 index 0000000000..b0f5342787 --- /dev/null +++ b/server/src/modules/apps/services/widget-config/moduleViewer.js @@ -0,0 +1,31 @@ +export const moduleViewerConfig = { + name: 'ModuleViewer', + displayName: 'Module', + description: 'Module', + component: 'ModuleViewer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: {}, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: {}, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, + }; + \ No newline at end of file diff --git a/server/src/modules/modules/IModulesController.ts b/server/src/modules/modules/IModulesController.ts new file mode 100644 index 0000000000..cd84b9c534 --- /dev/null +++ b/server/src/modules/modules/IModulesController.ts @@ -0,0 +1,6 @@ +import { User } from '@entities/user.entity'; +import { AppCreateDto } from '@modules/apps/dto'; + +export interface IModulesController { + create(user: User, appCreateDto: AppCreateDto): Promise; +} diff --git a/server/src/modules/modules/constants/index.ts b/server/src/modules/modules/constants/index.ts new file mode 100644 index 0000000000..4adfa4a519 --- /dev/null +++ b/server/src/modules/modules/constants/index.ts @@ -0,0 +1,4 @@ +export enum FEATURE_KEY { + CREATE_MODULE = 'create_module', + GET_MODULES = 'get_modules', +} diff --git a/server/src/modules/modules/module.ts b/server/src/modules/modules/module.ts new file mode 100644 index 0000000000..dd871434d0 --- /dev/null +++ b/server/src/modules/modules/module.ts @@ -0,0 +1,52 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { getImportPath } from '@modules/app/constants'; +import { ThemesModule } from '@modules/organization-themes/module'; +import { FoldersModule } from '@modules/folders/module'; +import { FolderAppsModule } from '@modules/folder-apps/module'; +import { OrganizationsModule } from '@modules/organizations/module'; +import { AppEnvironmentsModule } from '@modules/app-environments/module'; +import { OrganizationRepository } from '@modules/organizations/repository'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; +import { VersionRepository } from '@modules/versions/repository'; +import { DataSourcesModule } from '@modules/data-sources/module'; +import { AiModule } from '@modules/ai/module'; +import { AppsRepository } from '@modules/apps/repository'; +@Module({}) +export class ModulesModule { + static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { + const importPath = await getImportPath(configs.IS_GET_CONTEXT); + const { ModulesController } = await import(`${importPath}/modules/modules.controller`); + const { AppsService } = await import(`${importPath}/apps/service`); + const { AppsUtilService } = await import(`${importPath}/apps/util.service`); + const { PageService } = await import(`${importPath}/apps/services/page.service`); + const { EventsService } = await import(`${importPath}/apps/services/event.service`); + const { ComponentsService } = await import(`${importPath}/apps/services/component.service`); + const { PageHelperService } = await import(`${importPath}/apps/services/page.util.service`); + + return { + module: ModulesModule, + imports: [ + await FolderAppsModule.register(configs), + await ThemesModule.register(configs), + await FoldersModule.register(configs), + await OrganizationsModule.register(configs), + await AppEnvironmentsModule.register(configs), + await DataSourcesModule.register(configs), + await AiModule.register(configs), + ], + controllers: [ModulesController], + providers: [ + AppsService, + VersionRepository, + AppsRepository, + PageService, + EventsService, + AppsUtilService, + ComponentsService, + PageHelperService, + OrganizationRepository, + DataSourcesRepository, + ], + }; + } +} diff --git a/server/src/modules/modules/modules.controller.ts b/server/src/modules/modules/modules.controller.ts new file mode 100644 index 0000000000..705d81dece --- /dev/null +++ b/server/src/modules/modules/modules.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Post, UseGuards, Body } from '@nestjs/common'; +import { User } from '@modules/app/decorators/user.decorator'; +import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; +import { AppCreateDto } from '@modules/apps/dto'; +import { IModulesController } from '@modules/modules/IModulesController'; +import { InitModule } from '@modules/app/decorators/init-module'; +import { MODULES } from '@modules/app/constants/modules'; +import { InitFeature } from '@modules/app/decorators/init-feature.decorator'; +import { FEATURE_KEY } from '@modules/modules/constants'; + +@InitModule(MODULES.MODULES) +@Controller('modules') +export class ModulesController implements IModulesController { + @InitFeature(FEATURE_KEY.CREATE_MODULE) + @UseGuards(JwtAuthGuard) + @Post() + async create(@User() user, @Body() appCreateDto: AppCreateDto): Promise { + throw new Error('Method not implemented.'); + } +} From 10f946130fbe7786b63cb18c1512f6bd1c157b51 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 22 Apr 2025 02:49:10 +0530 Subject: [PATCH 05/88] Inspector Revamp --- .../assets/images/icons/editor/file-code.svg | 3 + frontend/package-lock.json | 23 ++ frontend/package.json | 1 + .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 13 +- .../LeftSidebarInspector/DefaultCopyIcon.jsx | 19 ++ .../LeftSidebarInspector/HiddenOptions.jsx | 127 +++++++++++ .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 202 ++++++++++++------ .../LeftSidebarInspector/JSONViewer.jsx | 69 ++++++ .../LeftSidebarInspector.jsx | 80 +++---- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 136 ++++++++++++ .../LeftSidebarInspector/TreeViewHeader.jsx | 148 +++++++++++++ .../useCallbackActions.js | 69 +++++- .../LeftSidebar/LeftSidebarInspector/utils.js | 97 +++++++-- .../_stores/slices/componentsSlice.js | 5 + .../_stores/slices/inspectorSlice.js | 34 +++ frontend/src/AppBuilder/_stores/store.js | 2 + frontend/src/_styles/components.scss | 6 +- frontend/src/_styles/theme.scss | 195 ++++++++++++++++- 18 files changed, 1083 insertions(+), 146 deletions(-) create mode 100644 frontend/assets/images/icons/editor/file-code.svg create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx create mode 100644 frontend/src/AppBuilder/_stores/slices/inspectorSlice.js diff --git a/frontend/assets/images/icons/editor/file-code.svg b/frontend/assets/images/icons/editor/file-code.svg new file mode 100644 index 0000000000..4dc470055c --- /dev/null +++ b/frontend/assets/images/icons/editor/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 56c9e6c5c1..4f841128fc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -106,6 +106,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", @@ -34033,6 +34034,11 @@ "hermes-estree": "0.23.1" } }, + "node_modules/highlight-words-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz", + "integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "license": "BSD-3-Clause", @@ -41628,6 +41634,23 @@ } } }, + "node_modules/react-highlight-words": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.21.0.tgz", + "integrity": "sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==", + "dependencies": { + "highlight-words-core": "^1.2.0", + "memoize-one": "^4.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/react-highlight-words/node_modules/memoize-one": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" + }, "node_modules/react-hot-toast": { "version": "2.4.1", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index 789fdae8df..c5bf6fb965 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -101,6 +101,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index f6d6419ac4..a5fde2712b 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -154,17 +154,18 @@ const RenderWidget = ({ ? null : ['hover', 'focus'] : !resolvedGeneralProperties?.tooltip?.toString().trim() - ? null - : ['hover', 'focus'] + ? null + : ['hover', 'focus'] } overlay={(props) => renderTooltip({ props, text: inCanvas - ? `${SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) - ? resolvedProperties?.tooltip - : resolvedGeneralProperties?.tooltip - }` + ? `${ + SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) + ? resolvedProperties?.tooltip + : resolvedGeneralProperties?.tooltip + }` : `${t(`widget.${component?.name}.description`, component?.description)}`, }) } diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx new file mode 100644 index 0000000000..fa46cf9694 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +export const DefaultCopyIcon = () => ( + + + +); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx new file mode 100644 index 0000000000..c2055f4b8e --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from 'react'; +import { ToolTip } from '@/_components/ToolTip'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import cx from 'classnames'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; + +export const HiddenOptions = (props) => { + const { nodeSpecificFilteredActions, generalActionsFiltered, darkMode, setActionClicked, data } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const closeMenu = () => { + setShowMenu(false); + setActionClicked(false); + }; + + const copyPath = () => { + generalActionsFiltered[0].dispatchAction(data?.selectedNodePath); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActionsFiltered[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderOptions = () => { + return nodeSpecificFilteredActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + if (icon) { + return ( +
+ + {/* ${name === 'Go to component' ? '' : currentNode} */} + { + event.stopPropagation(); + dispatchAction(data); + }} + > + + + +
+ ); + } + }); + }; + + return ( +
+ {renderOptions()} + + +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + > + + Copy value +
+
+
+ + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + setActionClicked((prev) => !prev); + }} + className="copy-menu-options-icon" + style={{ + outline: 'none', + }} + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 87194f3ca2..6be923a0e1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -1,79 +1,145 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import TreeView, { flattenTree } from 'react-accessible-treeview'; -import WidgetIcon from '@/../assets/images/icons/widgets'; -import SolidIcon from '@/_ui/Icon/SolidIcons'; -import { ToolTip } from '@/_components/ToolTip'; -import { extractComponentName } from './utils'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import Fuse from 'fuse.js'; +import JSONViewer from './JSONViewer'; +import { SearchBox } from '@/_components'; +import { Node } from './Node'; +import { v4 as uuidv4 } from 'uuid'; +import { isEmpty } from 'lodash'; + +const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { + const searchValue = useStore((state) => state.inspectorSearchValue, shallow); + // const getSelectedNodes = useStore((state) => state.getSelectedNodes, shallow); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); + const [selectedNodePath, setSelectedNodePath] = React.useState(null); + const selectedNodes = useStore((state) => state.selectedNodes, shallow); + + function fuzzySearch(query, searchablePaths) { + const list = Array.from(searchablePaths); + const fuse = new Fuse(list, { threshold: 0.3 }); + return fuse.search(query).map((result) => result.item); + } + + const [searchedSet, pathSet] = useMemo(() => { + const result = fuzzySearch(searchValue, searchablePaths); + const expandedIdSet = new Set(); + result.forEach((id) => { + const pathArray = id.split('.'); + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdSet.has(parentPath)) { + expandedIdSet.add(parentPath); + } + } + }); + return [new Set(result), expandedIdSet]; + }, [searchValue, JSON.stringify(searchablePaths)]); + + // const recursiveFn = (obj) => { + // if (!obj || typeof obj !== 'object') return []; + // let isCompletelyExposed = false; + // obj?.children?.forEach((child) => { + // const { id } = child; + // if (searchedSet.has(id)) { + // isCompletelyExposed = true; + // } + // }); + // const newChildren = + // obj?.children + // ?.filter((child) => { + // return isCompletelyExposed || pathSet.has(child.id); + // }) + // ?.map((child) => { + // return recursiveFn(child); + // }) || []; + + // return { + // ...obj, + // children: newChildren, + // }; + // }; + + // const formattedData = useMemo(() => { + // return searchValue ? recursiveFn(data) : data; + // }, [data, searchValue]); + + const key = useMemo(() => { + return uuidv4(); + }, [JSON.stringify(data), selectedNodePath]); -const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, callbackActions = [] }) => { const flattendedData = flattenTree(data); - const renderNodeIcons = (node) => { - const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; - if (icon && icon?.iconPath) { - return ( - - ); - } - if (icon && icon.jsx) { - if (icon?.tooltipMessage) { - return ( - -
{icon.jsx()}
-
- ); - } - return icon.jsx(); - } + const backFn = () => { + setSelectedNodePath(null); }; + const selectedData = selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + const expandedIds = [...Array.from(pathSet), ...selectedNodes]; + + const filteredIds = useMemo(() => { + const expandedIdsSet = new Set(expandedIds); + const filtered = flattendedData.filter((item) => { + const { metadata } = item || {}; + const { path } = metadata || {}; + return expandedIdsSet.has(path); + }); + return filtered.map((item) => item.id); + }, [flattendedData, expandedIds]); + + console.log('selectedData', selectedData); return ( - { - const { element, getNodeProps, level, handleSelect, handleExpand, isExpanded, isDisabled, isBranch } = props; - const nodeIcon = renderNodeIcons(element.name); - const metadata = element.metadata || {}; - const { type } = metadata; - - const actions = callbackActions.filter((action) => [type, 'all'].includes(action.for)); - - return ( -
- {(isBranch || level === 1) && ( -
- {isExpanded ? ( - - ) : ( - - )} -
- )} - {nodeIcon &&
{nodeIcon}
} -
- {element.name} -
+ <> + {!selectedNodePath || isEmpty(selectedData) ? ( +
+
+ setSearchValue(e.target.value)} + onClearCallback={() => setSearchValue('')} + placeholder={`Search`} + customClass={`tj-inspector-search-input tj-text-xsm`} + showClearButton={false} + width={300} + />
- ); - }} - /> + + { + const { element } = props; + const { metadata } = element || {}; + const { path } = metadata || {}; + const data = { + nodeName: element.name, + selectedNodePath: path, + }; + + return ( + + ); + }} + /> +
+ ) : ( + + )} + ); }; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx new file mode 100644 index 0000000000..bddd778522 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { JSONTree } from 'react-json-tree'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft'; +import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight'; +import { getTheme } from './utils'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { TreeViewHeader } from './TreeViewHeader'; +import useCallbackActions from './useCallbackActions'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +export const JSONViewer = (props) => { + const { data, path, darkMode, backFn } = props; + const [theme, setTheme] = useState(() => getTheme(darkMode)); + const callbackActions = useCallbackActions() || []; + const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions; + const optionsData = { + nodeName: path?.split('.')?.slice(-1)?.[0] || '', + selectedNodePath: path, + }; + + const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || []; + + useEffect(() => { + setTheme(() => getTheme(darkMode)); + }, [darkMode]); + + return ( +
+ + + { + const key = keyPath[0]; + if (!key && key != 0) return ''; + return key; + }} + valueRenderer={(raw, value) => { + if (typeof value === 'function') { + return ( + + function + + ); + } + + return {raw}; + }} + /> +
+ ); +}; + +export default JSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 6cbd53ed8f..4e3dbf9c73 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { HeaderSection } from '@/_ui/LeftSidebar'; @@ -7,7 +7,7 @@ import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; -import useCallbackActions from './useCallbackActions'; + import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { @@ -19,31 +19,39 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); - const pathToBeInspected = useStore((state) => state.pathToBeInspected); + const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants'])); + const iconsList = useIconList({ exposedComponentsVariables, componentIdNameMapping, exposedQueries, }); - const callbackActions = useCallbackActions(); - console.log('callbackActions', callbackActions); const sortedComponents = useMemo(() => { - return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables); + return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables, searchablePaths.current); }, [exposedComponentsVariables, componentIdNameMapping]); const sortedQueries = useMemo(() => { - return formatInspectorQueryData(queryNameIdMapping, exposedQueries); + return formatInspectorQueryData(queryNameIdMapping, exposedQueries, searchablePaths.current); }, [exposedQueries, queryNameIdMapping]); - const sortedVariables = useMemo(() => formatInspectorDataMisc(exposedVariables), [exposedVariables]); + const sortedVariables = useMemo( + () => formatInspectorDataMisc(exposedVariables, 'variables', searchablePaths.current), + [exposedVariables] + ); - const sortedConstants = useMemo(() => formatInspectorDataMisc(exposedConstants), [exposedConstants]); + const sortedConstants = useMemo( + () => formatInspectorDataMisc(exposedConstants, 'constants', searchablePaths.current), + [exposedConstants] + ); - const sortedPageVariables = useMemo(() => formatInspectorDataMisc(exposedPageVariables), [exposedPageVariables]); + const sortedPageVariables = useMemo( + () => formatInspectorDataMisc(exposedPageVariables, 'page', searchablePaths.current), + [exposedPageVariables] + ); const sortedGlobalVariables = useMemo( - () => formatInspectorDataMisc(exposedGlobalVariables), + () => formatInspectorDataMisc(exposedGlobalVariables, 'globals', searchablePaths.current), [exposedGlobalVariables] ); @@ -52,28 +60,40 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { name: '', children: [ { + id: 'queries', name: 'Queries', children: sortedQueries, + metadata: { path: 'queries' }, }, { + id: 'components', name: 'Components', children: sortedComponents, + metadata: { path: 'components' }, }, { + id: 'globals', name: 'Globals', children: sortedGlobalVariables, + metadata: { path: 'globals' }, }, { + id: 'variables', name: 'Variables', children: sortedVariables, + metadata: { path: 'variables' }, }, { + id: 'page', name: 'Page', children: sortedPageVariables, + metadata: { path: 'page' }, }, { + id: 'constants', name: 'Constants', children: sortedConstants, + metadata: { path: 'constants' }, }, ], }; @@ -81,29 +101,13 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { return jsontreeData; }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); - const handleNodeExpansion = (path, data, currentNode) => { - if (pathToBeInspected && path?.length > 0) { - const shouldExpand = pathToBeInspected.includes(path[path.length - 1]); - - // Scroll to the component in the inspector - if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) { - const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`); - if (target) { - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - } - - return shouldExpand; - } else return false; - }; - return (
- +
{ darkMode={darkMode} styles={{ width: '28px', padding: 0 }} data-cy={`left-sidebar-inspector`} - variant="tertiary" + variant="ghostBlack" className="left-sidebar-header-btn" leftIcon={pinned ? 'unpin' : 'pin'} iconWidth="14" @@ -120,28 +124,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
+
- {/* */}
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx new file mode 100644 index 0000000000..27fafb22ae --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import { extractComponentName } from './utils'; +import { ToolTip } from '@/_components/ToolTip'; +import Highlighter from 'react-highlight-words'; +import cx from 'classnames'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import OverflowTooltip from '@/_components/OverflowTooltip'; +import { HiddenOptions } from './HiddenOptions'; +import useCallbackActions from './useCallbackActions'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; + +const renderNodeIcons = (node, iconsList, darkMode) => { + const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; + if (icon && icon?.iconPath) { + return ( + + ); + } + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx()}
+
+ ); + } + return icon.jsx(); + } +}; + +export const Node = (props) => { + const { + element, + getNodeProps, + level, + handleSelect, + handleExpand, + isExpanded, + isDisabled, + isBranch, + darkMode, + setSelectedNodePath, + searchValue, + iconsList, + data, + } = props; + + const [actionClicked, setActionClicked] = useState(false); + const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow); + const callbackActions = useCallbackActions() || []; + const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode); + const metadata = element.metadata || {}; + const { type } = metadata; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for)); + const onSelect = (node) => { + const { isBranch, element } = node || {}; + const { metadata } = element || {}; + const { path } = metadata || {}; + if (!isBranch) { + setSelectedNodePath(path); + } else { + setSelectedNodePath(null); + } + + setSelectedNodes(path); + }; + const nodeSpecificFilteredActions = + nodeSpecificActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }) || []; + + const generalActions = callbackActions.filter((action) => action.for === 'all'); + const generalActionsFiltered = generalActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }); + + return ( + //
+
onSelect(props)} + style={{ + marginLeft: 22 * (level - 1), + opacity: isDisabled ? 0.5 : 1, + height: level === 1 ? '28px' : '32px', + display: 'flex', + alignItems: 'center', + color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', + cursor: isBranch || level === 1 ? 'pointer' : 'default', + }} + > + {(isBranch || level === 1) && ( +
+ {isExpanded ? ( + + ) : ( + + )} +
+ )} +
+ {nodeIcon &&
{nodeIcon}
} +
+ + + +
+
+ +
+
+
+ //
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx new file mode 100644 index 0000000000..7b6d43acfb --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx @@ -0,0 +1,148 @@ +import React, { useEffect, useState } from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft'; +import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import cx from 'classnames'; +import { ToolTip } from '@/_components/ToolTip'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; + +export const TreeViewHeader = (props) => { + const { path, backFn, darkMode, data, nodeSpecificActions, type, generalActions } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const pathArray = path.split('.'); + const parentNode = pathArray[0]; + + const closeMenu = () => { + setShowMenu(false); + }; + + const copyPath = () => { + generalActions[0].dispatchAction(data?.selectedNodePath); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActions[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderOptions = () => { + return ( + <> +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + > + + Copy value +
+
+ + {nodeSpecificActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + if (icon) { + return ( +
+ { + event.stopPropagation(); + dispatchAction(data); + setShowMenu(false); + }} + className="option" + > + + {name} + +
+ ); + } + })} + + ); + }; + + return ( +
+
+ +
+ + + {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} + + + {pathArray.length > 1 && + pathArray.slice(1).map((item, index) => ( + <> + + + {item.charAt(0).toUpperCase() + item.slice(1)} + + + ))} + + + {renderOptions()} + + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + }} + className="copy-menu-options-icon json-viewer-options-btn" + style={{ + outline: 'none', + }} + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 937fe45b2c..6651e5813f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -10,19 +10,34 @@ const useCallbackActions = () => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const runQuery = useStore((state) => state.queryPanel.runQuery); const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll); + const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getQueryIdFromName = useStore((state) => state.getQueryIdFromName, shallow); const handleRemoveComponent = (component) => { - deleteComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + deleteComponents([componentId]); }; const handleSelectComponentOnEditor = (component) => { - if (currentPageComponents?.[component.id]) { - setSelectedComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + if (currentPageComponents?.[componentId]) { + setSelectedComponents([componentId]); } }; - const handleRunQuery = (query, currentNode) => { - runQuery(query.id, currentNode, undefined, 'edit', {}, true); + const handleRunQuery = (data) => { + const { nodeName } = data; + const queryId = getQueryIdFromName(nodeName); + runQuery(queryId, nodeName, undefined, 'edit', {}, true); + }; + + const selectQuery = (data) => { + const { nodeName } = data; + const id = getQueryIdFromName(nodeName); + setSelectedQuery(id); }; const copyToClipboard = (data) => { @@ -32,7 +47,8 @@ const useCallbackActions = () => { }; const handleAutoScrollToComponent = (data) => { - const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id); + const componentId = getComponentIdFromName(data.nodeName); + const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId); if (!isAccessible) { if (isOnCanvas) { toast.success( @@ -60,6 +76,16 @@ const useCallbackActions = () => { src: 'assets/images/icons/editor/play.svg', width: 8, height: 8, + enableInspectorTreeView: false, + }, + { + name: 'View query', + dispatchAction: selectQuery, + icon: true, + src: 'assets/images/icons/editor/file-code.svg', + width: 14, + height: 14, + enableInspectorTreeView: true, }, ], enableForAllChildren: false, @@ -68,10 +94,30 @@ const useCallbackActions = () => { { for: 'components', actions: [ - { name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }, - { name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, iconName: 'select' }, + { + name: 'Select Widget', + dispatchAction: handleSelectComponentOnEditor, + icon: false, + onSelect: true, + enableInspectorTreeView: false, + }, + { + name: 'Go to component', + dispatchAction: handleAutoScrollToComponent, + icon: true, + iconName: 'select', + enableInspectorTreeView: true, + }, ...(!shouldFreeze - ? [{ name: 'Delete Component', dispatchAction: handleRemoveComponent, icon: true, iconName: 'trash' }] + ? [ + { + name: 'Delete Component', + dispatchAction: handleRemoveComponent, + icon: true, + iconName: 'trash', + enableInspectorTreeView: false, + }, + ] : []), ], enableForAllChildren: false, @@ -79,7 +125,10 @@ const useCallbackActions = () => { }, { for: 'all', - actions: [{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false }], + actions: [ + { name: 'Copy value', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + { name: 'Copy path', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + ], }, ]; return callbackActions; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 5fc6176bf4..13ad6cc3f4 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -1,21 +1,41 @@ -export const formatInspectorDataMisc = (obj) => { +export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => { if (typeof obj !== 'object' || obj === null) return []; const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path, level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || (path === 'page.variables' ? level > 2 : level > 1)) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data.reduce((acc, [name, value]) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'misc' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'misc', + path: currentPath, + ...((path === 'page.variables' ? level === 2 : level === 1) && { + data: typeof value === 'object' ? JSON.stringify(value) : value, + }), + }, + }, + ]; }, []); }; - return reduceData(data); + return reduceData(data, type); }; -export const formatInspectorComponentData = (componentIdNameMapping, exposedComponentsVariables) => { +export const formatInspectorComponentData = ( + componentIdNameMapping, + exposedComponentsVariables, + searchablePaths = new Set() +) => { const data = Object.entries(componentIdNameMapping) .map(([key, name]) => ({ key, @@ -24,23 +44,37 @@ export const formatInspectorComponentData = (componentIdNameMapping, exposedComp })) .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path = 'components', level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || level > 1) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data .filter((item) => item.name) .reduce((acc, { key, name, value }) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'components' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'components', + path: currentPath, + ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), + }, + }, + ]; }, []); }; return reduceData(data); }; -export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => { +export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => { const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); const _sortedQueries = Object.entries(exposedQueries) .map(([key, value]) => ({ @@ -50,16 +84,30 @@ export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => })) .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path = 'queries', level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || level > 1) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data .filter((item) => item.name) .reduce((acc, { id, name, value }) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'queries' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'queries', + path: currentPath, + ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), + }, + }, + ]; }, []); }; @@ -76,3 +124,26 @@ export const extractComponentName = (path) => { return null; // Return null if the pattern doesn't match } }; + +export const getTheme = (darkMode) => { + return { + scheme: 'custom', + author: 'chris kempson (http://chriskempson.com)', + base00: 'transparent', + base01: '#303030', + base02: '#505050', + base03: '#b0b0b0', + base04: '#d0d0d0', + base05: '#1B1F24', + base06: '#f5f5f5', + base07: '#ffffff', + base08: '#fb0120', + base09: '#9467BD', + base0A: '#fda331', + base0B: '#2CA02C', + base0C: '#76c7b7', + base0D: '#e4e0db', + base0E: '#d381c3', + base0F: '#be643c', + }; +}; diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index b746f1237b..cd7dc8a361 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -6,6 +6,7 @@ import { checkSubstringRegex, hasArrayNotation, parsePropertyPath, + resolveCode, } from '@/AppBuilder/_stores/utils'; import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; @@ -1574,6 +1575,10 @@ export const createComponentsSlice = (set, get) => ({ const { modules } = get(); return modules[moduleId].queryIdNameMapping; }, + getQueryIdFromName: (queryName, moduleId = 'canvas') => { + const { modules } = get(); + return modules[moduleId].queryNameIdMapping[queryName]; + }, getContainerChildrenMapping: (id) => { const { containerChildrenMapping } = get(); return containerChildrenMapping[id] || []; diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js new file mode 100644 index 0000000000..b0b8a95e84 --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js @@ -0,0 +1,34 @@ +const initialState = { + selectedNodes: new Set(), + searchedNodes: new Set(), + inspectorSearchValue: '', + inspectorSearchResults: new Set(), +}; + +export const createInspectorSlice = (set, get) => ({ + ...initialState, + getSelectedNodes: () => { + const selectedNodes = get().selectedNodes; + return Array.from(selectedNodes); + }, + setSelectedNodes: (node) => { + const selectedNodes = get().selectedNodes; + const newSelectedNodes = new Set(selectedNodes); + if (newSelectedNodes.has(node)) { + newSelectedNodes.delete(node); + } else { + newSelectedNodes.add(node); + } + set({ selectedNodes: newSelectedNodes }); + }, + getInspectorSearchResults: () => { + const inspectorSearchResults = get().inspectorSearchResults; + return Array.from(inspectorSearchResults); + }, + setInspectorSearchValue: (value) => { + set({ inspectorSearchValue: value }); + }, + setInspectorSearchResults: (results) => { + set({ inspectorSearchResults: results }); + }, +}); diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 4d1392fc7c..547975d8e0 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -28,6 +28,7 @@ import { createDebuggerSlice } from './slices/debuggerSlice'; import { createGitSyncSlice } from './slices/gitSyncSlice'; import { createAiSlice } from './slices/aiSlice'; import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice'; +import { createInspectorSlice } from './slices/inspectorSlice'; export default create( zustandDevTools( @@ -60,6 +61,7 @@ export default create( ...createGitSyncSlice(...state), ...createAiSlice(...state), ...createWhiteLabellingSlice(...state), + ...createInspectorSlice(...state), })), { name: 'App Builder Store', anonymousActionType: 'unknown' } ) diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index e84756dca7..6390f20c20 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -95,10 +95,10 @@ $btn-dark-color: #FFFFFF; } .leftsidebar-panel-header { - background-color: var(--slate3); - padding: 12px 16px; + // background-color: var(--slate3); + padding: 12px 16px 8px 16px; min-height: 52px; - border-bottom: 1px solid var(--slate5); + // border-bottom: 1px solid var(--slate5); .panel-header-container { diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 0bc2abcdd3..4d4d57341a 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18881,8 +18881,10 @@ section.ai-message-prompt-input-wrapper { .basic.tree { list-style: none; margin: 0; - padding: 23.4px; + padding: 20px; + padding-top: 0px; } + .basic .tree-node, .basic .tree-node-group { list-style: none; @@ -18930,4 +18932,195 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; font-size:12px; +} + + +.tj-inspector-search-input { + width: 300px; + height: 32px; + border-radius: 6px; + background-color: var(--base) !important; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; +} + +.node-content { + display: flex; + align-items: center; + padding: 0px 8px; + height: 100%; + width: 100%; + border-radius: 6px; +} + +.node-content-active { + .copy-menu-options-icon { + background-color: var(--interactive-overlays-fill-hover); + } +} + +.node-content-hoverable:hover, .node-content-active { + background-color: var(--interactive-overlays-fill-hover); + .node-actions { + display: flex; + } + +} + +.json-viewer { + font-size: 11px; + + ul { + background-color: transparent !important; + padding-left: 6px !important; + padding-right: 10px !important; + // li{ + // text-indent: 0px !important; + // padding-top: 0px !important; + // height:18.46px; + // } + // ul > li:hover { + // background-color: var(--interactive-overlays-fill-hover); // or any color you prefer + // } + } + + + +} + +.json-viewer-node-value { + font-weight: 500; +} + +.json-viewer-header { + font-size: 12px; + font-weight: 500; + display: flex; + flex-direction: row; + margin-left:20px; + margin-bottom:18px; + margin-right: 18px; +} + +.json-viewer-back-btn { + display: flex; + align-items: center; + + &:hover { + cursor: pointer; + background-color:var(--interactive-overlays-fill-hover); + border-radius: 4px; + } + +} + +.json-viewer-options-btn { + display: flex; + align-items: center; + margin-left: auto; + + &:hover { + cursor: pointer; + background-color:var(--interactive-overlays-fill-hover); + border-radius: 4px; + } +} + + +.node-highlight { + background-color: #FFD43B; + padding:0px; +} + +.node-actions { + + justify-content: center; + align-items: center; + margin-left:auto; + margin-right: 24px; + display: none; + gap: 4px; +} + +.node-action-icon { + display:flex; + justify-content: center; + align-items: center; + height: 20px; + width: 20px; + border-radius: 4px; + margin-right:4px; + + &:hover { + background-color: var(--button-outline-hover); + } + +} + +.copy-menu-options-icon { + border: 1px solid var(--border-weak, #E4E7EB); + display: flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + border-radius: 4px; + box-shadow: var(--elevation-100-box-shadow); + background-color: var(--base); + + &:hover { + background-color: var(--button-outline-hover); + } +} + +.copy-menu-options{ + width: 144px; + border: none; + background: transparent; + border-radius: 10px; + &.dark-theme { + .popover-body { + box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; + } + } + .popover-body { + width: 100%; + padding: 8px; + border-radius: 10px; + background: var(--background-surface-layer-01); + box-shadow: 0px 0px 1px 0px rgba(48, 50, 51, 0.05), 0px 8px 16px 0px rgba(48, 50, 51, 0.1); + + .menu-options { + .option { + display: flex; + padding: 6px 8px; + align-items: center; + gap: 6px; + height: 30px; + align-self: stretch; + border-radius: 6px; + cursor: pointer; + &:hover{ + background: rgba(136, 144, 153, 0.08); + } + } + } + } +} + + +.invisible-overlay { + position: fixed; + width:287px; + height:20px; + left:28px; + z-index: 9999; + transform: translateY(-3px); + + &:hover { + background-color: var(--interactive-overlays-fill-hover); + border-radius: 4px; + } } \ No newline at end of file From 773cbcf4dc2a278b4cceee634d43085a44253254 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Fri, 25 Apr 2025 21:12:32 +0530 Subject: [PATCH 06/88] Added the logic to drop the modules and load the modules as a viewer --- frontend/ee | 2 +- frontend/src/AppBuilder/AppBuilder.jsx | 29 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 39 ++- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 9 +- .../src/AppBuilder/AppCanvas/Container.jsx | 14 +- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 21 +- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 37 ++- frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 4 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 36 ++- .../AppBuilder/AppCanvas/appCanvasUtils.js | 11 +- .../src/AppBuilder/AppCanvas/selecto.scss | 15 +- .../AppBuilder/AppCanvas/useSidebarMargin.js | 4 +- .../src/AppBuilder/CodeEditor/PreviewBox.jsx | 4 +- .../CodeEditor/SingleLineCodeEditor.jsx | 4 +- .../AppBuilder/Header/AppVersionsManager.jsx | 12 +- .../AppBuilder/Header/CreateVersionModal.jsx | 4 +- .../src/AppBuilder/Header/EditAppName.jsx | 9 +- .../AppBuilder/Header/EditVersionModal.jsx | 4 +- .../src/AppBuilder/Header/EditorHeader.jsx | 7 +- .../PromoteVersionButton.jsx | 4 +- .../ReleaseVersionButton.jsx | 4 +- .../RightTopHeaderButtons.jsx | 12 +- .../LeftSidebar/Debugger/Debugger.jsx | 2 +- .../LeftSidebar/GlobalSettings/AppExport.jsx | 4 +- .../GlobalSettings/CanvasSettings.jsx | 4 +- .../LeftSidebar/GlobalSettings/SlugInput.jsx | 10 +- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 7 +- .../LeftSidebarInspector.jsx | 26 +- .../LeftSidebar/PageMenu/PageHandlerMenu.jsx | 6 +- .../LeftSidebar/PageMenu/PageMenuItem.jsx | 12 +- .../QueryManager/QueryEditors/Workflows.jsx | 4 +- .../src/AppBuilder/QueryPanel/QueryCard.jsx | 4 +- .../Inspector/Components/DatetimePickerV2.jsx | 4 +- .../RightSideBar/Inspector/EventManager.jsx | 8 +- .../Viewer/MobileNavigationMenu.jsx | 12 +- frontend/src/AppBuilder/Viewer/PageGroup.jsx | 4 +- frontend/src/AppBuilder/Viewer/Viewer.jsx | 34 ++- .../Viewer/ViewerSidebarNavigation.jsx | 6 +- .../AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 4 +- frontend/src/AppBuilder/Widgets/Modal.jsx | 4 +- .../AppBuilder/Widgets/ModalV2/ModalV2.jsx | 4 +- .../src/AppBuilder/Widgets/Table/Table.jsx | 6 +- .../AppBuilder/_contexts/ModuleContext.jsx | 16 +- frontend/src/AppBuilder/_hooks/useAppData.js | 204 ++++++++------ .../src/AppBuilder/_stores/slices/appSlice.js | 146 +++++++--- .../_stores/slices/componentsSlice.js | 252 +++++++++++------- .../_stores/slices/createSelectors.js | 13 - .../_stores/slices/dataQuerySlice.js | 67 +++-- .../_stores/slices/debuggerSlice.js | 22 +- .../_stores/slices/dependencySlice.js | 47 ++-- .../AppBuilder/_stores/slices/eventsSlice.js | 156 ++++++----- .../_stores/slices/leftSideBarSlice.js | 6 +- .../AppBuilder/_stores/slices/loaderSlice.js | 43 ++- .../AppBuilder/_stores/slices/modeSlice.js | 31 ++- .../_stores/slices/multiplayerSlice.js | 4 +- .../_stores/slices/pageMenuSlice.js | 20 +- .../_stores/slices/queryPanelSlice.js | 136 ++++++---- .../_stores/slices/resolvedSlice.js | 57 ++-- frontend/src/AppLoader/AppLoader.jsx | 2 +- frontend/src/_styles/theme.scss | 17 +- .../PromoteVersionButton.jsx | 4 +- .../PromoteConfirmationModal.jsx | 4 +- .../ReleaseVersionButton.jsx | 4 +- server/ee | 2 +- server/src/modules/apps/constants/index.ts | 1 + server/src/modules/apps/service.ts | 12 +- .../src/modules/apps/services/page.service.ts | 16 ++ server/src/modules/apps/util.service.ts | 51 +++- 68 files changed, 1153 insertions(+), 630 deletions(-) delete mode 100644 frontend/src/AppBuilder/_stores/slices/createSelectors.js diff --git a/frontend/ee b/frontend/ee index 45c8130ae5..f00cc3fde4 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 45c8130ae5ee82abdfe1843c25dbdf694aac3433 +Subproject commit f00cc3fde44dcb082aa81d75abc9f5e879f3aade diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 48b280aa7c..5190312efa 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -14,6 +14,7 @@ import EditorHeader from '@/AppBuilder/Header'; import LeftSidebar from '@/AppBuilder/LeftSidebar'; import Popups from './Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import { shallow } from 'zustand/shallow'; // const EditorHeader = lazy(() => import('@/AppBuilder/Header')); // const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar')); @@ -22,13 +23,13 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; // const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel')); // TODO: split Loader into separate component and remove editor loading state from Editor -export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType }) => { +export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType = 'front-end' }) => { useAppData(appId, moduleId, darkMode); - const isEditorLoading = useStore((state) => state.isEditorLoading); - const currentMode = useStore((state) => state.currentMode); + const isEditorLoading = useStore((state) => state.loaderStore.modules[moduleId].isEditorLoading, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const isModuleEditor = appType === 'module'; - const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode); + const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow); const changeToDarkMode = (newMode) => { updateIsTJDarkMode(newMode); @@ -46,19 +47,19 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod return (
- Loading...
}> - - - - {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - + + Loading...
}> + + + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } + - - - + + +
); diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e8183cc535..e708926dc4 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -18,15 +18,19 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = false }) => { + const moduleId = useModuleId(); + const isModuleMode = useIsModuleMode(); const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); - const canvasHeight = useStore((state) => state.canvasHeight); - const creationMode = useStore((state) => state.app.creationMode); - const environmentLoadingState = useStore((state) => state.environmentLoadingState || state.isEditorLoading); - const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth()); + const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight); + const creationMode = useStore((state) => state.appStore.modules[moduleId].app.creationMode); + const environmentLoadingState = useStore( + (state) => state.environmentLoadingState || state.loaderStore.modules[moduleId].isEditorLoading + ); + const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth(moduleId)); const gridWidth = canvasWidth / NO_OF_GRIDS; - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const pageSidebarStyle = useStore((state) => state?.pageSettings?.definition?.properties?.style, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const queryPanelHeight = useStore((state) => state?.queryPanel?.queryPanelHeight || 0); @@ -41,8 +45,6 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); - const moduleId = useModuleId(); - const isModuleMode = useIsModuleMode(); const hideSidebar = isModuleMode || isPagesSidebarHidden; @@ -55,14 +57,29 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { useEffect(() => { function handleResize() { - const _canvasWidth = document.getElementById('real-canvas')?.getBoundingClientRect()?.width; + const _canvasWidth = + moduleId === 'canvas' + ? document.getElementById('real-canvas')?.getBoundingClientRect()?.width + : document.getElementById(moduleId)?.getBoundingClientRect()?.width; if (_canvasWidth !== 0) setCanvasWidth(_canvasWidth); } - window.addEventListener('resize', handleResize); + + if (moduleId === 'canvas') { + window.addEventListener('resize', handleResize); + } else { + const elem = document.getElementById(moduleId); + const resizeObserver = new ResizeObserver(handleResize); + if (elem) resizeObserver.observe(elem); + + return () => { + if (elem) resizeObserver.unobserve(elem); + resizeObserver.disconnect(); + }; + } handleResize(); return () => window.removeEventListener('resize', handleResize); - }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]); + }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned, moduleId]); const styles = useMemo(() => { const canvasBgColor = diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index ad972f7e90..10a738adcb 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -4,6 +4,7 @@ import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -18,10 +19,11 @@ export const ConfigHandle = ({ showHandle, componentType, visibility, - hideDelete, + isModuleContainer, }) => { + const moduleId = useModuleId(); const shouldFreeze = useStore((state) => state.getShouldFreeze()); - const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow); + const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow); const isMultipleComponentsSelected = useStore( (state) => (findHighestLevelofSelection(state?.selectedComponents)?.length > 1 ? true : false), shallow @@ -42,6 +44,7 @@ export const ConfigHandle = ({ // If one component is hovered and one is selected, show the handle for the hovered component return ( isWidgetHovered || + isModuleContainer || (showHandle && (!isMultipleComponentsSelected || (isModal && isModalOpen)) && !anyComponentHovered) ); }, shallow); @@ -124,7 +127,7 @@ export const ConfigHandle = ({ data-cy={`${componentName.toLowerCase()}-inspect-button`} className="config-handle-inspect" /> - {!hideDelete && ( + {!isModuleContainer && ( { diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 6a5afd83e2..b12f9a9634 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -21,6 +21,7 @@ import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; //TODO: Revisit the logic of height (dropRef) /* @@ -46,8 +47,10 @@ export const Container = React.memo( componentType, appType, }) => { + const moduleId = useModuleId(); const realCanvasRef = useRef(null); - const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); + const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); + const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); @@ -56,12 +59,13 @@ export const Container = React.memo( shallow ); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); + const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; - }, [componentType, index, currentMode]); + }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', @@ -144,7 +148,7 @@ export const Container = React.memo( } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; useEffect(() => { - useGridStore.getState().actions.setSubContainerWidths(id, getContainerCanvasWidth() / NO_OF_GRIDS); + useGridStore.getState().actions.setSubContainerWidths(id, gridWidth); // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasWidth, listViewMode, columns]); @@ -229,7 +233,7 @@ export const Container = React.memo( }} className={cx('real-canvas', { 'sub-canvas': id !== 'canvas', - 'show-grid': isOverCurrent && (index === 0 || index === null), + 'show-grid': isOverCurrent && (index === 0 || index === null) && currentMode === 'edit', })} id={id === 'canvas' ? 'real-canvas' : `canvas-${id}`} data-cy="real-canvas" diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index abddc4681f..adef3eea83 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -29,7 +29,7 @@ import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; - +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -38,12 +38,13 @@ const RESIZABLE_CONFIG = { export const GRID_HEIGHT = 10; export default function Grid({ gridWidth, currentLayout, appType }) { + const moduleId = useModuleId(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [boxList, setBoxList] = useState([]); - const currentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + const currentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const selectedComponents = useStore((state) => state.selectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); @@ -128,7 +129,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const noOfBoxs = Object.values(boxList || []).length; useEffect(() => { - updateCanvasBottomHeight(boxList); + updateCanvasBottomHeight(boxList, moduleId); noOfBoxs != 0; // eslint-disable-next-line react-hooks/exhaustive-deps }, [noOfBoxs, triggerCanvasUpdater]); @@ -329,12 +330,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }; const isComponentVisible = (id) => { - // Return TRUE if it is a module container - if (appType === 'module') { - return true; - } - - const component = getResolvedComponent(id); + const component = getResolvedComponent(id, null, moduleId); let visibility; if (isArray(component)) { visibility = component?.[0]?.properties?.visibility ?? component?.[0]?.styles?.visibility ?? null; @@ -415,6 +411,10 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const moveableBox = document.querySelector(`.moveable-control-box`); const showConfigHandle = (e) => { const targetId = e.target.offsetParent.getAttribute('target-id'); + const componentType = getComponentTypeFromId(targetId); + if (componentType === 'ModuleContainer') { + return; + } useStore.getState().setHoveredComponentBoundaryId(targetId); }; const hideConfigHandle = () => { @@ -646,7 +646,6 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }} onResizeEnd={(e) => { try { - console.log('end--- e.target.id', e.target.id); useGridStore.getState().actions.setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index b26586dc08..5e72d09797 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -7,6 +7,8 @@ import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; import { BOX_PADDING } from './appCanvasConstants'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; + const shouldAddBoxShadowAndVisibility = [ 'Table', 'TextInput', @@ -38,23 +40,27 @@ const RenderWidget = ({ inCanvas = false, darkMode, }) => { - const componentDefinition = useStore((state) => state.getComponentDefinition(id), shallow); + const moduleId = useModuleId(); + const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow); const component = componentDefinition?.component; const componentName = component?.name; const [key, setKey] = useState(Math.random()); const resolvedProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.properties, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.properties, + shallow + ); + const resolvedStyles = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.styles, shallow ); - const resolvedStyles = useStore((state) => state.getResolvedComponent(id, subContainerIndex)?.styles, shallow); const fireEvent = useStore((state) => state.eventsSlice.fireEvent, shallow); const resolvedGeneralProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.general, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.general, shallow ); const resolvedGeneralStyles = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.generalStyles, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.generalStyles, shallow ); const unResolvedValidation = componentDefinition?.component?.definition?.validation || {}; @@ -64,10 +70,13 @@ const RenderWidget = ({ const setExposedValue = useStore((state) => state.setExposedValue, shallow); const setExposedValues = useStore((state) => state.setExposedValues, shallow); const setDefaultExposedValues = useStore((state) => state.setDefaultExposedValues, shallow); - const resolvedValidation = useStore((state) => state.getResolvedComponent(id)?.validation, shallow); + const resolvedValidation = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.validation, + shallow + ); const parentId = component?.parent; const customResolvables = useStore( - (state) => state.resolvedStore.modules.canvas?.customResolvables?.[parentId], + (state) => state.resolvedStore.modules[moduleId]?.customResolvables?.[parentId], shallow ); const { t } = useTranslation(); @@ -100,31 +109,31 @@ const RenderWidget = ({ (key, value) => { // Check if the component is inside the subcontainer and it has its own onOptionChange(setExposedValue) function if (onOptionChange === null) { - setExposedValue(id, key, value); + setExposedValue(id, key, value, moduleId); // Trigger an update when the child components is directly linked to any component - updateDependencyValues(`components.${id}.${key}`); + updateDependencyValues(`components.${id}.${key}`, moduleId); } else { onOptionChange(key, value, id, subContainerIndex); } }, - [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange] + [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange, moduleId] ); const setExposedVariables = useCallback( (exposedValues) => { if (onOptionsChange === null) { - setExposedValues(id, 'components', exposedValues); + setExposedValues(id, 'components', exposedValues, moduleId); } else { onOptionsChange(exposedValues, id, subContainerIndex); } }, - [id, setExposedValues, onOptionsChange] + [id, setExposedValues, onOptionsChange, moduleId] ); const fireEventWrapper = useCallback( (eventName, options) => { - fireEvent(eventName, id, 'canvas', customResolvables?.[subContainerIndex] ?? {}, options); + fireEvent(eventName, id, moduleId, customResolvables?.[subContainerIndex] ?? {}, options); return Promise.resolve(); }, - [fireEvent, id, customResolvables, subContainerIndex] + [fireEvent, id, customResolvables, subContainerIndex, moduleId] ); const onComponentClick = useStore((state) => state.eventsSlice.onComponentClickEvent); diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index c92cf75a1e..24426f6050 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -5,8 +5,10 @@ import './selecto.scss'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; import { findHighestLevelofSelection } from './Grid/gridUtils'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { + const moduleId = useModuleId(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); @@ -16,7 +18,7 @@ export const EditorSelecto = () => { const filterSelectedComponentsByHighestLevel = (selectedIds) => { const highestLevelComponents = findHighestLevelofSelection( selectedIds.map((id) => { - const component = getComponentDefinition(id); + const component = getComponentDefinition(id, moduleId); return { ...component, id, diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index ac60a5790f..42b07dacbe 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -6,6 +6,8 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle'; import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { NO_OF_GRIDS } from './appCanvasConstants'; const WidgetWrapper = memo( ({ @@ -20,23 +22,27 @@ const WidgetWrapper = memo( mode, darkMode, }) => { + const moduleId = useModuleId(); const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow); const stylesDefinition = useStore( - (state) => state.getComponentDefinition(id)?.component?.definition?.styles, + (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - const layoutData = useStore((state) => state.getComponentDefinition(id)?.layouts?.[currentLayout], shallow); + let layoutData = useStore((state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); - const componentType = useStore((state) => state.getComponentDefinition(id)?.component?.component, shallow); + const componentType = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.component?.component, + shallow + ); const setHoveredComponentForGrid = useStore((state) => state.setHoveredComponentForGrid, shallow); const canShowInCurrentLayout = useStore((state) => { - const others = state.getResolvedComponent(id, subContainerIndex)?.others; + const others = state.getResolvedComponent(id, subContainerIndex, moduleId)?.others; return others?.[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop']; }); const visibility = useStore((state) => { - const component = state.getResolvedComponent(id, subContainerIndex); + const component = state.getResolvedComponent(id, subContainerIndex, moduleId); if (component?.properties?.visibility === false || component?.styles?.visibility === false) return false; return true; }); @@ -45,6 +51,13 @@ const WidgetWrapper = memo( return null; } + // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer + if (componentType === 'ModuleContainer' && mode === 'view') { + layoutData.top = 0; + layoutData.left = 0; + layoutData.width = NO_OF_GRIDS; + } + const width = gridWidth * layoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); const styles = { @@ -55,6 +68,8 @@ const WidgetWrapper = memo( border: visibility === false ? `1px solid var(--border-default)` : 'none', }; + const isModuleContainer = componentType === 'ModuleContainer'; + if (!componentType) return null; return ( <> @@ -65,6 +80,7 @@ const WidgetWrapper = memo( 'position-absolute': readOnly, 'active-target': isWidgetActive, 'opacity-0': isDragging || isResizing, + 'module-container': isModuleContainer, })} data-id={`${id}`} id={id} @@ -74,12 +90,12 @@ const WidgetWrapper = memo( // zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null, ...styles, }} - onMouseEnter={(e) => { - if (isDragging) return; + onMouseEnter={() => { + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(id); }} onMouseLeave={() => { - if (isDragging) return; + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(''); }} > @@ -91,8 +107,8 @@ const WidgetWrapper = memo( showHandle={isWidgetActive} componentType={componentType} visibility={visibility} - customClassName={componentType === 'ModuleContainer' ? 'module-container' : ''} - hideDelete={componentType === 'ModuleContainer'} + customClassName={isModuleContainer ? 'module-container' : ''} + isModuleContainer={isModuleContainer} /> )} 1 ? 's' : ''} pasted successfully`); } -export const getCanvasWidth = (currentLayout) => { - if (currentLayout === 'mobile') { - return CANVAS_WIDTHS.deviceWindowWidth; +export const getCanvasWidth = (moduleId = 'canvas') => { + if (moduleId !== 'canvas') { + return '100%'; } + + // if (currentLayout === 'mobile') { + // return CANVAS_WIDTHS.deviceWindowWidth; + // } const windowWidth = window.innerWidth; const widthInPx = windowWidth - (CANVAS_WIDTHS.leftSideBarWidth + CANVAS_WIDTHS.rightSideBarWidth); const canvasMaxWidth = useStore.getState().globalSettings.canvasMaxWidth; diff --git a/frontend/src/AppBuilder/AppCanvas/selecto.scss b/frontend/src/AppBuilder/AppCanvas/selecto.scss index 5602b35d5a..c0f750642c 100644 --- a/frontend/src/AppBuilder/AppCanvas/selecto.scss +++ b/frontend/src/AppBuilder/AppCanvas/selecto.scss @@ -1,20 +1,21 @@ .active-target { outline: 1px solid #4af; -} +} -.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { +.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { outline: 1px solid #4af; z-index: 4 !important; } -.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { +.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { // outline: 1px solid #4af; z-index: 4 !important; } +.main-editor-canvas .widget-target.module-container { + outline: dotted 2px #CCD1D5 !important; +} + // .main-editor-canvas .widget-target:hover { // outline: 1px solid #4af; -// } - - - +// } \ No newline at end of file diff --git a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js index 969e60f398..b9ea871ca2 100644 --- a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js +++ b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js @@ -3,11 +3,13 @@ import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const useSidebarMargin = (canvasContainerRef) => { + const moduleId = useModuleId(); const [editorMarginLeft, setEditorMarginLeft] = useState(0); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); useEffect(() => { if (mode !== 'view') setEditorMarginLeft(isSidebarOpen ? LEFT_SIDEBAR_WIDTH : 0); diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx index 2429973c25..8234bf8af6 100644 --- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx @@ -13,6 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { Overlay } from 'react-bootstrap'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const sanitizeLargeDataset = (data, callback) => { const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -90,11 +91,12 @@ export const PreviewBox = ({ isWorkspaceVariable, validationFn, }) => { + const moduleId = useModuleId(); const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); const [coersionData, setCoersionData] = useState(null); const [largeDataset, setLargeDataset] = useState(false); - const globals = useStore((state) => state.getAllExposedValues().constants || {}, shallow); + const globals = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow); const secrets = useStore((state) => state.getSecrets(), shallow); const getPreviewContent = (content, type) => { diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index c1c0e03aa5..8f36315a64 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -22,8 +22,10 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { + const moduleId = useModuleId(); const { initialValue, onChange, enablePreview = true, portalProps } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); @@ -32,7 +34,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r const [errorStateActive, setErrorStateActive] = useState(false); const [cursorInsidePreview, setCursorInsidePreview] = useState(false); const validationFn = restProps?.validationFn; - const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow); + const componentDefinition = useStore((state) => state.getComponentDefinition(componentId, moduleId), shallow); const parentId = componentDefinition?.component?.parent; const customResolvables = useStore((state) => state.resolvedStore.modules.canvas?.customResolvables, shallow); diff --git a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx index 05009fd531..6ec4a22dd1 100644 --- a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx +++ b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx @@ -6,6 +6,7 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { decodeEntities } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const appVersionLoadingStatus = Object.freeze({ loading: 'loading', @@ -14,6 +15,7 @@ const appVersionLoadingStatus = Object.freeze({ }); export const AppVersionsManager = function ({ darkMode }) { + const moduleId = useModuleId(); const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ @@ -54,15 +56,15 @@ export const AppVersionsManager = function ({ darkMode }) { deleteVersionAction: state.deleteVersionAction, selectedEnvironment: state.selectedEnvironment, currentLayout: state.currentLayout, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, releasedVersionId: state.releasedVersionId, editingVersion: state.editingVersion, setCurrentVersionId: state.setCurrentVersionId, currentVersionId: state.currentVersionId, - creationMode: state.app.creationMode, - isPublic: state.app.isPublic, - isViewer: state.isViewer, - currentMode: state.currentMode, + creationMode: state.appStore.modules[moduleId].app.creationMode, + isPublic: state.appStore.modules[moduleId].app.isPublic, + isViewer: state.appStore.modules[moduleId].isViewer, + currentMode: state.modeStore.modules[moduleId].currentMode, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index a4cb87ad55..888b91963d 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import Select from '@/_ui/Select'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CreateVersionModal = ({ showCreateAppVersion, @@ -17,6 +18,7 @@ const CreateVersionModal = ({ fetchingOrgGit, handleCommitOnVersionCreation = () => {}, }) => { + const moduleId = useModuleId(); const [isCreatingVersion, setIsCreatingVersion] = useState(false); const [versionName, setVersionName] = useState(''); @@ -35,7 +37,7 @@ const CreateVersionModal = ({ developmentVersions: state.developmentVersions, featureAccess: state.license.featureAccess, editingVersion: state.currentVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, currentVersionId: state.currentVersionId, setCurrentVersionId: state.setCurrentVersionId, selectedVersion: state.selectedVersion, diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 7211bb085b..94da928483 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -8,10 +8,17 @@ import { InfoOrErrorBox } from './InfoOrErrorBox'; import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; function EditAppName() { + const moduleId = useModuleId(); const [appId, appName, setAppName, appCreationMode] = useStore( - (state) => [state.app.appId, state.app.appName, state.setAppName, state.app.creationMode], + (state) => [ + state.appStore.modules[moduleId].app.appId, + state.appStore.modules[moduleId].app.appName, + state.setAppName, + state.appStore.modules[moduleId].app.creationMode, + ], shallow ); diff --git a/frontend/src/AppBuilder/Header/EditVersionModal.jsx b/frontend/src/AppBuilder/Header/EditVersionModal.jsx index 3a126c6285..bb79468ef3 100644 --- a/frontend/src/AppBuilder/Header/EditVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/EditVersionModal.jsx @@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => { + const moduleId = useModuleId(); const [isEditingVersion, setIsEditingVersion] = useState(false); const { updateVersionNameAction, @@ -15,7 +17,7 @@ export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) (state) => ({ updateVersionNameAction: state.updateVersionNameAction, selectedVersion: state.selectedVersion, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index c5420c98bf..dfa8d8940f 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -13,11 +13,14 @@ import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; import { ModuleEditorBanner } from '@/modules/Modules/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; + export const EditorHeader = ({ darkMode, isModuleEditor }) => { + const moduleId = useModuleId(); const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ - isSaving: state.app.isSaving, - saveError: state.app.saveError, + isSaving: state.appStore.modules[moduleId].app.isSaving, + saveError: state.appStore.modules[moduleId].app.saveError, isVersionReleased: state.isVersionReleased, }), shallow diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx index f8902157bc..a3638c9a97 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx @@ -4,12 +4,14 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import PromoteConfirmationModal from './PromoteConfirmationModal'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { + const moduleId = useModuleId(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ - isSaving: state.app.isSaving, + isSaving: state.appStore.modules[moduleId].app.isSaving, editingVersion: state.currentVersionId, selectedEnvironment: state.selectedEnvironment, environments: state.environments, diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx index 494eaa52e9..3052f05d17 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx @@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const moduleId = useModuleId(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( @@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() { editingVersion: state.editingVersion, isEditorFreezed: state.isEditorFreezed, updateReleasedVersionId: state.updateReleasedVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, versionToBeReleased: state.currentVersionId, // selectedVersionId: state.selectedVersion.id, }), diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index 021887c824..d709945ca9 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -8,6 +8,7 @@ import { isEmpty } from 'lodash'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RightTopHeaderButtons = ({ isModuleEditor }) => { return ( @@ -21,6 +22,7 @@ const RightTopHeaderButtons = ({ isModuleEditor }) => { }; const PreviewAndShareIcons = () => { + const moduleId = useModuleId(); const { featureAccess, currentPageHandle, @@ -36,14 +38,14 @@ const PreviewAndShareIcons = () => { } = useStore( (state) => ({ featureAccess: state.license?.featureAccess, - currentPageHandle: state?.currentPageHandle, + currentPageHandle: state?.modules[moduleId].currentPageHandle, selectedEnvironment: state.selectedEnvironment, isVersionReleased: state.releasedVersionId === state.selectedVersion?.id, editingVersion: state.editingVersion, - appId: state.app.appId, - app: state.app.app, - slug: state.app.slug, - isPublic: state.app.isPublic, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app.app, + slug: state.appStore.modules[moduleId].app.slug, + isPublic: state.appStore.modules[moduleId].app.isPublic, currentVersionId: state.currentVersionId, selectedVersion: state.selectedVersion, }), diff --git a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx index 7b775e2b08..3823e2bfff 100644 --- a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx @@ -10,7 +10,7 @@ function Debugger({ pinned, setPinned }) { shallow ); - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules.canvas.currentPageId); const logsToBeShown = logs.filter((log) => log.page === currentPageId); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx index 4ffa20d26d..bea8953829 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx @@ -3,11 +3,13 @@ import { Button } from '@/components/ui/Button/Button'; import ExportAppModal from '@/HomePage/ExportAppModal'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const AppExport = ({ darkMode }) => { + const moduleId = useModuleId(); const { app } = useStore( (state) => ({ - app: state.app, + app: state.appStore.modules[moduleId].app, }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx index 84d808f345..5d8edf4f41 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx @@ -11,8 +11,10 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton'; import { useTranslation } from 'react-i18next'; import { Confirm } from '@/Editor/Viewer/Confirm'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CanvasSettings = ({ darkMode }) => { + const moduleId = useModuleId(); const { globalSettings, globalSettingsChanged, @@ -24,7 +26,7 @@ const CanvasSettings = ({ darkMode }) => { (state) => ({ globalSettings: state.globalSettings, updateGlobalSettings: state.updateGlobalSettings, - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, globalSettingsChanged: state.globalSettingsChanged, toggleAppMaintenance: state.toggleAppMaintenance, resolveOthers: state.resolveOthers, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx index ae69b4cbf6..269ea98e20 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx @@ -8,9 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes'; import useStore from '@/AppBuilder/_stores/store'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; // import { useStore } from '@/store'; const SlugInput = () => { + const moduleId = useModuleId(); const { slug: oldSlug, appId, @@ -20,11 +22,11 @@ const SlugInput = () => { } = useStore( (state) => ({ globalSettings: state.globalSettings, - slug: state.app.slug, - appId: state.app.appId, - app: state.app, + slug: state.appStore.modules[moduleId].app.slug, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app, setApp: state.setApp, - currentPage: state.modules.canvas.pages[state.currentPageId], + currentPage: state.modules[moduleId].pages[state.modules[moduleId].currentPageIndex], }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 4a3507c59e..c2c43f7647 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -10,6 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector'; import GlobalSettings from './GlobalSettings'; import '../../_styles/left-sidebar.scss'; import Debugger from './Debugger/Debugger'; +import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; // TODO: remove passing refs to LeftSidebarItem and use state @@ -24,6 +25,8 @@ export const BaseLeftSidebar = ({ renderAIChat = () => null, isModuleEditor = false, }) => { + const moduleId = useModuleId(); + const appType = useAppType(); const [ pinned, selectedSidebarItem, @@ -41,7 +44,7 @@ export const BaseLeftSidebar = ({ state.selectedSidebarItem, state.setIsLeftSideBarPinned, state.setSelectedSidebarItem, - state.currentMode, + state.modeStore.modules[moduleId].currentMode, state.queryPanel.queryPanelHeight, state.debugger.unreadErrorCount, state.debugger.resetUnreadErrorCount, @@ -99,6 +102,8 @@ export const BaseLeftSidebar = ({ // popoverContentHeight={popoverContentHeight} setPinned={setPinned} pinned={pinned} + moduleId={moduleId} + appType={appType} /> ); case 'tooljetai': diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index a1e483d8e1..6ba24ba7be 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -7,6 +7,7 @@ import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; import useCallbackActions from './useCallbackActions'; +import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const sortAndReduce = (obj) => { return Object.entries(obj) @@ -17,17 +18,17 @@ const sortAndReduce = (obj) => { }, {}); }; -const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { - const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); - const exposedQueries = useStore((state) => state.getAllExposedValues().queries || {}, shallow); - const exposedVariables = useStore((state) => state.getAllExposedValues().variables || {}, shallow); - const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow); - const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); - const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); - const exposedModuleInputs = useStore((state) => state.getAllExposedValues().input || {}, shallow); +const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }) => { + const exposedComponentsVariables = useStore((state) => state.getAllExposedValues(moduleId).components, shallow); + const exposedQueries = useStore((state) => state.getAllExposedValues(moduleId).queries || {}, shallow); + const exposedVariables = useStore((state) => state.getAllExposedValues(moduleId).variables || {}, shallow); + const exposedConstants = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow); + const exposedPageVariables = useStore((state) => state.getAllExposedValues(moduleId).page || {}, shallow); + const exposedGlobalVariables = useStore((state) => state.getAllExposedValues(moduleId).globals || {}, shallow); + const exposedModuleInputs = useStore((state) => state.getAllExposedValues(moduleId).input || {}, shallow); - const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); - const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); + const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(moduleId), shallow); + const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(moduleId), shallow); const pathToBeInspected = useStore((state) => state.pathToBeInspected); const iconsList = useIconList({ exposedComponentsVariables, @@ -87,7 +88,9 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { jsontreeData['variables'] = sortedVariables; jsontreeData['page'] = sortedPageVariables; jsontreeData['constants'] = sortedConstants; - if (sortedModuleInputs) jsontreeData['input'] = sortedModuleInputs; + if (appType === 'module') { + jsontreeData['input'] = sortedModuleInputs; + } return jsontreeData; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -99,6 +102,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { sortedPageVariables, sortedGlobalVariables, sortedModuleInputs, + appType, ]); const handleNodeExpansion = (path, data, currentNode) => { diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index e2a395ecab..9cd9e5dcf5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -2,8 +2,10 @@ import React from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { Button } from '@/_ui/LeftSidebar'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const PageHandlerMenu = ({ darkMode }) => { + const moduleId = useModuleId(); const setShowEditingPopover = useStore((state) => state.setShowEditingPopover); const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal); @@ -42,7 +44,7 @@ export const PageHandlerMenu = ({ darkMode }) => { closePageEditPopover(); }; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const page = editingPage; const isHomePage = page?.id === homePageId; const showMenu = showEditingPopover; @@ -116,7 +118,7 @@ export const PageHandlerMenu = ({ darkMode }) => { text="Mark home" iconSrc={'assets/images/icons/home.svg'} closeMenu={() => {}} - callback={() => markAsHomePage(editingPage.id)} + callback={() => markAsHomePage(editingPage.id, moduleId)} /> )} diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index ca193084fb..962f40554b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -16,12 +16,14 @@ import { RenameInput } from './RenameInput'; import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { - const homePageId = useStore((state) => state.app.homePageId); + const moduleId = useModuleId(); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const isHomePage = page.id === homePageId; - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const isSelected = page.id === currentPageId; const isHidden = page?.hidden ?? false; const isDisabled = page?.disabled ?? false; @@ -135,9 +137,9 @@ export const PageMenuItem = withRouter( if (currentPageId === page.id) { return; } - switchPage(page.id, page.handle); - setCurrentPageHandle(page.handle); - }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle]); + switchPage(page.id, page.handle, [], moduleId); + setCurrentPageHandle(page.handle, moduleId); + }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle, moduleId]); const handlePageMenuSettings = useCallback( (event) => { diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index 9aa77601e5..367a71e5c3 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,13 +5,15 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export function Workflows({ options, optionsChanged, currentState }) { + const moduleId = useModuleId(); const [workflowOptions, setWorkflowOptions] = useState([]); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); useEffect(() => { appsService.getWorkflows(appId).then(({ workflows }) => { diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx index fbad517a81..afe6aa6604 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx @@ -13,9 +13,11 @@ import useStore from '@/AppBuilder/_stores/store'; import { Confirm } from '@/Editor/Viewer/Confirm'; // TODO: enable delete query confirmation popup import { debounce } from 'lodash'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => { - const appId = useStore((state) => state.app.appId); + const moduleId = useModuleId(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow); const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery); diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx index 8a24e5eb92..20d7e8c66f 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import useStore from '@/AppBuilder/_stores/store'; import styles from '@/_ui/Select/styles'; import moment from 'moment-timezone'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const DATE_FORMAT_OPTIONS = [ { @@ -101,12 +102,13 @@ const DatetimePickerV2 = ({ componentMeta, componentName, darkMode, ...restProps allComponents, pages, } = restProps; + const moduleId = useModuleId(); const items = []; const additionalActions = []; const properties = []; const formatting = []; const validations = Object.keys(componentMeta.validation || {}); - const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); + const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id, null, moduleId)?.properties); const isDateFormatFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false; const isTimeFormatFxOn = componentMeta?.definition?.properties?.timeFormat?.fxActive || false; const dateFormat = resolvedProperties?.dateFormat ?? resolvedProperties?.format; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index c3add3f1cc..c1a51eab11 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -30,6 +30,7 @@ import { appService } from '@/_services'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EventManager = ({ sourceId, @@ -43,6 +44,7 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { + const moduleId = useModuleId(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup @@ -57,7 +59,7 @@ export const EventManager = ({ const allAppEvents = useEvents(); const { createAppVersionEventHandlers, deleteAppVersionEventHandler, updateAppVersionEventHandlers } = useEventActions(); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const eventsUpdatedLoader = useStore((state) => state.eventsSlice.getEventsUpdatedLoader(), shallow); const eventsCreatedLoader = useStore((state) => state.eventsSlice.getEventsCreatedLoader(), shallow); @@ -95,9 +97,9 @@ export const EventManager = ({ return a.index - b.index; }); - setEvents(sortedEvents || []); + setEvents(sortedEvents || [], moduleId); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentEvents)]); + }, [JSON.stringify(currentEvents), moduleId]); let actionOptions = ActionTypes.map((action) => { return { name: action.name, value: action.id }; diff --git a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx index e6c8179328..c6759bc931 100644 --- a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx +++ b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx @@ -8,11 +8,13 @@ import Cross from '@/_ui/Icon/solidIcons/Cross'; import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import * as Icons from '@tabler/icons-react'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch, currentPageId, icon }) => { + const moduleId = useModuleId(); const [isExpanded, setIsExpanded] = useState(true); const groupActive = currentPage.pageGroupId === pageGroup?.id; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const handleToggle = () => { setIsExpanded(!isExpanded); }; @@ -78,8 +80,9 @@ const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch }; const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, currentPage }) => { + const moduleId = useModuleId(); const tree = buildTree(pages); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
@@ -123,6 +126,7 @@ const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, cu }; const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeToDarkMode, showDarkModeToggle }) => { + const moduleId = useModuleId(); const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); const license = useStore((state) => state.license); @@ -134,7 +138,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; var styles = { bmBurgerButton: { @@ -189,7 +193,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan const isLicensed = !_.get(license, 'featureAccess.licenseStatus.isExpired', true) && _.get(license, 'featureAccess.licenseStatus.isLicenseValid', false); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return ( <> diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 94a55d85a3..28fa683df6 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -8,6 +8,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import OverflowTooltip from '@/_components/OverflowTooltip'; import cx from 'classnames'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; @@ -141,11 +142,12 @@ const RenderPageGroup = ({ export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => { // Don't render empty folders if displaying only icons + const moduleId = useModuleId(); const tree = buildTree(pages, !!labelStyle?.label?.hidden); const currentPageId = useStore((state) => state.currentPageId); const currentPage = pages.find((page) => page.id === currentPageId); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
{/* page.id)}> */} diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index abf0ef6646..79de594291 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -15,6 +15,7 @@ import ViewerSidebarNavigation from './ViewerSidebarNavigation'; import { shallow } from 'zustand/shallow'; import Popups from '../Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import Spinner from '@/_ui/Spinner'; export const Viewer = ({ id: appId, @@ -50,28 +51,29 @@ export const Viewer = ({ toggleCurrentLayout, } = useStore( (state) => ({ - isEditorLoading: state.isEditorLoading, - currentMode: state.currentMode, + isEditorLoading: state.loaderStore.modules[moduleId].isEditorLoading, + currentMode: state.modeStore.modules[moduleId].currentMode, currentLayout: state.currentLayout, editingVersion: state.editingVersion, selectedVersion: state.selectedVersion, currentCanvasWidth: state.currentCanvasWidth, - appName: state.app.appName, - homePageId: state?.app.homepageId, - currentPageId: state.currentPageId, + appName: state.appStore.modules[moduleId].app.appName, + homePageId: state.appStore.modules[moduleId].app.homepageId, + currentPageId: state.modules[moduleId].currentPageId, globalSettings: state.globalSettings, - pages: state.modules.canvas.pages, + pages: state.modules[moduleId].pages, modules: state.modules, globalSettingsChanged: state.globalSettingsChanged, pageSettings: state.pageSettings, updateCanvasHeight: state.updateCanvasBottomHeight, - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, setIsViewer: state.setIsViewer, toggleCurrentLayout: state.toggleCurrentLayout, }), shallow ); - const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + + const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const currentPageComponents = useMemo(() => getCurrentPageComponents, [getCurrentPageComponents]); const changeDarkMode = useStore((state) => state.changeDarkMode); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); @@ -124,9 +126,9 @@ export const Viewer = ({ useEffect(() => { const isMobileDevice = deviceWindowWidth < 600; toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop'); - setIsViewer(true); + setIsViewer(true, moduleId); return () => { - setIsViewer(false); + setIsViewer(false, moduleId); }; }, []); @@ -171,8 +173,8 @@ export const Viewer = ({ if (isEditorLoading) { return ( -
- +
+ {moduleMode ? : }
); } else if (isMaintenanceOn) { @@ -252,9 +254,13 @@ export const Viewer = ({ changeToDarkMode={changeToDarkMode} /> )} - +
- {/* {!licenseValid && isAppLoaded && } */} {isMobilePreviewMode &&
} {isMobilePreviewMode &&
}
diff --git a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx index d913bffd91..94a43bee72 100644 --- a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx +++ b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx @@ -9,6 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { RenderPageAndPageGroup } from './PageGroup'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const ViewerSidebarNavigation = ({ isMobileDevice, @@ -20,10 +21,11 @@ export const ViewerSidebarNavigation = ({ isSidebarPinned, toggleSidebarPinned, }) => { + const moduleId = useModuleId(); const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {}; const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const license = useStore((state) => state.license); if (isMobileDevice) { @@ -95,7 +97,7 @@ export const ViewerSidebarNavigation = ({ version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; const isLicensed = diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index 8aa7b578ac..a9e1c58ae9 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -26,6 +26,7 @@ import cx from 'classnames'; import { useGridStore } from '@/_stores/gridStore'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ @@ -40,11 +41,12 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { + const moduleId = useModuleId(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; const { accentColor } = styles; - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [lastSelectedCard, setLastSelectedCard] = useState({}); // eslint-disable-next-line react-hooks/exhaustive-deps const columnDataAsObj = useMemo(() => convertArrayToObj(columnData), [JSON.stringify(columnData)]); diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index e0f099205f..fe1c433b83 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -7,6 +7,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { debounce } from 'lodash'; var tinycolor = require('tinycolor2'); +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ id, @@ -20,6 +21,7 @@ export const Modal = function Modal({ dataCy, height, }) { + const moduleId = useModuleId(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, @@ -48,7 +50,7 @@ export const Modal = function Modal({ const titleAlignment = properties.titleAlignment ?? 'left'; const size = properties.size ?? 'lg'; const [modalWidth, setModalWidth] = useState(); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas); /**** Start - Logic to reset the zIndex of modal control box ****/ diff --git a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx index bfe1431ad0..3c9329a3ea 100644 --- a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx +++ b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx @@ -16,6 +16,7 @@ import { createModalStyles } from '@/AppBuilder/Widgets/ModalV2/helpers/stylesFa import { onShowSideEffects, onHideSideEffects } from '@/AppBuilder/Widgets/ModalV2/helpers/sideEffects'; import '@/AppBuilder/Widgets/ModalV2/style.scss'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const ModalV2 = function Modal({ id, @@ -29,6 +30,7 @@ export const ModalV2 = function Modal({ dataCy, height, }) { + const moduleId = useModuleId(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, @@ -56,7 +58,7 @@ export const ModalV2 = function Modal({ const titleAlignment = properties.titleAlignment ?? 'left'; const size = properties.size ?? 'lg'; const setSelectedComponentAsModal = useStore((state) => state.setSelectedComponentAsModal, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const computedModalBodyHeight = getModalBodyHeight(modalHeight, showHeader, showFooter, headerHeight, footerHeight); const headerHeightPx = getModalHeaderHeight(showHeader, headerHeight); diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 7d378e7d70..0473de3280 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -49,6 +49,7 @@ import { EmptyState } from './Components/EmptyState'; import { LoadingState } from './Components/LoadingState'; // eslint-disable-next-line import/no-unresolved import { useVirtualizer } from '@tanstack/react-virtual'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; // utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row const utilityForNestedNewRow = (row) => { @@ -87,11 +88,12 @@ export const Table = React.memo( // events, // setProperty, }) => { - const component = useStore((state) => state.getComponentDefinition(id), shallow); + const moduleId = useModuleId(); + const component = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const exposedNewRows = useStore((state) => state.getExposedValueOfComponent(id)?.newRows || [], shallow); const validateWidget = useStore((state) => state.validateWidget, shallow); const validateDates = useStore((state) => state.validateDates, shallow); - const mode = useStore((state) => state.currentMode); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode); const { color, diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index f07e30ad65..bbdf89d936 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -2,8 +2,12 @@ import React, { createContext, useContext } from 'react'; export const ModuleContext = createContext(); -export const ModuleProvider = ({ moduleId, isModuleMode, appType, children }) => { - return {children}; +export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor, children }) => { + return ( + + {children} + + ); }; export const useModuleId = () => { @@ -30,3 +34,11 @@ export const useAppType = () => { } return context.appType; }; + +export const useIsModuleEditor = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useIsModuleEditor must be used within a ModuleProvider'); + } + return context.isModuleEditor; +}; diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 3caabf5db2..4104868eda 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -28,7 +28,7 @@ import { distinctUntilChanged } from 'rxjs'; import { convertAllKeysToSnakeCase } from '../_stores/utils'; import { getPreviewQueryParams } from '@/_helpers/routes'; import { useLocation, useMatch, useParams } from 'react-router-dom'; - +import { useMounted } from '@/_hooks/use-mount'; /** * this is to normalize the query transformation options to match the expected schema. Takes care of corrupted data. * This will get redundanted once api response for appdata is made uniform across all the endpoints. @@ -62,11 +62,15 @@ const useAppData = ( { environmentId, versionId } = {}, moduleMode = false ) => { + const mounted = useMounted(); + const initModules = useStore((state) => state.initModules, shallow); + moduleMode && !mounted && initModules(moduleId); + const { state } = useLocation(); const [currentSession, setCurrentSession] = useState(); const setEditorLoading = useStore((state) => state.setEditorLoading); const setApp = useStore((state) => state.setApp); - const app = useStore((state) => state.app); + const app = useStore((state) => state.appStore.modules[moduleId].app); const user = useStore((state) => state.user); const setCurrentVersionId = useStore((state) => state.setCurrentVersionId); const currentVersionId = useStore((state) => state.currentVersionId); @@ -86,7 +90,7 @@ const useAppData = ( const previousVersion = usePrevious(currentVersionId); const events = useStore((state) => state.eventsSlice.module[moduleId]?.events || []); const pages = useStore((state) => state.modules[moduleId]?.pages || []); - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const setResolvedConstants = useStore((state) => state.setResolvedConstants); const setSecrets = useStore((state) => state.setSecrets); const setQueryMapping = useStore((state) => state.setQueryMapping); @@ -110,7 +114,7 @@ const useAppData = ( const setIsEditorFreezed = useStore((state) => state.setIsEditorFreezed); const appMode = useStore((state) => state.globalSettings.appMode); const previousEnvironmentId = usePrevious(selectedEnvironment?.id); - const isComponentLayoutReady = useStore((state) => state.isComponentLayoutReady, shallow); + const isComponentLayoutReady = useStore((state) => state.appStore.modules[moduleId].isComponentLayoutReady, shallow); const pageSwitchInProgress = useStore((state) => state.pageSwitchInProgress); const setPageSwitchInProgress = useStore((state) => state.setPageSwitchInProgress); const selectedVersion = useStore((state) => state.selectedVersion); @@ -118,7 +122,6 @@ const useAppData = ( const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow); const setModulesList = useStore((state) => state.setModulesList, shallow); - const initModules = useStore((state) => state.initModules, shallow); const setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); @@ -128,7 +131,7 @@ const useAppData = ( const setSelectedSidebarItem = useStore((state) => state.setSelectedSidebarItem); const toggleLeftSidebar = useStore((state) => state.toggleLeftSidebar); const pathParams = useParams(); - const slug = pathParams?.slug; + const slug = moduleMode ? '' : pathParams?.slug; const match = useMatch('/applications/:slug/:pageHandle'); @@ -159,11 +162,6 @@ const useAppData = ( } }; - useEffect(() => { - if (!moduleMode) return; - initModules(moduleId); - }, [initModules, moduleId, moduleMode]); - useEffect(() => { if (pageSwitchInProgress && !moduleMode) { const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId); @@ -175,7 +173,7 @@ const useAppData = ( }, [pageSwitchInProgress, currentPageId, moduleMode]); useEffect(() => { - if (moduleMode) return; + // if (moduleMode) return; const subscription = authenticationService.currentSession .pipe( distinctUntilChanged((prev, curr) => { @@ -212,14 +210,32 @@ const useAppData = ( }, [appMode, darkMode, moduleId]); useEffect(() => { - if (!currentSession || moduleMode) { + if (!currentSession) { return; } - const queryParams = getPreviewQueryParams(); - const isPublicAccess = - (currentSession?.load_app && currentSession?.authentication_failed) || (!queryParams.version && mode !== 'edit'); - const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; + let appDataPromise; + + // if (moduleMode) { + // appDataPromise = appService.fetchApp(appId); + // appDataPromise.then(async (result) => { + // let appData = { ...result }; + // console.log('appData--- ', appData); + // return; + // // setApp({ + // // appName: appData.name, + // // appId: appData.id, + // // slug: appData.slug, + // // }); + // }); + // } + + const queryParams = moduleMode ? {} : getPreviewQueryParams(); + const isPublicAccess = moduleMode + ? false + : (currentSession?.load_app && currentSession?.authentication_failed) || + (!queryParams.version && mode !== 'edit'); + const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; if (isPublicAccess) { appDataPromise = appService.fetchAppBySlug(slug); } else { @@ -275,7 +291,7 @@ const useAppData = ( editorEnvironment?.name ); - setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); + !moduleMode && setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public); @@ -298,36 +314,42 @@ const useAppData = ( const homePageId = appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id; - setApp({ - appName: appData.name, - appId: appData.id, - slug: appData.slug, - currentAppEnvironmentId: editorEnvironment.id, - isMaintenanceOn: - 'is_maintenance_on' in result - ? result.is_maintenance_on - : 'isMaintenanceOn' in result - ? result.isMaintenanceOn - : false, - organizationId: appData.organizationId || appData.organization_id, - homePageId: homePageId, - isPublic: appData.is_public, - creationMode: appData.creation_mode, - }); - setIsEditorFreezed(appData.should_freeze_editor); - setGlobalSettings( - mapKeys(appData.editing_version?.global_settings || appData.global_settings, (value, key) => camelCase(key)) + setApp( + { + appName: appData.name, + appId: appData.id, + slug: appData.slug, + currentAppEnvironmentId: editorEnvironment.id, + isMaintenanceOn: + 'is_maintenance_on' in result + ? result.is_maintenance_on + : 'isMaintenanceOn' in result + ? result.isMaintenanceOn + : false, + organizationId: appData.organizationId || appData.organization_id, + homePageId: homePageId, + isPublic: appData.is_public, + creationMode: appData.creation_mode, + }, + moduleId ); + if (!moduleMode) { + setIsEditorFreezed(appData.should_freeze_editor); + setGlobalSettings( + mapKeys(appData.editing_version?.global_settings || appData.global_settings, (value, key) => camelCase(key)) + ); + setPageSettings( + computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) + ); + } + setPages(pages, moduleId); - setPageSettings( - computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) - ); // set starting page as homepage initially let startingPage = appData.pages.find((page) => page.id === homePageId); - if (initialLoadRef.current) { + if (initialLoadRef.current && !moduleMode) { // if initial load, check if the path has a page handle and set that as the starting page const initialLoadPath = location.pathname.split('/').pop(); const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup); @@ -344,18 +366,23 @@ const useAppData = ( // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`); } - setCurrentPageHandle(startingPage.handle); - updateFeatureAccess(); + setCurrentPageHandle(startingPage.handle, moduleId); setCurrentPageId(startingPage.id, moduleId); - setResolvedPageConstants({ - id: startingPage?.id, - handle: startingPage?.handle, - name: startingPage?.name, - }); + setResolvedPageConstants( + { + id: startingPage?.id, + handle: startingPage?.handle, + name: startingPage?.name, + }, + moduleId + ); setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); - setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); - setAppHomePageId(homePageId); + updateEventsField('events', appData.events, moduleId); + if (!moduleMode) { + updateFeatureAccess(); + setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); + } + setAppHomePageId(homePageId, moduleId); const queryData = isPublicAccess || (mode !== 'edit' && appData.is_public) @@ -363,10 +390,13 @@ const useAppData = ( : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id); const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries; dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); - setQueries(dataQueries); + setQueries(dataQueries, moduleId); if (dataQueries?.length > 0) { - setSelectedQuery(dataQueries[0]?.id); - initialiseResolvedQuery(dataQueries.map((query) => query.id)); + !moduleMode && setSelectedQuery(dataQueries[0]?.id); + initialiseResolvedQuery( + dataQueries.map((query) => query.id), + moduleId + ); } const constants = constantsResp?.constants; @@ -380,26 +410,31 @@ const useAppData = ( orgSecrets[constant.name] = constant.value; } }); - setResolvedConstants(orgConstants); - setSecrets(orgSecrets); + setResolvedConstants(orgConstants, moduleId); + setSecrets(orgSecrets, moduleId); } setQueryMapping(moduleId); - setResolvedGlobals('environment', editorEnvironment); - setResolvedGlobals('mode', { value: mode }); - setResolvedGlobals('currentUser', { - ...user, - groups: currentSession?.groups, - role: currentSession?.role?.name, - ssoUserInfo: currentSession?.ssoUserInfo, - ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata) - ? { metadata: currentSession?.currentUser?.metadata } - : {}), - }); - setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search)))); + setResolvedGlobals('environment', editorEnvironment, moduleId); + setResolvedGlobals('mode', { value: mode }, moduleId); + setResolvedGlobals( + 'currentUser', + { + ...user, + groups: currentSession?.groups, + role: currentSession?.role?.name, + ssoUserInfo: currentSession?.ssoUserInfo, + ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata) + ? { metadata: currentSession?.currentUser?.metadata } + : {}), + }, + moduleId + ); + setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))), moduleId); initDependencyGraph(moduleId); - setCurrentMode(mode); // TODO: set mode based on the slug/appDef + setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef if ( + !moduleMode && state.ai && state?.prompt && initialLoadRef.current && @@ -412,7 +447,7 @@ const useAppData = ( showWalkthrough = false; } // fetchDataSources(appData.editing_version.id, editorEnvironment.id); - if (!isPublicAccess) { + if (!isPublicAccess && !moduleMode) { const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env'); useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams); fetchGlobalDataSources( @@ -421,24 +456,25 @@ const useAppData = ( editorEnvironment.id ); } - useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed - updateReleasedVersionId(appData.current_version_id); + if (!moduleMode) { + useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed + updateReleasedVersionId(appData.current_version_id); + } - setEditorLoading(false); + setEditorLoading(false, moduleId); initialLoadRef.current = false; // only show if app is not created from prompt - if (showWalkthrough) initEditorWalkThrough(); - checkAndSetTrueBuildSuggestionsFlag(); + if (showWalkthrough && !moduleMode) initEditorWalkThrough(); + !moduleMode && checkAndSetTrueBuildSuggestionsFlag(); return () => { document.title = retrieveWhiteLabelText(); }; }); - }, [setApp, setEditorLoading, currentSession, moduleMode]); + }, [setApp, setEditorLoading, currentSession, moduleMode, moduleId]); useEffect(() => { - if (moduleMode) return; if (isComponentLayoutReady) { - runOnLoadQueries().then(() => { + runOnLoadQueries(moduleId).then(() => { let startingPage = pages.find((page) => page.id === currentPageId); const currentPageEvents = events.filter( (event) => event.target === 'page' && event.sourceId === startingPage.id @@ -462,13 +498,13 @@ const useAppData = ( const isVersionChanged = currentVersionId && previousVersion && currentVersionId != previousVersion; if (isEnvChanged || isVersionChanged) { - setEditorLoading(true); + setEditorLoading(true, moduleId); clearSelectedComponents(); if (isEnvChanged) { setEnvironmentLoadingState('loading'); } appVersionService.getAppVersionData(appId, selectedVersion?.id).then(async (appData) => { - cleanUpStore(); + cleanUpStore(false); const { should_freeze_editor } = appData; setIsEditorFreezed(should_freeze_editor); @@ -503,7 +539,7 @@ const useAppData = ( ); setCurrentPageId(startingPage.id, moduleId); setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); + updateEventsField('events', appData.events, moduleId); // const queryData = await dataqueryService.getAll(currentVersionId); if (isEnvChanged) { @@ -529,7 +565,7 @@ const useAppData = ( const queryData = await dataqueryService.getAll(currentVersionId); const dataQueries = queryData.data_queries; dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); - setQueries(dataQueries); + setQueries(dataQueries, moduleId); if (dataQueries?.length > 0) { setSelectedQuery(dataQueries[0]?.id); initialiseResolvedQuery(dataQueries.map((query) => query.id)); @@ -558,10 +594,10 @@ const useAppData = ( setQueryMapping(moduleId); initDependencyGraph(moduleId); - setEditorLoading(false); + setEditorLoading(false, moduleId); }); } - }, [selectedEnvironment?.id, currentVersionId, moduleMode]); + }, [selectedEnvironment?.id, currentVersionId, moduleMode, moduleId]); useEffect(() => { if (moduleMode) return; diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index 9f2501f444..4ee2e8ac9e 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -9,26 +9,84 @@ import { replaceEntityReferencesWithIds } from '../utils'; import _ from 'lodash'; const initialState = { - app: {}, - canvasHeight: null, isSaving: false, - globalSettings: {}, pageSwitchInProgress: false, isTJDarkMode: localStorage.getItem('darkMode') === 'true', + globalSettings: {}, isViewer: false, isComponentLayoutReady: false, + appStore: { + modules: { + canvas: { + canvasHeight: null, + app: {}, + isViewer: false, + isComponentLayoutReady: false, + }, + }, + }, }; export const createAppSlice = (set, get) => ({ ...initialState, - setIsViewer: (isViewer) => set(() => ({ isViewer }), false, 'setIsViewer'), - setApp: (app) => set(() => ({ app }), false, 'setApp'), - setAppName: (name) => set((state) => ({ app: { ...state.app, appName: name } }), false, 'setAppName'), - setAppHomePageId: (homePageId) => set((state) => ({ app: { ...state.app, homePageId } }), false, 'setAppHomePageId'), - setIsComponentLayoutReady: (isReady) => - set(() => ({ isComponentLayoutReady: isReady }), false, 'setIsComponentLayoutReady'), - setCanvasHeight: (canvasHeight) => set({ canvasHeight }, false, 'setCanvasHeight'), - updateCanvasBottomHeight: (components) => { + initializeAppSlice: (moduleId) => { + set( + (state) => { + state.appStore.modules[moduleId] = { ...initialState.appStore.modules.canvas }; + }, + false, + 'initializeAppSlice' + ); + }, + setIsViewer: (isViewer, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].isViewer = isViewer; + }, + false, + 'setIsViewer' + ), + setApp: (app, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app = app; + }, + false, + 'setApp' + ), + setAppName: (name, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app.appName = name; + }, + false, + 'setAppName' + ), + setAppHomePageId: (homePageId, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app.homePageId = homePageId; + }, + false, + 'setAppHomePageId' + ), + setIsComponentLayoutReady: (isReady, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].isComponentLayoutReady = isReady; + }, + false, + 'setIsComponentLayoutReady' + ), + setCanvasHeight: (canvasHeight, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].canvasHeight = canvasHeight; + }, + false, + 'setCanvasHeight' + ), + updateCanvasBottomHeight: (components, moduleId = 'canvas') => { const { currentLayout, currentMode, setCanvasHeight } = get(); const maxHeight = Object.values(components).reduce((max, component) => { const layout = component?.layouts?.[currentLayout]; @@ -40,23 +98,25 @@ export const createAppSlice = (set, get) => ({ }, 0); const bottomPadding = currentMode === 'view' ? 100 : 300; const frameHeight = currentMode === 'view' ? 45 : 85; - setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`); + setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`, moduleId); }, - setIsAppSaving: (isSaving) => { + setIsAppSaving: (isSaving, moduleId = 'canvas') => { set( (state) => { - state.app.isSaving = isSaving; + state.appStore.modules[moduleId].app.isSaving = isSaving; }, false, 'setIsAppSaving' ); }, setGlobalSettings: (globalSettings) => set(() => ({ globalSettings }), false, 'setGlobalSettings'), - toggleAppMaintenance: () => { - const { isMaintenanceOn, appId } = get().app; + toggleAppMaintenance: (moduleId = 'canvas') => { + const { isMaintenanceOn, appId } = get().appStore.modules[moduleId].app; appsService.setMaintenance(appId, !isMaintenanceOn).then(() => { - set((state) => ({ app: { ...state.app, isMaintenanceOn: !isMaintenanceOn } })); + set((state) => { + state.appStore.modules[moduleId].app.isMaintenanceOn = !isMaintenanceOn; + }); if (isMaintenanceOn) { toast.success('Application is on maintenance.'); } else { @@ -64,9 +124,9 @@ export const createAppSlice = (set, get) => ({ } }); }, - globalSettingsChanged: async (options) => { - const componentNameIdMapping = get().modules.canvas.componentNameIdMapping; - const queryNameIdMapping = get().modules.canvas.queryNameIdMapping; + globalSettingsChanged: async (options, moduleId = 'canvas') => { + const componentNameIdMapping = get().modules[moduleId].componentNameIdMapping; + const queryNameIdMapping = get().modules[moduleId].queryNameIdMapping; for (const [key, value] of Object.entries(options)) { if (value?.[1]?.a == undefined) { options[key] = value; @@ -77,10 +137,10 @@ export const createAppSlice = (set, get) => ({ } // Replace entity references with ids if present const newOptions = replaceEntityReferencesWithIds(options, componentNameIdMapping, queryNameIdMapping); - const { app, currentVersionId, currentPageId } = get(); + const { appStore, currentVersionId, currentPageId } = get(); try { const res = await appVersionService.autoSaveApp( - app.appId, + appStore.modules[moduleId].app.appId, currentVersionId, { globalSettings: newOptions }, 'global_settings', @@ -93,7 +153,7 @@ export const createAppSlice = (set, get) => ({ console.error('Error updating page:', error); } }, - switchPage: (pageId, handle, queryParams = []) => { + switchPage: (pageId, handle, queryParams = [], moduleId = 'canvas') => { get().debugger.resetUnreadErrorCount(); // reset stores if (get().pageSwitchInProgress) { @@ -120,15 +180,15 @@ export const createAppSlice = (set, get) => ({ const isPreview = currentMode !== 'edit'; //!TODO clear all queued tasks cleanUpStore(true); - setCurrentPageId(pageId, 'canvas'); - setComponentNameIdMapping('canvas'); - setQueryMapping('canvas'); + setCurrentPageId(pageId, moduleId); + setComponentNameIdMapping(moduleId); + setQueryMapping(moduleId); const isLicenseValid = !_.get(license, 'featureAccess.licenseStatus.isExpired', true) && _.get(license, 'featureAccess.licenseStatus.isLicenseValid', false); - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const filteredQueryParams = queryParams.filter(([key, value]) => { if (!value) return false; if (key === 'env' && isLicenseValid) return false; @@ -136,7 +196,7 @@ export const createAppSlice = (set, get) => ({ }); const queryParamsString = filteredQueryParams.map(([key, value]) => `${key}=${value}`).join('&'); - const slug = get().app.slug; + const slug = get().appStore.modules[moduleId].app.slug; navigate( `/${isPreview ? 'applications' : getWorkspaceId() + '/apps'}/${slug ?? appId}/${handle}?${queryParamsString}`, @@ -147,11 +207,14 @@ export const createAppSlice = (set, get) => ({ } ); const newPage = pages.find((p) => p.id === pageId); - setResolvedPageConstants({ - id: newPage?.id, - handle: newPage?.handle, - name: newPage?.name, - }); + setResolvedPageConstants( + { + id: newPage?.id, + handle: newPage?.handle, + name: newPage?.name, + }, + moduleId + ); setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(queryParamsString)))); initDependencyGraph('canvas'); setPageSwitchInProgress(true); @@ -159,8 +222,9 @@ export const createAppSlice = (set, get) => ({ setPageSwitchInProgress: (isInProgress) => set(() => ({ pageSwitchInProgress: isInProgress }), false, 'setPageSwitchInProgress'), - cleanUpStore: (isPageSwitch = false) => { - get().resetUndoRedoStack(); + cleanUpStore: (isPageSwitch = false, moduleId) => { + const { resetUndoRedoStack, initModules } = get(); + resetUndoRedoStack(); set((state) => { state.modules.canvas.componentNameIdMapping = {}; state.selectedComponents = []; @@ -176,26 +240,30 @@ export const createAppSlice = (set, get) => ({ state.resolvedStore.modules.canvas.customResolvables = {}; state.resolvedStore.modules.canvas.exposedValues.components = {}; state.resolvedStore.modules.canvas.exposedValues.page.variables = {}; + // initModules(moduleId); }); }, - setSlug: (slug) => { + setSlug: (slug, moduleId = 'canvas') => { set( (state) => { - state.app.slug = slug; + state.appStore.modules[moduleId].app.slug = slug; }, false, 'setSlug' ); }, - setIsPublic: (isPublic) => { + setIsPublic: (isPublic, moduleId = 'canvas') => { set( (state) => { - state.app.isPublic = isPublic; + state.appStore.modules[moduleId].app.isPublic = isPublic; }, false, 'setIsPublic' ); }, + getAppId: (moduleId = 'canvas') => { + return get().appStore.modules[moduleId].app.appId; + }, updateIsTJDarkMode: (newMode) => set({ isTJDarkMode: newMode }, false, 'updateIsTJDarkMode'), }); diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index c90f12e746..741846c7b4 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -29,19 +29,19 @@ import { findHighestLevelofSelection } from '@/AppBuilder/AppCanvas/Grid/gridUti const initialState = { modules: { canvas: { + currentPageId: null, + currentPageIndex: 0, pages: [], componentNameIdMapping: {}, queryNameIdMapping: {}, queryIdNameMapping: {}, + currentPageHandle: null, }, }, - currentPageId: null, - currentPageIndex: 0, containerChildrenMapping: { canvas: [], }, selectedComponents: [], - currentPageHandle: null, showWidgetDeleteConfirmation: false, focusedParentId: null, modalsOpenOnCanvas: [], @@ -53,7 +53,8 @@ export const createComponentsSlice = (set, get) => ({ initializeComponentsSlice: (moduleId) => { set( (state) => { - state.modules[moduleId] = { ...state.modules[moduleId], ...initialState.modules.canvas }; + state.modules[moduleId] = { ...initialState.modules.canvas }; + state.containerChildrenMapping[moduleId] = []; }, false, 'initializeComponentsSlice' @@ -80,16 +81,16 @@ export const createComponentsSlice = (set, get) => ({ ); }, - setCurrentPageId: (id, moduleId) => + setCurrentPageId: (id, moduleId = 'canvas') => set( (state) => { - const currentPageIndex = state.modules.canvas.pages.findIndex((page) => page.id === id); + const currentPageIndex = state.modules[moduleId].pages.findIndex((page) => page.id === id); const currentPageComponents = state.modules[moduleId].pages[currentPageIndex]?.components || {}; - state.currentPageIndex = currentPageIndex; - state.currentPageId = id; - state.containerChildrenMapping = { canvas: [] }; + state.modules[moduleId].currentPageIndex = currentPageIndex; + state.modules[moduleId].currentPageId = id; + state.containerChildrenMapping[moduleId] = []; Object.entries(currentPageComponents).forEach(([componentId, component]) => { - const parentId = component.component.parent || 'canvas'; + const parentId = component.component.parent || moduleId; if (!state.containerChildrenMapping[parentId]) { state.containerChildrenMapping[parentId] = []; } @@ -99,10 +100,10 @@ export const createComponentsSlice = (set, get) => ({ false, 'setCurrentPageId' ), - setCurrentPageHandle: (handle) => { + setCurrentPageHandle: (handle, moduleId = 'canvas') => { set( (state) => { - state.currentPageHandle = handle; + state.modules[moduleId].currentPageHandle = handle; }, false, 'setCurrentPageHandle' @@ -159,7 +160,7 @@ export const createComponentsSlice = (set, get) => ({ }, setComponentNameIdMapping: (moduleId = 'canvas') => { - const components = get().getCurrentPageComponents(); + const components = get().getCurrentPageComponents(moduleId); set( (state) => { Object.entries(components).forEach(([componentId, component]) => { @@ -172,12 +173,13 @@ export const createComponentsSlice = (set, get) => ({ }, setComponentName: (componentId, newName, moduleId = 'canvas') => { - const { renameComponentNameIdMapping, saveComponentChanges } = get(); + const { renameComponentNameIdMapping, saveComponentChanges, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); let oldName = ''; set( (state) => { - oldName = state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name; - state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name = newName; + oldName = state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name; + state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name = newName; }, false, 'setComponentName' @@ -187,7 +189,7 @@ export const createComponentsSlice = (set, get) => ({ [componentId]: { component: { name: newName } }, }; - saveComponentChanges(diff, 'components', 'update'); + saveComponentChanges(diff, 'components', 'update', moduleId); renameComponentNameIdMapping(oldName, newName, moduleId); }, @@ -237,7 +239,15 @@ export const createComponentsSlice = (set, get) => ({ get().checkAndSetTrueBuildSuggestionsFlag(); }, - generateDependencyGraphForRefs: (allRefs, key, paramType, property, unResolvedValue, isUpdate = false) => { + generateDependencyGraphForRefs: ( + allRefs, + key, + paramType, + property, + unResolvedValue, + isUpdate = false, + moduleId = 'canvas' + ) => { const { addDependency, updateDependency } = get(); if (allRefs.length !== 0) { allRefs.forEach(({ entityType, entityNameOrId, entityKey }, index) => { @@ -246,9 +256,9 @@ export const createComponentsSlice = (set, get) => ({ : `${entityType}.${entityKey}`; const propertyPath = paramType === undefined ? `others.${key}` : `components.${key}.${paramType}.${property}`; if (isUpdate && index === 0) { - updateDependency(propertyValue, propertyPath, unResolvedValue); + updateDependency(propertyValue, propertyPath, unResolvedValue, moduleId); } else { - addDependency(propertyValue, propertyPath, unResolvedValue); + addDependency(propertyValue, propertyPath, unResolvedValue, moduleId); } }); } @@ -340,7 +350,7 @@ export const createComponentsSlice = (set, get) => ({ const length = Object.keys(customResolvables).length; if (length === 0) { const resolvedValue = shouldResolve - ? resolveDynamicValues(value, getAllExposedValues(), customResolvables, false, []) + ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables, false, []) : value; if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) { componentResolvedValues[componentId] = index === null ? deepClone(DEFAULT_COMPONENT_STRUCTURE) : []; @@ -382,7 +392,7 @@ export const createComponentsSlice = (set, get) => ({ // Loop all the index and set the resolved value for (let i = 0; i < length; i++) { const resolvedValue = shouldResolve - ? resolveDynamicValues(value, getAllExposedValues(), customResolvables[i], false, []) + ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables[i], false, []) : value; if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) { componentResolvedValues[componentId] = []; @@ -435,14 +445,14 @@ export const createComponentsSlice = (set, get) => ({ const length = Object.keys(customResolvables).length; if (length === 0) { const resolvedValue = shouldResolve - ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables, false, []) + ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables, false, []) : value; setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, index, moduleId); } else { // Loop all the index and set the resolved value for (let i = 0; i < length; i++) { const resolvedValue = shouldResolve - ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables[i], false, []) + ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables[i], false, []) : value; setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, i, moduleId); } @@ -637,7 +647,15 @@ export const createComponentsSlice = (set, get) => ({ ); lodashSet(updatedPropertyValue, [index, ...keys], updatedValue); if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + generateDependencyGraphForRefs( + allRefs, + componentId, + paramType, + propertyWithArrayValue, + unResolvedValue, + false, + moduleId + ); } }); } else { @@ -654,7 +672,15 @@ export const createComponentsSlice = (set, get) => ({ ); updatedPropertyValue[index] = updatedValue; if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + generateDependencyGraphForRefs( + allRefs, + componentId, + paramType, + propertyWithArrayValue, + unResolvedValue, + false, + moduleId + ); } } }); @@ -671,7 +697,7 @@ export const createComponentsSlice = (set, get) => ({ moduleId ); if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue); + generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, false, moduleId); } return { allRefs, unResolvedValue, updatedValue }; } @@ -709,7 +735,7 @@ export const createComponentsSlice = (set, get) => ({ addToDependencyGraph: (moduleId = 'canvas', componentId, component) => { const { updateDependencyGraphAndResolvedValues, getResolvedComponent } = get(); //TODO: Replace with object of component types - let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) }; const componentType = componentTypes.find((comp) => component.component === comp.component); ['properties', 'general', 'generalStyles', 'others', 'styles', 'validation'].forEach((key) => { updateDependencyGraphAndResolvedValues( @@ -726,7 +752,7 @@ export const createComponentsSlice = (set, get) => ({ initDependencyGraph: (moduleId) => { const { getCurrentPageComponents, addToDependencyGraph, setResolvedComponents, resolveOthers } = get(); - const components = getCurrentPageComponents(); + const components = getCurrentPageComponents(moduleId); //TODO: Replace with object of component types let resolvedComponentValues = {}; @@ -762,9 +788,9 @@ export const createComponentsSlice = (set, get) => ({ get().modules[moduleId].componentNameIdMapping, get().modules[moduleId].queryNameIdMapping ); - const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(), {}, false, []); + const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(moduleId), {}, false, []); resolvedValues[key] = resolvedValue; - generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate); + generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate, moduleId); } else { resolvedValues[key] = item; } @@ -795,7 +821,9 @@ export const createComponentsSlice = (set, get) => ({ canAddToParent, getComponentNameFromId, deleteComponentNameIdMapping, + getCurrentPageId, } = get(); + const currentPageId = getCurrentPageId(moduleId); // This is made into a promise to wait for the saveComponentChanges to complete so that the caller can await it return new Promise((resolve) => { if ( @@ -809,7 +837,7 @@ export const createComponentsSlice = (set, get) => ({ } const newComponents = componentDefinitions.reduce((acc, componentDefinition) => { const currentComponents = { - ...getCurrentPageComponents(), + ...getCurrentPageComponents(moduleId), ...Object.fromEntries(acc.map((component) => [component.id, component])), }; const componentName = @@ -863,7 +891,7 @@ export const createComponentsSlice = (set, get) => ({ if (!state.containerChildrenMapping[parentId].includes(newComponent.id)) { state.containerChildrenMapping[parentId].push(newComponent.id); } - const page = state.modules[moduleId].pages.find((page) => page.id === state.currentPageId); + const page = state.modules[moduleId].pages.find((page) => page.id === currentPageId); page.components[newComponent.id] = newComponent; }, skipUndoRedo), false, @@ -874,7 +902,7 @@ export const createComponentsSlice = (set, get) => ({ get().setSelectedComponents(selectedComponents.map((component) => component.id)); if (saveAfterAction) { - saveComponentChanges(diff, 'components', 'create') + saveComponentChanges(diff, 'components', 'create', moduleId) .then(() => { resolve(); // Resolve the promise after all operations are complete }) @@ -899,7 +927,11 @@ export const createComponentsSlice = (set, get) => ({ selectedComponents, deleteComponentNameIdMapping, removeNode, + getCurrentPageId, + checkIfComponentIsModule, + clearModuleFromStore, } = get(); + const currentPageId = getCurrentPageId(moduleId); const appEvents = get().eventsSlice.getModuleEvents(moduleId); const componentNames = []; const _selectedComponents = selected?.length ? selected : selectedComponents; @@ -908,7 +940,7 @@ export const createComponentsSlice = (set, get) => ({ withUndoRedo((state) => { const toDeleteComponents = []; const toDeleteEvents = []; - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const findAllChildComponents = (componentId) => { if (!toDeleteComponents.includes(componentId)) { @@ -929,7 +961,7 @@ export const createComponentsSlice = (set, get) => ({ findAllChildComponents(componentId); }); - const page = state.modules?.canvas?.pages.find((page) => page.id === state.currentPageId); + const page = state.modules?.[moduleId]?.pages.find((page) => page.id === currentPageId); const resolvedComponents = state.resolvedStore.modules?.[moduleId]?.components; const componentsExposedValues = state.resolvedStore.modules?.[moduleId]?.exposedValues.components; @@ -941,12 +973,18 @@ export const createComponentsSlice = (set, get) => ({ ); }); + if (checkIfComponentIsModule(id, moduleId)) { + clearModuleFromStore(id); + } + // Remove the container itself if it's a container if (state.containerChildrenMapping[id]) { delete state.containerChildrenMapping[id]; } if (state.containerChildrenMapping?.canvas?.includes(id)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter((wid) => wid !== id); + state.containerChildrenMapping[moduleId].canvas = state.containerChildrenMapping[moduleId].filter( + (wid) => wid !== id + ); } componentNames.push(page.components[id]?.component?.name); const eventsToRemove = appEvents.filter((event) => event.sourceId === id).map((event) => event.id); @@ -955,7 +993,7 @@ export const createComponentsSlice = (set, get) => ({ delete resolvedComponents[id]; // Remove the component from the resolved store delete componentsExposedValues[id]; // Remove the component from the exposed values state.selectedComponents = []; // Empty the selected components - removeNode(`components.${id}`); + removeNode(`components.${id}`, moduleId); state.showWidgetDeleteConfirmation = false; // Set it to false always }); @@ -963,7 +1001,7 @@ export const createComponentsSlice = (set, get) => ({ state.eventsSlice.module[moduleId].events = filteredEvents; if (saveAfterAction) { - saveComponentChanges(toDeleteComponents, 'components', 'delete') + saveComponentChanges(toDeleteComponents, 'components', 'delete', moduleId) .then(() => { get().multiplayer.broadcastUpdates({ selectedComponents: _selectedComponents }, 'components', 'delete'); // Show delete toast message @@ -989,7 +1027,7 @@ export const createComponentsSlice = (set, get) => ({ 'deleteComponents' ); componentNames.forEach((componentName) => { - deleteComponentNameIdMapping(componentName); + deleteComponentNameIdMapping(componentName, moduleId); }); }, @@ -1038,12 +1076,14 @@ export const createComponentsSlice = (set, get) => ({ getComponentDefinition, currentLayout, checkValueAndResolve, + getCurrentPageIndex, } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); let hasParentChanged = false; let oldParentId; set( withUndoRedo((state) => { - const page = state.modules[moduleId].pages[state.currentPageIndex]; + const page = state.modules[moduleId].pages[currentPageIndex]; if (page) { // ============ Component layout update logic ============ Object.entries(componentLayouts).forEach(([componentId, layout]) => { @@ -1067,8 +1107,8 @@ export const createComponentsSlice = (set, get) => ({ state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter( (id) => id !== componentId ); - } else if (state.containerChildrenMapping.canvas.includes(componentId)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter( + } else if (state.containerChildrenMapping[moduleId].includes(componentId)) { + state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter( (id) => id !== componentId ); } @@ -1080,7 +1120,7 @@ export const createComponentsSlice = (set, get) => ({ } state.containerChildrenMapping[newParentId].push(componentId); } else { - state.containerChildrenMapping.canvas.push(componentId); + state.containerChildrenMapping[moduleId].push(componentId); } } // ============ Parent update logic ends ============ @@ -1147,7 +1187,7 @@ export const createComponentsSlice = (set, get) => ({ }, {}); if (saveAfterAction) { - saveComponentChanges(diff, 'components/layout', 'update'); + saveComponentChanges(diff, 'components/layout', 'update', moduleId); get().multiplayer.broadcastUpdates(diff, 'components/layout', 'update'); } }, @@ -1163,7 +1203,7 @@ export const createComponentsSlice = (set, get) => ({ { skipUndoRedo = false, saveAfterAction = true } = {} ) => { const { - currentPageIndex, + getCurrentPageIndex, saveComponentChanges, withUndoRedo, updateResolvedValues, @@ -1174,12 +1214,14 @@ export const createComponentsSlice = (set, get) => ({ checkValueAndResolve, getResolvedComponent, setResolvedComponent, + getCurrentMode, } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); const { component } = getComponentDefinition(componentId, moduleId); const oldValue = component.definition[paramType][property]; if (Array.isArray(oldValue?.value)) { - const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) }; resolvedComponent[componentId][paramType][property] = []; const { updatedValue } = checkValueAndResolve( @@ -1219,8 +1261,8 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - const currentMode = get().currentMode; - if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update'); + const currentMode = getCurrentMode(moduleId); + if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update'); } @@ -1271,18 +1313,18 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - const currentMode = get().currentMode; - if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update'); + const currentMode = getCurrentMode(moduleId); + if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update'); } if (attr !== 'value' || skipResolve) return; if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true); + generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true, moduleId); } else { const propertyPath = `components.${componentId}.${paramType}.${property}`; - removeDependency(propertyPath, true); + removeDependency(propertyPath, true, moduleId); } }, @@ -1315,8 +1357,8 @@ export const createComponentsSlice = (set, get) => ({ state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter( (id) => id !== componentId ); - } else if (state.containerChildrenMapping.canvas.includes(componentId)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter( + } else if (state.containerChildrenMapping[moduleId].includes(componentId)) { + state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter( (id) => id !== componentId ); } @@ -1328,7 +1370,7 @@ export const createComponentsSlice = (set, get) => ({ } state.containerChildrenMapping[newParentId].push(componentId); } else { - state.containerChildrenMapping.canvas.push(componentId); + state.containerChildrenMapping[moduleId].push(componentId); } }, skipUndoRedo), false, @@ -1379,7 +1421,7 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - saveComponentChanges(diff, 'components', 'update'); + saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, newParentId }, 'components', 'parent'); } }, @@ -1411,19 +1453,17 @@ export const createComponentsSlice = (set, get) => ({ state.focusedParentId = parentId; }); }, - saveComponentChanges: (diff, type, operation) => { + saveComponentChanges: (diff, type, operation, moduleId = 'canvas') => { set( (state) => { - state.app.isSaving = true; + state.appStore.modules[moduleId].app.isSaving = true; }, false, 'setAppSavingChanges' ); - const { - app: { appId }, - currentVersionId, - currentPageId, - } = get(); + const { getAppId, currentVersionId, getCurrentPageId } = get(); + const appId = getAppId(moduleId); + const currentPageId = getCurrentPageId(moduleId); return new Promise((resolve) => { appVersionService @@ -1447,7 +1487,7 @@ export const createComponentsSlice = (set, get) => ({ .finally(() => { set( (state) => { - state.app.isSaving = false; + state.appStore.modules[moduleId].app.isSaving = false; }, false, 'setAppSavingChanges' @@ -1473,7 +1513,8 @@ export const createComponentsSlice = (set, get) => ({ }, turnOffAutoComputeLayout: async (moduleId = 'canvas') => { - const { app, currentPageId, currentVersionId } = get(); + const { app, getCurrentPageId, currentVersionId } = get(); + const currentPageId = getCurrentPageId(moduleId); set( (state) => { state.modules[moduleId].pages[state.currentPageIndex].autoComputeLayout = false; @@ -1490,38 +1531,44 @@ export const createComponentsSlice = (set, get) => ({ }); }, - getCurrentPageId: () => get().currentPageId, + getCurrentPageId: (moduleId = 'canvas') => get().modules[moduleId].currentPageId, + getCurrentPageIndex: (moduleId = 'canvas') => get().modules[moduleId].currentPageIndex, - getComponentsFromAllPages: () => { + getComponentsFromAllPages: (moduleId = 'canvas') => { const { modules } = get(); return Object.fromEntries( - modules.canvas.pages.flatMap((page) => + modules[moduleId].pages.flatMap((page) => Object.entries(page.components).map(([id, { component }]) => [id, component.name]) ) ); }, - getCurrentPageComponents: () => { - const { modules, currentPageId } = get(); - const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId); - return modules.canvas.pages[currentPageIndex]?.components || []; + getCurrentPageComponents: (moduleId = 'canvas') => { + const { modules, getCurrentPageId } = get(); + const currentPageId = getCurrentPageId(moduleId); + const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId); + return modules[moduleId].pages[currentPageIndex]?.components || []; }, - getCurrentPageComponentIds: () => { - const { pages, currentPageId, modules } = get(); - const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId); + getCurrentPageComponentIds: (moduleId = 'canvas') => { + const { pages, getCurrentPageId, modules } = get(); + const currentPageId = getCurrentPageId(moduleId); + const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId); return Object.keys(pages[currentPageIndex]?.components || {}); }, getCurrentPage: (moduleId = 'canvas') => { - const { modules, currentPageId } = get(); + const { modules, getCurrentPageId } = get(); + const currentPageId = getCurrentPageId(moduleId); const currentPage = modules[moduleId].pages.find((page) => page.id === currentPageId); return currentPage; }, // Get the component definition from the component id getComponentDefinition: (componentId, moduleId = 'canvas') => { - const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().currentPageId); + const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().getCurrentPageId(moduleId)); + // if (componentId === 'd78554b8-2af0-4add-9d7d-0032bb4c90ce') + // console.trace('here--- getComponentDefinition--- ', componentId, moduleId, currentPage?.components[componentId]); return currentPage?.components[componentId]; }, @@ -1531,24 +1578,26 @@ export const createComponentsSlice = (set, get) => ({ }, // Get the component name from the component id getComponentNameFromId: (componentId, moduleId = 'canvas') => { - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.name; }, getComponentTypeFromId: (componentId, moduleId = 'canvas') => { - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.component; }, getComponentNameIdMapping: (moduleId = 'canvas') => { const { modules } = get(); return modules[moduleId].componentNameIdMapping; }, - getComponentIdNameMapping: () => { + getComponentIdNameMapping: (moduleId = 'canvas') => { const { getComponentNameIdMapping } = get(); - return Object.fromEntries(Object.entries(getComponentNameIdMapping()).map(([name, id]) => [id, name])); + return Object.fromEntries(Object.entries(getComponentNameIdMapping(moduleId)).map(([name, id]) => [id, name])); }, - getSelectedComponentsDefinition: () => { + getSelectedComponentsDefinition: (moduleId = 'canvas') => { const { selectedComponents, getCurrentPageComponents } = get(); - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const _selected = []; for (let componentId of selectedComponents) { const component = { @@ -1578,7 +1627,7 @@ export const createComponentsSlice = (set, get) => ({ }, getChildComponents: (parentId, moduleId = 'canvas') => { const { getCurrentPageComponents } = get(); - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const childComponents = Object.entries(allComponents) .filter(([_, component]) => component.component.parent === parentId) .reduce((acc, [id, component]) => { @@ -1607,8 +1656,8 @@ export const createComponentsSlice = (set, get) => ({ } else { const [entityType, entityId, type, ...keys] = dependency.split('.'); const key = keys.join('.'); - const unResolvedValue = getNodeData(dependency); - const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(), {}, false, []); + const unResolvedValue = getNodeData(dependency, moduleId); + const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), {}, false, []); if (type === undefined) { set( @@ -1622,7 +1671,7 @@ export const createComponentsSlice = (set, get) => ({ } else { const shouldValidate = entityType === 'components' && entityId; const validatedValue = shouldValidate - ? get().debugger.validateProperty(entityId, type, key, resolvedValue) + ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId) : resolvedValue; // logic to handle the key like options[0].visible. It will resolve the visible directly and update the resolved store @@ -1637,7 +1686,7 @@ export const createComponentsSlice = (set, get) => ({ lodashSet( state.resolvedStore.modules[moduleId][entityType][entityId], ['properties', 'shouldRender'], - (getResolvedComponent(entityId)?.['properties']?.['shouldRender'] ?? 0) + 1 + (getResolvedComponent(entityId, null, moduleId)?.['properties']?.['shouldRender'] ?? 0) + 1 ); }, false, @@ -1686,24 +1735,24 @@ export const createComponentsSlice = (set, get) => ({ } }, - getParentIdFromDependency: (dependency) => { + getParentIdFromDependency: (dependency, moduleId = 'canvas') => { const { getComponentDefinition } = get(); const componentId = dependency.split('.')[1]; - const component = getComponentDefinition(componentId); + const component = getComponentDefinition(componentId, moduleId); return component?.component?.parent; }, updateChildComponentResolvedValues: (dependency, path, length, moduleId = 'canvas') => { const { getCustomResolvables, getNodeData, getAllExposedValues, getParentIdFromDependency } = get(); const [entityType, entityId, type, key] = dependency.split('.'); - const parentId = getParentIdFromDependency(dependency); - const unResolvedValue = getNodeData(dependency); + const parentId = getParentIdFromDependency(dependency, moduleId); + const unResolvedValue = getNodeData(dependency, moduleId); // Loop through the customResolvables and update the resolved value for (let i = 0; i < length; i++) { const resolvedValue = resolveDynamicValues( unResolvedValue, - getAllExposedValues(), + getAllExposedValues(moduleId), getCustomResolvables(parentId, i, moduleId), // passing the parent ID and index to get the custom resolvables of the child false, [] @@ -1711,7 +1760,7 @@ export const createComponentsSlice = (set, get) => ({ // If the index is not in the resolved store then add it with first index data const shouldValidate = entityType === 'components' && entityId; const validatedValue = shouldValidate - ? get().debugger.validateProperty(entityId, type, key, resolvedValue) + ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId) : resolvedValue; set( @@ -1734,7 +1783,8 @@ export const createComponentsSlice = (set, get) => ({ getParentComponentType: (parentId, moduleId) => { if (!parentId) return null; - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); // Remove the tab id or any other details from the parent id (ie, -modal, -calendar, -0 from parentId) const parentUUID = parentId.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] || parentId; const component = modules[moduleId].pages[currentPageIndex].components[parentUUID]; @@ -1831,8 +1881,8 @@ export const createComponentsSlice = (set, get) => ({ return match; // Return the original match if no mapping is found }); }, - calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition) => { - const componentDefinition = get().getComponentDefinition(componentId); + calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition, moduleId = 'canvas') => { + const componentDefinition = get().getComponentDefinition(componentId, moduleId); const layoutData = componentDefinition?.layouts?.[currentLayout]; const componentType = componentDefinition?.component?.component; const label = componentDefinition?.component?.definition?.properties?.label; @@ -1856,8 +1906,8 @@ export const createComponentsSlice = (set, get) => ({ } const { alignment = { value: null }, width = { value: null }, auto = { value: null } } = stylesDefinition ?? {}; const resolvedLabel = label?.value?.length ?? 0; - const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues()) ?? 0; - const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues()) ?? false; + const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues(moduleId)) ?? 0; + const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues(moduleId)) ?? false; const resolvedAlignment = alignment.value === 'top' || alignment.value === 'side' @@ -1890,4 +1940,6 @@ export const createComponentsSlice = (set, get) => ({ state.modalsOpenOnCanvas = newModalOpenOnCanvas; }); }, + checkIfComponentIsModule: (componentId, moduleId = 'canvas') => + get().getComponentDefinition(componentId, moduleId)?.component?.component === 'ModuleViewer', }); diff --git a/frontend/src/AppBuilder/_stores/slices/createSelectors.js b/frontend/src/AppBuilder/_stores/slices/createSelectors.js deleted file mode 100644 index 50fd299f4e..0000000000 --- a/frontend/src/AppBuilder/_stores/slices/createSelectors.js +++ /dev/null @@ -1,13 +0,0 @@ -// import { useStore } from 'zustand'; - -// const createSelectors = (_store) => { -// const store = _store; -// store.use = {}; -// for (const k of Object.keys(store.getState())) { -// store.use[k] = () => useStore(_store, (s) => s[k]); -// } - -// return store; -// }; - -// export { createSelectors }; diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 7f7868416d..fee6c8f863 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -21,10 +21,20 @@ const initialState = { }; export const createDataQuerySlice = (set, get) => ({ + initializeDataQuerySlice: (moduleId = 'canvas') => { + set( + (state) => { + state.dataQuery.queries.modules[moduleId] = []; + }, + false, + 'initializeDataQuerySlice' + ); + }, dataQuery: { ...initialState, - checkExistingQueryName: (newName) => get().dataQuery.queries.modules.canvas.some((query) => query.name === newName), - getCurrentModuleQueries: (moduleId) => get().dataQuery.queries.modules[moduleId], + checkExistingQueryName: (newName, moduleId = 'canvas') => + get().dataQuery.queries.modules[moduleId].some((query) => query.name === newName), + getCurrentModuleQueries: (moduleId = 'canvas') => get().dataQuery.queries.modules[moduleId], setQueries: (queries, moduleId = 'canvas') => { set( (state) => { @@ -48,7 +58,7 @@ export const createDataQuerySlice = (set, get) => ({ }, createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas') => { const appVersionId = get().currentVersionId; - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const { options: defaultOptions, name } = getDefaultOptions(selectedDataSource); const options = { ...defaultOptions, ...customOptions }; const kind = selectedDataSource.kind; @@ -120,12 +130,16 @@ export const createDataQuerySlice = (set, get) => ({ get().addNewQueryMapping(data.id, data.name, moduleId); //! we need default value in store so that query can be resolved if referenced from other entity - get().setResolvedQuery(data.id, { - isLoading: false, - data: [], - rawData: [], - id: data.id, - }); + get().setResolvedQuery( + data.id, + { + isLoading: false, + data: [], + rawData: [], + id: data.id, + }, + moduleId + ); }) .catch((error) => { set((state) => { @@ -219,8 +233,8 @@ export const createDataQuerySlice = (set, get) => ({ }) .finally(() => setIsAppSaving(false)); - get().removeNode(`queries.${queryId}`); - get().updateDependencyValues(`queries.${queryId}`); + get().removeNode(`queries.${queryId}`, moduleId); + get().updateDependencyValues(`queries.${queryId}`, moduleId); }, duplicateQuery: (id, appId, moduleId = 'canvas') => { set((state) => { @@ -265,12 +279,16 @@ export const createDataQuerySlice = (set, get) => ({ get().addNewQueryMapping(data.id, data.name, moduleId); //! we need default value in store so that query can be resolved if referenced from other entity - get().setResolvedQuery(data.id, { - isLoading: false, - data: [], - rawData: [], - id: data.id, - }); + get().setResolvedQuery( + data.id, + { + isLoading: false, + data: [], + rawData: [], + id: data.id, + }, + moduleId + ); const events = getEventsByComponentsId(queryToClone.id); @@ -403,12 +421,23 @@ export const createDataQuerySlice = (set, get) => ({ }) .finally(() => setIsAppSaving(false)); }, 500), - runOnLoadQueries: async () => { + runOnLoadQueries: async (moduleId = 'canvas') => { const queries = get().dataQuery.queries.modules.canvas; try { for (const query of queries) { if ((query.options.runOnPageLoad || query.options.run_on_page_load) && isQueryRunnable(query)) { - await get().queryPanel.runQuery(query.id, query.name, undefined, undefined, {}, false, true, 'canvas'); + await get().queryPanel.runQuery( + query.id, + query.name, + undefined, + undefined, + {}, + undefined, + undefined, + false, + true, + moduleId + ); } } return Promise.resolve(); diff --git a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js index f4bab3d4ee..912ee596ae 100644 --- a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js @@ -32,7 +32,7 @@ export const createDebuggerSlice = (set, get) => ({ log: (log) => { set( (state) => { - log.page = get().currentPageId; + log.page = get().getCurrentPageId('canvas'); state.debugger.logs.unshift(log); if (log.logLevel === 'error') state.debugger.unreadErrorCount++; }, @@ -44,7 +44,7 @@ export const createDebuggerSlice = (set, get) => ({ logMultiple: (logs) => { set( (state) => { - state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().currentPageId }))); + state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().getCurrentPageId('canvas') }))); state.debugger.unreadErrorCount += logs.length; }, false, @@ -84,20 +84,20 @@ export const createDebuggerSlice = (set, get) => ({ return transformedStyles; }, - validateComponents: (components) => { + validateComponents: (components, moduleId = 'canvas') => { const validateComponent = get().debugger.validateComponent; const entries = Object.entries(components).map(([id, component]) => { // If component is an array, validate each component in the array and return the array if (Array.isArray(component)) { - return [id, component.map((c) => validateComponent(id, c))]; + return [id, component.map((c) => validateComponent(id, c, moduleId))]; } - return [id, validateComponent(id, component)]; + return [id, validateComponent(id, component, moduleId)]; }); return Object.fromEntries(entries); }, - validateComponent: (id, component) => { - const componentDefinition = get().getComponentDefinition(id); + validateComponent: (id, component, moduleId = 'canvas') => { + const componentDefinition = get().getComponentDefinition(id, moduleId); const componentName = componentDefinition.component.name; const componentType = componentDefinition.component.component; const componentMeta = componentTypeDefinitionMap[componentType]; @@ -135,7 +135,7 @@ export const createDebuggerSlice = (set, get) => ({ }; const logs = allErrors.map((error) => ({ - page: get().currentPageId, + page: get().getCurrentPageId('canvas'), type: 'component', kind: 'component', key: `${componentName} - ${error.property}`, @@ -158,10 +158,10 @@ export const createDebuggerSlice = (set, get) => ({ return newComponent; }, - validateProperty: (componentId, type, property, value) => { + validateProperty: (componentId, type, property, value, moduleId = 'canvas') => { const log = get().debugger.log; - const componentDefinition = get().getComponentDefinition(componentId); + const componentDefinition = get().getComponentDefinition(componentId, moduleId); const componentName = componentDefinition.component.name; const componentType = componentDefinition.component.component; const componentMeta = componentTypeDefinitionMap[componentType]; @@ -179,7 +179,7 @@ export const createDebuggerSlice = (set, get) => ({ if (valid === false) { log({ - page: get().currentPageId, + page: get().getCurrentPageId('canvas'), type: 'component', kind: 'component', key: `${componentName} - ${componentMeta[type][property]?.displayName}`, diff --git a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js index 8bea44819d..66fc0d1b16 100644 --- a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js @@ -12,48 +12,61 @@ const initialState = { export const createDependencySlice = (set, get) => ({ ...initialState, + initializeDependencySlice: (moduleId) => { + set( + (state) => { + state.dependencyGraph.modules[moduleId] = { + graph: new DependencyGraph(), + }; + }, + false, + 'initializeDependencySlice' + ); + }, - addDependency: (fromPath, toPath, nodeData) => { - if (!get().checkIfDependencyExists(fromPath, toPath)) { + addDependency: (fromPath, toPath, nodeData, moduleId = 'canvas') => { + if (!get().checkIfDependencyExists(fromPath, toPath, moduleId)) { set((state) => { - state.dependencyGraph.modules.canvas.graph.addDependency(fromPath, toPath, nodeData); + state.dependencyGraph.modules[moduleId].graph.addDependency(fromPath, toPath, nodeData); return { ...state }; }); } }, - updateDependency: (newFromPath, toPath, nodeData) => + updateDependency: (newFromPath, toPath, nodeData, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.updateDependency(newFromPath, toPath, nodeData); + state.dependencyGraph.modules[moduleId].graph.updateDependency(newFromPath, toPath, nodeData); return { ...state }; }), - removeDependency: (toPath, clearToPath = false) => + removeDependency: (toPath, clearToPath = false, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.removeDependency(toPath, clearToPath); + state.dependencyGraph.modules[moduleId].graph.removeDependency(toPath, clearToPath); return { ...state }; }), - removeNode: (path) => + removeNode: (path, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.removeNode(path); + state.dependencyGraph.modules[moduleId].graph.removeNode(path); return { ...state }; }), - getNodeData: (path) => get().dependencyGraph.modules.canvas.graph.getNodeData(path), + getNodeData: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getNodeData(path), - getDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDependencies(path), + getDependencies: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependencies(path), - getDirectDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependencies(path), + getDirectDependencies: (path, moduleId = 'canvas') => + get().dependencyGraph.modules[moduleId].graph.getDirectDependencies(path), - getDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDependents(path), + getDependents: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependents(path), - getDirectDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependents(path), + getDirectDependents: (path, moduleId = 'canvas') => + get().dependencyGraph.modules[moduleId].graph.getDirectDependents(path), - getOverallOrder: () => get().dependencyGraph.modules.canvas.graph.getOverallOrder(), + getOverallOrder: (moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getOverallOrder(), - checkIfDependencyExists: (fromPath, toPath) => { - const dependencies = get().getDependencies(fromPath); + checkIfDependencyExists: (fromPath, toPath, moduleId = 'canvas') => { + const dependencies = get().getDependencies(fromPath, moduleId); return dependencies.includes(toPath); }, }); diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index 60ac1b6fe7..8c577f1022 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -58,8 +58,8 @@ export const useEventActions = (moduleId = 'canvas') => { ); const memoizedUpdateEventsField = useCallback( - (field, value) => updateEventsField(field, value, moduleId), - [updateEventsField, moduleId] + (field, value, moduleId) => updateEventsField(field, value, moduleId), + [updateEventsField] ); return { @@ -71,6 +71,17 @@ export const useEventActions = (moduleId = 'canvas') => { }; export const createEventsSlice = (set, get) => ({ + initializeEventsSlice: (moduleId) => { + set( + (state) => { + state.eventsSlice.module[moduleId] = { + ...initialState.module.canvas, + }; + }, + false, + 'initializeEventsSlice' + ); + }, eventsSlice: { ...initialState, setEvents: (events, moduleId = 'canvas') => { @@ -97,25 +108,17 @@ export const createEventsSlice = (set, get) => ({ ); }, fireEvent: (eventName, id, moduleId, customResolvables, options) => { - const { eventsSlice } = get(); - const { - handleEvent, - isEditorLoading, - module: { - [moduleId]: { events }, - }, - } = eventsSlice; + const { eventsSlice, getCurrentMode, getEditorLoading } = get(); + const { handleEvent } = eventsSlice; + const events = get().eventsSlice.module[moduleId].events; const componentEvents = events.filter((event) => event.sourceId === id); - const mode = get().currentMode; - if (isEditorLoading) return; - // if (mode === 'edit' && eventName === 'onClick') { - // onComponentClick(id, component); - // } + const mode = getCurrentMode(moduleId); + if (getEditorLoading(moduleId)) return; handleEvent( eventName, componentEvents, { ...options, customVariables: { ...customResolvables } }, - 'canvas', + moduleId, mode ); }, @@ -128,7 +131,7 @@ export const createEventsSlice = (set, get) => ({ }, } = eventsSlice; const componentEvents = events.filter((event) => event.sourceId === id); - executeActionsForEventId('onClick', componentEvents, mode); + executeActionsForEventId('onClick', componentEvents, mode, moduleId); }, addEvent: (event, moduleId = 'canvas') => set((state) => { @@ -163,46 +166,46 @@ export const createEventsSlice = (set, get) => ({ createAppVersionEventHandlers: async (event, moduleId) => { // get().actions.setIsSaving(true); // set({ eventsCreatedLoader: true }); - get().eventsSlice.updateEventsField('eventsCreatedLoader', true); - const appId = get().app.appId; + get().eventsSlice.updateEventsField('eventsCreatedLoader', true, moduleId); + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; appVersionService .createAppVersionEventHandler(appId, versionId, event) .then((response) => { - get().eventsSlice.updateEventsField('eventsCreatedLoader', false); - get().eventsSlice.addEvent(response); + get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId); + get().eventsSlice.addEvent(response, moduleId); }) .catch((err) => { - get().eventsSlice.updateEventsField('eventsCreatedLoader', false); + get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId); toast.error(err?.error || 'An error occurred while creating the event handler'); }); }, deleteAppVersionEventHandler: async (eventId, index, moduleId = 'canvas') => { - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; - get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index); + get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index, moduleId); const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId); - get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null); + get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null, moduleId); if (response?.affected === 1) { - get().eventsSlice.removeEvent(eventId); + get().eventsSlice.removeEvent(eventId, moduleId); } }, updateAppVersionEventHandlers: async (events, updateType = 'update', param, moduleId = 'canvas') => { if (param === 'actionId') { - get().eventsSlice.updateEventsField('actionsUpdatedLoader', true); + get().eventsSlice.updateEventsField('actionsUpdatedLoader', true, moduleId); } if (param === 'eventId') { - get().eventsSlice.updateEventsField('eventsUpdatedLoader', true); + get().eventsSlice.updateEventsField('eventsUpdatedLoader', true, moduleId); } const componentNameIdMapping = get().modules['canvas'].componentNameIdMapping; const queryNameIdMapping = get().modules['canvas'].queryNameIdMapping; //! Revisit this - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; const newEvents = replaceEntityReferencesWithIds(events, componentNameIdMapping, queryNameIdMapping); const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, newEvents, updateType); - get().eventsSlice.updateEventsField('actionsUpdatedLoader', false); - get().eventsSlice.updateEventsField('eventsUpdatedLoader', false); + get().eventsSlice.updateEventsField('actionsUpdatedLoader', false, moduleId); + get().eventsSlice.updateEventsField('eventsUpdatedLoader', false, moduleId); set((state) => { const eventsInState = state.eventsSlice.getModuleEvents('canvas'); const newEvents = eventsInState.map((event) => { @@ -265,19 +268,19 @@ export const createEventsSlice = (set, get) => ({ return foundEvent && foundEvent.name === eventName; }); try { - return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode); + return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode, moduleId); } catch (error) { console.error(error); } }, - onEvent: async (eventName, events, options = {}, mode = 'edit') => { + onEvent: async (eventName, events, options = {}, mode = 'edit', moduleId = 'canvas') => { const executeActionsForEventId = get().eventsSlice.executeActionsForEventId; const customVariables = options?.customVariables ?? {}; const { setExposedValue } = get(); if (eventName === 'onPageLoad') { // for onPageLoad events, we need to execute the actions after the page is loaded - executeActionsForEventId('onPageLoad', events, mode, customVariables); + executeActionsForEventId('onPageLoad', events, mode, customVariables, moduleId); } if (eventName === 'onTrigger') { const { queryPanel, dataQuery } = get(); @@ -286,7 +289,7 @@ export const createEventsSlice = (set, get) => ({ const { queryName, parameters } = options; const queryId = queries.filter((query) => query.name === queryName && isQueryRunnable(query))?.[0]?.id; if (!queryId) return; - runQuery(queryId, queryName, true, mode, parameters); + runQuery(queryId, queryName, true, mode, parameters, undefined, undefined, false, false, moduleId); } if (eventName === 'onTableActionButtonClicked') { const { action, tableActionEvents } = options; @@ -295,7 +298,7 @@ export const createEventsSlice = (set, get) => ({ if (action && executeableActions) { for (const event of executeableActions) { if (event?.event?.actionId) { - await get().eventsSlice.executeAction(event.event, mode, customVariables); + await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId); } } } else { @@ -309,7 +312,7 @@ export const createEventsSlice = (set, get) => ({ if (column && tableColumnEvents) { for (const event of tableColumnEvents) { if (event?.event?.actionId) { - await get().eventsSlice.executeAction(event.event, mode, customVariables); + await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId); } } } else { @@ -320,13 +323,13 @@ export const createEventsSlice = (set, get) => ({ if (eventName === 'onCalendarEventSelect') { const { id, calendarEvent } = options; setExposedValue(id, 'selectedEvent', calendarEvent); - executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables); + executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables, moduleId); } if (eventName === 'onCalendarSlotSelect') { const { id, selectedSlots } = options; setExposedValue(id, 'selectedSlots', selectedSlots); - executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables); + executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables, moduleId); } if ( @@ -384,31 +387,31 @@ export const createEventsSlice = (set, get) => ({ 'onTableDataDownload', ].includes(eventName) ) { - executeActionsForEventId(eventName, events, mode, customVariables); + executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } if (eventName === 'onBulkUpdate') { - await executeActionsForEventId(eventName, events, mode, customVariables); + await executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } if (['onDataQuerySuccess', 'onDataQueryFailure'].includes(eventName)) { if (!events || !Array.isArray(events) || events.length === 0) return; - await executeActionsForEventId(eventName, events, mode, customVariables); + await executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } }, - executeActionsForEventId: async (eventId, events = [], mode, customVariables) => { + executeActionsForEventId: async (eventId, events = [], mode, customVariables, moduleId = 'canvas') => { if (!events || !Array.isArray(events) || events.length === 0) return; const filteredEvents = events ?.filter((event) => event?.event.eventId === eventId) ?.sort((a, b) => a.index - b.index); for (const event of filteredEvents) { - await get().eventsSlice.executeAction(event, mode, customVariables); + await get().eventsSlice.executeAction(event, mode, customVariables, moduleId); } }, logError(errorType, errorKind, error, eventObj = '', options = {}, logLevel = 'error') { const { event = eventObj } = eventObj; const pages = get().modules.canvas.pages; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const currentPage = pages.find((page) => page.id === currentPageId); const componentIdMapping = get().modules['canvas'].componentNameIdMapping; const componentName = Object.keys(componentIdMapping).find( @@ -477,7 +480,7 @@ export const createEventsSlice = (set, get) => ({ timestamp: moment().toISOString(), }); }, - executeAction: debounce(async (eventObj, mode, customVariables = {}) => { + executeAction: debounce(async (eventObj, mode, customVariables = {}, moduleId = 'canvas') => { const { event = eventObj } = eventObj; const { getExposedValueOfComponent, getResolvedValue } = get(); @@ -567,7 +570,7 @@ export const createEventsSlice = (set, get) => ({ eventId, false, false, - 'canvas' + moduleId ); } catch (error) { get().eventsSlice.logError('run_query', 'run-query', error, eventObj, { @@ -819,13 +822,8 @@ export const createEventsSlice = (set, get) => ({ return { ...param, value: value, - // value: resolveCode(re.valueWithBrackets, getAllExposedValues()), }; }); - // const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({ - // ...param, - // value: resolveReferences(param.value, getAllExposedValues(), customVariables), - // })); const actionPromise = action && action(...actionArguments.map((argument) => argument.value)); return actionPromise ?? Promise.resolve(); @@ -844,7 +842,7 @@ export const createEventsSlice = (set, get) => ({ throw new Error('No page ID provided'); } const { switchPage } = get(); - const page = get().modules.canvas.pages.find((page) => page.id === event.pageId); + const page = get().modules[moduleId].pages.find((page) => page.id === event.pageId); const queryParams = event.queryParams || []; if (!page.disabled) { const resolvedQueryParams = []; @@ -864,7 +862,7 @@ export const createEventsSlice = (set, get) => ({ } } }); - switchPage(page.id, page.handle, resolvedQueryParams); + switchPage(page.id, page.handle, resolvedQueryParams, moduleId); } else { toast.error('Page is disabled'); //!TODO push to debugger @@ -888,14 +886,14 @@ export const createEventsSlice = (set, get) => ({ } }), - generateAppActions: (queryId, mode, isPreview = false) => { + generateAppActions: (queryId, mode, isPreview = false, moduleId = 'canvas') => { const { getCurrentPageComponents, dataQuery, eventsSlice, queryPanel, modules } = get(); const { previewQuery } = queryPanel; const { executeAction } = eventsSlice; - const currentComponents = Object.entries(getCurrentPageComponents()); + const currentComponents = Object.entries(getCurrentPageComponents(moduleId)); - const runQuery = (queryName = '', parameters) => { - const query = dataQuery.queries.modules['canvas'].find((query) => { + const runQuery = (queryName = '', parameters, moduleId = 'canvas') => { + const query = dataQuery.queries.modules[moduleId].find((query) => { const isFound = query.name === queryName; if (isPreview) { return isFound; @@ -928,7 +926,7 @@ export const createEventsSlice = (set, get) => ({ parameters: processedParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setVariable = (key = '', value = '') => { @@ -938,7 +936,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -948,7 +946,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -958,7 +956,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'unset-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -968,14 +966,14 @@ export const createEventsSlice = (set, get) => ({ alertType, message, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logout = () => { const event = { actionId: 'logout', }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const showModal = (modalName = '') => { @@ -990,7 +988,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'show-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const closeModal = (modalName = '') => { @@ -1005,7 +1003,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'close-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setLocalStorage = (key = '', value = '') => { @@ -1014,7 +1012,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const copyToClipboard = (contentToCopy = '') => { @@ -1022,7 +1020,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'copy-to-clipboard', contentToCopy, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const goToApp = (slug = '', queryParams = []) => { @@ -1031,7 +1029,7 @@ export const createEventsSlice = (set, get) => ({ slug, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const generateFile = (fileName, fileType, data) => { @@ -1045,7 +1043,7 @@ export const createEventsSlice = (set, get) => ({ data, fileType, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setPageVariable = (key = '', value = '') => { @@ -1054,7 +1052,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const getPageVariable = (key = '') => { @@ -1062,7 +1060,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-page-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const unsetPageVariable = (key = '') => { @@ -1070,10 +1068,10 @@ export const createEventsSlice = (set, get) => ({ actionId: 'unset-page-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; - const switchPage = (pageHandle, queryParams = []) => { + const switchPage = (pageHandle, queryParams = [], moduleId = 'canvas') => { if (isPreview) { mode != 'view' && toast('Page will not be switched for query preview', { @@ -1081,7 +1079,7 @@ export const createEventsSlice = (set, get) => ({ }); return Promise.resolve(); } - const pages = modules.canvas.pages; + const pages = modules[moduleId].pages; const transformedPageHandle = pageHandle?.toLowerCase(); const pageId = pages.find((page) => page.handle === transformedPageHandle)?.id; @@ -1098,7 +1096,7 @@ export const createEventsSlice = (set, get) => ({ pageId, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logInfo = (log) => { @@ -1107,7 +1105,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log-info', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logError = (log) => { @@ -1116,7 +1114,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log-error', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const log = (log) => { @@ -1125,7 +1123,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; return { diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 367ca4cf0c..4bff1a5988 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -37,9 +37,9 @@ export const createLeftSideBarSlice = (set, get) => ({ toggleLeftSidebar(true); } }, - getComponentIdToAutoScroll: (componentId) => { + getComponentIdToAutoScroll: (componentId, moduleId = 'canvas') => { const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get(); - const currentPageComponents = getCurrentPageComponents(); + const currentPageComponents = getCurrentPageComponents(moduleId); let targetComponentId = componentId; let current = componentId; @@ -66,7 +66,7 @@ export const createLeftSideBarSlice = (set, get) => ({ const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id - const { currentTab } = getAllExposedValues().components?.[tabId] || {}; + const { currentTab } = getAllExposedValues(moduleId).components?.[tabId] || {}; const activeTabIndex = Number(currentTab); nextPossibleCandidate = tabId; diff --git a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js index 6b1da506e1..6a0ef71271 100644 --- a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js @@ -1,10 +1,43 @@ const initialState = { - isEditorLoading: true, - isCanvasLoading: false, + loaderStore: { + modules: { + canvas: { + isEditorLoading: true, + }, + }, + }, }; -export const createLoaderSlice = (set) => ({ +export const createLoaderSlice = (set, get) => ({ ...initialState, - setEditorLoading: (status) => set(() => ({ isEditorLoading: status }), false, 'setEditorLoading'), - setCanvasLoading: (status) => set(() => ({ isCanvasLoading: status }), false, 'setCanvasLoading'), + initializeLoaderSlice: (moduleId) => { + set( + (state) => { + state.loaderStore.modules[moduleId] = { + ...initialState.loaderStore.modules.canvas, + }; + }, + false, + 'initializeLoaderSlice' + ); + }, + setEditorLoading: (status, moduleId = 'canvas') => + set( + (state) => { + state.loaderStore.modules[moduleId].isEditorLoading = status; + }, + false, + 'setEditorLoading' + ), + setIsLoaderLoading: (status, moduleId = 'canvas') => + set( + (state) => { + state.loaderStore.modules[moduleId] = { + isLoaderLoading: status, + }; + }, + false, + 'setIsLoaderLoading' + ), + getEditorLoading: (moduleId) => get().loaderStore.modules[moduleId].isEditorLoading, }); diff --git a/frontend/src/AppBuilder/_stores/slices/modeSlice.js b/frontend/src/AppBuilder/_stores/slices/modeSlice.js index a24d5c93d6..ce2672a91a 100644 --- a/frontend/src/AppBuilder/_stores/slices/modeSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/modeSlice.js @@ -1,8 +1,33 @@ const initialState = { - currentMode: 'view', + modeStore: { + modules: { + canvas: { + currentMode: 'view', + }, + }, + }, }; -export const createModeSlice = (set) => ({ +export const createModeSlice = (set, get) => ({ ...initialState, - setCurrentMode: (currentMode) => set(() => ({ currentMode }), false, 'setCurrentMode'), + initializeModeSlice: (moduleId) => { + set( + (state) => { + state.modeStore.modules[moduleId] = { + ...initialState.modeStore.modules.canvas, + }; + }, + false, + 'initializeModeSlice' + ); + }, + setCurrentMode: (currentMode, moduleId = 'canvas') => + set( + (state) => { + state.modeStore.modules[moduleId].currentMode = currentMode; + }, + false, + 'setCurrentMode' + ), + getCurrentMode: (moduleId) => get().modeStore.modules[moduleId].currentMode, }); diff --git a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js index f025563f66..8043a0bd18 100644 --- a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js @@ -21,14 +21,14 @@ export const createMultiplayerSlice = (set, get) => ({ diff, type, operation, - pageId: get().currentPageId, + pageId: get().getCurrentPageId('canvas'), versionId: get().selectedVersion?.id, }); } }, processUpdate: ({ diff, type, operation, pageId, versionId }) => { - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const currentVersionId = get().selectedVersion?.id; if (currentPageId === pageId && currentVersionId === versionId) diff --git a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js index 9403142033..b09287d443 100644 --- a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js @@ -192,10 +192,8 @@ export const createPageMenuSlice = (set, get) => { updatePageGroupName: (pageId, value) => updatePageGroupName(pageId, [value])(set, get), // unsure about this one clonePage: async (pageId) => { - const { - app: { appId }, - currentVersionId, - } = get(); + const { getAppId, currentVersionId } = get(); + const appId = getAppId('canvas'); const pages = get().modules.canvas.pages; const data = await appVersionService.clonePage(appId, currentVersionId, pageId); const newPages = data?.pages; @@ -218,7 +216,7 @@ export const createPageMenuSlice = (set, get) => { pageId: pageId, }; const pages = get().modules.canvas.pages; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const switchPage = get().switchPage; if (pages.length === 1) { toast.error('You cannot delete the only page in your app.'); @@ -243,11 +241,11 @@ export const createPageMenuSlice = (set, get) => { * If home page is in the group, the group cannot be deleted * If current page is in the group, the page will be switched to home page */ - deletePageGroup: async (pageGroupId, deleteAssociatedPages = false) => { + deletePageGroup: async (pageGroupId, deleteAssociatedPages = false, moduleId = 'canvas') => { const { app, currentVersionId } = get(); const pages = get().modules.canvas.pages; - const homePageId = get().app.homePageId; + const homePageId = get().appStore.modules[moduleId].app.homePageId; const diff = { pageId: pageGroupId, deleteAssociatedPages, @@ -260,7 +258,7 @@ export const createPageMenuSlice = (set, get) => { if (pages[i].id === homePageId && pages[i].pageGroupId === pageGroupId) { isHomePageInGroup = true; } - if (pages[i].id === get().currentPageId && pages[i].pageGroupId === pageGroupId) { + if (pages[i].id === get().getCurrentPageId('canvas') && pages[i].pageGroupId === pageGroupId) { isCurrentPageInGroup = true; } } @@ -302,14 +300,14 @@ export const createPageMenuSlice = (set, get) => { await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete'); } }, - markAsHomePage: async (pageId) => { + markAsHomePage: async (pageId, moduleId = 'canvas') => { const { app, currentVersionId, editingPage } = get(); const diff = { homePageId: pageId, }; set((state) => { - state.app.homePageId = pageId; + state.appStore.modules[moduleId].app.homePageId = pageId; state.showEditingPopover = false; state.editingPage = null; }); @@ -317,7 +315,7 @@ export const createPageMenuSlice = (set, get) => { }, reorderPages: async (reorderdPages) => { const diff = {}; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); // update index of everything to avoid inconsistencies reorderdPages.forEach((page, index) => { diff[page.id] = { diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index fd49d2a5e4..733d2854f7 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -66,13 +66,13 @@ export const createQueryPanelSlice = (set, get) => ({ 'setQueryPanelHeight' ); }, // updateQueryPanelHeight - setSelectedQuery: (queryId) => { + setSelectedQuery: (queryId, moduleId = 'canvas') => { set((state) => { if (queryId === null) { state.queryPanel.selectedQuery = null; return; } - const query = get().dataQuery.queries.modules.canvas.find((query) => query.id === queryId); + const query = get().dataQuery.queries.modules[moduleId].find((query) => query.id === queryId); state.queryPanel.selectedQuery = query; return; }); @@ -162,7 +162,7 @@ export const createQueryPanelSlice = (set, get) => ({ 'setLoadingDataQueries' ), - onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit') => { + onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit', moduleId = 'canvas') => { const { queryPanel, dataQuery, setResolvedQuery } = get(); const { runQuery } = queryPanel; const { queryConfirmationList } = dataQuery; @@ -185,13 +185,21 @@ export const createQueryPanelSlice = (set, get) => ({ true, mode, queryConfirmationData.parameters, - queryConfirmationData.shouldSetPreviewData + undefined, + undefined, + queryConfirmationData.shouldSetPreviewData, + false, + moduleId ); !isConfirm && - setResolvedQuery(queryConfirmationData.queryId, { - isLoading: false, - }); + setResolvedQuery( + queryConfirmationData.queryId, + { + isLoading: false, + }, + moduleId + ); }, runQuery: ( @@ -212,7 +220,7 @@ export const createQueryPanelSlice = (set, get) => ({ dataQuery: dataQuerySlice, queryPanel, setResolvedQuery, - app, + appStore, selectedEnvironment, isPublicAccess, currentVersionId, @@ -244,14 +252,18 @@ export const createQueryPanelSlice = (set, get) => ({ let dataQuery = {}; //for viewer we will only get the environment id from the url - const { currentAppEnvironmentId, environmentId } = app; + const { currentAppEnvironmentId, environmentId } = appStore.modules[moduleId].app; if (shouldSetPreviewData) { setPreviewPanelExpanded(true); setPreviewLoading(true); - setResolvedQuery(queryId, { - isLoading: true, - }); + setResolvedQuery( + queryId, + { + isLoading: true, + }, + moduleId + ); queryPreviewData && setPreviewData(''); } @@ -274,8 +286,8 @@ export const createQueryPanelSlice = (set, get) => ({ // const queryState = { ...getCurrentState(), parameters }; const queryState = { ...get().getAllExposedValues('canvas'), parameters }; const options = getQueryVariables(dataQuery.options, queryState, { - components: get().getComponentNameIdMapping(), - queries: get().getQueryNameIdMapping(), + components: get().getComponentNameIdMapping(moduleId), + queries: get().getQueryNameIdMapping(moduleId), }); if (dataQuery.options?.requestConfirmation) { const queryConfirmation = { @@ -305,18 +317,22 @@ export const createQueryPanelSlice = (set, get) => ({ queryPreviewData && setPreviewData(''); } - setResolvedQuery(queryId, { - isLoading: true, - data: [], - rawData: [], - id: queryId, - }); + setResolvedQuery( + queryId, + { + isLoading: true, + data: [], + rawData: [], + id: queryId, + }, + moduleId + ); let queryExecutionPromise = null; if (query.kind === 'runjs') { - queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters); + queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters, moduleId); } else if (query.kind === 'runpy') { - queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState); + queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState, moduleId); } else if (query.kind === 'workflows') { queryExecutionPromise = executeWorkflow( moduleId, @@ -400,16 +416,20 @@ export const createQueryPanelSlice = (set, get) => ({ isQuerySuccessLog: false, }); - setResolvedQuery(queryId, { - isLoading: false, - ...(query.kind === 'restapi' - ? { - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } - : {}), - }); + setResolvedQuery( + queryId, + { + isLoading: false, + ...(query.kind === 'restapi' + ? { + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } + : {}), + }, + moduleId + ); resolve(data); onEvent('onDataQueryFailure', queryEvents); @@ -426,9 +446,13 @@ export const createQueryPanelSlice = (set, get) => ({ 'edit' ); if (finalData.status === 'failed') { - setResolvedQuery(queryId, { - isLoading: false, - }); + setResolvedQuery( + queryId, + { + isLoading: false, + }, + moduleId + ); resolve(finalData); onEvent('onDataQueryFailure', queryEvents); @@ -460,14 +484,18 @@ export const createQueryPanelSlice = (set, get) => ({ errorTarget: 'Queries', }); - setResolvedQuery(queryId, { - isLoading: false, - data: finalData, - rawData, - metadata: data?.metadata, - request: data?.metadata?.request, - response: data?.metadata?.response, - }); + setResolvedQuery( + queryId, + { + isLoading: false, + data: finalData, + rawData, + metadata: data?.metadata, + request: data?.metadata?.request, + response: data?.metadata?.response, + }, + moduleId + ); resolve({ status: 'ok', data: finalData }); onEvent('onDataQuerySuccess', queryEvents, mode); @@ -527,7 +555,7 @@ export const createQueryPanelSlice = (set, get) => ({ } // const queryState = { ...getCurrentState(), parameters }; - const queryState = { ...get().getAllExposedValues(), parameters }; + const queryState = { ...get().getAllExposedValues(moduleId), parameters }; const options = getQueryVariables(query.options, queryState, { components: get().getComponentNameIdMapping(), queries: get().getQueryNameIdMapping(), @@ -683,14 +711,14 @@ export const createQueryPanelSlice = (set, get) => ({ const queriesInCurentState = deepClone(resolvedState.queries); const appStateVars = deepClone(resolvedState.variables) ?? {}; if (!isEmpty(query)) { - const actions = generateAppActions(query.id, mode, isPreview); + const actions = generateAppActions(query.id, mode, isPreview, moduleId); for (const key of Object.keys(queriesInCurentState)) { queriesInCurentState[key] = { ...queriesInCurentState[key], run: () => { const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key); - return actions.runQuery(query.name); + return actions.runQuery(query.name, undefined, moduleId); }, getData: () => { @@ -842,12 +870,10 @@ export const createQueryPanelSlice = (set, get) => ({ // queries: updatedQueries, // }); }, - executeWorkflow: async (moduleId, workflowId, _blocking = false, params = {}, appEnvId) => { - const { - app: { appId }, - getAllExposedValues, - } = get(); - const currentState = getAllExposedValues(); + executeWorkflow: async (moduleId = 'canvas', workflowId, _blocking = false, params = {}, appEnvId) => { + const { getAppId, getAllExposedValues } = get(); + const appId = getAppId('canvas'); + const currentState = getAllExposedValues(moduleId); const resolvedParams = get().resolveReferences(moduleId, params, currentState, {}, {}); try { @@ -885,7 +911,7 @@ export const createQueryPanelSlice = (set, get) => ({ return isValidCode; } - const currentState = getAllExposedValues(); + // const currentState = getAllExposedValues(); let result = {}, error = null; @@ -895,7 +921,7 @@ export const createQueryPanelSlice = (set, get) => ({ parameters = {}; } - const actions = generateAppActions(queryId, mode, isPreview); + const actions = generateAppActions(queryId, mode, isPreview, moduleId); const queryDetails = dataQuery.queries.modules?.[moduleId].find((q) => q.id === queryId); @@ -930,7 +956,7 @@ export const createQueryPanelSlice = (set, get) => ({ const processedParams = {}; const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key); query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name])); - return actions.runQuery(query.name, processedParams); + return actions.runQuery(query.name, processedParams, moduleId); }, getData: () => { diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 94c7433e90..e60acddd7e 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -45,7 +45,6 @@ export const createResolvedSlice = (set, get) => ({ set( (state) => { state.resolvedStore.modules[moduleId] = { - ...state.resolvedStore.modules[moduleId], ...initialState.resolvedStore.modules.canvas, }; }, @@ -74,7 +73,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedGlobals' ); Object.entries(values).forEach(() => { - get().updateDependencyValues(`globals.${objKey}`); + get().updateDependencyValues(`globals.${objKey}`, moduleId); }); }, setResolvedConstants: (constants = {}, moduleId = 'canvas') => { @@ -88,7 +87,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`constants.${key}`); + get().updateDependencyValues(`constants.${key}`, moduleId); }); }, @@ -111,7 +110,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedPageConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`page.${key}`); + get().updateDependencyValues(`page.${key}`, moduleId); }); }, @@ -124,7 +123,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setVariables' ); - get().updateDependencyValues(`variables.${key}`); + get().updateDependencyValues(`variables.${key}`, moduleId); }, getVariable: (key, moduleId = 'canvas') => { @@ -139,8 +138,8 @@ export const createResolvedSlice = (set, get) => ({ false, 'unsetVariable' ); - get().removeNode(`variables.${key}`); - get().updateDependencyValues(`variables.${key}`); + get().removeNode(`variables.${key}`, moduleId); + get().updateDependencyValues(`variables.${key}`, moduleId); }, // page.variables @@ -152,7 +151,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setPageVariable' ); - get().updateDependencyValues(`page.variables.${key}`); + get().updateDependencyValues(`page.variables.${key}`, moduleId); }, getPageVariable: (key, moduleId = 'canvas') => { @@ -166,8 +165,8 @@ export const createResolvedSlice = (set, get) => ({ false, 'unsetPageVariable' ); - get().removeNode(`page.variables.${key}`); - get().updateDependencyValues(`page.variables.${key}`); + get().removeNode(`page.variables.${key}`, moduleId); + get().updateDependencyValues(`page.variables.${key}`, moduleId); }, setResolvedQuery: (queryId, details, moduleId = 'canvas') => { @@ -184,13 +183,13 @@ export const createResolvedSlice = (set, get) => ({ Object.entries(details).forEach(([key, value]) => { if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders', 'metadata'].includes(key)) { - if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`); + if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`, moduleId); } }); // Flag to update the codehinter suggestions get().checkAndSetTrueBuildSuggestionsFlag(); }, - initialiseResolvedQuery(querIds, moduleId = 'canvas') { + initialiseResolvedQuery: (querIds, moduleId = 'canvas') => { const defaultObject = {}; querIds.forEach((queryId) => { defaultObject[queryId] = { @@ -219,7 +218,7 @@ export const createResolvedSlice = (set, get) => ({ setResolvedComponents: (components, moduleId = 'canvas') => { const validateComponents = get().debugger.validateComponents; - const validatedComponents = validateComponents(components); + const validatedComponents = validateComponents(components, moduleId); set( (state) => { @@ -248,7 +247,7 @@ export const createResolvedSlice = (set, get) => ({ } */ setResolvedComponentByProperty: (componentId, type, property, value, index = null, moduleId = 'canvas') => { - value = get().debugger.validateProperty(componentId, type, property, value); + value = get().debugger.validateProperty(componentId, type, property, value, moduleId); set( (state) => { @@ -309,7 +308,7 @@ export const createResolvedSlice = (set, get) => ({ payload: { componentId, property, value, moduleId }, } ); - get().updateDependencyValues(`components.${componentId}.${property}`); + get().updateDependencyValues(`components.${componentId}.${property}`, moduleId); }, setExposedValues: (id, type, values, moduleId = 'canvas') => { @@ -330,7 +329,7 @@ export const createResolvedSlice = (set, get) => ({ } ); Object.entries(values).forEach(([key, value]) => { - if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`); + if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`, moduleId); }); }, @@ -339,7 +338,7 @@ export const createResolvedSlice = (set, get) => ({ if (val && Object.keys(val).length > 0) return; const component = componentTypeDefinitionMap[componentType]; if (!component) return; - const parentComponentType = get().getComponentDefinition(parentId)?.component?.component; + const parentComponentType = get().getComponentDefinition(parentId, moduleId)?.component?.component; if (['Form', 'Listview'].includes(parentComponentType)) return; const exposedVariables = component.exposedVariables || {}; get().setExposedValues(id, 'components', exposedVariables, moduleId); @@ -395,7 +394,7 @@ export const createResolvedSlice = (set, get) => ({ }, getExposedValueOfComponent: (componentId, moduleId = 'canvas') => { try { - const components = get().getCurrentPageComponents(); + const components = get().getCurrentPageComponents(moduleId); const { component: { parent: parentId, name: componentName }, } = components[componentId]; @@ -548,7 +547,7 @@ export const createResolvedSlice = (set, get) => ({ const objectType = typeof object; let error; - const state = _state ?? get().getAllExposedValues(); + const state = _state ?? get().getAllExposedValues(moduleId); if (_state?.parameters) { state.parameters = { ..._state.parameters }; @@ -599,11 +598,29 @@ export const createResolvedSlice = (set, get) => ({ false, 'setModuleInputs' ); - get().updateDependencyValues(`input.${key}`); + get().updateDependencyValues(`input.${key}`, moduleId); + }, + setModuleOutputs: (key, value, moduleId = 'canvas') => { + set( + (state) => { + if (!state.resolvedStore.modules[moduleId].exposedValues.output) { + state.resolvedStore.modules[moduleId].exposedValues.output = {}; + } + state.resolvedStore.modules[moduleId].exposedValues.output[key] = value; + }, + false, + 'setModuleOutputs' + ); + get().updateDependencyValues(`output.${key}`, moduleId); }, clearModuleInputs: (moduleId = 'canvas') => { set((state) => { state.resolvedStore.modules[moduleId].exposedValues.input = {}; }); }, + clearModuleOutputs: (moduleId = 'canvas') => { + set((state) => { + state.resolvedStore.modules[moduleId].exposedValues.output = {}; + }); + }, }); diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index 2808a04545..e276360843 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -14,7 +14,7 @@ const AppLoader = (props) => { switch (appType) { case 'front-end': - return ; + return ; case 'workflow': return ; case 'module': diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index ea548eea8e..133cdd366d 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -807,7 +807,7 @@ button { } } - .editor .viewer .main { + .viewer .main { height: auto !important; .canvas-container { @@ -2724,6 +2724,14 @@ hr { max-height: 10px; z-index: 100; min-width: 108px; + + &.module-container { + .handle-content { + cursor: move; + color: #fff; + background: #c6cad0 !important; + } + } } @@ -7571,6 +7579,13 @@ tbody { .apploader { height: 100vh; + &.module-mode { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + .app-container { height: 100%; display: flex; diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx index b75a1a8e32..2eada0e175 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx @@ -4,12 +4,14 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { PromoteConfirmationModal } from './components'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { + const moduleId = useModuleId(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ - isSaving: state.app.isSaving, + isSaving: state.appStore.modules[moduleId].app.isSaving, editingVersion: state.currentVersionId, selectedEnvironment: state.selectedEnvironment, environments: state.environments, diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx index 40abc2cf2d..f876dc86fe 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx @@ -9,8 +9,10 @@ import ArrowRightIcon from '@assets/images/icons/arrow-right.svg'; import '@/_styles/versions.scss'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteConfirmationModal = React.memo(({ data, onClose }) => { + const moduleId = useModuleId(); const [promotingEnvironment, setPromotingEnvironment] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true' || false; const currentVersionId = useStore((state) => state.currentVersionId); @@ -22,7 +24,7 @@ const PromoteConfirmationModal = React.memo(({ data, onClose }) => { (state) => ({ promoteAppVersionAction: state.promoteAppVersionAction, selectedVersion: state.selectedVersion, - creationMode: state.app.creationMode, + creationMode: state.appStore.modules[moduleId].app.creationMode, }), shallow ); diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx index 494eaa52e9..3052f05d17 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx @@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const moduleId = useModuleId(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( @@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() { editingVersion: state.editingVersion, isEditorFreezed: state.isEditorFreezed, updateReleasedVersionId: state.updateReleasedVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, versionToBeReleased: state.currentVersionId, // selectedVersionId: state.selectedVersion.id, }), diff --git a/server/ee b/server/ee index 683647f83d..9bc998392e 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 +Subproject commit 9bc998392ea9af9934a5099881c28a3ff7edeab1 diff --git a/server/src/modules/apps/constants/index.ts b/server/src/modules/apps/constants/index.ts index 234950ca4d..81b7b8cae4 100644 --- a/server/src/modules/apps/constants/index.ts +++ b/server/src/modules/apps/constants/index.ts @@ -15,6 +15,7 @@ export enum FEATURE_KEY { export enum APP_TYPES { FRONT_END = 'front-end', WORKFLOW = 'workflow', + MODULE = 'module', } export enum LayoutDimensionUnits { diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 60c6f8bc15..f17704776c 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -18,7 +18,7 @@ import { VersionReleaseDto, } from './dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { APP_TYPES, FEATURE_KEY } from './constants'; +import { FEATURE_KEY } from './constants'; import { camelizeKeys, decamelizeKeys } from 'humps'; import { App } from '@entities/app.entity'; import { AppsUtilService } from './util.service'; @@ -37,6 +37,7 @@ import { DataSource } from '@entities/data_source.entity'; import { AppVersion } from '@entities/app_version.entity'; import { PageService } from './services/page.service'; import { EventsService } from './services/event.service'; +import { ComponentsService } from './services/component.service'; import { LICENSE_FIELD } from '@modules/licensing/constants'; import { AppEnvironment } from '@entities/app_environments.entity'; import { OrganizationThemesUtilService } from '@modules/organization-themes/util.service'; @@ -57,7 +58,8 @@ export class AppsService implements IAppsService { protected readonly pageService: PageService, protected readonly eventService: EventsService, protected readonly organizationThemeUtilService: OrganizationThemesUtilService, - protected readonly aiUtilService: AiUtilService + protected readonly aiUtilService: AiUtilService, + protected readonly componentsService: ComponentsService ) {} async create(user: User, appCreateDto: AppCreateDto) { const { name, icon, type } = appCreateDto; @@ -210,6 +212,12 @@ export class AppsService implements IAppsService { apps = await this.appsUtilService.all(user, parseInt(page || '1'), searchKey, type); } + if (type === 'module') { + for (const app of apps) { + app.moduleContainer = await this.pageService.findModuleContainer(app); + } + } + const totalCount = await this.appsUtilService.count(user, searchKey, type); const totalPageCount = folderId ? totalFolderCount : totalCount; diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts index fa0b2864e0..7b8c3dbc8a 100644 --- a/server/src/modules/apps/services/page.service.ts +++ b/server/src/modules/apps/services/page.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { Page } from '@entities/page.entity'; +import { App } from '@entities/app.entity'; import { ComponentsService } from './component.service'; import { CreatePageDto, UpdatePageDto } from '../dto/page'; import { dbTransactionWrap, dbTransactionForAppVersionAssociationsUpdate } from 'src/helpers/database.helper'; @@ -14,6 +15,7 @@ import { PageHelperService } from './page.util.service'; import * as _ from 'lodash'; import { AppVersion } from '@entities/app_version.entity'; import { IPageService } from '../interfaces/services/IPageService'; +import { find } from 'lodash'; @Injectable() export class PageService implements IPageService { @@ -304,4 +306,18 @@ export class PageService implements IPageService { return await this.pageHelperService.rearrangePagesOrderPostDeletion(pageExists, manager); }, appVersionId); } + + async findModuleContainer(app: App): Promise { + const version = app.appVersions[0]; + const pages = await this.findPagesForVersion(version.id); + const page = pages[0]; + const components = await this.componentsService.getAllComponents(page.id); + + const moduleContainer = find( + Object.values(components), + (component) => component?.component?.component === 'ModuleContainer' + ); + + return moduleContainer; + } } diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 36def10913..4f17f17fa4 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -37,6 +37,8 @@ import { DataSourcesRepository } from '@modules/data-sources/repository'; import { IAppsUtilService } from './interfaces/IUtilService'; import { DataSourcesUtilService } from '@modules/data-sources/util.service'; import { AppVersionUpdateDto } from '@dto/app-version-update.dto'; +import { Component } from 'src/entities/component.entity'; +import { Layout } from 'src/entities/layout.entity'; @Injectable() export class AppsUtilService implements IAppsUtilService { @@ -89,8 +91,51 @@ export class AppsUtilService implements IAppsUtilService { }) ); + if (type === 'module') { + const moduleContainer = await manager.save( + manager.create(Component, { + name: 'ModuleContainer', + type: 'ModuleContainer', + pageId: defaultHomePage.id, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + }, + styles: { + backgroundColor: { value: '#fff' }, + }, + displayPreferences: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{true}}' }, + }, + }) + ); + + await manager.save( + manager.create(Layout, { + component: moduleContainer, + type: 'desktop', + top: 50, + left: 6, + height: 400, + width: 38, + }) + ); + + await manager.save( + manager.create(Layout, { + component: moduleContainer, + type: 'mobile', + top: 50, + left: 6, + height: 400, + width: 38, + }) + ); + } + // Set default values for app version - appVersion.showViewerNavigation = true; + appVersion.showViewerNavigation = type === 'module' ? false : true; appVersion.homePageId = defaultHomePage.id; appVersion.globalSettings = { hideHeader: false, @@ -407,6 +452,10 @@ export class AppsUtilService implements IAppsUtilService { .addSelect(['user.firstName', 'user.lastName']) .where('viewable_apps.organizationId = :organizationId', { organizationId: user.organizationId }); + if (type === 'module') { + viewableAppsQb.leftJoinAndSelect('viewable_apps.appVersions', 'versions'); + } + if (type) viewableAppsQb.andWhere('viewable_apps.type = :type', { type: type }); if (searchKey) { From bcccc00615fe4b8877b01407b19665654a7042b5 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 09:51:32 +0700 Subject: [PATCH 07/88] Added a logic to search the modules --- frontend/src/AppBuilder/AppBuilder.jsx | 2 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 16 ++++++++--- .../ComponentsManagerTab.jsx | 27 ++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 5190312efa..cee2abc0e4 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -54,7 +54,7 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e708926dc4..85693efceb 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -18,7 +18,7 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = false }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEditor, isViewer = false }) => { const moduleId = useModuleId(); const isModuleMode = useIsModuleMode(); const canvasContainerRef = useRef(); @@ -147,7 +147,12 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = fa )} - + {environmentLoadingState !== 'loading' && (
+ )}
diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx index ca98316a73..d3bc00cdfa 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx @@ -21,6 +21,7 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { }, [componentTypes]); const [filteredComponents, setFilteredComponents] = useState(componentList); + const [searchQuery, setSearchQuery] = useState(''); const [activeTab, setActiveTab] = useState(1); const _shouldFreeze = useStore((state) => state.getShouldFreeze()); const isAutoMobileLayout = useStore((state) => state.currentLayout === 'mobile' && state.getIsAutoMobileLayout()); @@ -29,9 +30,14 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const handleSearchQueryChange = useCallback( debounce((e) => { const { value } = e.target; - filterComponents(value); + setSearchQuery(value); + + if (activeTab === 1) { + filterComponents(value); + } + // No need to filter modules here as we pass searchQuery to ModuleManager }, 125), - [] + [activeTab] ); const filterComponents = useCallback((value) => { @@ -162,13 +168,15 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const handleChangeTab = (tab) => { setActiveTab(tab); + // When changing tabs, we don't need to reset the search + // The search query will be applied to the new tab }; const renderSection = () => { if (activeTab === 1) { return
{segregateSections()}
; } - return ; + return ; }; return ( @@ -184,10 +192,17 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { initialValue={''} callBack={(e) => handleSearchQueryChange(e)} onClearCallback={() => { - filterComponents(''); + setSearchQuery(''); + if (activeTab === 1) { + filterComponents(''); + } }} - placeholder={t('globals.searchComponents', 'Search widgets')} - customClass={`tj-widgets-search-input tj-text-xsm`} + placeholder={ + activeTab === 1 + ? t('globals.searchComponents', 'Search widgets') + : t('globals.searchModules', 'Search modules') + } + customClass={`tj-widgets-search-input tj-text-xsm`} showClearButton={false} width={266} /> From a6badfa7e9e3fb204a604716c2817903a1b05923 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 11:42:53 +0700 Subject: [PATCH 08/88] Added a logic to restrict the movement from modules to canvas & restricted deleting the module container --- frontend/ee | 2 +- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 5 +++- .../src/AppBuilder/AppCanvas/Container.jsx | 3 +- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 25 +++++++++++----- .../AppCanvas/Grid/helpers/dragEnd.js | 29 +++++++++++++------ .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 23 ++++++++++++++- .../AppCanvas/appCanvasConstants.js | 13 +++++++++ .../_stores/slices/componentsSlice.js | 4 ++- 8 files changed, 82 insertions(+), 22 deletions(-) diff --git a/frontend/ee b/frontend/ee index f00cc3fde4..b88b2cc56c 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit f00cc3fde44dcb082aa81d75abc9f5e879f3aade +Subproject commit b88b2cc56c66aaa54cd31b7197f74939103e085b diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 10a738adcb..6c4040671b 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -5,6 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { DROPPABLE_PARENTS } from '../appCanvasConstants'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -69,7 +70,9 @@ export const ConfigHandle = ({ if (componentType === 'Tabs') { setFocusedParentId(`${id}-${currentTab}`); } else { - setFocusedParentId(id); + if (DROPPABLE_PARENTS.has(componentType)) { + setFocusedParentId(id); + } } }} > diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index b12f9a9634..629e435892 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -68,13 +68,14 @@ export const Container = React.memo( }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ - accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', + accept: 'box', hover: (item) => { item.canvasRef = realCanvasRef?.current; item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); }, drop: async ({ componentType, component }, monitor) => { + if (appType === 'module' && componentType !== 'ModuleContainer') return; const didDrop = monitor.didDrop(); if (didDrop) return; if (componentType === 'PDF' && !isPDFSupported()) { diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index adef3eea83..baf7c18b53 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -28,7 +28,7 @@ import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; -import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; +import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { @@ -37,7 +37,7 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout, appType }) { +export default function Grid({ gridWidth, currentLayout, appType, isModuleEditor }) { const moduleId = useModuleId(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); @@ -50,6 +50,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const isGroupHandleHoverd = useIsGroupHandleHoverd(); + const openModalWidgetId = useOpenModalWidgetId(); const moveableRef = useRef(null); const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow); @@ -454,9 +455,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { widgetId = widgetId.split('-').slice(0, -1).join('-'); widgetType = boxList.find(({ id }) => id === widgetId)?.component?.component; } - if ( - !['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'Listview', 'Container', 'Table'].includes(widgetType) - ) { + if (!DROPPABLE_PARENTS.has(widgetType)) { isDroppable = false; } } @@ -470,10 +469,15 @@ export default function Grid({ gridWidth, currentLayout, appType }) { .map(({ component }) => component.component); const parentId = draggedOverElemId?.length > 36 ? draggedOverElemId.slice(0, 36) : draggedOverElemId; const parentWidgetType = getComponentTypeFromId(parentId); - const restrictedWidgetsTobeDropped = + let restrictedWidgetsTobeDropped = RESTRICTED_WIDGETS_CONFIG?.[parentWidgetType]?.filter((widgetType) => widgetsTypeToBeDropped.includes(widgetType) ) || []; + + if (isModuleEditor && parentId === undefined) { + restrictedWidgetsTobeDropped = widgetsTypeToBeDropped; + // useGridStore.getState().actions.setIsGroupHandleHoverd(false); + } const isParentChangeAllowed = isEmpty(restrictedWidgetsTobeDropped); if (!isParentChangeAllowed) { @@ -498,7 +502,12 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }); // Show error message - toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + if (isModuleEditor) { + // Added this to hide configHandle when multiple components were dragged using the configHandle and placed outside the module + setSelectedComponents([]); + } else { + toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + } } const parentElm = draggedOverElem || document.getElementById('real-canvas'); @@ -853,7 +862,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { if (!e.lastEvent) return; // Build the drag context from the event - const dragContext = dragContextBuilder({ event: e, widgets: boxList }); + const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor }); const { target, source, dragged } = dragContext; const targetSlotId = target?.slotId; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js index a9405d043e..0c78f8730a 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js @@ -54,6 +54,7 @@ import { RESTRICTED_WIDGETS_CONFIG, RESTRICTED_WIDGET_SLOTS_CONFIG, } from '@/AppBuilder/WidgetManager/configs/restrictedWidgetsConfig'; +import { DROPPABLE_PARENTS } from '../../appCanvasConstants'; const CANVAS_ID = 'canvas'; const REAL_CANVAS_ID = 'real-canvas'; @@ -84,8 +85,6 @@ export class DragEntity { * This class helps determine if a slot is valid and handles various properties like modals. */ export class DropAreaEntity { - static dropAreaWidgets = ['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'ModalV2', 'Listview', 'Container', 'Table']; - constructor(widget, slotId) { this.widget = widget; // The widget that owns this slot this.id = widget?.id || CANVAS_ID; // ID of the widget @@ -119,7 +118,7 @@ export class DropAreaEntity { // Determines if the slot is a valid drop target get isDroppable() { - return DropAreaEntity.dropAreaWidgets.includes(this.widgetType); + return DROPPABLE_PARENTS.has(this.widgetType); } // Returns the type of slot (header, footer, body, etc.) @@ -143,7 +142,7 @@ export class DropAreaEntity { * - Any restrictions based on parent-child relationships */ export class DragContext { - constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets }) { + constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets, isModuleEditor = false }) { const sourceWidgetId = sourceSlotId?.slice(0, 36); const sourceWidget = getWidgetById(widgets, sourceWidgetId); @@ -156,6 +155,7 @@ export class DragContext { this.target = new DropAreaEntity(targetWidget, targetSlotId); this.dragged = new DragEntity(draggedWidget); this.widgets = widgets; + this.isModuleEditor = isModuleEditor; } /** @@ -168,27 +168,38 @@ export class DragContext { } get isDroppable() { - const { dragged, target } = this; + const { dragged, target, isModuleEditor } = this; + + // If the target is the canvas and we are in module editor, + // then we don't want to drop the widget outside the module + if (isModuleEditor && target.id === 'canvas') { + return false; + } const restrictedWidgetsOnTarget = RESTRICTED_WIDGETS_CONFIG?.[target.widgetType] || []; const restrictedWidgetsOnTargetSlot = RESTRICTED_WIDGET_SLOTS_CONFIG?.[target.slotType] || []; const restrictedWidgets = [...restrictedWidgetsOnTarget, ...restrictedWidgetsOnTargetSlot]; return !restrictedWidgets.includes(dragged.widgetType); - ß; } } /** * Constructs the **dragging context** by gathering all relevant details from the event. */ -export function dragContextBuilder({ event, widgets }) { +export function dragContextBuilder({ event, widgets, isModuleEditor = false }) { const draggedWidgetId = event.target.id; const draggedWidget = getWidgetById(widgets, draggedWidgetId); const sourceSlotId = draggedWidget.parent; // Initialize drag context - const context = new DragContext({ widgets, draggedWidgetId, sourceSlotId, targetSlotId: sourceSlotId }); + const context = new DragContext({ + widgets, + draggedWidgetId, + sourceSlotId, + targetSlotId: sourceSlotId, + isModuleEditor, + }); // Determine the potential drop target const targetSlotId = getDroppableSlotIdOnScreen(event, widgets); @@ -210,7 +221,7 @@ export const getDroppableSlotIdOnScreen = (event, widgets) => { .map((ele) => extractSlotId(ele)) .filter((slotId) => { const widgetType = getWidgetById(widgets, slotId.slice(0, 36))?.component?.component || CANVAS_ID; - return DropAreaEntity.dropAreaWidgets.includes(widgetType); + return DROPPABLE_PARENTS.has(widgetType); }); return slotId; diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 1aa54dfc7b..47ac8dcfd0 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -5,7 +5,7 @@ import { pasteComponents, copyComponents } from './appCanvasUtils'; import useKeyHooks from '@/_hooks/useKeyHooks'; import { shallow } from 'zustand/shallow'; -export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => { +export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth, isModuleEditor = false }) => { const canvasRef = useRef(null); const focusedParentId = useStore((state) => state.focusedParentId, shallow); const handleUndo = useStore((state) => state.handleUndo); @@ -18,10 +18,13 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const containerChildrenMapping = useStore((state) => state.containerChildrenMapping, shallow); + const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); + useHotkeys('meta+z, control+z', handleUndo, { enabled: mode === 'edit' }); useHotkeys('meta+shift+z, control+shift+z', handleRedo, { enabled: mode === 'edit' }); const paste = async () => { + if (isModuleEditor && !focusedParentId) return; if (navigator.clipboard && typeof navigator.clipboard.readText === 'function') { try { const cliptext = await navigator.clipboard.readText(); @@ -61,6 +64,24 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } enableReleasedVersionPopupState(); return; } + + // Disable cut, copy, paste, delete shortcuts in module editor + // or when a ModuleContainer is selected + if (isModuleEditor) { + const selectedComponents = getSelectedComponents(); + if ( + selectedComponents.length > 0 && + selectedComponents.some((id) => { + const componentType = getComponentTypeFromId(id, 'canvas'); + return componentType === 'ModuleContainer'; + }) + ) { + if (['KeyC', 'KeyX', 'KeyV', 'Backspace'].includes(key)) { + return; + } + } + } + switch (key) { case 'Escape': handleEscapeKeyPress(); // clears the selected components diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js index e6a789fba0..cfadce5425 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js @@ -23,3 +23,16 @@ export const CONTAINER_FORM_CANVAS_PADDING = 7; export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1; export const BOX_PADDING = 2; + +export const DROPPABLE_PARENTS = new Set([ + 'Calendar', + 'Kanban', + 'Form', + 'Tabs', + 'Modal', + 'ModalV2', + 'Listview', + 'Container', + 'Table', + 'ModuleContainer', +]); diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 741846c7b4..e29365fb8d 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1451,7 +1451,9 @@ export const createComponentsSlice = (set, get) => ({ setFocusedParentId: (parentId) => { set((state) => { state.focusedParentId = parentId; - }); + }), + false, + { type: 'setFocusedParentId', payload: { parentId } }; }, saveComponentChanges: (diff, type, operation, moduleId = 'canvas') => { set( From 38861f5ca48ab48fcd6813b84b2546cc2cea9a51 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 12:11:41 +0700 Subject: [PATCH 09/88] Fix: unable to resize the module container --- server/src/modules/apps/util.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 4f17f17fa4..bd91930f83 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -100,6 +100,7 @@ export class AppsUtilService implements IAppsUtilService { properties: { inputItems: { value: [] }, outputItems: { value: [] }, + visibility: { value: '{{true}}' }, }, styles: { backgroundColor: { value: '#fff' }, From e3db264fae2ca10e76ecd2e1b2f0440cb37135f9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 17:10:32 +0700 Subject: [PATCH 10/88] Moved fetching of modules logic to appDef --- frontend/ee | 2 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 20 ++++++--- frontend/src/AppBuilder/_hooks/useAppData.js | 43 ++++++++++--------- server/src/modules/apps/service.ts | 3 +- .../src/modules/apps/services/page.service.ts | 16 +------ .../apps/services/page.util.service.ts | 4 ++ 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/frontend/ee b/frontend/ee index b88b2cc56c..51c8b41597 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit b88b2cc56c66aaa54cd31b7197f74939103e085b +Subproject commit 51c8b4159781d3fe664ab5c5ca15c97f1b485496 diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 85693efceb..e24be589d2 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -139,7 +139,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito >
diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 42b07dacbe..5a29c53f18 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -28,7 +28,14 @@ const WidgetWrapper = memo( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - let layoutData = useStore((state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], shallow); + let layoutData = useStore((state) => { + const layout = state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout]; + // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer + if (componentType === 'ModuleContainer' && mode === 'view') { + return { ...layout, top: 0, left: 0, width: NO_OF_GRIDS }; + } + return layout; + }, shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); @@ -51,12 +58,11 @@ const WidgetWrapper = memo( return null; } - // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer - if (componentType === 'ModuleContainer' && mode === 'view') { - layoutData.top = 0; - layoutData.left = 0; - layoutData.width = NO_OF_GRIDS; - } + // if (componentType === 'ModuleContainer' && mode === 'view') { + // layoutData.top = 0; + // layoutData.left = 0; + // layoutData.width = NO_OF_GRIDS; + // } const width = gridWidth * layoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 4104868eda..a900a84917 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -122,6 +122,9 @@ const useAppData = ( const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow); const setModulesList = useStore((state) => state.setModulesList, shallow); + const setModuleDefinition = useStore((state) => state.setModuleDefinition); + const getModuleDefinition = useStore((state) => state.getModuleDefinition); + const deleteModuleDefinition = useStore((state) => state.deleteModuleDefinition); const setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); @@ -215,33 +218,30 @@ const useAppData = ( } let appDataPromise; - - // if (moduleMode) { - // appDataPromise = appService.fetchApp(appId); - // appDataPromise.then(async (result) => { - // let appData = { ...result }; - // console.log('appData--- ', appData); - // return; - // // setApp({ - // // appName: appData.name, - // // appId: appData.id, - // // slug: appData.slug, - // // }); - // }); - // } - const queryParams = moduleMode ? {} : getPreviewQueryParams(); const isPublicAccess = moduleMode ? false : (currentSession?.load_app && currentSession?.authentication_failed) || (!queryParams.version && mode !== 'edit'); const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; - if (isPublicAccess) { - appDataPromise = appService.fetchAppBySlug(slug); + + if (moduleMode) { + const moduleDefinition = getModuleDefinition(appId); + if (moduleDefinition) { + // clean up the module definition from the store + deleteModuleDefinition(appId); + appDataPromise = Promise.resolve(moduleDefinition); + } else { + appDataPromise = appService.fetchApp(appId); + } } else { - appDataPromise = isPreviewForVersion - ? appVersionService.getAppVersionData(appId, versionId) - : appService.fetchApp(appId); + if (isPublicAccess) { + appDataPromise = appService.fetchAppBySlug(slug); + } else { + appDataPromise = isPreviewForVersion + ? appVersionService.getAppVersionData(appId, versionId) + : appService.fetchApp(appId); + } } // const appDataPromise = appService.fetchApp(appId); @@ -383,6 +383,9 @@ const useAppData = ( setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); } setAppHomePageId(homePageId, moduleId); + if (!moduleMode && appData.modules) { + setModuleDefinition(appData.modules); + } const queryData = isPublicAccess || (mode !== 'edit' && appData.is_public) diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index f17704776c..1e25e66206 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -214,7 +214,8 @@ export class AppsService implements IAppsService { if (type === 'module') { for (const app of apps) { - app.moduleContainer = await this.pageService.findModuleContainer(app); + const appVersionId = app?.appVersions[0]?.id; + app.moduleContainer = await this.pageService.findModuleContainer(appVersionId); } } diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts index 7b8c3dbc8a..02bea48dab 100644 --- a/server/src/modules/apps/services/page.service.ts +++ b/server/src/modules/apps/services/page.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { Page } from '@entities/page.entity'; -import { App } from '@entities/app.entity'; import { ComponentsService } from './component.service'; import { CreatePageDto, UpdatePageDto } from '../dto/page'; import { dbTransactionWrap, dbTransactionForAppVersionAssociationsUpdate } from 'src/helpers/database.helper'; @@ -15,7 +14,6 @@ import { PageHelperService } from './page.util.service'; import * as _ from 'lodash'; import { AppVersion } from '@entities/app_version.entity'; import { IPageService } from '../interfaces/services/IPageService'; -import { find } from 'lodash'; @Injectable() export class PageService implements IPageService { @@ -307,17 +305,7 @@ export class PageService implements IPageService { }, appVersionId); } - async findModuleContainer(app: App): Promise { - const version = app.appVersions[0]; - const pages = await this.findPagesForVersion(version.id); - const page = pages[0]; - const components = await this.componentsService.getAllComponents(page.id); - - const moduleContainer = find( - Object.values(components), - (component) => component?.component?.component === 'ModuleContainer' - ); - - return moduleContainer; + async findModuleContainer(appVersionId: string): Promise { + return this.pageHelperService.findModuleContainer(appVersionId); } } diff --git a/server/src/modules/apps/services/page.util.service.ts b/server/src/modules/apps/services/page.util.service.ts index cb10863972..aface2cc16 100644 --- a/server/src/modules/apps/services/page.util.service.ts +++ b/server/src/modules/apps/services/page.util.service.ts @@ -79,4 +79,8 @@ export class PageHelperService implements IPageHelperService { page.index = dto.index; return page; } + + public async findModuleContainer(appVersionId: string): Promise { + return null; + } } From 32de1c9929db97127fc1af7ca87a47cd4bc37ffc Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 19:08:14 +0700 Subject: [PATCH 11/88] Fixed bug on query creation --- frontend/ee | 2 +- frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js | 4 ++-- server/ee | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/ee b/frontend/ee index 51c8b41597..425820a97a 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 51c8b4159781d3fe664ab5c5ca15c97f1b485496 +Subproject commit 425820a97a5dbb3c8a26037f7f21252e465d07f1 diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index fee6c8f863..93db833f13 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -110,7 +110,7 @@ export const createDataQuerySlice = (set, get) => ({ return query; }); }); - setSelectedQuery(data.id, data); + setSelectedQuery(data.id, moduleId); if (shouldRunQuery) setQueryToBeRun(data); /** Checks if there is an API call cached. If yes execute it */ @@ -275,7 +275,7 @@ export const createDataQuerySlice = (set, get) => ({ ...state.dataQuery.queries.modules[moduleId], ]; }); - setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id }); + setSelectedQuery(data.id, moduleId); get().addNewQueryMapping(data.id, data.name, moduleId); //! we need default value in store so that query can be resolved if referenced from other entity diff --git a/server/ee b/server/ee index 9bc998392e..1363a2f4d5 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 9bc998392ea9af9934a5099881c28a3ff7edeab1 +Subproject commit 1363a2f4d5d936325b6dc28da9f952c5c2dba448 From 41db3e6817a25c118271554d7578a2e9ad6a05ea Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 29 Apr 2025 23:26:03 +0700 Subject: [PATCH 12/88] Fetched the app modules while previewing the app --- .../src/AppBuilder/AppCanvas/Container.jsx | 2 - .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 32 +++--- server/src/modules/apps/util.service.ts | 38 ++++++ server/src/modules/versions/repository.ts | 16 +++ server/src/modules/versions/service.ts | 108 ++++++++++-------- 5 files changed, 130 insertions(+), 66 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 629e435892..074d97cfeb 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -214,8 +214,6 @@ export const Container = React.memo( ? computeViewerBackgroundColor(darkMode, canvasBgColor) : id === 'canvas' ? canvasBgColor - : componentType === 'ModuleContainer' - ? 'inherit' : '#f0f0f0', width: getCanvasWidth(), maxWidth: (() => { diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 5a29c53f18..2e5c1c34ff 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -28,14 +28,10 @@ const WidgetWrapper = memo( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - let layoutData = useStore((state) => { - const layout = state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout]; - // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer - if (componentType === 'ModuleContainer' && mode === 'view') { - return { ...layout, top: 0, left: 0, width: NO_OF_GRIDS }; - } - return layout; - }, shallow); + const layoutData = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], + shallow + ); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); @@ -58,18 +54,18 @@ const WidgetWrapper = memo( return null; } - // if (componentType === 'ModuleContainer' && mode === 'view') { - // layoutData.top = 0; - // layoutData.left = 0; - // layoutData.width = NO_OF_GRIDS; - // } + let newLayoutData = layoutData; - const width = gridWidth * layoutData?.width; + if (componentType === 'ModuleContainer' && mode === 'view') { + newLayoutData = { ...layoutData, top: 0, left: 0, width: NO_OF_GRIDS }; + } + + const width = gridWidth * newLayoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); const styles = { width: width + 'px', height: visibility === false ? '10px' : `${height}px`, - transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`, + transform: `translate(${newLayoutData.left * gridWidth}px, ${newLayoutData.top}px)`, WebkitFontSmoothing: 'antialiased', border: visibility === false ? `1px solid var(--border-default)` : 'none', }; @@ -108,8 +104,8 @@ const WidgetWrapper = memo( {mode == 'edit' && ( { + const versionToLoad = versionId + ? await this.versionRepository.findVersion(versionId) + : app.currentVersionId + ? await this.versionRepository.findVersion(app.currentVersionId) + : await this.versionRepository.findVersion(app.editingVersion?.id); + + const modules = await dbTransactionWrap(async (manager) => { + const moduleComponents = await manager + .createQueryBuilder(Component, 'component') + .leftJoinAndSelect(Page, 'page', 'page.id = component.page_id') + .leftJoinAndSelect(AppVersion, 'app_version', 'app_version.id = page.app_version_id') + .leftJoinAndSelect(App, 'app', 'app.id = app_version.app_id') + .andWhere( + `component.type = :module ${allVersions ? '' : 'AND app_version.id = :appVersionId'} AND app.id = :appId`, + { + module: 'ModuleViewer', + appVersionId: versionToLoad.id, + appId: app.id, + } + ) + .getMany(); + + const moduleAppIds = moduleComponents.map((moduleComponent) => moduleComponent.properties.moduleAppId.value); + + const modules = + moduleAppIds.length > 0 + ? await manager + .createQueryBuilder(App, 'app') + .where('app.id IN (:...moduleAppIds)', { moduleAppIds }) + .distinct(true) + .getMany() + : []; + return modules; + }); + return modules; + } } diff --git a/server/src/modules/versions/repository.ts b/server/src/modules/versions/repository.ts index d67b56def1..110f41f2df 100644 --- a/server/src/modules/versions/repository.ts +++ b/server/src/modules/versions/repository.ts @@ -164,4 +164,20 @@ export class VersionRepository extends Repository { return appVersion.app; }, manager || this.manager); } + + async findVersionsFromApp(app: App, manager?: EntityManager): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const appVersions = await manager.find(AppVersion, { + where: { appId: app.id }, + relations: [ + 'app', + 'dataQueries', + 'dataQueries.dataSource', + 'dataQueries.plugins', + 'dataQueries.plugins.manifestFile', + ], + }); + return appVersions; + }, manager || this.manager); + } } diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts index e50b66b50b..1a46b18100 100644 --- a/server/src/modules/versions/service.ts +++ b/server/src/modules/versions/service.ts @@ -112,62 +112,78 @@ export class VersionService implements IVersionService { } async getVersion(app: App, user: User): Promise { - const versionId = app.appVersions[0].id; - const appVersion = await this.versionRepository.findVersion(versionId); - - const pagesForVersion = await this.pageService.findPagesForVersion(versionId); - const eventsForVersion = await this.eventsService.findEventsForVersion(versionId); - - const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion)); - - if ( - appCurrentEditingVersion && - !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT)) - ) { - const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); - appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; - } - - let shouldFreezeEditor = false; - if (appCurrentEditingVersion) { - const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT); - if (hasMultiEnvLicense) { - const currentEnvironment = await this.appEnvironmentUtilService.get( - user.organizationId, - appCurrentEditingVersion['currentEnvironmentId'] - ); - shouldFreezeEditor = currentEnvironment.priority > 1; + const prepareResponse = async (app: App, versionId: string) => { + let appVersion, + updatedVersionId = versionId; + if (updatedVersionId) { + appVersion = await this.versionRepository.findVersion(updatedVersionId); } else { + appVersion = await this.versionRepository.findVersionsFromApp(app); + appVersion = appVersion[0]; + updatedVersionId = appVersion.id; + } + + const pagesForVersion = await this.pageService.findPagesForVersion(updatedVersionId); + const eventsForVersion = await this.eventsService.findEventsForVersion(updatedVersionId); + + const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion)); + + if ( + appCurrentEditingVersion && + !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT)) + ) { const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; } - } - delete appCurrentEditingVersion['app']; + let shouldFreezeEditor = false; + if (appCurrentEditingVersion) { + const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT); + if (hasMultiEnvLicense) { + const currentEnvironment = await this.appEnvironmentUtilService.get( + user.organizationId, + appCurrentEditingVersion['currentEnvironmentId'] + ); + shouldFreezeEditor = currentEnvironment.priority > 1; + } else { + const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); + appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; + } + } - const appData = { - ...app, + delete appCurrentEditingVersion['app']; + + const appData = { + ...app, + }; + + delete appData['editingVersion']; + + const editingVersion = camelizeKeys(appCurrentEditingVersion); + + // Inject app theme + const appTheme = await this.organizationThemesUtilService.getTheme( + user.organizationId, + editingVersion?.globalSettings?.theme?.id + ); + + editingVersion['globalSettings']['theme'] = appTheme; + + return { + ...appData, + editing_version: editingVersion, + pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion), + events: eventsForVersion, + should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor, + }; }; - delete appData['editingVersion']; + const response = await prepareResponse(app, app.appVersions?.[0]?.id); + const modules = await this.appUtilService.fetchModules(app, false, undefined); - const editingVersion = camelizeKeys(appCurrentEditingVersion); + response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module, undefined))); - // Inject app theme - const appTheme = await this.organizationThemesUtilService.getTheme( - user.organizationId, - editingVersion?.globalSettings?.theme?.id - ); - - editingVersion['globalSettings']['theme'] = appTheme; - - return { - ...appData, - editing_version: editingVersion, - pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion), - events: eventsForVersion, - should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor, - }; + return response; } async update(app: App, user: User, appVersionUpdateDto: AppVersionUpdateDto) { From 42c72fbde1722a97264f5e5e128506056e9762d5 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 09:42:37 +0700 Subject: [PATCH 13/88] Fixed bugs on preview header and view by slug --- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 +- .../src/AppBuilder/AppCanvas/Container.jsx | 3 +- .../src/AppBuilder/Viewer/PreviewSettings.jsx | 24 +++--- frontend/src/AppBuilder/Viewer/Viewer.jsx | 7 +- frontend/src/AppBuilder/_hooks/useAppData.js | 6 ++ .../slices/environmentsAndVersionsSlice.js | 4 +- .../src/Editor/Viewer/PreviewSettings.jsx | 6 +- server/ee | 2 +- server/src/modules/apps/service.ts | 78 +++++++++++-------- 9 files changed, 78 insertions(+), 54 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e24be589d2..2b45319c02 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -46,7 +46,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); - const hideSidebar = isModuleMode || isPagesSidebarHidden; + const hideSidebar = isModuleMode || isPagesSidebarHidden || appType === 'module'; useEffect(() => { // Need to remove this if we shift setExposedVariable Logic outside of components diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 074d97cfeb..1238406e6b 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -159,7 +159,8 @@ export const Container = React.memo( !isPagesSidebarHidden && isViewerSidebarPinned && currentLayout !== 'mobile' && - currentMode !== 'edit' + currentMode !== 'edit' && + appType !== 'module' ) { return `calc(100% - ${pageSidebarStyle === 'icon' ? '65px' : '210px'})`; } diff --git a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx index 633a6dcf0e..e914a9bee4 100644 --- a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx +++ b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx @@ -9,8 +9,10 @@ import HeaderActions from '@/AppBuilder/Header/HeaderActions'; import { AppEnvironments } from '@/modules/Appbuilder/components'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { + const appType = useAppType(); const { setShowUndoRedoBtn, editingVersion } = useStore( (state) => ({ setShowUndoRedoBtn: state?.setShowUndoRedoBtn, @@ -32,14 +34,14 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { Preview settings - {editingVersion && ( + {editingVersion && appType !== 'module' && ( <>
)} - +
@@ -85,13 +87,17 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { {previewNavbar && ( - - - -
- - - + {appType !== 'module' && ( + <> + + + +
+ + + + + )}
{ const DEFAULT_CANVAS_WIDTH = 1292; const { t } = useTranslation(); const [isSidebarPinned, setIsSidebarPinned] = useState(localStorage.getItem('isPagesSidebarPinned') !== 'false'); - useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); + const appType = useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); const { isEditorLoading, @@ -80,7 +79,7 @@ export const Viewer = ({ const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow); const deviceWindowWidth = window.screen.width - 5; - const hideSidebar = moduleMode || isPagesSidebarHidden; + const hideSidebar = moduleMode || isPagesSidebarHidden || appType === 'module'; const computeCanvasMaxWidth = useCallback(() => { if (globalSettings?.maxCanvasWidth) { @@ -223,7 +222,7 @@ export const Viewer = ({
{ try { const head = document.head || document.getElementsByTagName('head')[0]; @@ -314,6 +316,8 @@ const useAppData = ( const homePageId = appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id; + appTypeRef.current = appData.type; + setApp( { appName: appData.name, @@ -616,6 +620,8 @@ const useAppData = ( ); // Adding a timeout of 2 seconds as fallback } }, [setModulesIsLoading, setModulesList, mode, moduleMode]); + + return appTypeRef.current; }; export default useAppData; diff --git a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js index 1edd3994c5..1077a22608 100644 --- a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js @@ -243,7 +243,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({ const versionIsAvailableInEnvironment = environment?.priority <= get().currentAppVersionEnvironment?.priority; if (!versionIsAvailableInEnvironment) { - const appId = useStore.getState().app.appId; + const { appId } = useStore.getState().appStore.modules.canvas.app; const response = await appEnvironmentService.postEnvironmentChangedAction({ appId, editorEnvironmentId: environmentId, @@ -285,7 +285,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({ promoteAppVersionAction: async (versionId, onSuccess, onFailure) => { try { - const appId = useStore.getState().app.appId; // Correct way to access appId + const { appId } = useStore.getState().appStore.modules.canvas.app; const response = await appVersionService.promoteEnvironment(appId, versionId, get().selectedEnvironment.id); set((state) => ({ diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx index 3103be3e16..a4b20b5e61 100644 --- a/frontend/src/Editor/Viewer/PreviewSettings.jsx +++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx @@ -15,6 +15,7 @@ import { useEditorStore } from '@/_stores/editorStore'; import Cross from '@/_ui/Icon/solidIcons/Cross'; import { checkIfLicenseNotValid } from '@/_helpers/appUtils'; import EnvironmentManager from '@/Editor/Header/EnvironmentManager'; +import { useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const PreviewSettings = ({ isMobileLayout, @@ -23,6 +24,7 @@ const PreviewSettings = ({ showHeader, darkMode, }) => { + const { appType } = useAppType(); const { featureAccess, currentAppEnvironment, setCurrentAppEnvironmentId } = useEditorStore( (state) => ({ featureAccess: state?.featureAccess, @@ -84,9 +86,9 @@ const PreviewSettings = ({
Preview settings - {editingVersion && _renderAppVersionsManager()} + {editingVersion && appType !== 'module' && _renderAppVersionsManager()}
- {editingVersion && _renderEnvironmentManager()} + {editingVersion && appType !== 'module' && _renderEnvironmentManager()} diff --git a/server/ee b/server/ee index 1363a2f4d5..3219183a1f 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1363a2f4d5d936325b6dc28da9f952c5c2dba448 +Subproject commit 3219183a1f54f368ed135f652261fbf7ff183687 diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 1e25e66206..d6d6397e05 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -348,44 +348,54 @@ export class AppsService implements IAppsService { } async getBySlug(app: App, user: User): Promise { - const versionToLoad = app.currentVersionId - ? await this.versionRepository.findVersion(app.currentVersionId) - : await this.versionRepository.findVersion(app.editingVersion?.id); + const prepareResponse = async (app) => { + const versionToLoad = app.currentVersionId + ? await this.versionRepository.findVersion(app.currentVersionId) + : await this.versionRepository.findVersion(app.editingVersion?.id); - const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : []; - const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : []; - const appTheme = await this.organizationThemeUtilService.getTheme( - app.organizationId, - versionToLoad?.globalSettings?.theme?.id - ); + const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : []; + const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : []; + const appTheme = await this.organizationThemeUtilService.getTheme( + app.organizationId, + versionToLoad?.globalSettings?.theme?.id + ); - if (app?.isPublic && user) { - this.eventEmitter.emit('auditLogEntry', { - userId: user.id, - organizationId: user.organizationId, - resourceId: app.id, - resourceType: MODULES.APP, - resourceName: app.name, - actionType: MODULE_INFO.APP.GET_BY_SLUG, - }); - } + if (app?.isPublic && user) { + this.eventEmitter.emit('auditLogEntry', { + userId: user.id, + organizationId: user.organizationId, + resourceId: app.id, + resourceType: MODULES.APP, + resourceName: app.name, + actionType: MODULE_INFO.APP.GET_BY_SLUG, + }); + } - // serialize - return { - current_version_id: app['currentVersionId'], - data_queries: versionToLoad?.dataQueries, - definition: versionToLoad?.definition, - is_public: app.isPublic, - is_maintenance_on: app.isMaintenanceOn, - name: app.name, - slug: app.slug, - events: eventsForVersion, - pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion), - homePageId: versionToLoad.homePageId, - globalSettings: { ...versionToLoad.globalSettings, theme: appTheme }, - showViewerNavigation: versionToLoad.showViewerNavigation, - pageSettings: versionToLoad?.pageSettings, + // serialize + return { + current_version_id: app['currentVersionId'], + data_queries: versionToLoad?.dataQueries, + definition: versionToLoad?.definition, + is_public: app.isPublic, + is_maintenance_on: app.isMaintenanceOn, + name: app.name, + slug: app.slug, + events: eventsForVersion, + pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion), + homePageId: versionToLoad.homePageId, + globalSettings: { ...versionToLoad.globalSettings, theme: appTheme }, + showViewerNavigation: versionToLoad.showViewerNavigation, + pageSettings: versionToLoad?.pageSettings, + }; }; + + const response = await prepareResponse(app); + + const modules = await this.appsUtilService.fetchModules(app, false, undefined); + + response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module))); + + return response; } async release(app: App, user: User, versionReleaseDto: VersionReleaseDto) { From eed8014509c9965b92144b7cf387f9a9fe88c3a9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 10:35:32 +0700 Subject: [PATCH 14/88] Cleaned up the logic of using module context --- frontend/src/AppBuilder/AppBuilder.jsx | 8 +++---- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 21 +++++-------------- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 4 ++-- .../src/AppBuilder/AppCanvas/Container.jsx | 4 ++-- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 6 +++--- .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 4 +++- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 4 ++-- frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 4 ++-- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 4 ++-- .../AppBuilder/AppCanvas/useSidebarMargin.js | 4 ++-- .../src/AppBuilder/CodeEditor/PreviewBox.jsx | 4 ++-- .../CodeEditor/SingleLineCodeEditor.jsx | 4 ++-- .../AppBuilder/Header/AppVersionsManager.jsx | 4 ++-- .../AppBuilder/Header/CreateVersionModal.jsx | 4 ++-- .../src/AppBuilder/Header/EditAppName.jsx | 4 ++-- .../AppBuilder/Header/EditVersionModal.jsx | 4 ++-- .../src/AppBuilder/Header/EditorHeader.jsx | 6 +++--- .../PromoteVersionButton.jsx | 4 ++-- .../ReleaseVersionButton.jsx | 4 ++-- .../RightTopHeaderButtons.jsx | 4 ++-- .../LeftSidebar/GlobalSettings/AppExport.jsx | 4 ++-- .../GlobalSettings/CanvasSettings.jsx | 4 ++-- .../LeftSidebar/GlobalSettings/SlugInput.jsx | 4 ++-- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 6 ++---- .../LeftSidebar/PageMenu/PageHandlerMenu.jsx | 4 ++-- .../LeftSidebar/PageMenu/PageMenuItem.jsx | 4 ++-- .../Components/QueryManagerHeader.jsx | 4 ++-- .../QueryManager/QueryEditors/Workflows.jsx | 4 ++-- .../src/AppBuilder/QueryPanel/QueryCard.jsx | 4 ++-- .../Inspector/Components/DatetimePickerV2.jsx | 4 ++-- .../RightSideBar/Inspector/EventManager.jsx | 4 ++-- .../AppBuilder/RightSideBar/RightSideBar.jsx | 6 ++++-- .../Viewer/MobileNavigationMenu.jsx | 8 +++---- frontend/src/AppBuilder/Viewer/PageGroup.jsx | 4 ++-- frontend/src/AppBuilder/Viewer/Viewer.jsx | 2 +- .../Viewer/ViewerSidebarNavigation.jsx | 4 ++-- .../AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 4 ++-- frontend/src/AppBuilder/Widgets/Modal.jsx | 4 ++-- .../AppBuilder/Widgets/ModalV2/ModalV2.jsx | 4 ++-- .../src/AppBuilder/Widgets/Table/Table.jsx | 4 ++-- .../AppBuilder/_contexts/ModuleContext.jsx | 8 +++++++ .../PromoteVersionButton.jsx | 4 ++-- .../PromoteConfirmationModal.jsx | 4 ++-- .../ReleaseVersionButton.jsx | 4 ++-- 44 files changed, 105 insertions(+), 106 deletions(-) diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index cee2abc0e4..9f7a8f4e2d 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -49,14 +49,14 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod Loading...
}> - - + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - + - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 2b45319c02..163c81e692 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Container } from './Container'; import Grid from './Grid'; import { EditorSelecto } from './Selecto'; -import { useModuleId, useIsModuleMode } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { HotkeyProvider } from './HotkeyProvider'; import './appCanvas.scss'; import useStore from '@/AppBuilder/_stores/store'; @@ -18,9 +18,8 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEditor, isViewer = false }) => { - const moduleId = useModuleId(); - const isModuleMode = useIsModuleMode(); +export const AppCanvas = ({ appId, isViewerSidebarPinned, isViewer = false }) => { + const { moduleId, isModuleMode, appType } = useModuleContext(); const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight); @@ -147,12 +146,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito )} - + {environmentLoadingState !== 'loading' && (
+ )}
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 6c4040671b..9215bb6a21 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -4,7 +4,7 @@ import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { DROPPABLE_PARENTS } from '../appCanvasConstants'; const CONFIG_HANDLE_HEIGHT = 20; @@ -22,7 +22,7 @@ export const ConfigHandle = ({ visibility, isModuleContainer, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const shouldFreeze = useStore((state) => state.getShouldFreeze()); const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow); const isMultipleComponentsSelected = useStore( diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 1238406e6b..78ec45823a 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -21,7 +21,7 @@ import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; //TODO: Revisit the logic of height (dropRef) /* @@ -47,7 +47,7 @@ export const Container = React.memo( componentType, appType, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index baf7c18b53..eb3b8f7b47 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -29,7 +29,7 @@ import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -37,8 +37,8 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout, appType, isModuleEditor }) { - const moduleId = useModuleId(); +export default function Grid({ gridWidth, currentLayout }) { + const { moduleId, isModuleEditor } = useModuleContext(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 47ac8dcfd0..5177e27af8 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -4,8 +4,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { pasteComponents, copyComponents } from './appCanvasUtils'; import useKeyHooks from '@/_hooks/useKeyHooks'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth, isModuleEditor = false }) => { +export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => { + const { isModuleEditor } = useModuleContext(); const canvasRef = useRef(null); const focusedParentId = useStore((state) => state.focusedParentId, shallow); const handleUndo = useStore((state) => state.handleUndo); diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 5e72d09797..0a7402d566 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -7,7 +7,7 @@ import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; import { BOX_PADDING } from './appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const shouldAddBoxShadowAndVisibility = [ 'Table', @@ -40,7 +40,7 @@ const RenderWidget = ({ inCanvas = false, darkMode, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow); const component = componentDefinition?.component; diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index 24426f6050..dfa27d3638 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -5,10 +5,10 @@ import './selecto.scss'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; import { findHighestLevelofSelection } from './Grid/gridUtils'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 2e5c1c34ff..0bd9df01e6 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -6,7 +6,7 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle'; import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { NO_OF_GRIDS } from './appCanvasConstants'; const WidgetWrapper = memo( @@ -22,7 +22,7 @@ const WidgetWrapper = memo( mode, darkMode, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow); const stylesDefinition = useStore( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, diff --git a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js index b9ea871ca2..d80cbd76cc 100644 --- a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js +++ b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js @@ -3,10 +3,10 @@ import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const useSidebarMargin = (canvasContainerRef) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [editorMarginLeft, setEditorMarginLeft] = useState(0); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx index 8234bf8af6..f62fbc52c4 100644 --- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx @@ -13,7 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { Overlay } from 'react-bootstrap'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const sanitizeLargeDataset = (data, callback) => { const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -91,7 +91,7 @@ export const PreviewBox = ({ isWorkspaceVariable, validationFn, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); const [coersionData, setCoersionData] = useState(null); diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 8f36315a64..5e93abffcf 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -22,10 +22,10 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { initialValue, onChange, enablePreview = true, portalProps } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); diff --git a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx index 6ec4a22dd1..56070684c0 100644 --- a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx +++ b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx @@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { decodeEntities } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const appVersionLoadingStatus = Object.freeze({ loading: 'loading', @@ -15,7 +15,7 @@ const appVersionLoadingStatus = Object.freeze({ }); export const AppVersionsManager = function ({ darkMode }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index 888b91963d..702ffb42f9 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import Select from '@/_ui/Select'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CreateVersionModal = ({ showCreateAppVersion, @@ -18,7 +18,7 @@ const CreateVersionModal = ({ fetchingOrgGit, handleCommitOnVersionCreation = () => {}, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isCreatingVersion, setIsCreatingVersion] = useState(false); const [versionName, setVersionName] = useState(''); diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 94da928483..e9dff6074a 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -8,10 +8,10 @@ import { InfoOrErrorBox } from './InfoOrErrorBox'; import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; function EditAppName() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [appId, appName, setAppName, appCreationMode] = useStore( (state) => [ state.appStore.modules[moduleId].app.appId, diff --git a/frontend/src/AppBuilder/Header/EditVersionModal.jsx b/frontend/src/AppBuilder/Header/EditVersionModal.jsx index bb79468ef3..c22dd74991 100644 --- a/frontend/src/AppBuilder/Header/EditVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/EditVersionModal.jsx @@ -4,10 +4,10 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isEditingVersion, setIsEditingVersion] = useState(false); const { updateVersionNameAction, diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index dfa8d8940f..302c462e03 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -13,10 +13,10 @@ import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; import { ModuleEditorBanner } from '@/modules/Modules/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const EditorHeader = ({ darkMode, isModuleEditor }) => { - const moduleId = useModuleId(); +export const EditorHeader = ({ darkMode }) => { + const { moduleId, isModuleEditor } = useModuleContext(); const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ isSaving: state.appStore.modules[moduleId].app.isSaving, diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx index a3638c9a97..263439228b 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx @@ -4,10 +4,10 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import PromoteConfirmationModal from './PromoteConfirmationModal'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx index 3052f05d17..48b77cc238 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx @@ -8,10 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index d709945ca9..9b144bdd88 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RightTopHeaderButtons = ({ isModuleEditor }) => { return ( @@ -22,7 +22,7 @@ const RightTopHeaderButtons = ({ isModuleEditor }) => { }; const PreviewAndShareIcons = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { featureAccess, currentPageHandle, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx index bea8953829..49c424fc04 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx @@ -3,10 +3,10 @@ import { Button } from '@/components/ui/Button/Button'; import ExportAppModal from '@/HomePage/ExportAppModal'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const AppExport = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { app } = useStore( (state) => ({ app: state.appStore.modules[moduleId].app, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx index 5d8edf4f41..efe3fb8766 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx @@ -11,10 +11,10 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton'; import { useTranslation } from 'react-i18next'; import { Confirm } from '@/Editor/Viewer/Confirm'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CanvasSettings = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { globalSettings, globalSettingsChanged, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx index 269ea98e20..536f3fd462 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx @@ -8,11 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes'; import useStore from '@/AppBuilder/_stores/store'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // import { useStore } from '@/store'; const SlugInput = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { slug: oldSlug, appId, diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index c2c43f7647..1c377807c3 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -10,7 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector'; import GlobalSettings from './GlobalSettings'; import '../../_styles/left-sidebar.scss'; import Debugger from './Debugger/Debugger'; -import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; // TODO: remove passing refs to LeftSidebarItem and use state @@ -23,10 +23,8 @@ export const BaseLeftSidebar = ({ switchDarkMode, renderAISideBarTrigger = () => null, renderAIChat = () => null, - isModuleEditor = false, }) => { - const moduleId = useModuleId(); - const appType = useAppType(); + const { moduleId, isModuleEditor, appType } = useModuleContext(); const [ pinned, selectedSidebarItem, diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index 9cd9e5dcf5..9c2562c7ed 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -2,10 +2,10 @@ import React from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { Button } from '@/_ui/LeftSidebar'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const PageHandlerMenu = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const setShowEditingPopover = useStore((state) => state.setShowEditingPopover); const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal); diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index 962f40554b..044eb376ae 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -16,11 +16,11 @@ import { RenameInput } from './RenameInput'; import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const isHomePage = page.id === homePageId; const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 75cbe9cef7..b6764ce8e5 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -12,10 +12,10 @@ import { Button } from 'react-bootstrap'; import { decodeEntities } from '@/_helpers/utils'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const updateQuerySuggestions = useStore((state) => state.queryPanel.updateQuerySuggestions); const previewQuery = useStore((state) => state.queryPanel.previewQuery); const renameQuery = useStore((state) => state.dataQuery.renameQuery); diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index 367a71e5c3..2fdd002906 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,10 +5,10 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export function Workflows({ options, optionsChanged, currentState }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [workflowOptions, setWorkflowOptions] = useState([]); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx index afe6aa6604..dc6f075d33 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx @@ -13,10 +13,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { Confirm } from '@/Editor/Viewer/Confirm'; // TODO: enable delete query confirmation popup import { debounce } from 'lodash'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow); diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx index 20d7e8c66f..665f7b6755 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx @@ -9,7 +9,7 @@ import cx from 'classnames'; import useStore from '@/AppBuilder/_stores/store'; import styles from '@/_ui/Select/styles'; import moment from 'moment-timezone'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const DATE_FORMAT_OPTIONS = [ { @@ -102,7 +102,7 @@ const DatetimePickerV2 = ({ componentMeta, componentName, darkMode, ...restProps allComponents, pages, } = restProps; - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const items = []; const additionalActions = []; const properties = []; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index c1a51eab11..01b797ca91 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -30,7 +30,7 @@ import { appService } from '@/_services'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EventManager = ({ sourceId, @@ -44,7 +44,7 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup diff --git a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx index 5efc153286..3322b9e143 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx @@ -4,8 +4,10 @@ import { ComponentConfigurationTab } from './ComponentConfigurationTab'; import ComponentsManagerTab from './ComponentManagerTab'; import cx from 'classnames'; import { PageSettings } from './PageSettingsTab'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const RightSideBar = ({ darkMode, isModuleEditor }) => { +export const RightSideBar = ({ darkMode }) => { + const { isModuleEditor } = useModuleContext(); const activeTab = useStore((state) => state.activeRightSideBarTab); const pageSettingSelected = useStore((state) => state.pageSettingSelected); @@ -17,7 +19,7 @@ export const RightSideBar = ({ darkMode, isModuleEditor }) => { {activeTab === 'components' ? ( ) : ( - + )}
diff --git a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx index c6759bc931..0862634c77 100644 --- a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx +++ b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx @@ -8,10 +8,10 @@ import Cross from '@/_ui/Icon/solidIcons/Cross'; import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import * as Icons from '@tabler/icons-react'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch, currentPageId, icon }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isExpanded, setIsExpanded] = useState(true); const groupActive = currentPage.pageGroupId === pageGroup?.id; const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); @@ -80,7 +80,7 @@ const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch }; const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, currentPage }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const tree = buildTree(pages); const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return ( @@ -126,7 +126,7 @@ const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, cu }; const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeToDarkMode, showDarkModeToggle }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); const license = useStore((state) => state.license); diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 28fa683df6..6dab237909 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -8,7 +8,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import OverflowTooltip from '@/_components/OverflowTooltip'; import cx from 'classnames'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; @@ -142,7 +142,7 @@ const RenderPageGroup = ({ export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => { // Don't render empty folders if displaying only icons - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const tree = buildTree(pages, !!labelStyle?.label?.hidden); const currentPageId = useStore((state) => state.currentPageId); diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index e71c26b28e..8931d8df1d 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -195,7 +195,7 @@ export const Viewer = ({ className={cx('viewer wrapper', { 'mobile-layout': currentLayout, 'theme-dark dark-theme': darkMode })} > - + {renderHeader()}
diff --git a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx index 94a43bee72..3e4a4b4810 100644 --- a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx +++ b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx @@ -9,7 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { RenderPageAndPageGroup } from './PageGroup'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ViewerSidebarNavigation = ({ isMobileDevice, @@ -21,7 +21,7 @@ export const ViewerSidebarNavigation = ({ isSidebarPinned, toggleSidebarPinned, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {}; const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index a9e1c58ae9..2032c08aa5 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -26,7 +26,7 @@ import cx from 'classnames'; import { useGridStore } from '@/_stores/gridStore'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ @@ -41,7 +41,7 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index fe1c433b83..e76070a1db 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -7,7 +7,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { debounce } from 'lodash'; var tinycolor = require('tinycolor2'); -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ id, @@ -21,7 +21,7 @@ export const Modal = function Modal({ dataCy, height, }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, diff --git a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx index 3c9329a3ea..01c07cf49f 100644 --- a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx +++ b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx @@ -16,7 +16,7 @@ import { createModalStyles } from '@/AppBuilder/Widgets/ModalV2/helpers/stylesFa import { onShowSideEffects, onHideSideEffects } from '@/AppBuilder/Widgets/ModalV2/helpers/sideEffects'; import '@/AppBuilder/Widgets/ModalV2/style.scss'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ModalV2 = function Modal({ id, @@ -30,7 +30,7 @@ export const ModalV2 = function Modal({ dataCy, height, }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 0473de3280..5921fd0e34 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -49,7 +49,7 @@ import { EmptyState } from './Components/EmptyState'; import { LoadingState } from './Components/LoadingState'; // eslint-disable-next-line import/no-unresolved import { useVirtualizer } from '@tanstack/react-virtual'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row const utilityForNestedNewRow = (row) => { @@ -88,7 +88,7 @@ export const Table = React.memo( // events, // setProperty, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const component = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const exposedNewRows = useStore((state) => state.getExposedValueOfComponent(id)?.newRows || [], shallow); const validateWidget = useStore((state) => state.validateWidget, shallow); diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index bbdf89d936..d0f2b96c31 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -10,6 +10,14 @@ export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor ); }; +export const useModuleContext = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useModuleContext must be used within a ModuleProvider'); + } + return context; +}; + export const useModuleId = () => { const context = useContext(ModuleContext); if (!context) { diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx index 2eada0e175..cb3ae6a1d1 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx @@ -4,10 +4,10 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { PromoteConfirmationModal } from './components'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx index f876dc86fe..46e05dadb8 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx @@ -9,10 +9,10 @@ import ArrowRightIcon from '@assets/images/icons/arrow-right.svg'; import '@/_styles/versions.scss'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteConfirmationModal = React.memo(({ data, onClose }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promotingEnvironment, setPromotingEnvironment] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true' || false; const currentVersionId = useStore((state) => state.currentVersionId); diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx index 3052f05d17..48b77cc238 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx @@ -8,10 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( From 5a90163c0b6a77930bf44bbc42f829a03c6ace8e Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 10:54:51 +0700 Subject: [PATCH 15/88] Fixed bug on running modules query from app. Passed version ID from modules property while running the queries --- frontend/ee | 2 +- frontend/src/AppBuilder/AppCanvas/Container.jsx | 1 + .../src/AppBuilder/_stores/slices/queryPanelSlice.js | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/ee b/frontend/ee index 425820a97a..b4e614bfdb 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 425820a97a5dbb3c8a26037f7f21252e465d07f1 +Subproject commit b4e614bfdb8fe855bd4141b1a33dca9ab9ab6c8e diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 78ec45823a..c6a1b979c1 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -85,6 +85,7 @@ export const Container = React.memo( return; } + // IMPORTANT: This logic needs to be changed when we implement the module versioning const moduleInfo = component?.moduleId ? { moduleId: component.moduleId, diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 733d2854f7..865bd1c8e5 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -284,7 +284,8 @@ export const createQueryPanelSlice = (set, get) => ({ } // const queryState = { ...getCurrentState(), parameters }; - const queryState = { ...get().getAllExposedValues('canvas'), parameters }; + const queryState = { ...get().getAllExposedValues(moduleId), parameters }; + const options = getQueryVariables(dataQuery.options, queryState, { components: get().getComponentNameIdMapping(moduleId), queries: get().getQueryNameIdMapping(moduleId), @@ -342,11 +343,16 @@ export const createQueryPanelSlice = (set, get) => ({ (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check ); } else { + let versionId = currentVersionId; + // IMPORTANT: This logic needs to be changed when we implement the module versioning + if (moduleId !== 'canvas') { + versionId = get().resolvedStore.modules.canvas.components[moduleId].properties.moduleVersionId; + } queryExecutionPromise = dataqueryService.run( queryId, options, query?.options, - currentVersionId, + versionId, !isPublicAccess ? (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id : undefined //TODO: currentAppEnvironmentId may no longer required. Need to check ); } From 58f640b1ed4a76cd9612c78274dbd858642e5dba Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 2 May 2025 02:42:21 +0530 Subject: [PATCH 16/88] Inspector revamped --- .../CustomJSONViewer/Components/ArrayNode.jsx | 11 ++ .../Components/BooleanNode.jsx | 12 ++ .../Components/FunctionNode.jsx | 12 ++ .../CustomJSONViewer/Components/NullNode.jsx | 12 ++ .../Components/NumberNode.jsx | 12 ++ .../Components/ObjectNode.jsx | 11 ++ .../CustomJSONViewer/Components/Row.jsx | 123 ++++++++++++++++++ .../Components/StringNode.jsx | 12 ++ .../CustomJSONViewer/CustomJSONViewer.jsx | 17 +++ .../CustomJSONViewer/styles.scss | 74 +++++++++++ .../LeftSidebarInspector/DefaultCopyIcon.jsx | 6 +- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 7 +- .../LeftSidebarInspector/JSONViewer.jsx | 42 +----- .../useCallbackActions.js | 8 +- .../LeftSidebar/LeftSidebarInspector/utils.js | 27 +--- 15 files changed, 313 insertions(+), 73 deletions(-) create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx new file mode 100644 index 0000000000..f9a0acf5ef --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const ArrayNode = ({ value }) => { + return ( +
+ {`[${value.length}]`} +
+ ); +}; + +export default ArrayNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx new file mode 100644 index 0000000000..d692a9c10c --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const BooleanNode = ({ value }) => { + return ( +
+ {value.toString()} +
+ ); +}; + +export default BooleanNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx new file mode 100644 index 0000000000..456b8238c4 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const FunctionNode = () => { + return ( +
+ function +
+ ); +}; + +export default FunctionNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx new file mode 100644 index 0000000000..40ff32737a --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NullNode = ({ value }) => { + return ( +
+ {value === null ? 'null' : 'undefined'} +
+ ); +}; + +export default NullNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx new file mode 100644 index 0000000000..8eb6e981b9 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NumberNode = ({ value }) => { + return ( +
+ {value} +
+ ); +}; + +export default NumberNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx new file mode 100644 index 0000000000..e89082cbd1 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const ObjectNode = ({ value }) => { + return ( +
+ {`{${Object.keys(value).length}}`} +
+ ); +}; + +export default ObjectNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx new file mode 100644 index 0000000000..ed5ef5a10c --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -0,0 +1,123 @@ +import React, { useState } from 'react'; +import StringNode from './StringNode'; +import FunctionNode from './FunctionNode'; +import NumberNode from './NumberNode'; +import BooleanNode from './BooleanNode'; +import NullNode from './NullNode'; +import ArrayNode from './ArrayNode'; +import ObjectNode from './ObjectNode'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ToolTip } from '@/_components/ToolTip'; +import { DefaultCopyIcon } from '../../DefaultCopyIcon'; +import { copyToClipboard } from '../../utils'; + +const Row = ({ label, value, level = 1, absolutePath }) => { + const [isExpanded, setIsExpanded] = useState(false); + const Node = () => { + if (typeof value === 'string') { + return ; + } else if (typeof value === 'undefined' || value === null) { + return ; + } else if (typeof value === 'number') { + return ; + } else if (typeof value === 'boolean') { + return ; + } else if (Array.isArray(value)) { + return ; + } else if (typeof value === 'object') { + return ; + } else if (typeof value === 'function') { + return ; + } + }; + + const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null; + const isArray = Array.isArray(value); + + return ( +
+
1 ? '8px' : '0px' }}> +
setIsExpanded((prev) => !prev)}> +
+ {(isArray || isObject) && + (isExpanded ? ( + + ) : ( + + ))} +
+
+ {label} +
+
+ +
+
+ + { + copyToClipboard(absolutePath); + }} + className="copy-to-clipboard json-viewer-action-icon" + > + + + + + { + copyToClipboard(value); + }} + className="json-viewer-action-icon" + > + + + +
+
+
+ {isExpanded && isObject && ( +
+ {Object.entries(value).map(([key, val]) => ( + + ))} +
+ )} + {isExpanded && isArray && ( +
+ {value.map((item, index) => { + return ( + + ); + })} +
+ )} +
+ ); +}; + +export default Row; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx new file mode 100644 index 0000000000..3c4bcc3644 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const StringNode = ({ value }) => { + return ( +
+ {`"${value}"`} +
+ ); +}; + +export default StringNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx new file mode 100644 index 0000000000..b5e392ee66 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Row from './Components/Row'; +import './styles.scss'; + +const CustomJSONViewer = ({ data, absolutePath }) => { + let modifiedData = data; + if (typeof data !== 'object') modifiedData = { '': data }; + return ( +
+ {Object.entries(modifiedData).map(([key, value], index) => { + return ; + })} +
+ ); +}; + +export default CustomJSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss new file mode 100644 index 0000000000..62a2adb902 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -0,0 +1,74 @@ +.custom-json-viewer { + margin-left: 16px; + margin-right: 16px; + font-family: "IBM Plex Sans"; + font-size: 12px; + color: var(--text-default, #1B1F24); + + + .json-viewer-row-container { + &:hover { + background-color: var(--interactive-overlays-fill-hover); + + .json-viewer-actions-container { + display: flex; + } + } + } + + .json-viewer-row { + width: 100%; + display: flex; + height: 20px; + align-items: center; + overflow: hidden; + + .json-viewer-expand-icon { + width: 12px; + height: 12px; + display: flex; + align-items: center; + justify-content: center; + } + + .json-viewer-label-container { + margin-left: 8px; + margin-right: 4px; + flex-shrink: 0; /* don’t shrink */ + white-space: nowrap; + } + + .json-viewer-value-container { + flex: 1; /* take available space */ + min-width: 0; /* allow shrinkage */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .json-viewer-actions-container { + display:none; + margin-left: auto; + margin-right: 4px; + height: 12px; + width:40px; + align-items: center; + flex-shrink: 0; + + .json-viewer-action-icon { + display: flex; + align-items: center; + justify-content: center; + height: 20px; + width: 20px; + + &:hover { + cursor: pointer; + background-color: var(--button-outline-hover); + border-radius: 4px; + } + } + } + + } +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx index fa46cf9694..6d26f2676b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -1,9 +1,9 @@ import React from 'react'; -export const DefaultCopyIcon = () => ( +export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( { const searchValue = useStore((state) => state.inspectorSearchValue, shallow); - // const getSelectedNodes = useStore((state) => state.getSelectedNodes, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); const [selectedNodePath, setSelectedNodePath] = React.useState(null); @@ -38,6 +37,7 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths return [new Set(result), expandedIdSet]; }, [searchValue, JSON.stringify(searchablePaths)]); + // Do not remove this code, once we have the data in the correct format, we can use this function to filter the data // const recursiveFn = (obj) => { // if (!obj || typeof obj !== 'object') return []; // let isCompletelyExposed = false; @@ -89,10 +89,9 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths return filtered.map((item) => item.id); }, [flattendedData, expandedIds]); - console.log('selectedData', selectedData); return ( <> - {!selectedNodePath || isEmpty(selectedData) ? ( + {!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
{ const { data, path, darkMode, backFn } = props; - const [theme, setTheme] = useState(() => getTheme(darkMode)); + const callbackActions = useCallbackActions() || []; const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions; @@ -23,10 +16,6 @@ export const JSONViewer = (props) => { const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || []; - useEffect(() => { - setTheme(() => getTheme(darkMode)); - }, [darkMode]); - return (
{ data={optionsData} type={type} /> - - { - const key = keyPath[0]; - if (!key && key != 0) return ''; - return key; - }} - valueRenderer={(raw, value) => { - if (typeof value === 'function') { - return ( - - function - - ); - } - - return {raw}; - }} - /> +
); }; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 6651e5813f..3b256afc2a 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -1,7 +1,7 @@ import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -// import { runQuery } from '@/AppBuilder/_utils/queryPanel'; +import { copyToClipboard } from './utils'; const useCallbackActions = () => { const deleteComponents = useStore((state) => state.deleteComponents, shallow); @@ -40,12 +40,6 @@ const useCallbackActions = () => { setSelectedQuery(id); }; - const copyToClipboard = (data) => { - const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); - navigator.clipboard.writeText(stringified); - return toast.success('Copied to the clipboard', { position: 'top-center' }); - }; - const handleAutoScrollToComponent = (data) => { const componentId = getComponentIdFromName(data.nodeName); const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 13ad6cc3f4..36a2afb8f5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -1,3 +1,5 @@ +import { toast } from 'react-hot-toast'; + export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => { if (typeof obj !== 'object' || obj === null) return []; const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); @@ -125,25 +127,8 @@ export const extractComponentName = (path) => { } }; -export const getTheme = (darkMode) => { - return { - scheme: 'custom', - author: 'chris kempson (http://chriskempson.com)', - base00: 'transparent', - base01: '#303030', - base02: '#505050', - base03: '#b0b0b0', - base04: '#d0d0d0', - base05: '#1B1F24', - base06: '#f5f5f5', - base07: '#ffffff', - base08: '#fb0120', - base09: '#9467BD', - base0A: '#fda331', - base0B: '#2CA02C', - base0C: '#76c7b7', - base0D: '#e4e0db', - base0E: '#d381c3', - base0F: '#be643c', - }; +export const copyToClipboard = (data) => { + const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); + navigator.clipboard.writeText(stringified); + return toast.success('Copied to the clipboard', { position: 'top-center' }); }; From 58078fdaeb4f3986bdf34f0f85ca7fc9f5daf6ae Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 2 May 2025 14:03:58 +0530 Subject: [PATCH 17/88] Updated to sprint 12 commits --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index dcd948d284..518f3334b1 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit dcd948d284b5f14a868480830e09b90496db8572 +Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a diff --git a/server/ee b/server/ee index 683647f83d..84ec48d0f6 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 +Subproject commit 84ec48d0f64fd6dc5f7677f71a5119219cc4ada4 From f1820e96206ddf5ee993915a1df619effec3afbe Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 6 May 2025 03:35:22 +0530 Subject: [PATCH 18/88] Inspector revamp fixes --- .../CustomJSONViewer/Components/Row.jsx | 2 +- .../CustomJSONViewer/styles.scss | 1 + .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 96 ++++++++++++------ .../LeftSidebarInspector.jsx | 23 ++--- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 45 ++++++--- .../LeftSidebarInspector/TreeViewHeader.jsx | 2 + .../LeftSidebar/LeftSidebarInspector/utils.js | 45 +-------- .../_stores/slices/inspectorSlice.js | 99 +++++++++++++++++++ frontend/src/_styles/theme.scss | 40 ++++++-- 9 files changed, 241 insertions(+), 112 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index ed5ef5a10c..ca8396875b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -43,7 +43,7 @@ const Row = ({ label, value, level = 1, absolutePath }) => { (isExpanded ? ( { const searchValue = useStore((state) => state.inspectorSearchValue, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getComponentDefinition = useStore((state) => state.getComponentDefinition, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); const [selectedNodePath, setSelectedNodePath] = React.useState(null); @@ -76,7 +78,27 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths setSelectedNodePath(null); }; - const selectedData = selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + const selectedData = (() => { + if (selectedNodePath?.startsWith('components.')) { + // Split the selectedNode path using . and grab the second element if it exists + const pathArray = selectedNodePath.split('.'); + const componentName = pathArray?.[1]; + const componentId = getComponentIdFromName(componentName); + const component = getComponentDefinition(componentId); + const parent = component?.component?.parent; + if (parent) { + const parentComponent = getComponentDefinition(parent); + const parentType = parentComponent?.component?.component; + if (parentType === 'Form') { + return { + id: componentId, + }; + } + } + } + return selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + })(); + const expandedIds = [...Array.from(pathSet), ...selectedNodes]; const filteredIds = useMemo(() => { @@ -86,7 +108,20 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths const { path } = metadata || {}; return expandedIdsSet.has(path); }); - return filtered.map((item) => item.id); + + return filtered + .map((item) => item.id) + .filter((path) => { + const pathArray = path.split('.'); + // One by one combine and check if the path is in expandedIds or not + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdsSet.has(parentPath)) { + return false; + } + } + return true; + }); }, [flattendedData, expandedIds]); return ( @@ -105,35 +140,36 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths width={300} />
+
+ { + const { element } = props; + const { metadata } = element || {}; + const { path } = metadata || {}; + const data = { + nodeName: element.name, + selectedNodePath: path, + }; - { - const { element } = props; - const { metadata } = element || {}; - const { path } = metadata || {}; - const data = { - nodeName: element.name, - selectedNodePath: path, - }; - - return ( - - ); - }} - /> + return ( + + ); + }} + /> +
) : ( diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 4e3dbf9c73..734f863080 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -7,8 +7,8 @@ import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; - -import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; +import { formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); @@ -18,6 +18,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); + const formatInspectorComponentData = useStore((state) => state.formatInspectorComponentData, shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants'])); @@ -109,18 +110,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
- setPinned(!pinned)} - darkMode={darkMode} - styles={{ width: '28px', padding: 0 }} - data-cy={`left-sidebar-inspector`} - variant="ghostBlack" - className="left-sidebar-header-btn" - leftIcon={pinned ? 'unpin' : 'pin'} - iconWidth="14" - fill={`var(--slate12)`} - > + variant="ghost" + fill="var(--icon-strong,#6A727C)" + size="medium" + />
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 27fafb22ae..1b01f48daf 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -9,6 +9,7 @@ import OverflowTooltip from '@/_components/OverflowTooltip'; import { HiddenOptions } from './HiddenOptions'; import useCallbackActions from './useCallbackActions'; import useStore from '@/AppBuilder/_stores/store'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; import { shallow } from 'zustand/shallow'; const renderNodeIcons = (node, iconsList, darkMode) => { @@ -56,21 +57,26 @@ export const Node = (props) => { const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow); const callbackActions = useCallbackActions() || []; const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const metadata = element.metadata || {}; - const { type } = metadata; + const { type, path } = metadata; const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for)); + const onExpand = (node) => { + const { element } = node || {}; + const { metadata } = element || {}; + const { path } = metadata || {}; + setSelectedNodes(path); + }; + const onSelect = (node) => { const { isBranch, element } = node || {}; const { metadata } = element || {}; - const { path } = metadata || {}; - if (!isBranch) { + const { path, type } = metadata || {}; + if (type) { setSelectedNodePath(path); - } else { - setSelectedNodePath(null); } - - setSelectedNodes(path); }; + const nodeSpecificFilteredActions = nodeSpecificActions?.[0]?.actions?.filter((action) => { return action.enableInspectorTreeView; @@ -84,27 +90,34 @@ export const Node = (props) => { return ( //
onSelect(props)} style={{ - marginLeft: 22 * (level - 1), + marginLeft: level > 1 ? 12 : 0, + // paddingLeft: '16px', opacity: isDisabled ? 0.5 : 1, height: level === 1 ? '28px' : '32px', display: 'flex', alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', - cursor: isBranch || level === 1 ? 'pointer' : 'default', + // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > - {(isBranch || level === 1) && ( + {!['queries', 'globals', 'variables'].includes(type) && (
- {isExpanded ? ( - - ) : ( - + {(isBranch || level === 1 || path === 'page.variables') && ( + onExpand(props)} + variant="ghost" + fill="var(--icon-default,#ACB2B9)" + size="small" + /> )}
)} +
onSelect(props)} className={cx('node-content', { 'node-content-hoverable': level !== 1, 'node-content-active': actionClicked, @@ -112,7 +125,7 @@ export const Node = (props) => { > {nodeIcon &&
{nodeIcon}
}
- + { className="copy-menu-options-icon json-viewer-options-btn" style={{ outline: 'none', + border: 'none', + boxShadow: 'none', }} > diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 36a2afb8f5..203b5ea298 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -19,7 +19,7 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) name, children: reduceData(value, currentPath, level + 1), metadata: { - type: 'misc', + type: type, path: currentPath, ...((path === 'page.variables' ? level === 2 : level === 1) && { data: typeof value === 'object' ? JSON.stringify(value) : value, @@ -33,49 +33,6 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) return reduceData(data, type); }; -export const formatInspectorComponentData = ( - componentIdNameMapping, - exposedComponentsVariables, - searchablePaths = new Set() -) => { - const data = Object.entries(componentIdNameMapping) - .map(([key, name]) => ({ - key, - name: name || key, - value: exposedComponentsVariables[key] ?? { id: key }, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); - - const reduceData = (obj, path = 'components', level = 1) => { - let data = obj; - if (!obj || typeof obj !== 'object' || level > 1) return []; - else if (!Array.isArray(obj)) { - data = Object.entries(obj); - } - return data - .filter((item) => item.name) - .reduce((acc, { key, name, value }) => { - const currentPath = path + `.${name}`; - searchablePaths.add(currentPath); - return [ - ...acc, - { - id: currentPath, - name, - children: reduceData(value, currentPath, level + 1), - metadata: { - type: 'components', - path: currentPath, - ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), - }, - }, - ]; - }, []); - }; - - return reduceData(data); -}; - export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => { const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); const _sortedQueries = Object.entries(exposedQueries) diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js index b0b8a95e84..521fff3b1b 100644 --- a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js @@ -31,4 +31,103 @@ export const createInspectorSlice = (set, get) => ({ setInspectorSearchResults: (results) => { set({ inspectorSearchResults: results }); }, + getAllComponentChildrenById: (id) => { + const { getComponentDefinition, getResolvedComponent } = get(); + const component = getComponentDefinition(id); + const componentType = component?.component?.component; + switch (componentType) { + case 'Container': + case 'Form': + case 'ModalV2': + return [ + ...get().getContainerChildrenMapping(id), + ...get().getContainerChildrenMapping(`${id}-header`), + ...get().getContainerChildrenMapping(`${id}-footer`), + ]; + case 'Tabs': { + const tabs = getResolvedComponent(id)?.properties?.tabs; + const children = Array.isArray(tabs) ? tabs : []; + const res = children + ?.map((tab) => { + const tabId = `${id}-${tab.id}`; + return get().getContainerChildrenMapping(tabId); + }) + .reduce((acc, curr) => { + return [...acc, ...curr]; + }, []); + return res; + } + default: + return get().getContainerChildrenMapping(id); + } + }, + + formatInspectorComponentData: ( + componentIdNameMapping, + exposedComponentsVariables, + searchablePaths = new Set(), + moduleId = 'canvas' + ) => { + const { getComponentDefinition, getAllComponentChildrenById } = get(); + const data = Object.entries(componentIdNameMapping) + .filter(([key]) => { + const component = getComponentDefinition(key, moduleId); + return !component?.component?.parent; + }) + .map(([key, name]) => { + const component = getComponentDefinition(key, moduleId); + let parentComponentType = null; + if (component?.component?.parent) { + const parentComponent = getComponentDefinition(component.component.parent, moduleId); + parentComponentType = parentComponent?.component?.component; + } + return { + key, + name: name || key, + parentType: parentComponentType, + }; + }) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj, path = 'components', level = 1) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + + return data + .filter((item) => item.name) + .reduce((acc, { key, name, parentType }) => { + const currentPath = `components.${name}`; + searchablePaths.add(currentPath); + const children = getAllComponentChildrenById(key).map((childKey) => { + const childComponent = getComponentDefinition(childKey); + let parentComponentType = null; + if (childComponent?.component?.parent) { + const parentComponent = getComponentDefinition(childComponent.component.parent); + parentComponentType = parentComponent?.component?.component; + } + return { + key: childKey, + name: childComponent?.component?.name, + parentType: parentComponentType, + }; + }); + + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(children, currentPath, level + 1), + metadata: { + type: 'components', + path: currentPath, + parentType: parentType, + }, + }, + ]; + }, []); + }; + + return reduceData(data); + }, }); diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 69bf8326e3..993dd6bdf3 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18877,12 +18877,24 @@ section.ai-message-prompt-input-wrapper { } } +.json-tree-view { + ul { + margin-left:16px !important; + border-left: 1px solid var(--slate6, #D7DBDF); + } + + ul[role="tree"] { + border-left: none !important; + } +} .basic.tree { list-style: none; margin: 0; - padding: 20px; + padding: 0px; + padding-right:20px; padding-top: 0px; + } .basic .tree-node, @@ -18919,6 +18931,7 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; justify-content: center; + flex-shrink: 0; } .node-icon { @@ -18932,6 +18945,8 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; font-size:12px; + flex:1; + min-width: 0px; } @@ -19016,16 +19031,24 @@ section.ai-message-prompt-input-wrapper { } +// .json-viewer-options-btn { +// display: flex; +// align-items: center; +// margin-left: auto; + +// &:hover { +// cursor: pointer; +// background-color:var(--interactive-overlays-fill-hover); +// border-radius: 4px; +// } +// } + .json-viewer-options-btn { - display: flex; - align-items: center; margin-left: auto; - &:hover { - cursor: pointer; - background-color:var(--interactive-overlays-fill-hover); - border-radius: 4px; - } + + align-items: center; + } @@ -19052,6 +19075,7 @@ section.ai-message-prompt-input-wrapper { width: 20px; border-radius: 4px; margin-right:4px; + flex-shrink: 0; &:hover { background-color: var(--button-outline-hover); From 4804ae4a706a7d504439b118e17a853cdf117f22 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 11:40:22 +0700 Subject: [PATCH 19/88] Merged with main & fixed the bugs --- .../GlobalSettings/MaintenanceMode.jsx | 4 +++- .../LeftSidebar/PageMenu/PagePermission.jsx | 10 +++++++--- frontend/src/AppBuilder/Viewer/Viewer.jsx | 2 +- frontend/src/AppBuilder/_hooks/useAppData.js | 15 ++++++++++----- frontend/src/_helpers/routes.js | 1 + server/ee | 2 +- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx index c2b969146f..8b1b1cece0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx @@ -3,12 +3,14 @@ import useStore from '@/AppBuilder/_stores/store'; import SwitchComponent from '@/components/ui/Switch/Index'; import { shallow } from 'zustand/shallow'; import { Confirm } from '@/Editor/Viewer/Confirm'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const MaintenanceMode = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const [showConfirmation, setConfirmationShow] = useState(false); const { isMaintenanceOn, toggleAppMaintenance } = useStore( (state) => ({ - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, toggleAppMaintenance: state.toggleAppMaintenance, }), shallow diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx index 6a4a1c516a..92464e4573 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx @@ -8,6 +8,7 @@ import { appPermissionService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import toast from 'react-hot-toast'; import Spinner from '@/_ui/Spinner'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PERMISSION_TYPES = { single: 'SINGLE', @@ -16,10 +17,11 @@ const PERMISSION_TYPES = { }; export default function PagePermission({ darkMode }) { + const { moduleId } = useModuleContext(); const showPagePermissionModal = useStore((state) => state.showPagePermissionModal); const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal); const editingPage = useStore((state) => state.editingPage); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const selectedUserGroups = useStore((state) => state.selectedUserGroups); const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups); const selectedUsers = useStore((state) => state.selectedUsers); @@ -360,7 +362,8 @@ export default function PagePermission({ darkMode }) { } const UserGroupSelect = () => { - const appId = useStore((state) => state.app.appId); + const { moduleId } = useModuleContext(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const selectedUserGroups = useStore((state) => state.selectedUserGroups); const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups); const [userGroups, setUserGroups] = useState([]); @@ -420,7 +423,8 @@ const UserGroupSelect = () => { }; const UserSelect = () => { - const appId = useStore((state) => state.app.appId); + const { moduleId } = useModuleContext(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const editingPage = useStore((state) => state.editingPage); const selectedUsers = useStore((state) => state.selectedUsers); const setSelectedUsers = useStore((state) => state.setSelectedUsers); diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index 8931d8df1d..48bfc7e632 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -202,7 +202,7 @@ export const Viewer = ({
diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 9440efca40..18abcc63be 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -358,13 +358,13 @@ const useAppData = ( global_settings.theme = baseTheme; } setGlobalSettings(global_settings); - - setPages(pages, moduleId); - setPageSettings( - computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) - ); } + setPages(pages, moduleId); + setPageSettings( + computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) + ); + // set starting page as homepage initially let startingPage = appData.pages.find((page) => page.id === homePageId); @@ -513,10 +513,15 @@ const useAppData = ( }; }) .catch((error) => { + console.log('error--- ', error); + if (isPublicAccess) { if (mode !== 'edit') { handleError('view', error); } + } else if (moduleMode) { + setEditorLoading(false, moduleId); + toast.error('Error fetching module data'); } }); }, [setApp, setEditorLoading, currentSession]); diff --git a/frontend/src/_helpers/routes.js b/frontend/src/_helpers/routes.js index 0f552cb955..495ca04e18 100644 --- a/frontend/src/_helpers/routes.js +++ b/frontend/src/_helpers/routes.js @@ -21,6 +21,7 @@ export const getPrivateRoute = (page, params = {}) => { workflows: '/workflows', workspace_constants: '/workspace-constants', profile_settings: '/profile-settings', + modules: '/modules', }; let url = routes[page]; diff --git a/server/ee b/server/ee index e4bada4648..f779cb9954 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e4bada464821218151cfa0c6ed1c7b31e18fa2c4 +Subproject commit f779cb99549ae248dbca9e3d0f658359651ede75 From 9e4ac4963921d2741226c30251c8ffd34553a893 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:02:04 +0700 Subject: [PATCH 20/88] Fix submodule reference --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index f779cb9954..7cc6976f78 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit f779cb99549ae248dbca9e3d0f658359651ede75 +Subproject commit 7cc6976f7801f538c64b1b13679c4d9117a8baa1 From fa89057aedccd523f02254b575d147a2f3ffefe9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:09:41 +0700 Subject: [PATCH 21/88] Fixed the frontend/ee reference and BE error --- frontend/ee | 2 +- server/src/modules/modules/module.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 0d68bbb8d9..1594d113de 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 0d68bbb8d929d3a06df845e7292d086980b0dc9d +Subproject commit 1594d113de132885db9e9b1263299692a74abcbc diff --git a/server/src/modules/modules/module.ts b/server/src/modules/modules/module.ts index dd871434d0..a7af218b81 100644 --- a/server/src/modules/modules/module.ts +++ b/server/src/modules/modules/module.ts @@ -11,6 +11,7 @@ import { VersionRepository } from '@modules/versions/repository'; import { DataSourcesModule } from '@modules/data-sources/module'; import { AiModule } from '@modules/ai/module'; import { AppsRepository } from '@modules/apps/repository'; +import { AppPermissionsModule } from '@modules/app-permissions/module'; @Module({}) export class ModulesModule { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { @@ -33,6 +34,7 @@ export class ModulesModule { await AppEnvironmentsModule.register(configs), await DataSourcesModule.register(configs), await AiModule.register(configs), + await AppPermissionsModule.register(configs), ], controllers: [ModulesController], providers: [ From c2e448e039e6e48413b513cd6270707b5dd885cb Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:56:36 +0700 Subject: [PATCH 22/88] Hide the global settings --- frontend/ee | 2 +- .../LeftSidebar/GlobalSettings/index.jsx | 19 +-------------- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 24 +++++++++---------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/frontend/ee b/frontend/ee index 1594d113de..5d358dc6e0 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 1594d113de132885db9e9b1263299692a74abcbc +Subproject commit 5d358dc6e0d3780383ed6b392a2e048b55e15c3b diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx index 3495239f41..d85917c797 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx @@ -11,26 +11,9 @@ import { ThemeSelect } from '@/modules/Appbuilder/components'; import MaintenanceMode from './MaintenanceMode'; import HideHeaderToggle from './HideHeaderToggle'; -const GlobalSettings = ({ darkMode, isModuleEditor }) => { +const GlobalSettings = ({ darkMode }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); - if (isModuleEditor) { - return ( -
-
- - - -
-
- -
-
-
-
- ); - } - return ( <>
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 0f49eb8ef9..74c25264f1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -192,18 +192,6 @@ export const BaseLeftSidebar = ({ tip="Debugger" ref={setSideBarBtnRefs('debugger')} /> - handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - isModuleEditor={isModuleEditor} - /> ); }; @@ -233,6 +221,18 @@ export const BaseLeftSidebar = ({ ref={setSideBarBtnRefs('page')} /> {renderCommonItems()} + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> ); }; From 4080f55f6e3c472f933d1f54f8540d808bd4af93 Mon Sep 17 00:00:00 2001 From: parthy007 Date: Tue, 18 Mar 2025 13:46:48 +0530 Subject: [PATCH 23/88] Fix: Connection for Salesforce, Google sheet, Slack , Grpc, Zendesk & Woocommerce Fetch the auth code using correct method in request Get the plugin object correctly for reloading Fix woocommerce operation rendering Resolve promise correctly Pass plugin_id as prop Resolve plugin saving issue with caching tokens Add json-message in query correctly Use auth code once without cache --- frontend/src/_components/Slack.jsx | 14 ++- frontend/src/_components/Zendesk.jsx | 15 ++- frontend/src/_services/datasource.service.js | 2 +- plugins/packages/grpc/lib/index.ts | 11 +- plugins/packages/grpc/lib/types.ts | 1 + plugins/packages/woocommerce/.gitignore | 3 +- .../packages/woocommerce/lib/operations.json | 105 ++++++++++++++++++ server/src/modules/data-sources/controller.ts | 2 +- .../src/modules/data-sources/util.service.ts | 19 ++-- server/src/modules/plugins/service.ts | 2 +- 10 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 plugins/packages/woocommerce/lib/operations.json diff --git a/frontend/src/_components/Slack.jsx b/frontend/src/_components/Slack.jsx index d986bccd5f..1301572357 100644 --- a/frontend/src/_components/Slack.jsx +++ b/frontend/src/_components/Slack.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { datasourceService } from '@/_services'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-hot-toast'; @@ -15,8 +15,16 @@ const Slack = ({ isDisabled, }) => { const [authStatus, setAuthStatus] = useState(null); - const whiteLabelText = retrieveWhiteLabelText(); + const [whiteLabelText, setWhiteLabelText] = useState(''); + const plugin_id = selectedDataSource?.plugin?.id; const { t } = useTranslation(); + useEffect(() => { + async function fetchLabel() { + const text = await retrieveWhiteLabelText(); + setWhiteLabelText(text); + } + fetchLabel(); + }, []); function authGoogle() { const provider = 'slack'; @@ -29,7 +37,7 @@ const Slack = ({ } datasourceService - .fetchOauth2BaseUrl(provider) + .fetchOauth2BaseUrl(provider, plugin_id, {}) .then((data) => { const authUrl = `${data.url}&scope=${scope}&access_type=offline&prompt=select_account`; diff --git a/frontend/src/_components/Zendesk.jsx b/frontend/src/_components/Zendesk.jsx index 7d03035152..c61927c52e 100644 --- a/frontend/src/_components/Zendesk.jsx +++ b/frontend/src/_components/Zendesk.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import Input from '@/_ui/Input'; @@ -18,17 +18,26 @@ const Zendesk = ({ isDisabled, optionsChanged, }) => { + const [whiteLabelText, setWhiteLabelText] = useState(''); const [authStatus, setAuthStatus] = useState(null); - const whiteLabelText = retrieveWhiteLabelText(); + useEffect(() => { + async function fetchLabel() { + const text = await retrieveWhiteLabelText(); + setWhiteLabelText(text); + } + fetchLabel(); + }, []); function authZendesk() { const provider = 'zendesk'; setAuthStatus('waiting_for_url'); const scope = options?.access_type?.value === 'read' ? 'read' : 'read%20write'; + const subDomain = options?.subdomain?.value; + const client_id = options?.client_id?.value; try { - const authUrl = `https://${options?.subdomain?.value}.zendesk.com/oauth/authorizations/new?response_type=code&client_id=${options?.client_id?.value}&redirect_uri=${window.location.origin}/oauth2/authorize&scope=${scope}`; + const authUrl = `https://${subDomain}.zendesk.com/oauth/authorizations/new?response_type=code&client_id=${client_id}&redirect_uri=${window.location.origin}/oauth2/authorize&scope=${scope}`; localStorage.setItem('sourceWaitingForOAuth', 'newSource'); localStorage.setItem('currentAppEnvironmentIdForOauth', currentAppEnvironmentId); optionchanged('provider', provider).then(() => { diff --git a/frontend/src/_services/datasource.service.js b/frontend/src/_services/datasource.service.js index 9ea24e99d0..49a6ade912 100644 --- a/frontend/src/_services/datasource.service.js +++ b/frontend/src/_services/datasource.service.js @@ -98,7 +98,7 @@ function setOauth2Token(dataSourceId, body, current_organization_id) { function fetchOauth2BaseUrl(provider, plugin_id = null, source_options = {}) { const payload = { provider, ...(plugin_id && { plugin_id }), ...(source_options && { source_options }) }; const requestOptions = { - method: 'GET', + method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(payload), diff --git a/plugins/packages/grpc/lib/index.ts b/plugins/packages/grpc/lib/index.ts index 30aa514c22..a118aadfb8 100644 --- a/plugins/packages/grpc/lib/index.ts +++ b/plugins/packages/grpc/lib/index.ts @@ -46,8 +46,17 @@ export default class GRPC implements QueryService { metadata.add(sourceOptions.grpc_apikey_key, sourceOptions.grpc_apikey_value); } + let jsonMessage = {}; + if (queryOptions.jsonMessage) { + try { + jsonMessage = JSON.parse(queryOptions.jsonMessage); + } catch (e) { + throw new QueryError('Invalid JSON message', {}, {}); + } + } + const result = await new Promise((resolve, reject) => { - clientStub[rpc]({}, metadata, (err: any, response: any) => { + clientStub[rpc](jsonMessage, metadata, (err: any, response: any) => { if (err) { reject(err); } diff --git a/plugins/packages/grpc/lib/types.ts b/plugins/packages/grpc/lib/types.ts index 5b3865c609..d38528276d 100644 --- a/plugins/packages/grpc/lib/types.ts +++ b/plugins/packages/grpc/lib/types.ts @@ -11,5 +11,6 @@ export type SourceOptions = { export type QueryOptions = { operation: string; serviceName: string; + jsonMessage: string; rpc: string; }; diff --git a/plugins/packages/woocommerce/.gitignore b/plugins/packages/woocommerce/.gitignore index cf1107688f..4c5f09d7c4 100644 --- a/plugins/packages/woocommerce/.gitignore +++ b/plugins/packages/woocommerce/.gitignore @@ -1,5 +1,4 @@ node_modules lib/*.d.* lib/*.js -lib/*.js.map -lib/operations.json \ No newline at end of file +lib/*.js.map \ No newline at end of file diff --git a/plugins/packages/woocommerce/lib/operations.json b/plugins/packages/woocommerce/lib/operations.json new file mode 100644 index 0000000000..e6041e3ee5 --- /dev/null +++ b/plugins/packages/woocommerce/lib/operations.json @@ -0,0 +1,105 @@ +{ + "title": "Woocommerce datasource", + "description": "A schema defining Woocommerce datasource", + "type": "api", + "defaults": {}, + "properties": { + "resource": { + "label": "Resource", + "key": "resource", + "className": "col-md-4", + "type": "dropdown-component-flip", + "description": "Resource select", + "list": [ + { "value": "product", "name": "Product" }, + { "value": "customer", "name": "Customer" }, + { "value": "order", "name": "Order" }, + { "value": "coupon", "name": "Coupon" } + ] + }, + "customer": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_customer", "name": "List all customers" }, + { "value": "update_customer", "name": "Update a customer" }, + { "value": "delete_customer", "name": "Delete a customer" }, + { "value": "batch_update_customer", "name": "Batch update customers" }, + { "value": "create_customer", "name": "Create a customer" }, + { "value": "retrieve_customer", "name": "Retrieve a customer" } + ] + }, + "list_customer": { + "page": { + "label": "Page", + "key": "page", + "type": "codehinter", + "description": "Enter page", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "", + "lineNumbers": false + }, + "context": { + "label": "Context", + "key": "context", + "type": "codehinter", + "description": "Enter context", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "", + "lineNumbers": false + } + } + }, + "product": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_product", "name": "List all products" }, + { "value": "update_product", "name": "Update a product" }, + { "value": "delete_product", "name": "Delete a product" }, + { "value": "batch_update_product", "name": "Batch update products" }, + { "value": "create_product", "name": "Create a product" }, + { "value": "retrieve_product", "name": "Retrieve a product" } + ] + } + }, + "order": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_order", "name": "List all orders" }, + { "value": "update_order", "name": "Update an order" }, + { "value": "delete_order", "name": "Delete an order" }, + { "value": "batch_update_order", "name": "Batch update orders" }, + { "value": "create_order", "name": "Create an order" }, + { "value": "retrieve_order", "name": "Retrieve an order" } + ] + } + }, + "coupon": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_coupon", "name": "List all coupons" }, + { "value": "create_coupon", "name": "Create a coupon" } + ] + } + } + } +} diff --git a/server/src/modules/data-sources/controller.ts b/server/src/modules/data-sources/controller.ts index a0b1845a3d..d4352c9e9f 100644 --- a/server/src/modules/data-sources/controller.ts +++ b/server/src/modules/data-sources/controller.ts @@ -111,7 +111,7 @@ export class DataSourcesController implements IDataSourcesController { @InitFeature(FEATURE_KEY.GET_OAUTH2_BASE_URL) @UseGuards(FeatureAbilityGuard) - @Get('fetch-oauth2-base-url') + @Post('fetch-oauth2-base-url') getAuthUrl(@Body() getDataSourceOauthUrlDto: GetDataSourceOauthUrlDto) { return this.dataSourcesService.getAuthUrl(getDataSourceOauthUrlDto); } diff --git a/server/src/modules/data-sources/util.service.ts b/server/src/modules/data-sources/util.service.ts index 961f817064..03ad5ddd52 100644 --- a/server/src/modules/data-sources/util.service.ts +++ b/server/src/modules/data-sources/util.service.ts @@ -189,17 +189,16 @@ export class DataSourcesUtilService implements IDataSourcesUtilService { /* Basic plan customer. lets update all environment options. this will help us to run the queries successfully when the user buys enterprise plan - */ - await Promise.all( - allEnvs.map(async (envToUpdate) => { - dataSource.options = ( - await this.appEnvironmentUtilService.getOptions(dataSourceId, organizationId, envToUpdate.id) - ).options; + */ - const newOptions = await this.parseOptionsForUpdate(dataSource, options, manager); - await this.appEnvironmentUtilService.updateOptions(newOptions, envToUpdate.id, dataSource.id, manager); - }) - ); + const newOptions = await this.parseOptionsForUpdate(dataSource, options, manager); + for (const env of allEnvs) { + dataSource.options = ( + await this.appEnvironmentUtilService.getOptions(dataSourceId, organizationId, env.id) + ).options; + + await this.appEnvironmentUtilService.updateOptions(newOptions, env.id, dataSource.id, manager); + } } const updatableParams = { id: dataSourceId, diff --git a/server/src/modules/plugins/service.ts b/server/src/modules/plugins/service.ts index 48398d5ebe..de1709eba7 100644 --- a/server/src/modules/plugins/service.ts +++ b/server/src/modules/plugins/service.ts @@ -47,7 +47,7 @@ export class PluginsService implements IPluginsService { async findOne(id: string) { return dbTransactionWrap((manager: EntityManager) => { - return manager.find(Plugin, { where: { id } }); + return manager.findOne(Plugin, { where: { id } }); }); } From 2f7492635ac9a0f58475303ed1e0744fe1561961 Mon Sep 17 00:00:00 2001 From: Parth <108089718+parthy007@users.noreply.github.com> Date: Wed, 7 May 2025 12:30:49 +0530 Subject: [PATCH 24/88] Fix: Prevent exposure of encrypted credentials during app export. (#12449) * Fix keys mismatch * Fix jira export issue --- .../DataSourceManager/DataSourceManager.jsx | 18 +++++++++++++++--- marketplace/plugins/jira/lib/manifest.json | 8 ++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx index 65ee96758b..96f72f0c18 100644 --- a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx @@ -21,7 +21,7 @@ import config from 'config'; import { capitalize, isEmpty } from 'lodash'; import { Card } from '@/_ui/Card'; import { withTranslation, useTranslation } from 'react-i18next'; -import { camelizeKeys, decamelizeKeys } from 'humps'; +import { camelizeKeys, decamelizeKeys, decamelize } from 'humps'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import { useAppVersionStore } from '@/_stores/appVersionStore'; @@ -248,14 +248,26 @@ class DataSourceManagerComponent extends React.Component { const scope = this.state?.scope || selectedDataSource?.scope; const parsedOptions = Object?.keys(options)?.map((key) => { - const keyMeta = dataSourceMeta.options[key]; + let keyMeta = dataSourceMeta.options[key]; + let isEncrypted = false; + if (keyMeta) { + isEncrypted = keyMeta.encrypted; + } + + // to resolve any casing mis-match + if (decamelize(key) !== key) { + const newKey = decamelize(key); + isEncrypted = dataSourceMeta.options[newKey].encrypted; + } + return { key: key, value: options[key].value, - encrypted: keyMeta ? keyMeta.encrypted : false, + encrypted: isEncrypted, ...(!options[key]?.value && { credential_id: options[key]?.credential_id }), }; }); + if (OAuthDs.includes(kind)) { const value = localStorage.getItem('OAuthCode'); parsedOptions.push({ key: 'code', value, encrypted: false }); diff --git a/marketplace/plugins/jira/lib/manifest.json b/marketplace/plugins/jira/lib/manifest.json index 4d9abdb583..210e6328f8 100644 --- a/marketplace/plugins/jira/lib/manifest.json +++ b/marketplace/plugins/jira/lib/manifest.json @@ -15,6 +15,9 @@ "options": { "url": { "type": "string" + }, + "personal_token": { + "encrypted": true } } }, @@ -57,11 +60,12 @@ "key": "personal_token", "type": "password", "description": "Enter your api token", - "hint": "You can generate a personal access token from your Jira account 'Manage account'." + "hint": "You can generate a personal access token from your Jira account 'Manage account'.", + "encrypted": true } } }, "required": [ "url" ] -} +} \ No newline at end of file From 53ba068947fd4cff18d6d51dc96a250471dcf567 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 8 May 2025 03:27:27 +0530 Subject: [PATCH 25/88] Design changes done --- .../LeftSidebarInspector/HiddenOptions.jsx | 7 +-- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 15 ++++++- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 44 ++++++++++--------- .../LeftSidebarInspector/TreeViewHeader.jsx | 29 ++++++------ .../LeftSidebarInspector/useIconList.js | 33 ++++++++++---- frontend/src/_styles/components.scss | 5 +-- frontend/src/_styles/theme.scss | 39 +++++++++++----- .../ui/Input/CommonInput/TextInput.jsx | 1 + frontend/src/components/ui/Input/Index.jsx | 2 + frontend/src/components/ui/Input/Input.jsx | 2 +- 10 files changed, 112 insertions(+), 65 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index c2055f4b8e..a2a4cebf80 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -74,7 +74,7 @@ export const HiddenOptions = (props) => { {renderOptions()} { setShowMenu((prev) => !prev); setActionClicked((prev) => !prev); }} - className="copy-menu-options-icon" + className="node-action-icon" style={{ outline: 'none', + ...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }), }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 43d29ed12c..a47d9fa060 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -7,6 +7,7 @@ import JSONViewer from './JSONViewer'; import { SearchBox } from '@/_components'; import { Node } from './Node'; import { v4 as uuidv4 } from 'uuid'; +import InputComponent from '@/components/ui/Input/Index'; import { isEmpty } from 'lodash'; const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { @@ -128,8 +129,8 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths <> {!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
-
- + {/* setSearchValue(e.target.value)} @@ -138,6 +139,16 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths customClass={`tj-inspector-search-input tj-text-xsm`} showClearButton={false} width={300} + /> */} + + setSearchValue(e.target.value)} + onClear={() => setSearchValue('')} + size="medium" + placeholder="Search" + value={searchValue} + {...(searchValue && { trailingAction: 'clear' })} />
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 1b01f48daf..86dffbf180 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -19,20 +19,21 @@ const renderNodeIcons = (node, iconsList, darkMode) => { ); } + if (icon && icon.jsx) { if (icon?.tooltipMessage) { return ( -
{icon.jsx()}
+
{icon.jsx({ height: 14, width: 14 })}
); } - return icon.jsx(); + return icon.jsx({ height: 14, width: 14 }); } }; @@ -91,30 +92,31 @@ export const Node = (props) => { //
1 ? 12 : 0, + // marginLeft: level > 1 ? 12 : 0, // paddingLeft: '16px', opacity: isDisabled ? 0.5 : 1, - height: level === 1 ? '28px' : '32px', + height: '20px', display: 'flex', alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', + fontWeight: level === 1 ? 500 : 400, // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > - {!['queries', 'globals', 'variables'].includes(type) && ( -
- {(isBranch || level === 1 || path === 'page.variables') && ( - onExpand(props)} - variant="ghost" - fill="var(--icon-default,#ACB2B9)" - size="small" - /> - )} -
- )} + {/* {!['queries', 'globals', 'variables'].includes(type) && ( */} +
+ {(isBranch || level === 1 || path === 'page.variables') && ( + onExpand(props)} + variant="ghost" + fill="var(--icon-default,#ACB2B9)" + size="small" + /> + )} +
+ {/* )} */}
onSelect(props)} @@ -134,7 +136,7 @@ export const Node = (props) => { />
-
+
{ return (
-
+ {/*
+
*/} +
+ {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} + + {pathArray.length > 1 && + pathArray.slice(1).map((item, index) => ( + <> + + + {item.charAt(0).toUpperCase() + item.slice(1)} + + + ))}
- - {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} - - - {pathArray.length > 1 && - pathArray.slice(1).map((item, index) => ( - <> - - - {item.charAt(0).toUpperCase() + item.slice(1)} - - - ))} - { const query = dataQueries.find((dataQuery) => dataQuery.id === queryId); if (!isEmpty(query)) { - return { iconName: query?.name, jsx: () => }; + return { + iconName: query?.name, + jsx: ({ height = 16, width = 16 }) => , + }; } }); @@ -37,7 +40,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -47,7 +52,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -62,7 +69,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.setChecked) { icons.push({ iconName: 'setChecked', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setValue as an alternative', isInfoIcon: true, @@ -78,7 +87,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -88,7 +99,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -98,7 +111,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.loading) { icons.push({ iconName: 'loading', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setLoading as an alternative', isInfoIcon: true, @@ -112,7 +127,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos return [ { iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 9fbe2f33f4..27736f0a6e 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -95,10 +95,7 @@ $btn-dark-color: #FFFFFF; } .leftsidebar-panel-header { - // background-color: var(--slate3); - padding: 12px 16px 8px 16px; - min-height: 52px; - // border-bottom: 1px solid var(--slate5); + padding: 12px 16px 0px 16px; .panel-header-container { diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 993dd6bdf3..03d5f363a9 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18878,13 +18878,24 @@ section.ai-message-prompt-input-wrapper { } .json-tree-view { - ul { - margin-left:16px !important; - border-left: 1px solid var(--slate6, #D7DBDF); - } + // ul { + // margin-left:16px !important; + // border-left: 1px solid var(--slate6, #D7DBDF); + // } ul[role="tree"] { + margin-left:16px !important; border-left: none !important; + ul { + margin-left:16px !important; + border-left: none !important; + ul { + margin-left:10px !important; + padding-left:16px !important; + border-left: 1px solid var(--slate6, #D7DBDF) !important; + } + } + } } @@ -18936,9 +18947,11 @@ section.ai-message-prompt-input-wrapper { .node-icon { display: flex; - align-items: center; + align-items: flex-end; justify-content: center; margin-right: 6px; + height: 14px; + width: 14px; } .node-label { @@ -18964,10 +18977,10 @@ section.ai-message-prompt-input-wrapper { .node-content { display: flex; align-items: center; - padding: 0px 8px; + padding: 0px 0px 0px 8px; height: 100%; width: 100%; - border-radius: 6px; + } .node-content-active { @@ -18978,6 +18991,7 @@ section.ai-message-prompt-input-wrapper { .node-content-hoverable:hover, .node-content-active { background-color: var(--interactive-overlays-fill-hover); + cursor: pointer; .node-actions { display: flex; } @@ -19014,9 +19028,13 @@ section.ai-message-prompt-input-wrapper { font-weight: 500; display: flex; flex-direction: row; - margin-left:20px; - margin-bottom:18px; + margin-left:16px; + margin-bottom:12px; margin-right: 18px; + margin-top: 8px; + height:28px; + align-items: center; + } .json-viewer-back-btn { @@ -19058,11 +19076,9 @@ section.ai-message-prompt-input-wrapper { } .node-actions { - justify-content: center; align-items: center; margin-left:auto; - margin-right: 24px; display: none; gap: 4px; } @@ -19104,6 +19120,7 @@ section.ai-message-prompt-input-wrapper { border: none; background: transparent; border-radius: 10px; + top: -5px !important; &.dark-theme { .popover-body { box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx index 1834d4799d..1dc3ce8d67 100644 --- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx +++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx @@ -46,6 +46,7 @@ const TextInput = ({ diff --git a/frontend/src/components/ui/Input/Index.jsx b/frontend/src/components/ui/Input/Index.jsx index 139019a198..dbb6806d6b 100644 --- a/frontend/src/components/ui/Input/Index.jsx +++ b/frontend/src/components/ui/Input/Index.jsx @@ -13,6 +13,7 @@ InputComponent.propTypes = { type: PropTypes.oneOf(['text', 'number', 'editable title', 'password', 'email']), value: PropTypes.string, onChange: PropTypes.func, + onClear: PropTypes.func, placeholder: PropTypes.string, name: PropTypes.string, id: PropTypes.string, @@ -32,6 +33,7 @@ InputComponent.propTypes = { InputComponent.defaultProps = { type: 'text', onChange: (e, validateObj) => {}, + onClear: () => {}, placeholder: '', name: '', id: '', diff --git a/frontend/src/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 33c3194f0b..610232eadf 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -19,7 +19,7 @@ const Input = React.forwardRef(({ className, size, type, ...props }, ref) => { type={isPasswordField && isPasswordVisible ? 'text' : type} className={cn( inputVariants({ size }), - `tw-relative tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`, + `tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`, className )} ref={ref} From 52abc1e283b8c33b4e3e018269d9b469bb40dd77 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 8 May 2025 15:56:12 +0700 Subject: [PATCH 26/88] Fix: Fixed issue on running queries from app & duplicate component --- .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 2 +- .../_stores/slices/queryPanelSlice.js | 33 ++++++++++++------- .../_stores/slices/resolvedSlice.js | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 5177e27af8..018383c61f 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -78,7 +78,7 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } return componentType === 'ModuleContainer'; }) ) { - if (['KeyC', 'KeyX', 'KeyV', 'Backspace'].includes(key)) { + if (['KeyC', 'KeyX', 'KeyV', 'KeyD', 'Backspace'].includes(key)) { return; } } diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 59f3ddba98..7344f54aec 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -471,7 +471,8 @@ export const createQueryPanelSlice = (set, get) => ({ query.options.transformation, query.options.transformationLanguage, query, - 'edit' + 'edit', + moduleId ); if (finalData.status === 'failed') { setResolvedQuery( @@ -683,7 +684,8 @@ export const createQueryPanelSlice = (set, get) => ({ query.options.transformation, query.options.transformationLanguage, query, - 'edit' + 'edit', + moduleId ); if (finalData?.status === 'failed') { onEvent('onDataQueryFailure', queryEvents); @@ -743,7 +745,7 @@ export const createQueryPanelSlice = (set, get) => ({ let result = {}; try { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); const queriesInCurentState = deepClone(resolvedState.queries); const appStateVars = deepClone(resolvedState.variables) ?? {}; if (!isEmpty(query)) { @@ -758,17 +760,17 @@ export const createQueryPanelSlice = (set, get) => ({ }, getData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].data; }, getRawData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].rawData; }, getloadingState: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].isLoading; }, }; @@ -810,14 +812,21 @@ export const createQueryPanelSlice = (set, get) => ({ return pyodide.isPyProxy(result) ? convertMapSet(result.toJs()) : result; }, - runTransformation: async (rawData, transformation, transformationLanguage = 'javascript', query, mode = 'edit') => { + runTransformation: async ( + rawData, + transformation, + transformationLanguage = 'javascript', + query, + mode = 'edit', + moduleId = 'canvas' + ) => { const data = rawData; const { queryPanel: { runPythonTransformation, createProxy }, getResolvedState, } = get(); let result = {}; - const currentState = getResolvedState(); + const currentState = getResolvedState(moduleId); if (transformationLanguage === 'python') { result = await runPythonTransformation(currentState, data, transformation, query, mode); @@ -1012,7 +1021,7 @@ export const createQueryPanelSlice = (set, get) => ({ //this will handle the preview case where you cannot find the queryDetails in state. formattedParams = { ...parameters }; } - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); const queriesInResolvedState = deepClone(resolvedState.queries); for (const key of Object.keys(resolvedState.queries)) { queriesInResolvedState[key] = { @@ -1028,17 +1037,17 @@ export const createQueryPanelSlice = (set, get) => ({ }, getData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].data; }, getRawData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].rawData; }, getloadingState: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].isLoading; }, }; diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 16de3dc461..46627307f9 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -533,7 +533,7 @@ export const createResolvedSlice = (set, get) => ({ }, // this function simply replaces the id with name for queries and components inside resolvedStore - getResolvedState: (key, moduleId = 'canvas') => { + getResolvedState: (moduleId = 'canvas', key) => { const state = { components: {}, queries: {}, From 2ec0506c8f75ed89d21461a4d78c7b7944d24dd4 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Sun, 11 May 2025 17:00:57 +0530 Subject: [PATCH 27/88] Padding issues and icon issues fixed --- .../CustomJSONViewer/Components/Row.jsx | 2 +- .../CustomJSONViewer/styles.scss | 1 - .../LeftSidebarInspector/DefaultCopyIcon.jsx | 6 ++--- .../LeftSidebarInspector/HiddenOptions.jsx | 8 +++--- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 5 +++- .../LeftSidebarInspector/TreeViewHeader.jsx | 10 ++++---- .../useCallbackActions.js | 6 ++--- frontend/src/_styles/theme.scss | 6 ++--- frontend/src/_ui/Icon/solidIcons/Corners.jsx | 25 +++++++++++++++++++ frontend/src/_ui/Icon/solidIcons/FileCode.jsx | 25 +++++++++++++++++++ frontend/src/_ui/Icon/solidIcons/index.js | 6 +++++ 11 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/Corners.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/FileCode.jsx diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index ca8396875b..4cc61fd1a4 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -82,7 +82,7 @@ const Row = ({ label, value, level = 1, absolutePath }) => { }} className="json-viewer-action-icon" > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss index b44967c39d..4121cc0e4f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -50,7 +50,6 @@ .json-viewer-actions-container { display:none; margin-left: auto; - margin-right: 4px; height: 12px; width:40px; align-items: center; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx index 6d26f2676b..c71b8ad5c3 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -1,11 +1,11 @@ import React from 'react'; -export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( +export const DefaultCopyIcon = ({ height = 12, width = 12, fill = '#6A727C' }) => ( @@ -13,7 +13,7 @@ export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( fillRule="evenodd" clipRule="evenodd" d="M19.3113 4.68871C18.5834 3.9609 17.4034 3.9609 16.6757 4.68871L15.8421 5.5223C15.4237 5.94071 14.7453 5.94071 14.3269 5.5223C13.9084 5.10389 13.9084 4.42549 14.3269 4.00707L15.1605 3.17348C16.7251 1.60884 19.2619 1.60884 20.8266 3.17348C22.3911 4.73811 22.3911 7.2749 20.8266 8.83954L19.9929 9.67313C19.5746 10.0916 18.8961 10.0916 18.4777 9.67313C18.0593 9.25471 18.0593 8.57633 18.4777 8.1579L19.3113 7.32431C20.0391 6.59651 20.0391 5.41651 19.3113 4.68871ZM17.406 6.59394C17.8244 7.01236 17.8244 7.69074 17.406 8.10917L15.1982 10.317C14.7798 10.7354 14.1014 10.7354 13.683 10.317C13.2645 9.89856 13.2645 9.22017 13.683 8.80174L15.8908 6.59394C16.3091 6.17551 16.9876 6.17551 17.406 6.59394ZM12.6651 7.184C13.0835 7.60241 13.0835 8.28081 12.6651 8.69923L11.8315 9.53283C11.1037 10.2606 11.1037 11.4406 11.8315 12.1684C12.5593 12.8962 13.7393 12.8962 14.4671 12.1684L15.3007 11.3348C15.7191 10.9164 16.3974 10.9164 16.8159 11.3348C17.2343 11.7533 17.2343 12.4316 16.8159 12.8501L15.9823 13.6837C14.4177 15.2483 11.8809 15.2483 10.3162 13.6837C8.75161 12.119 8.75161 9.58223 10.3162 8.0176L11.1498 7.184C11.5683 6.76559 12.2467 6.76559 12.6651 7.184ZM17.245 14.9463C14.983 17.2083 11.3156 17.2083 9.05356 14.9463C6.79156 12.6843 6.79156 9.01691 9.05356 6.7549L9.52276 6.28571H4.14286C2.95939 6.28571 2 7.2451 2 8.42857V19.8571C2 21.0406 2.95939 22 4.14286 22H15.5714C16.7549 22 17.7143 21.0406 17.7143 19.8571V14.4771L17.245 14.9463Z" - fill={'#C1C8CD'} + fill={'fill'} /> ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index a2a4cebf80..6c5f1daad0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -54,7 +54,7 @@ export const HiddenOptions = (props) => { dispatchAction(data); }} > - +
@@ -89,7 +89,7 @@ export const HiddenOptions = (props) => { }} className="option" > - + Copy path
{ }} className="option" > - + Copy value
@@ -120,7 +120,7 @@ export const HiddenOptions = (props) => { ...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }), }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 86dffbf180..bd7143ee9d 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -100,6 +100,8 @@ export const Node = (props) => { alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', fontWeight: level === 1 ? 500 : 400, + marginTop: level === 1 ? 4 : 0, + marginBottom: level === 1 ? 4 : 0, // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > @@ -136,12 +138,13 @@ export const Node = (props) => { />
-
+
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx index 6d6951279e..bb5c6c486f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx @@ -55,7 +55,7 @@ export const TreeViewHeader = (props) => { }} className="option" > - + Copy path
{ }} className="option" > - + Copy value
{nodeSpecificActions?.map((actionOption, index) => { - const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + const { name, icon, src, iconName, dispatchAction, width = 16, height = 16 } = actionOption; if (icon) { return (
@@ -85,7 +85,7 @@ export const TreeViewHeader = (props) => { }} className="option" > - + {name}
@@ -141,7 +141,7 @@ export const TreeViewHeader = (props) => { boxShadow: 'none', }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 3b256afc2a..1c4685b585 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -67,7 +67,7 @@ const useCallbackActions = () => { name: 'Run Query', dispatchAction: handleRunQuery, icon: true, - src: 'assets/images/icons/editor/play.svg', + iconName: 'play01', width: 8, height: 8, enableInspectorTreeView: false, @@ -76,7 +76,7 @@ const useCallbackActions = () => { name: 'View query', dispatchAction: selectQuery, icon: true, - src: 'assets/images/icons/editor/file-code.svg', + iconName: 'file-code', width: 14, height: 14, enableInspectorTreeView: true, @@ -99,7 +99,7 @@ const useCallbackActions = () => { name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, - iconName: 'select', + iconName: 'corners', enableInspectorTreeView: true, }, ...(!shouldFreeze diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 03d5f363a9..2009a3097b 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18938,7 +18938,7 @@ section.ai-message-prompt-input-wrapper { .node-expansion-icon { width:20px; height:20px; - margin-right: 2px; + margin-right: 4px; display: flex; align-items: center; justify-content: center; @@ -18977,7 +18977,6 @@ section.ai-message-prompt-input-wrapper { .node-content { display: flex; align-items: center; - padding: 0px 0px 0px 8px; height: 100%; width: 100%; @@ -19090,7 +19089,6 @@ section.ai-message-prompt-input-wrapper { height: 20px; width: 20px; border-radius: 4px; - margin-right:4px; flex-shrink: 0; &:hover { @@ -19118,7 +19116,7 @@ section.ai-message-prompt-input-wrapper { .copy-menu-options{ width: 144px; border: none; - background: transparent; + background-color: var(--background-surface-layer-01); border-radius: 10px; top: -5px !important; &.dark-theme { diff --git a/frontend/src/_ui/Icon/solidIcons/Corners.jsx b/frontend/src/_ui/Icon/solidIcons/Corners.jsx new file mode 100644 index 0000000000..72ca376981 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Corners.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const Corners = ({ style, fill = '#C1C8CD', width = '12', height = '13', className = '', viewBox = '0 0 12 13' }) => ( + + + + + +); + +export default Corners; diff --git a/frontend/src/_ui/Icon/solidIcons/FileCode.jsx b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx new file mode 100644 index 0000000000..ed1f3268c9 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const FileCode = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 12 12' }) => ( + + + + + +); + +export default FileCode; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 5daa4b7c91..8b643b5b65 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -234,6 +234,8 @@ import NewTabSmall from './NewTabSmall.jsx'; import Code from './Code.jsx'; import WorkflowV3 from './WorkflowV3.jsx'; import WorkspaceV3 from './WorkspaceV3.jsx'; +import FileCode from './FileCode.jsx'; +import Corners from './Corners.jsx'; const Icon = (props) => { switch (props.name) { @@ -369,6 +371,8 @@ const Icon = (props) => { return ; case 'expand': return ; + case 'file-code': + return ; case 'file01': return ; case 'filedownload': @@ -531,6 +535,8 @@ const Icon = (props) => { return ; case 'comments': return ; + case 'corners': + return ; case 'share': return ; case 'shield': From 6fdda6c9ef93a6cc05bb5d0b2a5a1022e2a43f68 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 13 May 2025 21:30:42 +0530 Subject: [PATCH 28/88] Added a feature to pass a query and run query from app to modules --- .../RightSideBar/Inspector/EventManager.jsx | 47 ++++++++++++------- .../AppBuilder/_stores/slices/eventsSlice.js | 22 +++++++-- frontend/src/_styles/theme.scss | 10 +++- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index 37cb9cb0b6..f979d0c676 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -46,11 +46,13 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { - const { moduleId } = useModuleContext(); + const { moduleId, isModuleEditor } = useModuleContext(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup ); + const moduleInputDummyQueries = useStore((state) => state.getModuleInputDummyQueries(), shallow); + const dataQueries = useStore((state) => { const queries = state.dataQuery?.queries?.modules?.canvas || []; if (callerQueryId) { @@ -396,6 +398,12 @@ export const EventManager = ({ return defaultValue; }; + const constructDataQueryOptions = () => { + const queries = dataQueries.filter((qry) => isQueryRunnable(qry)).map((qry) => ({ name: qry.name, value: qry.id })); + const moduleInputs = Object.entries(moduleInputDummyQueries).map(([key, value]) => ({ name: value, value: key })); + return [...queries, ...moduleInputs]; + }; + function eventPopover(event, index) { return ( + +
+
); }; const paramLabelWithoutDescription = (param) => { - return ( - - ); - }; + const label = type === 'request' ? param : param.name; - const paramType = (param) => { return ( -
- {type === 'query' && - param?.schema?.anyOf && - param?.schema?.anyOf.map((type, i) => - i < param.schema?.anyOf.length - 1 - ? type.type.substring(0, 3).toUpperCase() + '|' - : type.type.substring(0, 3).toUpperCase() - )} - {(type === 'path' || (type === 'query' && !param?.schema?.anyOf)) && - param?.schema?.type?.substring(0, 3).toUpperCase()} - {type === 'request' && parameters[param].type?.substring(0, 3).toUpperCase()} +
+
); }; + const paramType = (param) => { + let paramTypeValue; + + if (type === 'query') { + if (param?.schema?.anyOf) { + return ( +
+ {param.schema.anyOf.map((typeObj, i) => + i < param.schema.anyOf.length - 1 + ? (typeObj.type || '').toString().substring(0, 3).toUpperCase() + '|' + : (typeObj.type || '').toString().substring(0, 3).toUpperCase() + )} +
+ ); + } + paramTypeValue = param?.schema?.type; + } else if (type === 'path') { + paramTypeValue = param?.schema?.type; + } else if (type === 'request') { + paramTypeValue = parameters[param]?.type; + } + + const displayType = Array.isArray(paramTypeValue) ? paramTypeValue[0] : paramTypeValue; + + return
{displayType?.toString().substring(0, 3).toUpperCase() || ''}
; + }; + const paramDetails = (param) => { return ( -
- {(type === 'request' && parameters[param].description) || param?.description - ? paramLabelWithDescription(param) - : paramLabelWithoutDescription(param)} - {param.required && *} +
+
+ {(type === 'request' && parameters[param].description) || param?.description + ? paramLabelWithDescription(param) + : paramLabelWithoutDescription(param)} + {param.required && *} +
{paramType(param)}
); @@ -359,3 +366,34 @@ RenderParameterFields.propTypes = { removeParam: PropTypes.func, darkMode: PropTypes.bool, }; + +const AutoWidthText = ({ value, className }) => { + const spanRef = useRef(null); + const [width, setWidth] = useState(0); + + useLayoutEffect(() => { + if (spanRef.current) { + setWidth(spanRef.current.offsetWidth); + } + }, [value]); + + return ( +
+ + {value} + + {value} +
+ ); +}; diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index a71974afc4..bdc7f71054 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -245,7 +245,7 @@ const DynamicForm = ({ encrypted, placeholders = {}, editorType = 'basic', - specUrl = '', + spec_url = '', disabled = false, buttonText, text, @@ -486,7 +486,7 @@ const DynamicForm = ({ }; case 'react-component-api-endpoint': return { - specUrl: specUrl, + specUrl: spec_url, optionsChanged, options, darkMode, diff --git a/marketplace/package-lock.json b/marketplace/package-lock.json index 10de0c24d6..5c831b79c0 100644 --- a/marketplace/package-lock.json +++ b/marketplace/package-lock.json @@ -200,23 +200,23 @@ } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.812.0.tgz", - "integrity": "sha512-LWkP+Vb2f6aNaway06XvFZG3altSXltAClzCz9cTFuOfKG6V2X+0VWsW9cnFRV4+MFFJW3iQAaPMQ1fBO9Rusg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.817.0.tgz", + "integrity": "sha512-MNGwOJDQU0jpvsLLPSuPQDhPtDzFTc/k7rLmiKoPrIlgb3Y8pSF4crpJ+ZH3+xod2NWyyOVMEMQeMaKFFdMaKw==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-node": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -318,22 +318,22 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz", - "integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -366,9 +366,9 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz", - "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", @@ -387,11 +387,11 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz", - "integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -402,11 +402,11 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz", - "integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", @@ -422,17 +422,17 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz", - "integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -445,16 +445,16 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz", - "integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-ini": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -467,11 +467,11 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz", - "integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -483,13 +483,13 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz", - "integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", "dependencies": { - "@aws-sdk/client-sso": "3.812.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/token-providers": "3.812.0", + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -501,12 +501,12 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz", - "integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -558,11 +558,11 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz", - "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", @@ -591,11 +591,12 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz", - "integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", "dependencies": { - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -644,11 +645,11 @@ } }, "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz", - "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -2818,23 +2819,23 @@ } }, "node_modules/@aws-sdk/client-sagemaker": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker/-/client-sagemaker-3.812.0.tgz", - "integrity": "sha512-KX+/Iu8Cde32low/0c+MGx03CShRJ9PB57qJtPtG6qgz0PeZc8e+t6lBjyZt33iUKZ25/Mt9277tXaSmxGpktw==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker/-/client-sagemaker-3.817.0.tgz", + "integrity": "sha512-ziLmZu31SLUfBU+L43mTKJbxO7GyezHvAFVrk0GIZslHNBBwIsImv5j1aAqkOlvn36twH2n5CXJZPrEt0nH0+A==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-node": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -2939,22 +2940,22 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz", - "integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -2987,9 +2988,9 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/core": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz", - "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", @@ -3008,11 +3009,11 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz", - "integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -3023,11 +3024,11 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz", - "integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", @@ -3043,17 +3044,17 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz", - "integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -3066,16 +3067,16 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz", - "integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-ini": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -3088,11 +3089,11 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz", - "integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -3104,13 +3105,13 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz", - "integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", "dependencies": { - "@aws-sdk/client-sso": "3.812.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/token-providers": "3.812.0", + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -3122,12 +3123,12 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz", - "integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -3179,11 +3180,11 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz", - "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", @@ -3212,11 +3213,12 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/token-providers": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz", - "integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", "dependencies": { - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -3265,11 +3267,11 @@ } }, "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz", - "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -5245,11 +5247,11 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.812.0.tgz", - "integrity": "sha512-SrEGXP1zs2Cy3jjOwM8eh+UZkr28z7rvjF+cgV4bpOti5F/mzPyVoIxDkG8BQ2sZdAwa9rgEhhOl4CcKjoJoTA==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.817.0.tgz", + "integrity": "sha512-+dzgWGmdmMNDdeSF+VvONN+hwqoGKX5A6Z3+siMO4CIoKWN7u5nDOx/JLjTGdVQji3522pJjJ+o9veQJNWOMRg==", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.812.0", + "@aws-sdk/client-cognito-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -5427,21 +5429,21 @@ } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.812.0.tgz", - "integrity": "sha512-hT7Kr8Ao+NS9b8KCB/U8cmpr0DcWOZNZNRBGAOc4eq65JpsRv177QmSqjh75vhM9BzchH3VymcP4GeMoy4SuvA==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.817.0.tgz", + "integrity": "sha512-i6Q2MyktWHG4YG+EmLlnXTgNVjW9/yeNHSKzF55GTho5fjqfU+t9beJfuMWclanRCifamm3N5e5OCm52rVDdTQ==", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.812.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-cognito-identity": "3.812.0", - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-ini": "3.812.0", - "@aws-sdk/credential-provider-node": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/client-cognito-identity": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-cognito-identity": "3.817.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-node": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", @@ -5501,22 +5503,22 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz", - "integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -5561,9 +5563,9 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz", - "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", @@ -5582,11 +5584,11 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz", - "integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -5597,11 +5599,11 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz", - "integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", @@ -5617,17 +5619,17 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz", - "integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -5640,16 +5642,16 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz", - "integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.812.0", - "@aws-sdk/credential-provider-http": "3.812.0", - "@aws-sdk/credential-provider-ini": "3.812.0", - "@aws-sdk/credential-provider-process": "3.812.0", - "@aws-sdk/credential-provider-sso": "3.812.0", - "@aws-sdk/credential-provider-web-identity": "3.812.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -5662,11 +5664,11 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz", - "integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -5678,13 +5680,13 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz", - "integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", "dependencies": { - "@aws-sdk/client-sso": "3.812.0", - "@aws-sdk/core": "3.812.0", - "@aws-sdk/token-providers": "3.812.0", + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -5696,12 +5698,12 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz", - "integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", "dependencies": { - "@aws-sdk/core": "3.812.0", - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -5753,11 +5755,11 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz", - "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", @@ -5786,11 +5788,12 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz", - "integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", "dependencies": { - "@aws-sdk/nested-clients": "3.812.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -5839,11 +5842,11 @@ } }, "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz", - "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -6827,22 +6830,22 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.812.0.tgz", - "integrity": "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==", + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.817.0.tgz", + "integrity": "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.812.0", + "@aws-sdk/util-user-agent-node": "3.816.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -6944,9 +6947,9 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz", - "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", "dependencies": { "@aws-sdk/types": "3.804.0", "@smithy/core": "^3.3.3", @@ -7006,11 +7009,11 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz", - "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", "dependencies": { - "@aws-sdk/core": "3.812.0", + "@aws-sdk/core": "3.816.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", @@ -7076,11 +7079,11 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.812.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz", - "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==", + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.812.0", + "@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -8992,14 +8995,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "engines": { - "node": ">=14" - } - }, "node_modules/@gar/promisify": { "version": "1.1.3", "dev": true, @@ -10075,9 +10070,9 @@ } }, "node_modules/@mistralai/mistralai": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.6.0.tgz", - "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.7.0.tgz", + "integrity": "sha512-yM12kf1mGxSBCZWVvSA8gMvLG1lZ+MilvHUJskU4QWVWc+uYOgupZPRgDarPerzEp6/jm9XDR/rCO7U3ElNAOg==", "dependencies": { "zod-to-json-schema": "^3.24.1" }, @@ -11639,16 +11634,16 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@qdrant/js-client-rest": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.14.0.tgz", - "integrity": "sha512-2sM2g17FSkN2sNCSeAfqxHRr+SPEVnUQLXBjVv/whm4YQ4JjZ53Jiy1iShk95G+xBf3hKBhJdj8itRnor03IYw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.14.1.tgz", + "integrity": "sha512-CkCCTDc4gCXq+hhjB3yDw9Hs/PxCJ0bKqk/LjAAmuL9+nDm/RPue4C/tGOIMlzouTQ2l6J6t+JPeM//j38VFug==", "dependencies": { "@qdrant/openapi-typescript-fetch": "1.2.6", "@sevinf/maybe": "0.5.0", - "undici": "~5.28.5" + "undici": "^6.0.0" }, "engines": { - "node": ">=18.0.0", + "node": ">=18.17.0", "pnpm": ">=8" }, "peerDependencies": { @@ -12565,6 +12560,10 @@ "resolved": "plugins/azurerepos", "link": true }, + "node_modules/@tooljet-marketplace/clickup": { + "resolved": "plugins/clickup", + "link": true + }, "node_modules/@tooljet-marketplace/cohere": { "resolved": "plugins/cohere", "link": true @@ -23425,14 +23424,11 @@ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" }, "node_modules/undici": { - "version": "5.28.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", - "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "engines": { - "node": ">=14.0" + "node": ">=18.17" } }, "node_modules/undici-types": { @@ -23970,9 +23966,9 @@ } }, "node_modules/zod": { - "version": "3.25.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.13.tgz", - "integrity": "sha512-Q8mvk2iWi7rTDfpQBsu4ziE7A6AxgzJ5hzRyRYQkoV3A3niYsXVwDaP1Kbz3nWav6S+VZ6k2OznFn8ZyDHvIrg==", + "version": "3.25.30", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.30.tgz", + "integrity": "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA==", "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -23987,7 +23983,6 @@ } }, "plugins/anthropic": { - "name": "@tooljet-marketplace/anthropic", "version": "1.0.0", "dependencies": { "@anthropic-ai/sdk": "^0.32.1", @@ -24022,7 +24017,6 @@ } }, "plugins/azurerepos": { - "name": "@tooljet-marketplace/azurerepos", "version": "1.0.0", "dependencies": { "@tooljet-marketplace/common": "^1.0.0", @@ -24033,8 +24027,17 @@ "typescript": "^4.7.4" } }, + "plugins/clickup": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/cohere": { - "name": "@tooljet-marketplace/cohere", "version": "1.0.0", "dependencies": { "@tooljet-marketplace/common": "^1.0.0", @@ -24065,7 +24068,6 @@ } }, "plugins/gemini": { - "name": "@tooljet-marketplace/gemini", "version": "1.0.0", "dependencies": { "@google/generative-ai": "^0.21.0", @@ -24101,7 +24103,6 @@ } }, "plugins/hugging_face": { - "name": "@tooljet-marketplace/huggingface", "version": "1.0.0", "dependencies": { "@tooljet-marketplace/common": "^1.0.0" @@ -24121,7 +24122,6 @@ } }, "plugins/mistral_ai": { - "name": "@tooljet-marketplace/mistral", "version": "1.0.0", "dependencies": { "@mistralai/mistralai": "^1.4.0", @@ -24206,7 +24206,6 @@ } }, "plugins/qdrant": { - "name": "@tooljet-marketplace/qdrant", "version": "1.0.0", "dependencies": { "@qdrant/js-client-rest": "^1.12.0", @@ -24287,7 +24286,6 @@ } }, "plugins/weaviate": { - "name": "@tooljet-marketplace/weaviate", "version": "1.0.0", "dependencies": { "@tooljet-marketplace/common": "^1.0.0", @@ -24299,4 +24297,4 @@ } } } -} +} \ No newline at end of file diff --git a/marketplace/plugins/clickup/.gitignore b/marketplace/plugins/clickup/.gitignore new file mode 100644 index 0000000000..23e6609462 --- /dev/null +++ b/marketplace/plugins/clickup/.gitignore @@ -0,0 +1,5 @@ +node_modules +lib/*.d.* +lib/*.js +lib/*.js.map +dist/* \ No newline at end of file diff --git a/marketplace/plugins/clickup/README.md b/marketplace/plugins/clickup/README.md new file mode 100644 index 0000000000..d6030e2327 --- /dev/null +++ b/marketplace/plugins/clickup/README.md @@ -0,0 +1,4 @@ + +# ClickUp + +Documentation on: https://docs.tooljet.com/docs/data-sources/clickup \ No newline at end of file diff --git a/marketplace/plugins/clickup/__tests__/index.js b/marketplace/plugins/clickup/__tests__/index.js new file mode 100644 index 0000000000..6875592b04 --- /dev/null +++ b/marketplace/plugins/clickup/__tests__/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const clickup = require('../lib'); + +describe('clickup', () => { + it.todo('needs tests'); +}); diff --git a/marketplace/plugins/clickup/lib/icon.svg b/marketplace/plugins/clickup/lib/icon.svg new file mode 100644 index 0000000000..2f983484c1 --- /dev/null +++ b/marketplace/plugins/clickup/lib/icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/marketplace/plugins/clickup/lib/index.ts b/marketplace/plugins/clickup/lib/index.ts new file mode 100644 index 0000000000..dc6e051bae --- /dev/null +++ b/marketplace/plugins/clickup/lib/index.ts @@ -0,0 +1,114 @@ +import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-marketplace/common'; +import { SourceOptions } from './types'; +import got, { Headers } from 'got'; + +export default class Clickup implements QueryService { + authHeader(token: string): Headers { + return { Authorization: token }; + } + + async run(sourceOptions: SourceOptions, queryOptions: any, dataSourceId: string): Promise { + const operation = queryOptions.operation; + const apiKey = sourceOptions.apiKey; + const baseUrl = 'https://api.clickup.com/api'; + const path = queryOptions['path']; + + const pathParams = queryOptions['params']['path']; + const queryParams = queryOptions['params']['query']; + const bodyParams = queryOptions['params']['request']; + + // Replace path params in URL + let modifiedPath = path; + for (const param of Object.keys(pathParams)) { + modifiedPath = modifiedPath.replace(`{${param}}`, pathParams[param]); + } + + const url = `${baseUrl}${modifiedPath}`; + + try { + let response; + + if (operation === 'get' || operation === 'delete') { + response = await got(url, { + method: operation, + headers: this.authHeader(apiKey), + searchParams: queryParams, + }); + } else { + // post, put, patch operations + const resolvedBodyParams = this.resolveBodyparams(bodyParams); + response = await got(url, { + method: operation, + headers: this.authHeader(apiKey), + json: resolvedBodyParams, + searchParams: queryParams, + }); + } + + return { + status: 'ok', + data: JSON.parse(response.body), + }; + } catch (err) { + const errorMessage = err.message || 'An unknown error occurred'; + const errorDetails: any = {}; + + if (err.response) { + const { statusCode, body } = err.response; + errorDetails.statusCode = statusCode; + + try { + const parsedBody = JSON.parse(body); + errorDetails.error = parsedBody.err || null; + errorDetails.code = parsedBody.ECODE || null; + } catch (parseError) { + errorDetails.rawBody = body; + } + } + + throw new QueryError('Query could not be completed', errorMessage, errorDetails); + } + } + + async testConnection(sourceOptions: SourceOptions): Promise { + const apiKey = sourceOptions.apiKey; + + try { + const response = await got('https://api.clickup.com/api/v2/user', { + headers: this.authHeader(apiKey), + }); + + const data = JSON.parse(response.body); + + if (data?.user?.id) { + return { + status: 'ok', + }; + } else { + throw new QueryError('User information not found', 'Invalid API key or insufficient permissions', {}); + } + } catch (error) { + throw new QueryError('Connection could not be established', error.response?.body || error.message, {}); + } + } + + private resolveBodyparams(bodyParams: object): object { + if (typeof bodyParams === 'string') { + return bodyParams; + } + + const expectedResult = {}; + + for (const key of Object.keys(bodyParams)) { + if (typeof bodyParams[key] === 'object') { + for (const subKey of Object.keys(bodyParams[key])) { + expectedResult[`${key}[${subKey}]`] = bodyParams[key][subKey]; + } + } else { + expectedResult[key] = bodyParams[key]; + } + } + + return expectedResult; + } +} diff --git a/marketplace/plugins/clickup/lib/manifest.json b/marketplace/plugins/clickup/lib/manifest.json new file mode 100644 index 0000000000..fbfc423224 --- /dev/null +++ b/marketplace/plugins/clickup/lib/manifest.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json", + "title": "ClickUp datasource", + "description": "Clickup plugin for task, list, and doc management", + "type": "api", + "source": { + "name": "ClickUp", + "kind": "clickup", + "exposedVariables": { + "isLoading": false, + "data": {}, + "rawData": {} + }, + "options": { + "apiKey": { + "type": "string", + "encrypted": true + } + } + }, + "defaults": {}, + "properties": { + "apiKey": { + "label": "API Key", + "key": "apiKey", + "type": "password", + "description": "Enter your Personal API Token", + "helpText": "For generating API Key, visit: ClickUp authentication docs" + } + }, + "required": [ + "apiKey" + ] +} \ No newline at end of file diff --git a/marketplace/plugins/clickup/lib/operations.json b/marketplace/plugins/clickup/lib/operations.json new file mode 100644 index 0000000000..fa2ef8b045 --- /dev/null +++ b/marketplace/plugins/clickup/lib/operations.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "ClickUp datasource", + "description": "A schema defining ClickUp datasource", + "type": "api", + "defaults": {}, + "properties": { + "operation": { + "label": "", + "key": "clickup_operation", + "type": "react-component-api-endpoint", + "description": "Single select dropdown for operation", + "spec_url": "https://developer.clickup.com/openapi/673cf4cfdca96a0019533cad" + } + } +} \ No newline at end of file diff --git a/marketplace/plugins/clickup/lib/types.ts b/marketplace/plugins/clickup/lib/types.ts new file mode 100644 index 0000000000..00e976f831 --- /dev/null +++ b/marketplace/plugins/clickup/lib/types.ts @@ -0,0 +1,3 @@ +export type SourceOptions = { + apiKey: string; +}; diff --git a/marketplace/plugins/clickup/package.json b/marketplace/plugins/clickup/package.json new file mode 100644 index 0000000000..e46d35689d --- /dev/null +++ b/marketplace/plugins/clickup/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tooljet-marketplace/clickup", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1", + "build": "ncc build lib/index.ts -o dist", + "watch": "ncc build lib/index.ts -o dist --watch" + }, + "homepage": "https://github.com/tooljet/tooljet#readme", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "typescript": "^4.7.4", + "@vercel/ncc": "^0.34.0" + } +} diff --git a/marketplace/plugins/clickup/tsconfig.json b/marketplace/plugins/clickup/tsconfig.json new file mode 100644 index 0000000000..a18a801b14 --- /dev/null +++ b/marketplace/plugins/clickup/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib" + }, + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/plugins/packages/stripe/lib/operations.json b/plugins/packages/stripe/lib/operations.json index 8ec4bed410..4532254d99 100644 --- a/plugins/packages/stripe/lib/operations.json +++ b/plugins/packages/stripe/lib/operations.json @@ -1,16 +1,16 @@ { - "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", - "title": "Stripe datasource", - "description": "A schema defining stripe datasource", - "type": "api", - "defaults": {}, - "properties": { - "operation": { - "label": "", - "key": "stripe_operation", - "type": "react-component-api-endpoint", - "description": "Single select dropdown for operation", - "specUrl": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json" - } + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "Stripe datasource", + "description": "A schema defining stripe datasource", + "type": "api", + "defaults": {}, + "properties": { + "operation": { + "label": "", + "key": "stripe_operation", + "type": "react-component-api-endpoint", + "description": "Single select dropdown for operation", + "spec_url": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json" } } +} \ No newline at end of file diff --git a/server/src/assets/marketplace/plugins.json b/server/src/assets/marketplace/plugins.json index fe24e4a6a7..b16d3727e9 100644 --- a/server/src/assets/marketplace/plugins.json +++ b/server/src/assets/marketplace/plugins.json @@ -221,5 +221,13 @@ "id": "azurerepos", "author": "Tooljet", "timestamp": "Mon, 23 Dec 2024 11:57:30 GMT" + }, + { + "name": "ClickUp", + "description": "ClickUp plugin for task, list, and doc management", + "version": "1.0.0", + "id": "clickup", + "author": "Tooljet", + "timestamp": "Wed, 16 Apr 2025 15:31:38 GMT" } ] \ No newline at end of file From e96a868b0ef119f1a66a6ed55ccd814fb27cf586 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 27 May 2025 22:13:03 +0530 Subject: [PATCH 54/88] fix: bugfixes on canvas height, datepicker portal --- frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 +- .../src/AppBuilder/RightSideBar/Inspector/EventManager.jsx | 2 +- frontend/src/AppBuilder/Viewer/Viewer.jsx | 4 ++-- frontend/src/AppBuilder/_stores/slices/appSlice.js | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index d8c99b21b6..10ab1fa054 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -161,7 +161,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, isViewer = false }) => pageSidebarStyle={pageSidebarStyle} appType={appType} /> -
+ {appType !== 'module' &&
}
)} diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index f979d0c676..d260b33814 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -401,7 +401,7 @@ export const EventManager = ({ const constructDataQueryOptions = () => { const queries = dataQueries.filter((qry) => isQueryRunnable(qry)).map((qry) => ({ name: qry.name, value: qry.id })); const moduleInputs = Object.entries(moduleInputDummyQueries).map(([key, value]) => ({ name: value, value: key })); - return [...queries, ...moduleInputs]; + return [...moduleInputs, ...queries]; }; function eventPopover(event, index) { diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index 48bfc7e632..95543de779 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -116,8 +116,8 @@ export const Viewer = ({ }, []); useEffect(() => { - updateCanvasHeight(currentPageComponents); - }, [currentPageComponents, updateCanvasHeight]); + updateCanvasHeight(currentPageComponents, moduleId); + }, [currentPageComponents, moduleId, updateCanvasHeight]); const changeToDarkMode = (newMode) => { switchDarkMode(newMode); diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index 7ecc2401c7..6962c6752d 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -88,7 +88,8 @@ export const createAppSlice = (set, get) => ({ 'setCanvasHeight' ), updateCanvasBottomHeight: (components, moduleId = 'canvas') => { - const { currentLayout, currentMode, setCanvasHeight } = get(); + const { currentLayout, getCurrentMode, setCanvasHeight } = get(); + const currentMode = getCurrentMode(moduleId); const maxHeight = Object.values(components).reduce((max, component) => { const layout = component?.layouts?.[currentLayout]; if (!layout) { From 7c820b735c7161af93828bd1197446db41396e58 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 28 May 2025 03:25:18 +0530 Subject: [PATCH 55/88] CSS bug fixes --- .../CustomJSONViewer/Components/Row.jsx | 38 +++++++++++++++++-- .../CustomJSONViewer/CustomJSONViewer.jsx | 6 ++- .../CustomJSONViewer/styles.scss | 14 +++++-- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 8 +++- .../LeftSidebarInspector/JSONViewer.jsx | 4 +- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 2 +- .../LeftSidebarInspector/useIconList.js | 1 - frontend/src/_styles/theme.scss | 2 + 8 files changed, 60 insertions(+), 15 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index f7d74ff5a6..4ec5b674a9 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -10,9 +10,26 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { ToolTip } from '@/_components/ToolTip'; import { DefaultCopyIcon } from '../../DefaultCopyIcon'; -import { copyToClipboard } from '../../utils'; +import { copyToClipboard, extractComponentName } from '../../utils'; +import WidgetIcon from '@/../assets/images/icons/widgets'; -const Row = ({ label, value, level = 1, absolutePath }) => { +const renderNodeIcons = (node, iconsList, darkMode) => { + const icon = iconsList.filter((icon) => icon?.iconName === node)[0]; + + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx({ height: 14, width: 14 })}
+
+ ); + } + return icon.jsx({ height: 14, width: 14 }); + } + return null; +}; + +const Row = ({ label, value, level = 1, absolutePath, iconsList, darkMode }) => { const [isExpanded, setIsExpanded] = useState(false); const Node = () => { if (typeof value === 'string') { @@ -60,7 +77,10 @@ const Row = ({ label, value, level = 1, absolutePath }) => { ))}
- {label} + + {renderNodeIcons(label, iconsList, darkMode)} + {label} +
@@ -92,7 +112,15 @@ const Row = ({ label, value, level = 1, absolutePath }) => { {isExpanded && isObject && (
{Object.entries(value).map(([key, val]) => ( - + ))}
)} @@ -106,6 +134,8 @@ const Row = ({ label, value, level = 1, absolutePath }) => { value={item} level={level + 1} absolutePath={`${absolutePath}.${index}`} + iconsList={iconsList} + darkMode={darkMode} /> ); })} diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx index b5e392ee66..d37aff84aa 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx @@ -2,13 +2,15 @@ import React from 'react'; import Row from './Components/Row'; import './styles.scss'; -const CustomJSONViewer = ({ data, absolutePath }) => { +const CustomJSONViewer = ({ data, absolutePath, iconsList }) => { let modifiedData = data; if (typeof data !== 'object') modifiedData = { '': data }; return (
{Object.entries(modifiedData).map(([key, value], index) => { - return ; + return ( + + ); })}
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss index feebea681e..40369fcbd8 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -18,8 +18,11 @@ .json-viewer-row-container { min-width: max-content; + width: 100%; + position: relative; &:hover { background-color: var(--interactive-overlays-fill-hover); + width: 100%; .json-viewer-actions-container { display: flex; @@ -56,17 +59,20 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - } + padding-right: 80px; /* Add padding to prevent text from going under actions */ + } .json-viewer-actions-container { - display:none; + display: none; margin-left: auto; height: 12px; - width:40px; + width: 40px; align-items: center; flex-shrink: 0; position: absolute; - right:20px; + right: 16px; /* Align with the parent container's margin */ + background: var(--bg-default); /* Add background to ensure text doesn't show through */ + z-index: 1; /* Ensure actions stay on top */ .json-viewer-action-icon { display: flex; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index b45d0bfc20..38db232597 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -191,7 +191,13 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths
) : ( - + )} ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx index f7fae378e6..e1391adc2e 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx @@ -4,7 +4,7 @@ import useCallbackActions from './useCallbackActions'; import CustomJSONViewer from './CustomJSONViewer/CustomJSONViewer'; export const JSONViewer = (props) => { - const { data, path, darkMode, backFn } = props; + const { data, path, darkMode, backFn, iconsList } = props; const callbackActions = useCallbackActions() || []; const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; @@ -27,7 +27,7 @@ export const JSONViewer = (props) => { data={optionsData} type={type} /> - +
); }; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 9f33c0c8d9..d3e806a577 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -95,7 +95,7 @@ export const Node = (props) => { // marginLeft: level > 1 ? 12 : 0, // paddingLeft: '16px', opacity: isDisabled ? 0.5 : 1, - height: '20px', + height: '24px', display: 'flex', alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js index 90c85eb469..ab818d9786 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js @@ -141,7 +141,6 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos }) .flat() .filter((value) => value !== undefined); // Remove undefined values - const iconsList = useMemo( () => [...queryIcons, ...componentIcons, ...deprecatedIcons], [queryIcons, componentIcons, deprecatedIcons] diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 2009a3097b..d949d33d26 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18979,6 +18979,8 @@ section.ai-message-prompt-input-wrapper { align-items: center; height: 100%; width: 100%; + padding-left:4px; + padding-right:4px; } From f11c6d54e824f70d6f38c4353c21c87a35abea35 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 28 May 2025 03:32:15 +0530 Subject: [PATCH 56/88] Padding changes --- .../LeftSidebarInspector/CustomJSONViewer/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss index 40369fcbd8..eea3660631 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -59,7 +59,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - padding-right: 80px; /* Add padding to prevent text from going under actions */ + padding-right: 70px; /* Add padding to prevent text from going under actions */ } .json-viewer-actions-container { From 65483deb28db5c0b35e8ae173febeb1ae1b6ba83 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Wed, 28 May 2025 14:14:21 +0530 Subject: [PATCH 57/88] choreL updated submodule reference --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 7cc6976f78..7ce0a772f4 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 7cc6976f7801f538c64b1b13679c4d9117a8baa1 +Subproject commit 7ce0a772f4d76664a56efe2909ea9cdc1192ea3b From f6f0b6fa8d8dd44761fe2b5bc7c3447f9ed9d268 Mon Sep 17 00:00:00 2001 From: Parth <108089718+parthy007@users.noreply.github.com> Date: Thu, 29 May 2025 00:06:11 +0530 Subject: [PATCH 58/88] Fix MariaDB saving issue (#12952) --- .../components/DataSourceManager/DataSourceManager.jsx | 2 +- plugins/packages/mariadb/lib/manifest.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx index bb5c40b5d8..9f601bb412 100644 --- a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx @@ -258,7 +258,7 @@ class DataSourceManagerComponent extends React.Component { // to resolve any casing mis-match if (decamelize(key) !== key) { const newKey = decamelize(key); - isEncrypted = dataSourceMeta.options[newKey].encrypted; + isEncrypted = dataSourceMeta.options[newKey]?.encrypted; } return { diff --git a/plugins/packages/mariadb/lib/manifest.json b/plugins/packages/mariadb/lib/manifest.json index db1ff5afea..3d30bbda0a 100644 --- a/plugins/packages/mariadb/lib/manifest.json +++ b/plugins/packages/mariadb/lib/manifest.json @@ -19,7 +19,8 @@ "type": "string" }, "password": { - "type": "string" + "type": "string", + "encrypted": true }, "connectionLimit": { "type": "string" @@ -83,7 +84,8 @@ "label": "Password", "key": "password", "type": "password", - "description": "Enter password" + "description": "Enter password", + "encrypted": true }, "connectionLimit": { "label": "Connection Limit", From 82515379b0189a27ff029e3e7b639ec3d2c2b0f2 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Thu, 29 May 2025 12:05:47 +0530 Subject: [PATCH 59/88] refactor: moved inspector styles to a different file --- .../LeftSidebarInspector.jsx | 2 + .../LeftSidebarInspector/styles.scss | 280 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index cd6f358c47..d3703888a5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -10,6 +10,8 @@ import useIconList from './useIconList'; import { Button as ButtonComponent } from '@/components/ui/Button/Button'; import { formatInspectorDataMisc, formatInspectorQueryData } from './utils'; +import './styles.scss'; + const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); const exposedQueries = useStore((state) => state.getAllExposedValues().queries || {}, shallow); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss new file mode 100644 index 0000000000..162bbdce06 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss @@ -0,0 +1,280 @@ +.json-tree-view { + // ul { + // margin-left:16px !important; + // border-left: 1px solid var(--slate6, #D7DBDF); + // } + + ul[role="tree"] { + margin-left: 16px !important; + border-left: none !important; + + ul { + margin-left: 16px !important; + border-left: none !important; + + ul { + margin-left: 10px !important; + padding-left: 16px !important; + border-left: 1px solid var(--slate6, #D7DBDF) !important; + } + } + + } +} + +.basic.tree { + list-style: none; + margin: 0; + padding: 0px; + padding-right: 20px; + padding-top: 0px; + +} + +.basic .tree-node, +.basic .tree-node-group { + list-style: none; + margin: 0; + padding: 0; +} + +.basic .tree-branch-wrapper, +.basic .tree-node__leaf { + outline: none; +} + +// .basic .tree-node--focused { +// outline-color: rgb(77, 144, 254); +// outline-style: auto; +// outline-width: 2px; +// display: block; +// } + +.basic .tree-node__branch { + display: block; +} + +.basic .tree-node { + cursor: pointer; +} + +.node-expansion-icon { + width: 20px; + height: 20px; + margin-right: 4px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.node-icon { + display: flex; + align-items: flex-end; + justify-content: center; + margin-right: 6px; + height: 14px; + width: 14px; +} + +.node-label { + display: flex; + align-items: center; + font-size: 12px; + flex: 1; + min-width: 0px; +} + + +.tj-inspector-search-input { + width: 300px; + height: 32px; + border-radius: 6px; + background-color: var(--base) !important; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; +} + +.node-content { + display: flex; + align-items: center; + height: 100%; + width: 100%; + padding-left: 4px; + padding-right: 4px; + +} + +.node-content-active { + .copy-menu-options-icon { + background-color: var(--interactive-overlays-fill-hover); + } +} + +.node-content-hoverable:hover, +.node-content-active { + background-color: var(--interactive-overlays-fill-hover); + cursor: pointer; + + .node-actions { + display: flex; + } + +} + +.json-viewer { + font-size: 11px; + + ul { + background-color: transparent !important; + padding-left: 6px !important; + padding-right: 10px !important; + // li{ + // text-indent: 0px !important; + // padding-top: 0px !important; + // height:18.46px; + // } + // ul > li:hover { + // background-color: var(--interactive-overlays-fill-hover); // or any color you prefer + // } + } + + + +} + +.json-viewer-node-value { + font-weight: 500; +} + +.json-viewer-header { + font-size: 12px; + font-weight: 500; + display: flex; + flex-direction: row; + margin-left: 16px; + margin-bottom: 12px; + margin-right: 18px; + margin-top: 8px; + height: 28px; + align-items: center; + +} + +.json-viewer-back-btn { + display: flex; + align-items: center; + + &:hover { + cursor: pointer; + background-color: var(--interactive-overlays-fill-hover); + border-radius: 4px; + } + +} + +// .json-viewer-options-btn { +// display: flex; +// align-items: center; +// margin-left: auto; + +// &:hover { +// cursor: pointer; +// background-color:var(--interactive-overlays-fill-hover); +// border-radius: 4px; +// } +// } + +.json-viewer-options-btn { + margin-left: auto; + + + align-items: center; + +} + + +.node-highlight { + background-color: #FFD43B; + padding: 0px; +} + +.node-actions { + justify-content: center; + align-items: center; + margin-left: auto; + display: none; + gap: 4px; +} + +.node-action-icon { + display: flex; + justify-content: center; + align-items: center; + height: 20px; + width: 20px; + border-radius: 4px; + flex-shrink: 0; + + &:hover { + background-color: var(--button-outline-hover); + } + +} + +.copy-menu-options-icon { + border: 1px solid var(--border-weak, #E4E7EB); + display: flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + border-radius: 4px; + box-shadow: var(--elevation-100-box-shadow); + background-color: var(--base); + + &:hover { + background-color: var(--button-outline-hover); + } +} + +.copy-menu-options { + width: 144px; + border: none; + background-color: var(--background-surface-layer-01); + border-radius: 10px; + top: -5px !important; + + &.dark-theme { + .popover-body { + box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; + } + } + + .popover-body { + width: 100%; + padding: 8px; + border-radius: 10px; + background: var(--background-surface-layer-01); + box-shadow: 0px 0px 1px 0px rgba(48, 50, 51, 0.05), 0px 8px 16px 0px rgba(48, 50, 51, 0.1); + + .menu-options { + .option { + display: flex; + padding: 6px 8px; + align-items: center; + gap: 6px; + height: 30px; + align-self: stretch; + border-radius: 6px; + cursor: pointer; + + &:hover { + background: rgba(136, 144, 153, 0.08); + } + } + } + } +} \ No newline at end of file From 718c96e7d332f3d079f6acd6850f4e8b625c613f Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Thu, 29 May 2025 12:06:54 +0530 Subject: [PATCH 60/88] chore: update submodule references --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 518f3334b1..aaffc6e3b5 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a +Subproject commit aaffc6e3b5a0fa3d62a0c1b7471aab26823b5cc5 diff --git a/server/ee b/server/ee index bd6745ee0a..e37444897a 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit bd6745ee0a9344d5fcf42a79a50b8185ec43643a +Subproject commit e37444897aac3f09a173d4c7bcaaf7796cc7f9dc From e0dc90d36ababc416ea78236519a8f0f33092032 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Thu, 29 May 2025 12:29:27 +0530 Subject: [PATCH 61/88] chore: update submodule references --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index e37444897a..705e90dc9a 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e37444897aac3f09a173d4c7bcaaf7796cc7f9dc +Subproject commit 705e90dc9a9f03ed8ec7e56931bbfcf2f815beb4 From 2051ed32792bd1cf59fe861d359cd574c8eeb9b4 Mon Sep 17 00:00:00 2001 From: Parth <108089718+parthy007@users.noreply.github.com> Date: Thu, 29 May 2025 14:23:59 +0530 Subject: [PATCH 62/88] Remove the helper text from clickup manifest.json (#12955) --- marketplace/plugins/clickup/lib/manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/marketplace/plugins/clickup/lib/manifest.json b/marketplace/plugins/clickup/lib/manifest.json index fbfc423224..37eaad21a6 100644 --- a/marketplace/plugins/clickup/lib/manifest.json +++ b/marketplace/plugins/clickup/lib/manifest.json @@ -24,8 +24,7 @@ "label": "API Key", "key": "apiKey", "type": "password", - "description": "Enter your Personal API Token", - "helpText": "For generating API Key, visit:
ClickUp authentication docs" + "description": "Enter your Personal API Token" } }, "required": [ From db4cce3b0adea7fc1ac1b64d6d512e1426e36d79 Mon Sep 17 00:00:00 2001 From: Mekhla Asopa <59684099+Mekhla-Asopa@users.noreply.github.com> Date: Thu, 29 May 2025 14:29:05 +0530 Subject: [PATCH 63/88] Update cypress specs with delete datasource (#12954) * update filed validation and delete datasource * fixed pgsql spec --- cypress-tests/cypress/commands/commands.js | 26 +- .../cypress/constants/selectors/common.js | 12 +- .../cypress/constants/selectors/dataSource.js | 35 +++ .../cypress/constants/selectors/postgreSql.js | 2 + .../cypress/constants/texts/airTable.js | 11 +- .../cypress/constants/texts/postgreSql.js | 8 +- .../data-source/airTableHappyPath.cy.js | 95 ++++-- .../data-source/postgresHappyPath.cy.js | 274 +++++++++++++----- .../data-source/restAPIHappyPath.cy.js | 4 + 9 files changed, 355 insertions(+), 112 deletions(-) diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index d242eb1895..3bb63cc926 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -226,9 +226,9 @@ Cypress.Commands.add( .invoke("text") .then((text) => { cy.wrap(subject).realType(createBackspaceText(text)), - { - delay: 0, - }; + { + delay: 0, + }; }); } ); @@ -429,7 +429,6 @@ Cypress.Commands.add("visitSlug", ({ actualUrl }) => { }); }); - Cypress.Commands.add("releaseApp", () => { if (Cypress.env("environment") !== "Community") { cy.get(commonEeSelectors.promoteButton).click(); @@ -549,7 +548,7 @@ Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { } }); - function installPlugin (pluginName) { + function installPlugin(pluginName) { cy.get('[data-cy="-list-item"]').eq(1).click(); cy.wait(1000); @@ -605,3 +604,20 @@ Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => { }); }); }); + +Cypress.Commands.add( + "verifyRequiredFieldValidation", + (fieldName, expectedColor) => { + cy.get(commonSelectors.textField(fieldName)).should( + "have.css", + "border-color", + expectedColor + ); + cy.get(commonSelectors.labelFieldValidation(fieldName)) + .should("be.visible") + .and("have.text", `${fieldName} is required`); + cy.get(commonSelectors.labelFieldAlert(fieldName)) + .should("be.visible") + .and("have.text", `${fieldName} is required`); + } +); diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index ec104d67bc..7a238f56c7 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -1,5 +1,5 @@ export const cyParamName = (paramName = "") => { - return paramName.toLowerCase().replace(/\s+/g, "-"); + return String(paramName).toLowerCase().replace(/\s+/g, "-"); }; export const commonSelectors = { @@ -278,6 +278,16 @@ export const commonSelectors = { defaultModalTitle: '[data-cy="modal-title"]', workspaceConstantsIcon: '[data-cy="icon-workspace-constants"]', confirmationButton: '[data-cy="confirmation-button"]', + + textField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-text-field"]`; + }, + labelFieldValidation: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-validation-label"]`; + }, + labelFieldAlert: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-field-alert-text"]`; + }, }; export const commonWidgetSelector = { diff --git a/cypress-tests/cypress/constants/selectors/dataSource.js b/cypress-tests/cypress/constants/selectors/dataSource.js index bf5fc06dfa..86f5a24c58 100644 --- a/cypress-tests/cypress/constants/selectors/dataSource.js +++ b/cypress-tests/cypress/constants/selectors/dataSource.js @@ -101,6 +101,8 @@ export const dataSourceSelector = { unSavedModalTitle: '[data-cy="unsaved-changes-title"]', eventQuerySelectionField: '[data-cy="query-selection-field"]', connectionAlertText: '[data-cy="connection-alert-text"]', + requiredIndicator: '[data-cy="required-indicator"]', + informationIcon: '[data-cy="information-icon"]', deleteDSButton: (datasourceName) => { return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`; }, @@ -110,4 +112,37 @@ export const dataSourceSelector = { dataSourceNameButton: (dataSourceName) => { return `[data-cy="${cyParamName(dataSourceName)}-button"]`; }, + dropdownLabel: (label) => { + return `[data-cy="${cyParamName(label)}-dropdown-label"]`; + }, + textField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-text-field"]`; + }, + subSection: (header) => { + return `[data-cy="${cyParamName(header)}-section"]`; + }, + toggleInput: (toggleName) => { + return `[data-cy="${cyParamName(toggleName)}-toggle-input"]`; + }, + button: (buttonName) => { + return `[data-cy="button-${cyParamName(buttonName)}"]`; + }, + keyInputField: (header, index) => { + return `[data-cy="${cyParamName(header)}-key-input-field-${cyParamName(index)}"]`; + }, + valueInputField: (header, index) => { + return `[data-cy="${cyParamName(header)}-value-input-field-${cyParamName(index)}"]`; + }, + deleteButton: (header, index) => { + return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`; + }, + addMoreButton: (header) => { + return `[data-cy="${cyParamName(header)}-add-button"]`; + }, + dropdownField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-select-dropdown"]`; + }, + labelFieldValidation: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-validation-label"]`; + }, }; diff --git a/cypress-tests/cypress/constants/selectors/postgreSql.js b/cypress-tests/cypress/constants/selectors/postgreSql.js index 4f38357961..112da90779 100644 --- a/cypress-tests/cypress/constants/selectors/postgreSql.js +++ b/cypress-tests/cypress/constants/selectors/postgreSql.js @@ -87,6 +87,8 @@ export const postgreSqlSelector = { recordsInputField: '[data-cy="records-input-field"]', eventQuerySelectionField: '[data-cy="query-selection-field"]', + sslToggleInput: '[data-cy="ssl-enabled-toggle-input"]', + labelEncryptedText: '[data-cy="encrypted-text"]', }; export const airTableSelector = { diff --git a/cypress-tests/cypress/constants/texts/airTable.js b/cypress-tests/cypress/constants/texts/airTable.js index 44df3cf9e1..1604fd7590 100644 --- a/cypress-tests/cypress/constants/texts/airTable.js +++ b/cypress-tests/cypress/constants/texts/airTable.js @@ -1,6 +1,7 @@ export const airtableText = { - airtable: "Airtable", - cypressairtable: "cypress-Airtable", - ApiKey: "Personal access token", - apikeyPlaceholder: "**************", - }; \ No newline at end of file + airtable: "Airtable", + cypressairtable: "cypress-Airtable", + ApiKey: "Personal access token", + apikeyPlaceholder: "**************", + invalidAccessToken: "Authentication failed: Invalid personal access token", +}; diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js index 9db745b58d..d5c85c197b 100644 --- a/cypress-tests/cypress/constants/texts/postgreSql.js +++ b/cypress-tests/cypress/constants/texts/postgreSql.js @@ -17,13 +17,17 @@ export const postgreSqlText = { allCloudStorage: "Cloud Storages (4)", postgreSQL: "PostgreSQL", + labelConnectionType: "Connection type", + manualConnectionOption: "Manual connection", + connectionStringOption: "Connection string", labelHost: "Host", labelPort: "Port", labelSSL: "SSL", labelDbName: "Database name", labelUserName: "Username", labelPassword: "Password", - label: "Encrypted", + labelEncrypted: "Encrypted", + labelConnectionOptions: "Connection options", sslCertificate: "SSL certificate", whiteListIpText: "Please white-list our IP address if the data source is not publicly accessible", @@ -74,6 +78,8 @@ export const postgreSqlText = { guiOptionBulkUpdate: "Bulk update using primary key", buttonTextTestConnection: "Test connection", + editButtonText: "Edit", + unableAcquireConnectionAlertText: "Unable to acquire a connection", tabAdvanced: "Advanced", labelNoEventhandler: "No event handlers", diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js index 62a6bffcb1..0f3cf9c7a5 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js @@ -3,24 +3,15 @@ import { postgreSqlSelector, airTableSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; import { airtableText } from "Texts/airTable"; import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; - -import { - fillDataSourceTextField, - selectAndAddDataSource, -} from "Support/utils/postgreSql"; - -import { - deleteDatasource, - closeDSModal, - deleteAppandDatasourceAfterExecution, -} from "Support/utils/dataSource"; - +import { closeDSModal } from "Support/utils/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; - data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); +const airTable_apiKey = Cypress.env("airTable_apikey"); +const airTable_baseId = Cypress.env("airtabelbaseId"); +const airTable_tableName = Cypress.env("airtable_tableName"); +const airTable_recordID = Cypress.env("airtable_recordId"); describe("Data source Airtable", () => { beforeEach(() => { @@ -54,18 +45,71 @@ describe("Data source Airtable", () => { postgreSqlText.allCloudStorage ); - selectAndAddDataSource("databases", airtableText.airtable, data.dsName); - - cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dsName}-airtable`, + "airtable", + [ + { + key: "personal_access_token", + value: `${Cypress.env("airTable_apikey")}`, + encrypted: true, + }, + ] + ); + cy.reload(); + cy.get( + dataSourceSelector.dataSourceNameButton(`cypress-${data.dsName}-airtable`) + ) + .should("be.visible") + .click(); + cy.get( + dataSourceSelector.labelFieldName(airtableText.ApiKey) + ).verifyVisibleElement("have.text", `${airtableText.ApiKey}*`); + cy.get(postgreSqlSelector.labelEncryptedText).verifyVisibleElement( "have.text", - postgreSqlText.buttonTextSave + postgreSqlText.labelEncrypted + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).should( + "be.visible" + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).click(); + cy.verifyRequiredFieldValidation(airtableText.ApiKey, "rgb(226, 99, 103)"); + cy.get(dataSourceSelector.textField(airtableText.ApiKey)).should( + "be.visible" + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy ); - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation ); - deleteDatasource(`cypress-${data.dsName}-airtable`); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( + "have.text", + airtableText.invalidAccessToken + ); + + cy.apiDeleteGDS(`cypress-${data.dsName}-airtable`); }); it("Should verify the functionality of AirTable connection form.", () => { @@ -95,7 +139,7 @@ describe("Data source Airtable", () => { cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", - "Authentication failed: Invalid personal access token" + airtableText.invalidAccessToken ); cy.reload(); cy.apiUpdateGDS({ @@ -123,11 +167,6 @@ describe("Data source Airtable", () => { }); it("Should able to run the query with valid conection", () => { - const airTable_apiKey = Cypress.env("airTable_apikey"); - const airTable_baseId = Cypress.env("airtabelbaseId"); - const airTable_tableName = Cypress.env("airtable_tableName"); - const airTable_recordID = Cypress.env("airtable_recordId"); - cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, `cypress-${data.dsName}-airtable`, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js index 43268fb85d..b86ca7cb17 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js @@ -27,7 +27,7 @@ describe("Data sources", () => { .replaceAll("[^A-Za-z]", ""); }); - it.skip("Should verify elements on connection form", () => { + it("Should verify elements on connection form with validation", () => { cy.log(process.env.NODE_ENV); cy.log(postgreSqlText.allDatabase()); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -81,30 +81,147 @@ describe("Data sources", () => { `cypress-${data.dataSourceName}-postgresql` ); - cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + cy.get( + dataSourceSelector.dropdownLabel(postgreSqlText.labelConnectionType) + ).verifyVisibleElement("have.text", postgreSqlText.labelConnectionType); + cy.get(dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType)) + .should("be.visible") + .click(); + cy.contains( + `[id*="react-select-"]`, + postgreSqlText.connectionStringOption + ).click(); + + cy.get( + dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType) + ).should("be.visible"); + cy.get( + dataSourceSelector.labelFieldName(postgreSqlText.connectionStringOption) + ).verifyVisibleElement( "have.text", - postgreSqlText.labelHost + `${postgreSqlText.connectionStringOption}*` ); - cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + cy.get(postgreSqlSelector.labelEncryptedText).verifyVisibleElement( "have.text", - postgreSqlText.labelPort + postgreSqlText.labelEncrypted ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).should( + "be.visible" + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).click(); + cy.verifyRequiredFieldValidation( + postgreSqlText.connectionStringOption, + "rgb(226, 99, 103)" + ); + cy.get( + dataSourceSelector.textField(postgreSqlText.connectionStringOption) + ).should("be.visible"); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( + "have.text", + postgreSqlText.unableAcquireConnectionAlertText + ); + + cy.get(dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType)) + .should("be.visible") + .click(); + cy.contains( + `[id*="react-select-"]`, + postgreSqlText.manualConnectionOption + ).click(); + + cy.get( + dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType) + ).should("be.visible"); + + const requiredFields = [ + postgreSqlText.labelHost, + postgreSqlText.labelPort, + postgreSqlText.labelUserName, + postgreSqlText.labelPassword, + ]; + const sections = [ + postgreSqlText.labelHost, + postgreSqlText.labelPort, + postgreSqlText.labelDbName, + postgreSqlText.labelUserName, + postgreSqlText.labelPassword, + postgreSqlText.labelConnectionOptions, + ]; + sections.forEach((section) => { + if (section === postgreSqlText.labelConnectionOptions) { + cy.get(dataSourceSelector.keyInputField(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.valueInputField(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.deleteButton(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.addMoreButton(section)).should("be.visible"); + } else if (requiredFields.includes(section)) { + cy.get(dataSourceSelector.labelFieldName(section)).verifyVisibleElement( + "have.text", + `${section}*` + ); + cy.get(dataSourceSelector.textField(section)).should("be.visible"); + if (section === postgreSqlText.labelPassword) { + cy.get( + dataSourceSelector.button(postgreSqlText.editButtonText) + ).click(); + cy.verifyRequiredFieldValidation(section, "rgb(215, 45, 57)"); + } else { + cy.get(dataSourceSelector.textField(section)).click(); + cy.get(commonSelectors.textField(section)).should( + "have.css", + "border-color", + "rgba(0, 0, 0, 0)" + ); + cy.get(dataSourceSelector.textField(section)) + .type("123") + .clear() + .blur(); + cy.verifyRequiredFieldValidation(section, "rgb(215, 45, 57)"); + } + } else { + cy.get(dataSourceSelector.labelFieldName(section)).verifyVisibleElement( + "have.text", + section + ); + cy.get(dataSourceSelector.textField(section)).should("be.visible"); + } + }); cy.get(postgreSqlSelector.labelSsl).verifyVisibleElement( "have.text", postgreSqlText.labelSSL ); - cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement( - "have.text", - postgreSqlText.labelDbName - ); - cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( - "have.text", - postgreSqlText.labelUserName - ); - cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( - "have.text", - postgreSqlText.labelPassword - ); + cy.get(postgreSqlSelector.sslToggleInput).should("be.visible"); cy.get(postgreSqlSelector.labelSSLCertificate).verifyVisibleElement( "have.text", postgreSqlText.sslCertificate @@ -132,72 +249,85 @@ describe("Data sources", () => { "have.text", postgreSqlText.couldNotConnect ); - cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", - postgreSqlText.buttonTextSave + "connect ECONNREFUSED 127.0.0.1:5432" ); - cy.get(dataSourceSelector.connectionAlertText).should("be.visible"); - deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); + + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-postgresql`); }); - it.skip("Should verify the functionality of PostgreSQL connection form.", () => { - selectAndAddDataSource( - "databases", - postgreSqlText.postgreSQL, - data.dataSourceName + it("Should verify the functionality of PostgreSQL connection form.", () => { + cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-manual-pgsql`, + "postgresql", + [ + { key: "connection_type", value: "manual", encrypted: false }, + { key: "host", value: `${Cypress.env("pg_host")}`, encrypted: false }, + { key: "port", value: 5432, encrypted: false }, + { key: "ssl_enabled", value: false, encrypted: false }, + { key: "database", value: "postgres", encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { + key: "username", + value: `${Cypress.env("pg_user")}`, + encrypted: false, + }, + { + key: "password", + value: `${Cypress.env("pg_password")}`, + encrypted: true, + }, + { key: "ca_cert", value: null, encrypted: true }, + { key: "client_key", value: null, encrypted: true }, + { key: "client_cert", value: null, encrypted: true }, + { key: "root_cert", value: null, encrypted: true }, + { key: "connection_string", value: null, encrypted: true }, + ] ); - - fillDataSourceTextField( - postgreSqlText.labelHost, - postgreSqlText.placeholderEnterHost, - Cypress.env("pg_host") - ); - fillDataSourceTextField( - postgreSqlText.labelPort, - postgreSqlText.placeholderEnterPort, - "5432" - ); - cy.get('[data-cy="-toggle-input"]').then(($el) => { - if ($el.is(":checked")) { - cy.get('[data-cy="-toggle-input"]').uncheck(); - } - }); - fillDataSourceTextField( - postgreSqlText.labelDbName, - postgreSqlText.placeholderNameOfDB, - "postgres" - ); - fillDataSourceTextField( - postgreSqlText.labelUserName, - postgreSqlText.placeholderEnterUserName, - "postgres" - ); - fillDataSourceTextField( - postgreSqlText.labelPassword, - "**************", - Cypress.env("pg_password") - ); - + cy.get( + dataSourceSelector.dataSourceNameButton( + `cypress-${data.dataSourceName}-manual-pgsql` + ) + ) + .should("be.visible") + .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); cy.get(postgreSqlSelector.textConnectionVerified, { timeout: 10000, }).should("have.text", postgreSqlText.labelConnectionVerified); - cy.get(postgreSqlSelector.buttonSave).click(); - - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-manual-pgsql`); + cy.reload(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-string-pgsql`, + "postgresql", + [ + { key: "connection_type", value: "string", encrypted: false }, + { + key: "connection_string", + value: `${Cypress.env("pg_string")}`, + encrypted: true, + }, + ] ); - - cy.get(commonSelectors.globalDataSourceIcon).click(); cy.get( - `[data-cy="cypress-${data.dataSourceName}-postgresql-button"]` - ).verifyVisibleElement( - "have.text", - `cypress-${data.dataSourceName}-postgresql` - ); - - deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); + dataSourceSelector.dataSourceNameButton( + `cypress-${data.dataSourceName}-string-pgsql` + ) + ) + .should("be.visible") + .click(); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-string-pgsql`); }); it.skip("Should verify elements of the Query section.", () => { diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js index 9ec852d6ce..09559e2ba7 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js @@ -426,6 +426,7 @@ describe("Data source Rest API", () => { }); } ); + cy.apiDeleteApp(`${fake.companyName}-restAPI-CURD-App`); cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); }); it("Should verify response for basic authentication type connection", () => { @@ -488,6 +489,7 @@ describe("Data source Rest API", () => { method: "GET", urlSuffix: "/basic-auth/invaliduser/invalidpass", }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Basic-App`); cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); }); it("Should verify response for bearer authentication type connection", () => { @@ -545,6 +547,7 @@ describe("Data source Rest API", () => { urlSuffix: "/bearer", expectedResponseShape: { authenticated: true, token: "my-token-123" }, }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Bearer-App`); cy.intercept("GET", "api/data_sources?**").as("datasource"); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, @@ -597,6 +600,7 @@ describe("Data source Rest API", () => { method: "GET", urlSuffix: "/bearer", }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Bearer-invalid`); cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); }); it.skip("Should verify response for authentication code grant type connection", () => { From 844bc51979598df4c44507f79b0007f15e2b1589 Mon Sep 17 00:00:00 2001 From: Ganesh Kumar Date: Thu, 29 May 2025 18:00:54 +0530 Subject: [PATCH 64/88] version bump --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 4eba2a62eb..f982feb41b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.13.0 +3.14.0 diff --git a/frontend/.version b/frontend/.version index 4eba2a62eb..f982feb41b 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.13.0 +3.14.0 diff --git a/server/.version b/server/.version index 4eba2a62eb..f982feb41b 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.13.0 +3.14.0 From 4c7ff67a3db8d1ccd284bd14b14c2a7dde846a11 Mon Sep 17 00:00:00 2001 From: Ganesh Kumar Date: Fri, 30 May 2025 12:21:37 +0530 Subject: [PATCH 65/88] submodule reference updated --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 78ccb8918a..1310351fd9 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 78ccb8918ac5b0dc3f82988dab3bc29fef48c1d6 +Subproject commit 1310351fd980a55d0914dae8e293778590eec4b2 From ed234aa553b0cdd6a85778bd0098182f435edd93 Mon Sep 17 00:00:00 2001 From: Devanshu Gupta <86366994+devanshu-gupta2002@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:25:47 +0530 Subject: [PATCH 66/88] Feat/audit logs user (#12803) * feat: added default audit logs for logout, archive, unarchive, profile_update * feat: added resource data in audit table * feat: added archive workspace, invite redeem * feat: updated profile update audit * feat: updated user invite audit * feat: completed user actions audit * feat: added default audit logs for logout, archive, unarchive, profile_update * feat: added resource data in audit table * feat: added archive workspace, invite redeem * feat: updated profile update audit * feat: updated user invite audit * feat: completed user actions audit * fix: merge conflict * fix: ee commit * fix: removed logs * feat: added migration for resource_data * fix: updated action names * frontend ee commit * feat: added /user/instance route * fix: user instance update * fix: updated feature name * user ee commit * feat: added instance level archive * fix: user details update instance * feat: added self signup audit * ee audit commit * ee commit * metadata workspace field * fix: instace user unarchive data * fix: review fixes * fix: moved user name logic to service * remove log * ee commit * fix: user update password --- frontend/ee | 2 +- frontend/src/_services/user.service.js | 11 ++ server/ee | 2 +- .../1746520805456-AddResourceDataAudit.ts | 16 +++ server/src/entities/audit_log.entity.ts | 3 + .../modules/audit-logs/interfaces/IService.ts | 2 +- server/src/modules/audit-logs/types/index.ts | 2 + server/src/modules/auth/constants/feature.ts | 4 + server/src/modules/auth/service.ts | 17 +++ .../modules/onboarding/constants/feature.ts | 3 + server/src/modules/onboarding/service.ts | 34 ++++-- .../organization-users/constants/feature.ts | 25 +++- .../modules/organization-users/controller.ts | 4 +- .../organization-users/interfaces/IService.ts | 4 +- .../src/modules/organization-users/service.ts | 113 ++++++++++++++++-- .../organization-users/util.service.ts | 28 ++++- .../src/modules/profile/constants/feature.ts | 15 ++- server/src/modules/profile/service.ts | 71 ++++++++++- server/src/modules/profile/util.service.ts | 3 +- .../src/modules/session/constants/feature.ts | 5 +- server/src/modules/session/service.ts | 13 ++ server/src/modules/session/util.service.ts | 5 +- .../src/modules/users/constants/features.ts | 19 ++- server/src/modules/users/constants/index.ts | 1 + server/src/modules/users/controller.ts | 7 +- server/src/modules/users/dto/index.ts | 19 ++- .../modules/users/interfaces/IController.ts | 7 +- .../src/modules/users/interfaces/IService.ts | 7 +- server/src/modules/users/module.ts | 3 +- server/src/modules/users/service.ts | 7 +- server/src/modules/users/types.ts | 1 + 31 files changed, 384 insertions(+), 69 deletions(-) create mode 100644 server/migrations/1746520805456-AddResourceDataAudit.ts diff --git a/frontend/ee b/frontend/ee index 777446d71e..4ca98b6bb6 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 777446d71e78e5941d34353606a12d982820438f +Subproject commit 4ca98b6bb66d1d9845f8b326100945a969488f94 diff --git a/frontend/src/_services/user.service.js b/frontend/src/_services/user.service.js index 0046ca7c11..a2017ceb22 100644 --- a/frontend/src/_services/user.service.js +++ b/frontend/src/_services/user.service.js @@ -12,6 +12,7 @@ export const userService = { getAvatar, updateAvatar, updateUserType, + updateUserTypeInstance, getUserLimits, changeUserPassword, generateUserPassword, @@ -80,6 +81,16 @@ function updateUserType(userUpdateBody) { return fetch(`${config.apiUrl}/users/user-type`, requestOptions).then(handleResponse); } +function updateUserTypeInstance(userUpdateBody) { + const requestOptions = { + method: 'PATCH', + headers: authHeader(), + body: JSON.stringify(userUpdateBody), + credentials: 'include', + }; + return fetch(`${config.apiUrl}/users/user-type/instance`, requestOptions).then(handleResponse); +} + function changePassword(currentPassword, newPassword) { const body = { currentPassword, newPassword }; const requestOptions = { method: 'PATCH', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; diff --git a/server/ee b/server/ee index 1310351fd9..2f8843cb03 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1310351fd980a55d0914dae8e293778590eec4b2 +Subproject commit 2f8843cb03eef3ea8ef87878a050280b89637345 diff --git a/server/migrations/1746520805456-AddResourceDataAudit.ts b/server/migrations/1746520805456-AddResourceDataAudit.ts new file mode 100644 index 0000000000..1950038c12 --- /dev/null +++ b/server/migrations/1746520805456-AddResourceDataAudit.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class AddResourceDataAudit1746520805456 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + 'audit_logs', + new TableColumn({ + name: 'resource_data', + type: 'json', + isNullable: true, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/src/entities/audit_log.entity.ts b/server/src/entities/audit_log.entity.ts index 8a2b8a0806..49157d1ceb 100644 --- a/server/src/entities/audit_log.entity.ts +++ b/server/src/entities/audit_log.entity.ts @@ -23,6 +23,9 @@ export class AuditLog extends BaseEntity { @Column({ name: 'resource_type', type: 'enum', enum: MODULES }) resourceType: MODULES; + @Column('simple-json', { name: 'resource_data' }) + resourceData; + @Column({ name: 'action_type' }) actionType: string; diff --git a/server/src/modules/audit-logs/interfaces/IService.ts b/server/src/modules/audit-logs/interfaces/IService.ts index aff26abaed..80702b8fdb 100644 --- a/server/src/modules/audit-logs/interfaces/IService.ts +++ b/server/src/modules/audit-logs/interfaces/IService.ts @@ -7,6 +7,6 @@ export interface IAuditLogService { perform( { userId, organizationId, resourceId, resourceType, actionType, resourceName, metadata }: AuditLogFields, manager?: EntityManager - ): Promise; + ): Promise; findPerPage(user: User, query: AuditLogsQuery): Promise; } diff --git a/server/src/modules/audit-logs/types/index.ts b/server/src/modules/audit-logs/types/index.ts index 1bde3d3ec9..6d234fd18f 100644 --- a/server/src/modules/audit-logs/types/index.ts +++ b/server/src/modules/audit-logs/types/index.ts @@ -18,10 +18,12 @@ export interface AuditLogFields { organizationId: string; resourceId: string; resourceType: MODULES; + resourceData?: object; actionType: string; resourceName?: string; ipAddress?: string; metadata?: object; + organizationIds?: Array; } export interface Features { diff --git a/server/src/modules/auth/constants/feature.ts b/server/src/modules/auth/constants/feature.ts index b0f5bcf7c9..c21c4195ea 100644 --- a/server/src/modules/auth/constants/feature.ts +++ b/server/src/modules/auth/constants/feature.ts @@ -28,12 +28,15 @@ export const FEATURES: FeaturesConfig = { }, [FEATURE_KEY.FORGOT_PASSWORD]: { isPublic: true, + auditLogsKey: 'USER_PASSWORD_FORGOT', }, [FEATURE_KEY.RESET_PASSWORD]: { isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', }, [FEATURE_KEY.OAUTH_SIGN_IN]: { isPublic: true, + auditLogsKey: 'USER_LOGIN', }, [FEATURE_KEY.OAUTH_OPENID_CONFIGS]: { isPublic: true, @@ -43,6 +46,7 @@ export const FEATURES: FeaturesConfig = { }, [FEATURE_KEY.OAUTH_COMMON_SIGN_IN]: { isPublic: true, + auditLogsKey: 'USER_LOGIN', }, [FEATURE_KEY.OAUTH_SAML_RESPONSE]: { isPublic: true, diff --git a/server/src/modules/auth/service.ts b/server/src/modules/auth/service.ts index 7c9fb3ad89..cfea254fe0 100644 --- a/server/src/modules/auth/service.ts +++ b/server/src/modules/auth/service.ts @@ -124,6 +124,9 @@ export class AuthService implements IAuthService { organizationId: organization.id, resourceId: user.id, resourceName: user.email, + resourceData: { + auth_method: 'password', + }, }); } @@ -184,6 +187,13 @@ export class AuthService implements IAuthService { forgotPasswordToken: null, passwordRetryCount: 0, }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); } } @@ -195,6 +205,13 @@ export class AuthService implements IAuthService { } const forgotPasswordToken = uuid.v4(); await this.userRepository.updateOne(user.id, { forgotPasswordToken }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); this.eventEmitter.emit('emailEvent', { type: EMAIL_EVENTS.SEND_PASSWORD_RESET_EMAIL, payload: { diff --git a/server/src/modules/onboarding/constants/feature.ts b/server/src/modules/onboarding/constants/feature.ts index dde67c3912..b43028c4db 100644 --- a/server/src/modules/onboarding/constants/feature.ts +++ b/server/src/modules/onboarding/constants/feature.ts @@ -6,6 +6,7 @@ export const FEATURES: FeaturesConfig = { [MODULES.ONBOARDING]: { [FEATURE_KEY.ACTIVATE_ACCOUNT]: { isPublic: true, + auditLogsKey: 'USER_SIGNUP', }, // Account Activation [FEATURE_KEY.SETUP_SUPER_ADMIN]: { isPublic: true, @@ -15,6 +16,7 @@ export const FEATURES: FeaturesConfig = { }, // Signup [FEATURE_KEY.ACCEPT_INVITE]: { isPublic: true, + auditLogsKey: 'USER_INVITE_REDEEM', }, // Accept Invitation [FEATURE_KEY.RESEND_INVITE]: { isPublic: true, @@ -27,6 +29,7 @@ export const FEATURES: FeaturesConfig = { }, // Verify Organization Token [FEATURE_KEY.SETUP_ACCOUNT_FROM_TOKEN]: { isPublic: true, + auditLogsKey: 'USER_SIGNUP', }, // Setup Account From Token [FEATURE_KEY.CHECK_WORKSPACE_UNIQUENESS]: { isPublic: true, diff --git a/server/src/modules/onboarding/service.ts b/server/src/modules/onboarding/service.ts index c05c9502d2..33d6f9e5a7 100644 --- a/server/src/modules/onboarding/service.ts +++ b/server/src/modules/onboarding/service.ts @@ -120,7 +120,7 @@ export class OnboardingService implements IOnboardingService { const userParams = { email, password, firstName, lastName }; // Find the default workspace - const defaultWorkspace = await this.organizationRepository. getDefaultWorkspaceOfInstance(); + const defaultWorkspace = await this.organizationRepository.getDefaultWorkspaceOfInstance(); if (existingUser) { // Handling instance and workspace level signup for existing user @@ -133,7 +133,7 @@ export class OnboardingService implements IOnboardingService { manager ); } else { - if(defaultWorkspace && !signingUpOrganization) { + if (defaultWorkspace && !signingUpOrganization) { return await this.onboardingUtilService.createUserInDefaultWorkspace( userParams, defaultWorkspace, @@ -263,7 +263,8 @@ export class OnboardingService implements IOnboardingService { throw new BadRequestException('Please enter password'); } - const activateDefaultWorkspace = (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) || allowPersonalWorkspace; + const activateDefaultWorkspace = + (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) || allowPersonalWorkspace; if (activateDefaultWorkspace) { // Getting default workspace const defaultOrganizationUser: OrganizationUser = user.organizationUsers.find( @@ -277,11 +278,11 @@ export class OnboardingService implements IOnboardingService { // Activate default workspace await this.organizationUsersUtilService.activateOrganization(defaultOrganizationUser, manager); - if(defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId){ + if (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) { const personalWorkspaces = await this.organizationUsersUtilService.personalWorkspaces(user.id); - for(const personalWorkspace of personalWorkspaces){ + for (const personalWorkspace of personalWorkspaces) { // if any personal workspace left. activate those - await this.organizationUsersUtilService.activateOrganization(personalWorkspace, manager); + await this.organizationUsersUtilService.activateOrganization(personalWorkspace, manager); } } @@ -362,6 +363,9 @@ export class OnboardingService implements IOnboardingService { organizationId: organization?.id, resourceId: user.id, resourceName: user.email, + resourceData: { + signup_method: 'self-signup', + }, }); await this.licenseUserService.validateUser(manager); @@ -421,6 +425,13 @@ export class OnboardingService implements IOnboardingService { } const isWorkspaceSignup = organizationUser.source === WORKSPACE_USER_SOURCE.SIGNUP; await this.licenseUserService.validateUser(manager); + const auditLogEntry = { + userId: user.id, + organizationId: organization.id, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); return this.sessionUtilService.generateLoginResultPayload( response, user, @@ -534,6 +545,16 @@ export class OnboardingService implements IOnboardingService { Till now user doesn't have an organization. */ await this.licenseUserService.validateUser(manager); + const auditLogsData = { + userId: signupUser.id, + organizationId: signupUser.organizationUsers[0].organizationId, + resourceId: signupUser.id, + resourceName: signupUser.email, + resourceData: { + signup_method: 'invite-redemption', + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogsData); return this.onboardingUtilService.processOrganizationSignup( response, signupUser, @@ -566,7 +587,6 @@ export class OnboardingService implements IOnboardingService { if (user.status !== USER_STATUS.ACTIVE) { throw new BadRequestException(getUserErrorMessages(user.status)); } - RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { userId: user.id, organizationId: organizationUser.organizationId, diff --git a/server/src/modules/organization-users/constants/feature.ts b/server/src/modules/organization-users/constants/feature.ts index d3ee992027..27b9eb8f7e 100644 --- a/server/src/modules/organization-users/constants/feature.ts +++ b/server/src/modules/organization-users/constants/feature.ts @@ -6,12 +6,27 @@ export const FEATURES: FeaturesConfig = { [MODULES.ORGANIZATION_USER]: { [FEATURE_KEY.SUGGEST_USERS]: {}, [FEATURE_KEY.VIEW_ALL_USERS]: {}, - [FEATURE_KEY.USER_ARCHIVE_ALL]: {}, - [FEATURE_KEY.USER_ARCHIVE]: {}, - [FEATURE_KEY.USER_INVITE]: {}, + [FEATURE_KEY.USER_ARCHIVE_ALL]: { + isPublic: true, + auditLogsKey: 'USER_ARCHIVE', + }, + [FEATURE_KEY.USER_ARCHIVE]: { + isPublic: true, + auditLogsKey: 'USER_ARCHIVE', + }, + [FEATURE_KEY.USER_INVITE]: { + isPublic: true, + auditLogsKey: 'USER_INVITE', + }, [FEATURE_KEY.USER_BULK_UPLOAD]: {}, - [FEATURE_KEY.USER_UNARCHIVE]: {}, - [FEATURE_KEY.USER_UNARCHIVE_ALL]: {}, + [FEATURE_KEY.USER_UNARCHIVE]: { + isPublic: true, + auditLogsKey: 'USER_UNARCHIVE', + }, + [FEATURE_KEY.USER_UNARCHIVE_ALL]: { + isPublic: true, + auditLogsKey: 'USER_UNARCHIVE', + }, [FEATURE_KEY.USER_UPDATE]: {}, }, }; diff --git a/server/src/modules/organization-users/controller.ts b/server/src/modules/organization-users/controller.ts index 27d1d20b84..43d6c78af9 100644 --- a/server/src/modules/organization-users/controller.ts +++ b/server/src/modules/organization-users/controller.ts @@ -90,14 +90,14 @@ export class OrganizationUsersController implements IOrganizationUsersController if (user.id === userId) { throw new NotAcceptableException('Self archive not allowed'); } - await this.organizationUsersService.archiveFromAll(userId); + await this.organizationUsersService.archiveFromAll(userId, user); return; } @InitFeature(FEATURE_KEY.USER_UNARCHIVE_ALL) @Post(':userId/unarchive-all') async unarchiveAll(@User() user: UserEntity, @Param('userId') userId: string) { - await this.organizationUsersService.unarchiveUser(userId); + await this.organizationUsersService.unarchiveUser(userId, user); return; } diff --git a/server/src/modules/organization-users/interfaces/IService.ts b/server/src/modules/organization-users/interfaces/IService.ts index 57adc96882..bbed4de3c2 100644 --- a/server/src/modules/organization-users/interfaces/IService.ts +++ b/server/src/modules/organization-users/interfaces/IService.ts @@ -6,8 +6,8 @@ import { UpdateOrgUserDto } from '../dto'; export interface IOrganizationUsersService { updateOrgUser(organizationUserId: string, user: User, updateOrgUserDto: UpdateOrgUserDto): Promise; archive(id: string, organizationId: string, user?: User): Promise; - archiveFromAll(userId: string): Promise; - unarchiveUser(userId: string): Promise; + archiveFromAll(userId: string, user: User): Promise; + unarchiveUser(userId: string, user: User): Promise; unarchive(user: User, id: string, organizationId: string): Promise; inviteNewUser(currentUser: User, inviteNewUserDto: InviteNewUserDto): Promise; bulkUploadUsers(currentUser: User, fileStream: any, res: Response): Promise; diff --git a/server/src/modules/organization-users/service.ts b/server/src/modules/organization-users/service.ts index c4ed443dfa..467b5e9539 100644 --- a/server/src/modules/organization-users/service.ts +++ b/server/src/modules/organization-users/service.ts @@ -24,6 +24,9 @@ import { Response } from 'express'; import { UserCsvRow } from './interfaces'; import { IOrganizationUsersService } from './interfaces/IService'; 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'; @Injectable() export class OrganizationUsersService implements IOrganizationUsersService { constructor( @@ -38,7 +41,6 @@ export class OrganizationUsersService implements IOrganizationUsersService { async updateOrgUser(organizationUserId: string, user: User, updateOrgUserDto: UpdateOrgUserDto) { const { firstName, lastName, addGroups, role, userMetadata } = updateOrgUserDto; - const organizationUser = await this.organizationUsersRepository.findOne({ where: { id: organizationUserId, organizationId: user.organizationId }, }); @@ -81,35 +83,84 @@ export class OrganizationUsersService implements IOrganizationUsersService { } async archive(id: string, organizationId: string, user?: User): Promise { - const organizationUser = await this.organizationUsersRepository.findOneOrFail({ - where: { id, organizationId }, - relations: ['user'], - }); + await dbTransactionWrap(async (manager: EntityManager) => { + const organizationUser = await manager.findOneOrFail(OrganizationUser, { + where: { id, organizationId }, + relations: ['user'], + }); - await this.organizationUsersUtilService.throwErrorIfUserIsLastActiveAdmin(organizationUser?.user, organizationId); - await this.organizationUsersRepository.update(id, { - status: WORKSPACE_USER_STATUS.ARCHIVED, - invitationToken: null, + await this.organizationUsersUtilService.throwErrorIfUserIsLastActiveAdmin(organizationUser?.user, organizationId); + await manager.update(OrganizationUser, id, { + status: WORKSPACE_USER_STATUS.ARCHIVED, + invitationToken: null, + }); + const organization = await manager.findOne(Organization, { + where: { id: organizationUser.organizationId }, + }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: organizationUser.user.email, + resourceData: { + archived_user: { + id: organizationUser.userId, + email: organizationUser.user.email, + first_name: organizationUser.user.firstName, + last_name: organizationUser.user.lastName, + }, + archived_user_workspace: { + workspace_name: organization.name, + workspace_id: organization.id, + }, + }, + }; + + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } - async archiveFromAll(userId: string): Promise { + async archiveFromAll(userId: string, user: User): Promise { await dbTransactionWrap(async (manager: EntityManager) => { + const archivedUserWorkspaces = await manager.find(OrganizationUser, { + where: { userId }, + relations: ['user'], + }); await manager.update( OrganizationUser, { userId }, { status: WORKSPACE_USER_STATUS.ARCHIVED, invitationToken: null } ); await this.organizationUsersUtilService.updateUserStatus(userId, USER_STATUS.ARCHIVED, manager); + const organizationIds = archivedUserWorkspaces.map((user) => user.organizationId); + const auditLogEntry = { + userId: user.id, + organizationIds: organizationIds, + resourceId: user.id, + resourceName: archivedUserWorkspaces[0].user.email, + resourceData: { + archived_user: { + id: archivedUserWorkspaces[0].userId, + email: archivedUserWorkspaces[0].user.email, + first_name: archivedUserWorkspaces[0].user.firstName, + last_name: archivedUserWorkspaces[0].user.lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } - async unarchiveUser(userId: string): Promise { + async unarchiveUser(userId: string, user: User): Promise { await dbTransactionWrap(async (manager: EntityManager) => { const targetUser = await manager.findOneOrFail(User, { where: { id: userId }, select: ['id', 'status', 'invitationToken', 'source'], }); + const unarchivedUserWorkspaces = await manager.find(OrganizationUser, { + where: { userId }, + relations: ['user'], + }); const { status, invitationToken } = targetUser; /* Special case. what if the user is archived when the status is invited. we were changing status to active before */ const updatedStatus = @@ -117,6 +168,22 @@ export class OrganizationUsersService implements IOrganizationUsersService { await this.organizationUsersUtilService.updateUserStatus(userId, updatedStatus, manager); await this.licenseUserService.validateUser(manager); await this.licenseOrganizationService.validateOrganization(manager); + const organizationIds = unarchivedUserWorkspaces.map((user) => user.organizationId); + const auditLogEntry = { + userId: user.id, + organizationIds: organizationIds, + resourceId: user.id, + resourceName: unarchivedUserWorkspaces[0].user.email, + resourceData: { + unarchived_user: { + id: unarchivedUserWorkspaces[0].userId, + email: unarchivedUserWorkspaces[0].user.email, + first_name: unarchivedUserWorkspaces[0].user.firstName, + last_name: unarchivedUserWorkspaces[0].user.lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } @@ -144,6 +211,29 @@ export class OrganizationUsersService implements IOrganizationUsersService { await this.licenseUserService.validateUser(manager); await this.licenseOrganizationService.validateOrganization(manager); + const organization = await manager.findOne(Organization, { + where: { id: organizationUser.organizationId }, + }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: organizationUser.user.email, + resourceData: { + unarchived_user: { + id: organizationUser.userId, + email: organizationUser.user.email, + first_name: organizationUser.user.firstName, + last_name: organizationUser.user.lastName, + }, + unarchived_user_workspace: { + workspace_name: organization.name, + workspace_id: organization.id, + }, + }, + }; + + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); if (organizationUser.user.invitationToken) { @@ -160,6 +250,7 @@ export class OrganizationUsersService implements IOrganizationUsersService { sender: user.firstName, }, }); + return; } diff --git a/server/src/modules/organization-users/util.service.ts b/server/src/modules/organization-users/util.service.ts index c040e2cff7..ba6deb3d65 100644 --- a/server/src/modules/organization-users/util.service.ts +++ b/server/src/modules/organization-users/util.service.ts @@ -1,7 +1,7 @@ import { User } from '@entities/user.entity'; import { dbTransactionWrap } from '@helpers/database.helper'; import { fullName, generateNextNameAndSlug } from '@helpers/utils.helper'; -import { EntityManager } from 'typeorm'; +import { EntityManager, In } from 'typeorm'; import { getUserStatusAndSource, lifecycleEvents, @@ -31,8 +31,6 @@ import { UserDetailsService } from './services/user-details.service'; import { FetchUserResponse, InvitedUserType, RoleUpdate, UserFilterOptions } from './types'; import { GroupPermissionsRepository } from '@modules/group-permissions/repository'; import { ERROR_HANDLER, ERROR_HANDLER_TITLE } from '@modules/organizations/constants'; -import { MODULE_INFO } from '@modules/app/constants/module-info'; -import { MODULES } from '@modules/app/constants/modules'; import { INSTANCE_USER_SETTINGS } from '@modules/instance-settings/constants'; import { OrganizationRepository } from '@modules/organizations/repository'; import * as uuid from 'uuid'; @@ -512,11 +510,33 @@ export class OrganizationUsersUtilService implements IOrganizationUsersUtilServi !user || !!user.invitationToken ); + const groupsArray = []; + if (inviteNewUserDto.groups && inviteNewUserDto.groups.length > 0) { + const groupQuery = { + organizationId: currentOrganization.id, + id: In(inviteNewUserDto.groups), + }; + const orgGroupPermissions = await this.groupPermissionsRepository.find({ + where: groupQuery, + select: ['id', 'name'], + }); + groupsArray.push(...orgGroupPermissions.map((group) => group.name)); + } RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { userId: currentUser.id, organizationId: currentOrganization.id, - resourceId: currentOrganization.id, + resourceId: updatedUser.id, resourceName: updatedUser.email, + resourceData: { + invited_user: { + id: updatedUser.id, + email: updatedUser.email, + first_name: updatedUser.firstName, + last_name: updatedUser.lastName, + role: inviteNewUserDto.role, + group: groupsArray, + }, + }, }); return organizationUser; diff --git a/server/src/modules/profile/constants/feature.ts b/server/src/modules/profile/constants/feature.ts index 0ef0b4f1c9..9b5ba2b08d 100644 --- a/server/src/modules/profile/constants/feature.ts +++ b/server/src/modules/profile/constants/feature.ts @@ -4,9 +4,18 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.PROFILE]: { - [FEATURE_KEY.UPDATE_AVATAR]: {}, + [FEATURE_KEY.UPDATE_AVATAR]: { + isPublic: true, + auditLogsKey: 'USER_PROFILE_UPDATE', + }, [FEATURE_KEY.GET]: {}, - [FEATURE_KEY.UPDATE]: {}, - [FEATURE_KEY.UPDATE_PASSWORD]: {}, + [FEATURE_KEY.UPDATE]: { + isPublic: true, + auditLogsKey: 'USER_PROFILE_UPDATE', + }, + [FEATURE_KEY.UPDATE_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_UPDATE', + }, }, }; diff --git a/server/src/modules/profile/service.ts b/server/src/modules/profile/service.ts index 9ac2979568..17ab95d476 100644 --- a/server/src/modules/profile/service.ts +++ b/server/src/modules/profile/service.ts @@ -7,6 +7,8 @@ import { ProfileUtilService } from '@modules/profile/util.service'; import { User } from '@entities/user.entity'; import { IProfileService } from '@modules/profile/interfaces/IService'; import { File } from '@entities/file.entity'; +import { RequestContext } from '@modules/request-context/service'; +import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; @Injectable() export class ProfileService implements IProfileService { @@ -25,19 +27,76 @@ export class ProfileService implements IProfileService { async addAvatar(userId: string, imageBuffer: Buffer, filename: string): Promise { return dbTransactionWrap(async (manager: EntityManager) => { - return this.serviceUtils.addAvatar(userId, imageBuffer, filename, manager); + const user = await this.userRepository.getUser({ + id: userId, + }); + const avatar = await this.serviceUtils.addAvatar(userId, imageBuffer, filename, manager); + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + resourceData: { + previous_user_details: { + avatar_id: user.avatarId, + }, + updated_user_details: { + avatar_id: avatar.id, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); + return avatar; }); } async updateUserPassword(userId: string, password: string): Promise { - await this.userRepository.updateOne(userId, { - password, - passwordRetryCount: 0, + return dbTransactionWrap(async (manager: EntityManager) => { + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + await this.userRepository.updateOne( + userId, + { + password, + passwordRetryCount: 0, + }, + manager + ); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } async updateUserName(userId: string, updateUserDto: ProfileUpdateDto): Promise { - const { first_name: firstName, last_name: lastName } = updateUserDto; - await this.userRepository.updateOne(userId, { firstName, lastName }); + return dbTransactionWrap(async (manager: EntityManager) => { + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + const { first_name: firstName, last_name: lastName } = updateUserDto; + await this.userRepository.updateOne(userId, { firstName, lastName }, manager); + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + resourceData: { + previous_user_details: { + first_name: user.firstName, + last_name: user.lastName, + }, + updated_user_details: { + first_name: firstName, + last_name: lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); + }); } } diff --git a/server/src/modules/profile/util.service.ts b/server/src/modules/profile/util.service.ts index 671ef60a85..2631627e2b 100644 --- a/server/src/modules/profile/util.service.ts +++ b/server/src/modules/profile/util.service.ts @@ -8,7 +8,7 @@ import { CreateFileDto } from '@modules/files/dto/index'; @Injectable() export class ProfileUtilService implements IProfileUtilService { - constructor(protected readonly filesRepository: FilesRepository, protected userRepository: UserRepository) {} + constructor(protected readonly filesRepository: FilesRepository, protected readonly userRepository: UserRepository) {} async addAvatar(userId: string, imageBuffer: Buffer, filename: string, manager?: EntityManager): Promise { const user = await this.userRepository.getUser({ @@ -31,6 +31,7 @@ export class ProfileUtilService implements IProfileUtilService { if (currentAvatarId) { await this.filesRepository.removeOne(currentAvatarId, manager); } + return avatar; } } diff --git a/server/src/modules/session/constants/feature.ts b/server/src/modules/session/constants/feature.ts index fd482bcfd6..9c887c9355 100644 --- a/server/src/modules/session/constants/feature.ts +++ b/server/src/modules/session/constants/feature.ts @@ -4,7 +4,10 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.SESSION]: { - [FEATURE_KEY.LOG_OUT]: {}, + [FEATURE_KEY.LOG_OUT]: { + isPublic: true, + auditLogsKey: 'USER_LOGOUT', + }, [FEATURE_KEY.GET_INVITED_USER_SESSION]: { isPublic: true, }, diff --git a/server/src/modules/session/service.ts b/server/src/modules/session/service.ts index ba0b089aa9..3aa0aedaf4 100644 --- a/server/src/modules/session/service.ts +++ b/server/src/modules/session/service.ts @@ -19,6 +19,8 @@ import { OrganizationRepository } from '@modules/organizations/repository'; import { OrganizationUsersRepository } from '@modules/organization-users/repository'; import { fullName, generateOrgInviteURL, isSuperAdmin } from '@helpers/utils.helper'; import { decamelizeKeys } from 'humps'; +import { RequestContext } from '@modules/request-context/service'; +import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; @Injectable() export class SessionService { @@ -34,6 +36,17 @@ export class SessionService { response.clearCookie('tj_auth_token'); await dbTransactionWrap(async (manager: EntityManager) => { await manager.delete(UserSessions, { id: sessionId, userId }); + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); }); } diff --git a/server/src/modules/session/util.service.ts b/server/src/modules/session/util.service.ts index 282a6179e5..fcfb61d9f0 100644 --- a/server/src/modules/session/util.service.ts +++ b/server/src/modules/session/util.service.ts @@ -44,6 +44,7 @@ export class SessionUtilService { protected readonly encryptionService: EncryptionService, protected readonly jwtService: JwtService ) {} + async terminateAllSessions(userId: string): Promise { await dbTransactionWrap(async (manager: EntityManager) => { await manager.delete(UserSessions, { userId }); @@ -333,7 +334,7 @@ export class SessionUtilService { }) : null; - const noWorkspaceAttachedInTheSession = await this.checkUserWorkspaceStatus(user.id) && !isSuperAdmin(user); + const noWorkspaceAttachedInTheSession = (await this.checkUserWorkspaceStatus(user.id)) && !isSuperAdmin(user); const isAllWorkspacesArchived = await this.#isAllWorkspacesArchivedBySuperAdmin(user.id); const onboardingFlags = await this.#onboardingFlags(user); const metadata = await this.metadataUtilService.fetchMetadata(); @@ -367,7 +368,7 @@ export class SessionUtilService { async #onboardingFlags(user: User) { let isFirstUserOnboardingCompleted = true; - let isOnboardingCompleted = true; + const isOnboardingCompleted = true; // const isOnboardingQuestionsEnabled = // this.configService.get('ENABLE_ONBOARDING_QUESTIONS_FOR_ALL_SIGN_UPS') === 'true'; diff --git a/server/src/modules/users/constants/features.ts b/server/src/modules/users/constants/features.ts index cc68400c25..1524e517a1 100644 --- a/server/src/modules/users/constants/features.ts +++ b/server/src/modules/users/constants/features.ts @@ -5,8 +5,21 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.USER]: { [FEATURE_KEY.GET_ALL_USERS]: {}, - [FEATURE_KEY.UPDATE_USER_TYPE]: {}, - [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: {}, - [FEATURE_KEY.CHANGE_USER_PASSWORD]: {}, + [FEATURE_KEY.UPDATE_USER_TYPE]: { + isPublic: true, + auditLogsKey: 'USER_DETAILS_UPDATE', + }, + [FEATURE_KEY.UPDATE_USER_TYPE_INSTANCE]: { + isPublic: true, + auditLogsKey: 'SET_AS_SUPERADMIN', + }, + [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', + }, + [FEATURE_KEY.CHANGE_USER_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', + }, }, }; diff --git a/server/src/modules/users/constants/index.ts b/server/src/modules/users/constants/index.ts index 0808b21fc1..9e14e275cf 100644 --- a/server/src/modules/users/constants/index.ts +++ b/server/src/modules/users/constants/index.ts @@ -3,4 +3,5 @@ export enum FEATURE_KEY { UPDATE_USER_TYPE = 'UPDATE_USER_TYPE', AUTO_UPDATE_USER_PASSWORD = 'AUTO_UPDATE_USER_PASSWORD', CHANGE_USER_PASSWORD = 'CHANGE_USER_PASSWORD', + UPDATE_USER_TYPE_INSTANCE = 'UPDATE_USER_TYPE_INSTANCE', } diff --git a/server/src/modules/users/controller.ts b/server/src/modules/users/controller.ts index 145320a22e..31d62ac5de 100644 --- a/server/src/modules/users/controller.ts +++ b/server/src/modules/users/controller.ts @@ -2,19 +2,20 @@ import { Controller, NotFoundException } from '@nestjs/common'; import { UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; import { IUserController } from './interfaces/IController'; import { ChangePasswordDto } from './dto'; +import { User } from '@entities/user.entity'; @Controller('users') export class UsersController implements IUserController { getAllUsers(query: { page?: number; searchText?: string; status?: string }): Promise { throw new NotFoundException(); } - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise { + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise { throw new NotFoundException(); } - autoUpdateUserPassword(userId: string): Promise<{ newPassword: string }> { + autoUpdateUserPassword(userId: string, user: User): Promise<{ newPassword: string }> { throw new NotFoundException(); } - changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto): Promise { + changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto, user: User): Promise { throw new NotFoundException(); } } diff --git a/server/src/modules/users/dto/index.ts b/server/src/modules/users/dto/index.ts index d7b5b1a450..ea2a8f58f0 100644 --- a/server/src/modules/users/dto/index.ts +++ b/server/src/modules/users/dto/index.ts @@ -11,12 +11,6 @@ export class UpdateUserTypeDto { @MaxLength(100) userId: string; - @IsNotEmpty() - @IsString() - @Transform(({ value }) => sanitizeInput(value)) - @MaxLength(100) - userType: USER_TYPE; - @IsOptional() @IsString() @Transform(({ value }) => sanitizeInput(value)) @@ -27,6 +21,19 @@ export class UpdateUserTypeDto { @Transform(({ value }) => sanitizeInput(value)) lastName: string; } +export class UpdateUserTypeInstanceDto { + @IsNotEmpty() + @IsString() + @Transform(({ value }) => sanitizeInput(value)) + @MaxLength(100) + userId: string; + + @IsNotEmpty() + @IsString() + @Transform(({ value }) => sanitizeInput(value)) + @MaxLength(100) + userType: USER_TYPE; +} @Exclude() export class AllUserResponse { diff --git a/server/src/modules/users/interfaces/IController.ts b/server/src/modules/users/interfaces/IController.ts index 793b7d2738..80b9ce9119 100644 --- a/server/src/modules/users/interfaces/IController.ts +++ b/server/src/modules/users/interfaces/IController.ts @@ -1,11 +1,12 @@ +import { User } from '@entities/user.entity'; import { UpdateUserTypeDto, ChangePasswordDto } from '../dto'; export interface IUserController { getAllUsers(query: { page?: number; searchText?: string; status?: string }): Promise; - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise; + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise; - autoUpdateUserPassword(userId: string): Promise<{ newPassword: string }>; + autoUpdateUserPassword(userId: string, user: User): Promise<{ newPassword: string }>; - changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto): Promise; + changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto, user: User): Promise; } diff --git a/server/src/modules/users/interfaces/IService.ts b/server/src/modules/users/interfaces/IService.ts index fe3cd48469..9676fd049e 100644 --- a/server/src/modules/users/interfaces/IService.ts +++ b/server/src/modules/users/interfaces/IService.ts @@ -1,3 +1,4 @@ +import { User } from '@entities/user.entity'; import { AllUserResponse, UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; export interface IUsersService { @@ -6,9 +7,9 @@ export interface IUsersService { users: AllUserResponse[]; }>; - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise; + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise; - updatePassword(userId: string, password: string): Promise; + updatePassword(userId: string, user: User, password: string): Promise; - autoUpdateUserPassword(userId: string): Promise; + autoUpdateUserPassword(userId: string, user: User): Promise; } diff --git a/server/src/modules/users/module.ts b/server/src/modules/users/module.ts index 963eb08fca..9ba2992f32 100644 --- a/server/src/modules/users/module.ts +++ b/server/src/modules/users/module.ts @@ -3,6 +3,7 @@ import { DynamicModule } from '@nestjs/common'; import { UserRepository } from './repository'; import { SessionModule } from '@modules/session/module'; import { FeatureAbilityFactory } from './ability'; +import { OrganizationUsersRepository } from '@modules/organization-users/repository'; export class UsersModule { static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise { @@ -15,7 +16,7 @@ export class UsersModule { module: UsersModule, imports: [await SessionModule.register(configs)], controllers: [UsersController], - providers: [UsersService, UserRepository, UsersUtilService, FeatureAbilityFactory], + providers: [UsersService, UserRepository, UsersUtilService, FeatureAbilityFactory, OrganizationUsersRepository], exports: [UsersUtilService], }; } diff --git a/server/src/modules/users/service.ts b/server/src/modules/users/service.ts index c1870f1f4d..a422f99a68 100644 --- a/server/src/modules/users/service.ts +++ b/server/src/modules/users/service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AllUserResponse, UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; import { IUsersService } from '@modules/users/interfaces/IService'; +import { User } from '@entities/user.entity'; @Injectable() export class UsersService implements IUsersService { @@ -9,13 +10,13 @@ export class UsersService implements IUsersService { ): Promise<{ meta: { total_pages: number; total_count: number; current_page: number }; users: AllUserResponse[] }> { throw new Error('Method not implemented.'); } - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise { + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise { throw new Error('Method not implemented.'); } - updatePassword(userId: string, password: string): Promise { + updatePassword(userId: string, user: User, password: string): Promise { throw new Error('Method not implemented.'); } - autoUpdateUserPassword(userId: string): Promise { + autoUpdateUserPassword(userId: string, user: User): Promise { throw new Error('Method not implemented.'); } } diff --git a/server/src/modules/users/types.ts b/server/src/modules/users/types.ts index c05429936a..37319f7b43 100644 --- a/server/src/modules/users/types.ts +++ b/server/src/modules/users/types.ts @@ -7,6 +7,7 @@ interface Features { [FEATURE_KEY.UPDATE_USER_TYPE]: FeatureConfig; [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: FeatureConfig; [FEATURE_KEY.CHANGE_USER_PASSWORD]: FeatureConfig; + [FEATURE_KEY.UPDATE_USER_TYPE_INSTANCE]: FeatureConfig; } export interface FeaturesConfig { From 12b0afbceb637bc64cbbbca8b38aa7523bf387cf Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 3 Jun 2025 16:29:31 +0530 Subject: [PATCH 67/88] fix: Passed moduleId to event execution --- .../AppBuilder/_stores/slices/eventsSlice.js | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index c0487f5848..e6d9b7131a 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -486,7 +486,7 @@ export const createEventsSlice = (set, get) => ({ const { getExposedValueOfComponent, getResolvedValue } = get(); if (event?.runOnlyIf) { - const shouldRun = getResolvedValue(event.runOnlyIf, customVariables); + const shouldRun = getResolvedValue(event.runOnlyIf, customVariables, moduleId); if (!shouldRun) { return false; } @@ -496,7 +496,8 @@ export const createEventsSlice = (set, get) => ({ //! TODO run only if conditions switch (event.actionId) { case 'show-alert': { - let message = getResolvedValue(event.message, customVariables); + let message = getResolvedValue(event.message, customVariables, moduleId); + if (typeof message === 'object') message = JSON.stringify(message); switch (event.alertType) { @@ -573,7 +574,7 @@ export const createEventsSlice = (set, get) => ({ const resolvedParams = {}; if (params) { Object.keys(params).map( - (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined)) + (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined, moduleId)) ); } // !Todo tackle confirm query part once done @@ -601,7 +602,7 @@ export const createEventsSlice = (set, get) => ({ } case 'open-webpage': { //! if resolvecode default value should be the value itself not empty string ... Ask KAVIN - const resolvedValue = getResolvedValue(event.url, customVariables); + const resolvedValue = getResolvedValue(event.url, customVariables, moduleId); // const url = resolveReferences(event.url, undefined, customVariables); window.open(resolvedValue, event?.windowTarget === 'newTab' ? '_blank' : '_self'); return Promise.resolve(); @@ -611,7 +612,7 @@ export const createEventsSlice = (set, get) => ({ if (!event.slug) { throw new Error('No application slug provided'); } - const resolvedValue = getResolvedValue(event.slug, customVariables); + const resolvedValue = getResolvedValue(event.slug, customVariables, moduleId); const slug = resolvedValue; const queryParams = event.queryParams?.reduce( (result, queryParam) => ({ @@ -649,23 +650,23 @@ export const createEventsSlice = (set, get) => ({ case 'close-modal': return get().eventsSlice.showModal(event.modal, false, eventObj); case 'copy-to-clipboard': { - const contentToCopy = getResolvedValue(event.contentToCopy, customVariables); + const contentToCopy = getResolvedValue(event.contentToCopy, customVariables, moduleId); copyToClipboard(contentToCopy); return Promise.resolve(); } case 'set-localstorage-value': { - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); localStorage.setItem(key, value); return Promise.resolve(); } case 'generate-file': { // const fileType = event.fileType; - const data = getResolvedValue(event.data, customVariables) || []; - const fileName = getResolvedValue(event.fileName, customVariables) || 'data.txt'; - const fileType = getResolvedValue(event.fileType, customVariables) || 'csv'; + const data = getResolvedValue(event.data, customVariables, moduleId) || []; + const fileName = getResolvedValue(event.fileName, customVariables, moduleId) || 'data.txt'; + const fileType = getResolvedValue(event.fileType, customVariables, moduleId) || 'csv'; const fileData = { csv: generateCSV, plaintext: (plaintext) => plaintext, @@ -676,14 +677,18 @@ export const createEventsSlice = (set, get) => ({ } case 'set-table-page': { - get().eventsSlice.setTablePageIndex(event.table, getResolvedValue(event.pageIndex), eventObj); + get().eventsSlice.setTablePageIndex( + event.table, + getResolvedValue(event.pageIndex, undefined, moduleId), + eventObj + ); break; } case 'set-custom-variable': { const { setVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); setVariable(key, value); return Promise.resolve(); // customAppVariables[key] = value; @@ -705,7 +710,7 @@ export const createEventsSlice = (set, get) => ({ case 'get-custom-variable': { const { getVariable } = get(); - const key = getResolvedValue(event.key, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); return getVariable(key); } @@ -717,7 +722,7 @@ export const createEventsSlice = (set, get) => ({ case 'unset-custom-variable': { const { unsetVariable } = get(); - const key = getResolvedValue(event.key, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); unsetVariable(key); return Promise.resolve(); // const customAppVariables = { ...getCurrentState().variables }; @@ -735,8 +740,8 @@ export const createEventsSlice = (set, get) => ({ case 'set-page-variable': { const { setPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); setPageVariable(key, value); return Promise.resolve(); // const customPageVariables = { @@ -767,7 +772,7 @@ export const createEventsSlice = (set, get) => ({ case 'get-page-variable': { const { getPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); return getPageVariable(key); } @@ -779,7 +784,7 @@ export const createEventsSlice = (set, get) => ({ case 'unset-page-variable': { const { unsetPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); unsetPageVariable(key); return Promise.resolve(); @@ -847,7 +852,7 @@ export const createEventsSlice = (set, get) => ({ // })); // console.log('actionArguments', event.componentSpecificActionParams); const actionArguments = event.componentSpecificActionParams.map((param) => { - const value = getResolvedValue(param.value, customVariables); + const value = getResolvedValue(param.value, customVariables, moduleId); return { ...param, value: value, @@ -879,8 +884,8 @@ export const createEventsSlice = (set, get) => ({ const resolvedQueryParams = []; queryParams.forEach((param) => { resolvedQueryParams.push([ - getResolvedValue(param[0], customVariables), - getResolvedValue(param[1], customVariables), + getResolvedValue(param[0], customVariables, moduleId), + getResolvedValue(param[1], customVariables, moduleId), ]); }); const currentUrlParams = new URLSearchParams(window.location.search); From efed245f08de31f5336cf361d3708196f940c679 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Wed, 4 Jun 2025 12:20:11 +0530 Subject: [PATCH 68/88] Merge branch 'main' into appbuilder/sprint-13 --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 4ca98b6bb6..aa3c4f603f 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 4ca98b6bb66d1d9845f8b326100945a969488f94 +Subproject commit aa3c4f603f549337fc88a772a6a31e18eaf38701 diff --git a/server/ee b/server/ee index 2f8843cb03..f70ac83c38 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 2f8843cb03eef3ea8ef87878a050280b89637345 +Subproject commit f70ac83c38e0a8b44aeb2a0fb2059690eb5e2f46 From ea5e6beb7e5c061da66d82e006294cd1b5b15cd1 Mon Sep 17 00:00:00 2001 From: Ajith KV Date: Wed, 4 Jun 2025 12:30:00 +0530 Subject: [PATCH 69/88] Update platform cypress workflow for enterprise edition (#12875) * Update platform cypress workflow * fix syntax * fix syntax * update the workflow * update workflow * update the port * correction in the .env variables * correction in the matrix check * correction in the matrix check * correction in the matrix check * fix for platform ee cypress workflow * Update the workflow for environment specific cypress env * Update ee config * update the file name * update the file name * update typo * update dockerfile * update the port * adding logs to check the migration * added redis to the dockerfile * added redis to the dockerfile * fix redis issue * corrections ce and ee steps * clean up * adding latest submodules * removing submodules * removing submodules --------- Co-authored-by: Adish M --- .github/workflows/cypress-platform.yml | 197 +++++++++++++------- cypress-tests/cypress-ee-platform.config.js | 114 +++++++++++ cypress-tests/cypress-platform.config.js | 2 +- cypress-tests/cypress.Dockerfile | 189 +++++++++++++++++++ cypress-tests/cypress/support/utils/apps.js | 2 +- server/scripts/preview.sh | 2 + 6 files changed, 432 insertions(+), 74 deletions(-) create mode 100644 cypress-tests/cypress-ee-platform.config.js create mode 100644 cypress-tests/cypress.Dockerfile diff --git a/.github/workflows/cypress-platform.yml b/.github/workflows/cypress-platform.yml index c6a0db4be6..a29c46d495 100644 --- a/.github/workflows/cypress-platform.yml +++ b/.github/workflows/cypress-platform.yml @@ -12,91 +12,106 @@ env: jobs: Cypress-Platform: runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') strategy: + fail-fast: false matrix: - edition: >- - ${{ - contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && fromJson('["ce"]') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && fromJson('["ee"]') || - fromJson('[]') - }} + edition: + - ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && 'ce' || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && 'ce' || contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && 'ce' || '' }} + - ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && 'ee' || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && 'ee' || '' }} + exclude: + - edition: "" steps: - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 18.18.2 - - - name: Set up Git authentication for private submodules + - name: Debug labels and matrix edition run: | - git config --global url."https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/" + echo "Labels: ${{ toJSON(github.event.pull_request.labels.*.name) }}" + echo "Matrix edition: ${{ matrix.edition }}" - - name: Checkout with Submodules + - name: Checkout uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} - - name: Checking out the correct branch for submodules EE + # Create Docker Buildx builder with platform configuration + - name: Set up Docker Buildx + run: | + mkdir -p ~/.docker/cli-plugins + curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx + chmod a+x ~/.docker/cli-plugins/docker-buildx + docker buildx create --name mybuilder --platform linux/arm64,linux/amd64 + docker buildx use mybuilder + + - name: Set DOCKER_CLI_EXPERIMENTAL + run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV + + - name: use mybuilder buildx + run: docker buildx use mybuilder + + - name: Docker Login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Set SAFE_BRANCH_NAME + run: echo "SAFE_BRANCH_NAME=$(echo ${{ env.BRANCH_NAME }} | tr '/' '-')" >> $GITHUB_ENV + + - name: Build CE Docker image + if: matrix.edition == 'ce' + uses: docker/build-push-action@v4 + with: + context: . + file: docker/ce-production.Dockerfile + push: true + tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + platforms: linux/amd64 + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build EE Docker image if: matrix.edition == 'ee' - run: | - git submodule update --init --recursive - git submodule foreach --recursive ' - git checkout ${{ env.BRANCH_NAME }} 2>/dev/null || git checkout main' - - - name: Set up Docker - uses: docker-practice/actions-setup-docker@master - - - name: Install and build dependencies - run: | - npm cache clean --force - npm install - npm install --prefix server - npm install --prefix frontend - npm run build:plugins - - - name: Local development setup - run: | - sudo docker network create tooljet - sudo docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_PORT=5432 -d postgres:13 - - - name: Run PostgREST Docker Container - run: | - sudo docker run -d --name postgrest --network tooljet -p 3001:3000 \ - -e PGRST_DB_URI="postgres://postgres:postgres@localhost:5432/tooljet" \ - -e PGRST_DB_ANON_ROLE="postgres" \ - -e PGRST_JWT_SECRET="r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" \ - -e PGRST_DB_PRE_CONFIG=postgrest.pre_config \ - postgrest/postgrest:v12.2.0 + uses: docker/build-push-action@v4 + with: + context: . + build-args: | + CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} + BRANCH_NAME=${{ github.event.pull_request.head.ref }} + file: cypress-tests/cypress.Dockerfile + push: true + tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + platforms: linux/amd64 + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - name: Set up environment variables run: | - echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env - echo "TOOLJET_HOST=http://localhost:8082" >> .env + echo "TOOLJET_EDITION=${{ matrix.edition }}" >> .env + echo "TOOLJET_HOST=http://localhost:3000" >> .env echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env echo "PG_DB=tooljet_development" >> .env echo "PG_USER=postgres" >> .env - echo "PG_HOST=localhost" >> .env + echo "PG_HOST=postgres" >> .env echo "PG_PASS=postgres" >> .env echo "PG_PORT=5432" >> .env echo "ENABLE_TOOLJET_DB=true" >> .env echo "TOOLJET_DB=tooljet_db" >> .env echo "TOOLJET_DB_USER=postgres" >> .env - echo "TOOLJET_DB_HOST=localhost" >> .env + echo "TOOLJET_DB_HOST=postgres" >> .env echo "TOOLJET_DB_PASS=postgres" >> .env echo "TOOLJET_DB_STATEMENT_TIMEOUT=60000" >> .env echo "TOOLJET_DB_RECONFIG=true" >> .env echo "PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" >> .env echo "PGRST_HOST=localhost:3001" >> .env echo "PGRST_DB_PRE_CONFIG=postgrest.pre_config" >> .env - echo "PGRST_DB_URI=postgres://postgres:postgres@localhost:5432/tooljet" >> .env + echo "PGRST_DB_URI=postgres://postgres:postgres@postgres/tooljet_db" >> .env echo "ENABLE_MARKETPLACE_FEATURE=true" >> .env echo "ENABLE_MARKETPLACE_DEV_MODE=true" >> .env echo "ENABLE_PRIVATE_APP_EMBED=true" >> .env @@ -105,29 +120,50 @@ jobs: echo "SSO_GIT_OAUTH2_CLIENT_ID=1234567890" >> .env echo "SSO_GIT_OAUTH2_CLIENT_SECRET=3346shfvkdjjsfkvxce32854e026a4531ed" >> .env - - name: Set up database - run: | - npm run --prefix server db:create - npm run --prefix server db:reset - sleep 5 + # Only add EE-specific env vars if edition is ee + if [ "${{ matrix.edition }}" = "ee" ]; then + echo "SSO_OPENID_NAME=tj-oidc-simulator" >> .env + echo "SSO_OPENID_CLIENT_ID=${{ secrets.SSO_OPENID_CLIENT_ID }}" >> .env + echo "SSO_OPENID_CLIENT_SECRET=${{ secrets.SSO_OPENID_CLIENT_SECRET }}" >> .env + echo "SSO_OPENID_WELL_KNOWN_URL=http://34.66.166.236:8080/.well-known/openid-configuration" >> .env + echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env + fi - - name: Start services + - name: Pulling the docker-compose file + run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data + + - name: Update docker-compose file run: | - cd plugins && npm start & - cd server && npm run start:dev & - cd frontend && npm start & + # Update docker-compose.yaml with the appropriate image based on edition + if [ "${{ matrix.edition }}" = "ce" ]; then + sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce|' docker-compose.yaml + elif [ "${{ matrix.edition }}" = "ee" ]; then + sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee|' docker-compose.yaml + fi + + - name: Install Docker Compose + run: | + curl -L "https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + + - name: Run docker-compose file + run: docker-compose up -d + + - name: Checking containers + run: docker ps -a + + - name: docker logs + run: sudo docker logs Tooljet-app - name: Wait for the server to be ready run: | - timeout 300 bash -c ' - until curl --silent --fail http://localhost:8082; do + timeout 500 bash -c ' + until curl --silent --fail http://localhost:3000; do sleep 5 done' - - name: Postgres logs - run: docker logs postgrest - - - name: Create Cypress environment file + - name: Create Cypress environment file for CE + if: matrix.edition == 'ce' id: create-json uses: jsdaniell/create-json@1.1.2 with: @@ -135,13 +171,30 @@ jobs: json: ${{ secrets.CYPRESS_SECRETS }} dir: "./cypress-tests" - - name: Run Cypress tests + - name: Run Cypress tests for CE + if: matrix.edition == 'ce' uses: cypress-io/github-action@v6 with: working-directory: ./cypress-tests - config: "baseUrl=http://localhost:8082" + config: "baseUrl=http://localhost:3000" config-file: cypress-platform.config.js + - name: Create Cypress environment file for EE + if: matrix.edition == 'ee' + uses: jsdaniell/create-json@1.1.2 + with: + name: "cypress.env.json" + json: ${{ secrets.CYPRESS_EE_SECRETS }} + dir: "./cypress-tests" + + - name: Run Cypress tests for EE + if: matrix.edition == 'ee' + uses: cypress-io/github-action@v6 + with: + working-directory: ./cypress-tests + config: "baseUrl=http://localhost:3000" + config-file: cypress-ee-platform.config.js + - name: Capture Screenshots uses: actions/upload-artifact@v4 if: always() diff --git a/cypress-tests/cypress-ee-platform.config.js b/cypress-tests/cypress-ee-platform.config.js new file mode 100644 index 0000000000..02b8c1d952 --- /dev/null +++ b/cypress-tests/cypress-ee-platform.config.js @@ -0,0 +1,114 @@ +const { defineConfig } = require("cypress"); +const { rmdir } = require("fs"); +const fs = require("fs"); +const XLSX = require("node-xlsx"); +const pg = require("pg"); +const path = require("path"); +const pdf = require("pdf-parse"); + +const environments = { + 'run-cypress-platform': { + baseUrl: "http://localhost:3000", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-subpath': { + baseUrl: "http://localhost:3000/apps", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-proxy': { + baseUrl: "http://localhost:4001", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-proxy-subpath': { + baseUrl: "http://localhost:4001/apps", + configFile: "cypress-platform.config.js" + } +}; + +const githubLabel = process.env.GITHUB_LABEL || 'run-cypress-platform'; +const environment = environments[githubLabel]; + +module.exports = defineConfig({ + execTimeout: 1800000, + defaultCommandTimeout: 30000, + requestTimeout: 30000, + pageLoadTimeout: 30000, + responseTimeout: 30000, + viewportWidth: 1440, + viewportHeight: 960, + chromeWebSecurity: false, + trashAssetsBeforeRuns: true, + e2e: { + setupNodeEvents (on, config) { + config.baseUrl = environment.baseUrl; + + on("task", { + readPdf (pathToPdf) { + return new Promise((resolve) => { + const pdfPath = path.resolve(pathToPdf); + let dataBuffer = fs.readFileSync(pdfPath); + pdf(dataBuffer).then(function ({ text }) { + resolve(text); + }); + }); + }, + }); + + on("task", { + readXlsx (filePath) { + return new Promise((resolve, reject) => { + try { + let dataBuffer = fs.readFileSync(filePath); + const jsonData = XLSX.parse(dataBuffer); + resolve(jsonData[0]["data"].toString()); + } catch (e) { + reject(e); + } + }); + }, + }); + + on("task", { + deleteFolder (folderName) { + return new Promise((resolve, reject) => { + rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { + if (err) { + console.error(err); + return reject(err); + } + resolve(null); + }); + }); + }, + }); + + on("task", { + dbConnection ({ dbconfig, sql }) { + const client = new pg.Pool(dbconfig); + return client.query(sql); + }, + }); + + return require("./cypress/plugins/index.js")(on, config); + }, + downloadsFolder: "cypress/downloads", + experimentalRunAllSpecs: true, + experimentalModfyObstructiveThirdPartyCode: true, + baseUrl: environment.baseUrl, + configFile: environment.configFile, + specPattern: [ + "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", + "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", + "cypress/e2e/happyPath/platform/eeTestcases/**/*.cy.js", + ], + numTestsKeptInMemory: 1, + redirectionLimit: 15, + experimentalMemoryManagement: true, + video: false, + videoUploadOnPasses: false, + retries: { + runMode: 2, + openMode: 0, + }, + }, +}); \ No newline at end of file diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index b565a0c1d1..6b1954140a 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -8,7 +8,7 @@ const pdf = require("pdf-parse"); const environments = { 'run-cypress-platform': { - baseUrl: "http://localhost:8082", + baseUrl: "http://localhost:3000", configFile: "cypress-platform.config.js" }, 'run-cypress-platform-subpath': { diff --git a/cypress-tests/cypress.Dockerfile b/cypress-tests/cypress.Dockerfile new file mode 100644 index 0000000000..373b3bafd3 --- /dev/null +++ b/cypress-tests/cypress.Dockerfile @@ -0,0 +1,189 @@ +FROM node:18.18.2-buster AS builder +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=4096" + +RUN mkdir -p /app + +WORKDIR /app + +ARG CUSTOM_GITHUB_TOKEN +ARG BRANCH_NAME + +# Clone and checkout the frontend repositorys +RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" + +RUN git config --global http.version HTTP/1.1 +RUN git config --global http.postBuffer 524288000 +RUN git clone https://github.com/ToolJet/ToolJet.git . + +# The branch name needs to be changed the branch with modularisation in CE repo +RUN git checkout ${BRANCH_NAME} + +RUN git submodule update --init --recursive + +# Checkout the same branch in submodules if it exists, otherwise stay on default branch +RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true' + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +ENV TOOLJET_EDITION=ee + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production +ENV TOOLJET_EDITION=ee + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM node:18.18.2-bullseye + +RUN apt-get update -yq \ + && apt-get install curl wget gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt -y install redis \ + && apt-get clean -y + +# copy postgrest executable +COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin + +ENV NODE_ENV=production +ENV TOOLJET_EDITION=ee +ENV NODE_OPTIONS="--max-old-space-size=4096" +RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + +WORKDIR / + +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json +# copy frontend build +COPY --from=builder /app/frontend/build ./app/frontend/build +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/ee/keys ./app/server/ee/keys +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +COPY --from=builder /app/server/dist ./app/server/dist + +WORKDIR /app + +# Install PostgreSQL +USER root +RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - +RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list +RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor --fix-missing + + +# Explicitly create PG main directory with correct ownership +RUN mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +RUN mkdir -p /var/log/supervisor /var/run/postgresql && \ + chown -R postgres:postgres /var/run/postgresql /var/log/supervisor + +# Remove existing data and create directory with proper ownership +RUN rm -rf /var/lib/postgresql/13/main && \ + mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +# Initialize PostgreSQL +RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main" + +# Configure Supervisor to manage PostgREST, ToolJet, and Redis +RUN echo "[supervisord] \n" \ + "nodaemon=true \n" \ + "user=root \n" \ + "\n" \ + "[program:redis] \n" \ + "command=redis-server /etc/redis/redis.conf \n" \ + "user=redis \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "stderr_logfile=/var/log/redis/redis-server.log \n" \ + "stdout_logfile=/var/log/redis/redis-server.log \n" \ + "\n" \ + "[program:postgrest] \n" \ + "command=/bin/postgrest \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "\n" \ + "[program:tooljet] \n" \ + "user=root \n" \ + "command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "stderr_logfile=/dev/stdout \n" \ + "stderr_logfile_maxbytes=0 \n" \ + "stdout_logfile=/dev/stdout \n" \ + "stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf + +# ENV defaults +ENV TOOLJET_HOST=http://localhost \ + PORT=3000 \ + NODE_ENV=production \ + LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ + SECRET_KEY_BASE=replace_with_secret_key_base \ + PG_DB=tooljet_production \ + PG_USER=postgres \ + PG_PASS=postgres \ + PG_HOST=localhost \ + ENABLE_TOOLJET_DB=true \ + TOOLJET_DB_HOST=localhost \ + TOOLJET_DB_USER=postgres \ + TOOLJET_DB_PASS=postgres \ + TOOLJET_DB=tooljet_db \ + PGRST_HOST=http://localhost:3001 \ + PGRST_SERVER_PORT=3001 \ + PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \ + PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ + PGRST_DB_PRE_CONFIG=postgrest.pre_config \ + REDIS_HOST=localhost \ + REDIS_PORT=6379 \ + REDIS_USER= \ + REDIS_PASSWORD= \ + ORM_LOGGING=true \ + DEPLOYMENT_PLATFORM=docker:local \ + HOME=/home/appuser \ + TERM=xterm + + +RUN chmod +x ./server/scripts/preview.sh +# Set the entrypoint +ENTRYPOINT ["./server/scripts/preview.sh"] diff --git a/cypress-tests/cypress/support/utils/apps.js b/cypress-tests/cypress/support/utils/apps.js index 0ddfd72ac7..2a6b9da2b9 100644 --- a/cypress-tests/cypress/support/utils/apps.js +++ b/cypress-tests/cypress/support/utils/apps.js @@ -135,7 +135,7 @@ export const resolveHost = () => { const baseUrl = Cypress.config("baseUrl"); const urlMapping = { - "http://localhost:8082": "http://localhost:8082", + "http://localhost:3000": "http://localhost:3000", "http://localhost:3000/apps": "http://localhost:3000/apps", "http://localhost:4001": "http://localhost:3000", "http://localhost:4001/apps": "http://localhost:3000/apps", diff --git a/server/scripts/preview.sh b/server/scripts/preview.sh index 6664902874..d45e6ccb60 100644 --- a/server/scripts/preview.sh +++ b/server/scripts/preview.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +redis-server /etc/redis/redis.conf & + # Fix ownership and permissions chown -R postgres:postgres /var/lib/postgresql /var/run/postgresql chmod 0700 /var/lib/postgresql/13/main From 297c9d3ae488ae6103b7589f667921799bbbb2de Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 4 Jun 2025 14:23:46 +0530 Subject: [PATCH 70/88] chore: added submodules --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 9c0e3ca27d..ba58b23e20 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 9c0e3ca27d4454456521743f1e452405bb4b7aa3 +Subproject commit ba58b23e205e2d5659e48cead5eeefa8164625b2 From 7a3a933d41b653c92b497f71a254e7fb97ca79b0 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 4 Jun 2025 16:11:02 +0530 Subject: [PATCH 71/88] fix: issues on module output linked to listView or variables --- frontend/src/AppBuilder/Widgets/Listview.jsx | 10 +++++----- .../TableExposedVariables.jsx | 4 +++- .../AppBuilder/_stores/slices/eventsSlice.js | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Listview.jsx b/frontend/src/AppBuilder/Widgets/Listview.jsx index cfdff57add..856ef57e49 100644 --- a/frontend/src/AppBuilder/Widgets/Listview.jsx +++ b/frontend/src/AppBuilder/Widgets/Listview.jsx @@ -24,7 +24,7 @@ export const Listview = function Listview({ }) { const { moduleId } = useModuleContext(); const getComponentNameFromId = useStore((state) => state.getComponentNameFromId, shallow); - const childComponents = useStore((state) => state.getChildComponents(id), shallow); + const childComponents = useStore((state) => state.getChildComponents(id, moduleId), shallow); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const fallbackProperties = { height: 100, showBorder: false, data: [] }; const fallbackStyles = { visibility: true, disabledState: false }; @@ -71,7 +71,7 @@ export const Listview = function Listview({ const onOptionChange = useCallback( (optionName, value, componentId, index) => { setChildrenData((prevData) => { - const componentName = getComponentNameFromId(componentId); + const componentName = getComponentNameFromId(componentId, moduleId); const changedData = { [componentName]: { [optionName]: value } }; const existingDataAtIndex = prevData[index] ?? {}; const newDataAtIndex = { @@ -86,13 +86,13 @@ export const Listview = function Listview({ return { ...prevData, ...newChildrenData }; }); }, - [getComponentNameFromId, setChildrenData] + [getComponentNameFromId, setChildrenData, moduleId] ); const onOptionsChange = useCallback( (exposedVariables, componentId, index) => { setChildrenData((prevData) => { - const componentName = getComponentNameFromId(componentId); + const componentName = getComponentNameFromId(componentId, moduleId); const existingDataAtIndex = prevData[index] ?? {}; const changedData = {}; Object.keys(exposedVariables).forEach((key) => { @@ -110,7 +110,7 @@ export const Listview = function Listview({ return { ...prevData, ...newChildrenData }; }); }, - [getComponentNameFromId, setChildrenData] + [getComponentNameFromId, setChildrenData, moduleId] ); function onRecordOrRowClicked(index) { diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx index 6ec4f66994..93f0721366 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx @@ -7,6 +7,7 @@ import { filterFunctions } from '../Header/_components/Filter/filterUtils'; import { isArray, debounce } from 'lodash'; import { useMounted } from '@/_hooks/use-mount'; import { usePrevious } from '@dnd-kit/utilities'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // Component to expose variables & fire events from the table // It might miss some variables which are tightly coupled with the component state export const TableExposedVariables = ({ @@ -19,6 +20,7 @@ export const TableExposedVariables = ({ pageIndex = 1, lastClickedRow, }) => { + const { moduleId } = useModuleContext(); const editedRows = useTableStore((state) => state.getAllEditedRows(id), shallow); const editedFields = useTableStore((state) => state.getAllEditedFields(id), shallow); const addNewRowDetails = useTableStore((state) => state.getAllAddNewRowDetails(id), shallow); @@ -314,7 +316,7 @@ export const TableExposedVariables = ({ // Create debounced function using useRef to persist between renders const debouncedSetProperty = useRef( debounce((sizing) => { - setComponentProperty(id, 'columnSizes', sizing, 'properties'); + setComponentProperty(id, 'columnSizes', sizing, 'properties', 'value', false, moduleId); }, 300) ).current; diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index e6d9b7131a..822644a9fc 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -689,7 +689,10 @@ export const createEventsSlice = (set, get) => ({ const { setVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); const value = getResolvedValue(event.value, customVariables, moduleId); - setVariable(key, value); + + console.log('here--- set-custom-variable', key, value, moduleId); + + setVariable(key, value, moduleId); return Promise.resolve(); // customAppVariables[key] = value; // const resp = useCurrentStateStore.getState().actions.setCurrentState({ @@ -711,19 +714,19 @@ export const createEventsSlice = (set, get) => ({ case 'get-custom-variable': { const { getVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); - return getVariable(key); + return getVariable(key, moduleId); } case 'unset-all-custom-variables': { const { unsetAllVariables } = get(); - unsetAllVariables(); + unsetAllVariables(moduleId); return Promise.resolve(); } case 'unset-custom-variable': { const { unsetVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); - unsetVariable(key); + unsetVariable(key, moduleId); return Promise.resolve(); // const customAppVariables = { ...getCurrentState().variables }; // delete customAppVariables[key]; @@ -742,7 +745,7 @@ export const createEventsSlice = (set, get) => ({ const { setPageVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); const value = getResolvedValue(event.value, customVariables, moduleId); - setPageVariable(key, value); + setPageVariable(key, value, moduleId); return Promise.resolve(); // const customPageVariables = { // ...getCurrentState().page.variables, @@ -773,19 +776,19 @@ export const createEventsSlice = (set, get) => ({ case 'get-page-variable': { const { getPageVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); - return getPageVariable(key); + return getPageVariable(key, moduleId); } case 'unset-all-page-variables': { const { unsetAllPageVariables } = get(); - unsetAllPageVariables(); + unsetAllPageVariables(moduleId); return Promise.resolve(); } case 'unset-page-variable': { const { unsetPageVariable } = get(); const key = getResolvedValue(event.key, customVariables, moduleId); - unsetPageVariable(key); + unsetPageVariable(key, moduleId); return Promise.resolve(); // useStore.getState().unsetPageVariable(key); From 0d358e441dc4ed4c569cc28480517379d0e44c2c Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 5 Jun 2025 09:06:36 +0530 Subject: [PATCH 72/88] chore: enabled console logs --- frontend/src/Editor/ErrorBoundary.jsx | 2 +- frontend/src/_ui/ErrorBoundary/index.jsx | 2 +- frontend/webpack.config.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/ErrorBoundary.jsx b/frontend/src/Editor/ErrorBoundary.jsx index 4ec47921c3..678bda4c9f 100644 --- a/frontend/src/Editor/ErrorBoundary.jsx +++ b/frontend/src/Editor/ErrorBoundary.jsx @@ -15,7 +15,7 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - console.log(error, errorInfo); + console.log('error--- 2--- ', error, errorInfo); } render() { diff --git a/frontend/src/_ui/ErrorBoundary/index.jsx b/frontend/src/_ui/ErrorBoundary/index.jsx index 4ec47921c3..61bd339227 100644 --- a/frontend/src/_ui/ErrorBoundary/index.jsx +++ b/frontend/src/_ui/ErrorBoundary/index.jsx @@ -15,7 +15,7 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - console.log(error, errorInfo); + console.log('error--- 1--- ', error, errorInfo); } render() { diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 986c7011b1..133cc7f6f7 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -89,7 +89,7 @@ module.exports = { keep_fnames: true, compress: { drop_debugger: true, - drop_console: true, + drop_console: false, }, }, parallel: environment === 'production', From d8d41de924b13424b7cbc1cc8926064173555abf Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 5 Jun 2025 10:40:52 +0530 Subject: [PATCH 73/88] chore: Added an alert for error --- frontend/src/Editor/ErrorBoundary.jsx | 3 +++ frontend/src/_ui/ErrorBoundary/index.jsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/src/Editor/ErrorBoundary.jsx b/frontend/src/Editor/ErrorBoundary.jsx index 678bda4c9f..87570ec750 100644 --- a/frontend/src/Editor/ErrorBoundary.jsx +++ b/frontend/src/Editor/ErrorBoundary.jsx @@ -15,6 +15,9 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service + alert( + `An error occurred: ${JSON.stringify(error)} and the error info is ${JSON.stringify(JSON.stringify(errorInfo))}` + ); console.log('error--- 2--- ', error, errorInfo); } diff --git a/frontend/src/_ui/ErrorBoundary/index.jsx b/frontend/src/_ui/ErrorBoundary/index.jsx index 61bd339227..2d3a64fae9 100644 --- a/frontend/src/_ui/ErrorBoundary/index.jsx +++ b/frontend/src/_ui/ErrorBoundary/index.jsx @@ -15,6 +15,9 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service + alert( + `An error occurred: ${JSON.stringify(error)} and the error info is ${JSON.stringify(JSON.stringify(errorInfo))}` + ); console.log('error--- 1--- ', error, errorInfo); } From 33379600b220601672e6c8963b7ba6626d29c1d8 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 5 Jun 2025 11:55:55 +0530 Subject: [PATCH 74/88] Data-cy inspector added --- .../CustomJSONViewer/Components/Row.jsx | 11 ++++++-- .../LeftSidebarInspector/HiddenOptions.jsx | 9 +++++- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 1 + .../LeftSidebarInspector.jsx | 28 ++++++++++++++----- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 5 ++-- .../LeftSidebarInspector/TreeViewHeader.jsx | 10 ++++++- 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index 4ec5b674a9..2e8abe7c0d 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -12,6 +12,7 @@ import { ToolTip } from '@/_components/ToolTip'; import { DefaultCopyIcon } from '../../DefaultCopyIcon'; import { copyToClipboard, extractComponentName } from '../../utils'; import WidgetIcon from '@/../assets/images/icons/widgets'; +import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers'; const renderNodeIcons = (node, iconsList, darkMode) => { const icon = iconsList.filter((icon) => icon?.iconName === node)[0]; @@ -76,13 +77,19 @@ const Row = ({ label, value, level = 1, absolutePath, iconsList, darkMode }) => /> ))}
-
+
{renderNodeIcons(label, iconsList, darkMode)} {label}
-
+
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index 5e407f75a1..efa3bae7a2 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -6,6 +6,7 @@ import { shallow } from 'zustand/shallow'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import cx from 'classnames'; import { DefaultCopyIcon } from './DefaultCopyIcon'; +import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers'; export const HiddenOptions = (props) => { const { nodeSpecificFilteredActions, generalActionsFiltered, darkMode, setActionClicked, data } = props; @@ -48,7 +49,11 @@ export const HiddenOptions = (props) => { const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; if (icon) { return ( -
+
{/* ${name === 'Go to component' ? '' : currentNode} */} { closeMenu(); }} className="option" + data-cy="inspector-copy-path" > Copy path @@ -103,6 +109,7 @@ export const HiddenOptions = (props) => { closeMenu(); }} className="option" + data-cy="inspector-copy-value" > Copy value diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 38db232597..120276bfc0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -157,6 +157,7 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths placeholder="Search" value={searchValue} {...(searchValue && { trailingAction: 'clear' })} + data-cy="inspector-search-input" />
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index d3703888a5..a5bb77966c 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -71,38 +71,43 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType } { id: 'queries', name: 'Queries', + children: sortedQueries, - metadata: { path: 'queries' }, + metadata: { type: 'queries', path: 'queries' }, }, { id: 'components', name: 'Components', + children: sortedComponents, - metadata: { path: 'components' }, + metadata: { type: 'components', path: 'components' }, }, { id: 'globals', name: 'Globals', + children: sortedGlobalVariables, - metadata: { path: 'globals' }, + metadata: { type: 'globals', path: 'globals' }, }, { id: 'variables', name: 'Variables', + children: sortedVariables, - metadata: { path: 'variables' }, + metadata: { type: 'variables', path: 'variables' }, }, { id: 'page', name: 'Page', + children: sortedPageVariables, - metadata: { path: 'page' }, + metadata: { type: 'page', path: 'page' }, }, { id: 'constants', name: 'Constants', children: sortedConstants, - metadata: { path: 'constants' }, + metadata: { type: 'constants', path: 'constants' }, }, ], }; @@ -117,7 +122,15 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType } } return jsontreeData; - }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables, sortedModuleInputs]); + }, [ + sortedComponents, + sortedQueries, + sortedVariables, + sortedConstants, + sortedPageVariables, + sortedGlobalVariables, + sortedModuleInputs, + ]); return (
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index d3e806a577..3f4c23c315 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -73,7 +73,7 @@ export const Node = (props) => { const { isBranch, element } = node || {}; const { metadata } = element || {}; const { path, type } = metadata || {}; - if (type) { + if (type && level !== 1) { setSelectedNodePath(path); } }; @@ -115,6 +115,7 @@ export const Node = (props) => { variant="ghost" fill="var(--icon-default,#ACB2B9)" size="small" + data-cy={`inspector-${type}-expand-button`} /> )}
@@ -128,7 +129,7 @@ export const Node = (props) => { })} > {nodeIcon &&
{nodeIcon}
} -
+
{ const { path, backFn, darkMode, data, nodeSpecificActions, type, generalActions } = props; @@ -54,6 +55,7 @@ export const TreeViewHeader = (props) => { closeMenu(); }} className="option" + data-cy="inspector-copy-path" > Copy path @@ -65,6 +67,7 @@ export const TreeViewHeader = (props) => { closeMenu(); }} className="option" + data-cy="inspector-copy-value" > Copy value @@ -75,7 +78,11 @@ export const TreeViewHeader = (props) => { const { name, icon, src, iconName, dispatchAction, width = 16, height = 16 } = actionOption; if (icon) { return ( -
+
{ @@ -140,6 +147,7 @@ export const TreeViewHeader = (props) => { border: 'none', boxShadow: 'none', }} + data-cy="inspector-menu-icon" >
From 6db968fbb1f111a24722374c60f63ea8d415444e Mon Sep 17 00:00:00 2001 From: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:47:36 +0530 Subject: [PATCH 75/88] Revert back browser tab titles (#12910) * fix tab title init * paid title * Fix automation frmo title.cy.js accordin to the requirement * Fix automation typos * profile settings * capital --------- Co-authored-by: Midhun G S Co-authored-by: emidhun Co-authored-by: Ajith KV --- .../commonTestcases/newSuits/appTitle.cy.js | 37 ++----------------- frontend/src/AppBuilder/_hooks/useAppData.js | 18 ++++++++- .../_helpers/white-label/whiteLabelling.js | 21 +++++++++-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js index b9bce11f9a..e98190bc81 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js @@ -1,34 +1,5 @@ import { fake } from "Fixtures/fake"; -import { textInputText } from "Texts/textInput"; -import { commonWidgetText, widgetValue, customValidation } from "Texts/common"; -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { buttonText } from "Texts/button"; -import { - verifyControlComponentAction, - randomString, -} from "Support/utils/editor/textInput"; -import { - openAccordion, - verifyAndModifyParameter, - openEditorSidebar, - verifyAndModifyToggleFx, - addDefaultEventHandler, - verifyComponentValueFromInspector, - selectColourFromColourPicker, - verifyBoxShadowCss, - verifyLayout, - verifyTooltip, - editAndVerifyWidgetName, - verifyPropertiesGeneralAccordion, - verifyStylesGeneralAccordion, - randomNumber, - closeAccordions, -} from "Support/utils/commonWidget"; -import { - selectCSA, - selectEvent, - addSupportCSAData, -} from "Support/utils/events"; +import { commonWidgetSelector } from "Selectors/common"; describe("Editor title", () => { const data = {}; @@ -44,8 +15,8 @@ describe("Editor title", () => { }); it("should verify titles", () => { cy.url().should("include", "/tooljets-workspace"); - // cy.title().should("eq", "Dashboard | ToolJet"); - cy.title().should("eq", "ToolJet"); + cy.title().should("eq", "Dashboard | ToolJet"); + // cy.title().should("eq", "ToolJet"); cy.log(data.appName); @@ -57,7 +28,7 @@ describe("Editor title", () => { cy.url().should("include", `/applications/${Cypress.env("appId")}`); // cy.title().should("eq", `${data.appName} | ToolJet`); - // cy.title().should("eq", `Preview - ${data.appName} | ToolJet`); + cy.title().should("eq", `Preview - ${data.appName} | ToolJet`); cy.go("back"); cy.releaseApp(); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 433a677dc7..9d47f2b780 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -122,6 +122,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const toggleLeftSidebar = useStore((state) => state.toggleLeftSidebar); const pathParams = useParams(); const slug = pathParams?.slug; + const licenseStatus = useStore((state) => state.isLicenseValid()); const match = useMatch('/applications/:slug/:pageHandle'); @@ -129,6 +130,13 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const initialLoadRef = useRef(true); + const { isReleasedVersionId } = useStore( + (state) => ({ + isReleasedVersionId: state?.releasedVersionId == state.currentVersionId || state.isVersionReleased, + }), + shallow + ); + const fetchAndInjectCustomStyles = async (isPublicAccess = false) => { try { const head = document.head || document.getElementsByTagName('head')[0]; @@ -462,8 +470,14 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v }, [isComponentLayoutReady]); useEffect(() => { - fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: app.appName }); - }, [app.appName]); + fetchAndSetWindowTitle({ + page: pageTitles.EDITOR, + appName: app.appName, + mode: mode, + isReleased: isReleasedVersionId, + licenseStatus: licenseStatus, + }); + }, [app.appName, isReleasedVersionId, licenseStatus, mode]); useEffect(() => { if (!themeAccess) return; diff --git a/frontend/src/_helpers/white-label/whiteLabelling.js b/frontend/src/_helpers/white-label/whiteLabelling.js index cfbbcbfc0c..c32573a96f 100644 --- a/frontend/src/_helpers/white-label/whiteLabelling.js +++ b/frontend/src/_helpers/white-label/whiteLabelling.js @@ -54,7 +54,8 @@ export async function setFaviconAndTitle(location) { 'data-sources': 'Data sources', 'audit-logs': 'Audit logs', 'account-settings': 'Profile settings', - settings: 'Profile settings', + settings: 'Settings', + 'profile-settings': 'Profile settings', login: '', signUp: '', error: '', @@ -65,9 +66,12 @@ export async function setFaviconAndTitle(location) { 'reset-password': '', 'workspace-constants': 'Workspace constants', setup: '', + '/': 'Dashboard', }; - const pageTitleKey = Object.keys(pageTitles).find((path) => location?.pathname.includes(path)); + const pageTitleKey = Object.keys(pageTitles) + .sort((a, b) => b.length - a.length) // Sort by length descending + .find((path) => location?.pathname.includes(path)); const pageTitle = pageTitles[pageTitleKey] || ''; document.title = pageTitle ? `${decodeEntities(pageTitle)} | ${whiteLabelText}` : `${decodeEntities(whiteLabelText)}`; @@ -77,6 +81,9 @@ export async function fetchAndSetWindowTitle(pageDetails) { const whiteLabelText = retrieveWhiteLabelText(); let pageTitleKey = pageDetails?.page || ''; let pageTitle = ''; + let mode = pageDetails?.mode || ''; + let isPreview = !pageDetails?.isReleased || false; + const license = pageDetails?.licenseStatus; switch (pageTitleKey) { case pageTitles.VIEWER: { const titlePrefix = pageDetails?.preview ? 'Preview - ' : ''; @@ -85,7 +92,11 @@ export async function fetchAndSetWindowTitle(pageDetails) { } case pageTitles.EDITOR: case pageTitles.WORKFLOW_EDITOR: { - pageTitle = pageDetails?.appName || 'My App'; + if (mode == 'edit') { + pageTitle = `${pageDetails?.appName}`; + } else { + pageTitle = `Preview - ${pageDetails?.appName}` || 'My App'; + } break; } default: { @@ -93,6 +104,10 @@ export async function fetchAndSetWindowTitle(pageDetails) { break; } } + if (!isPreview && mode === 'view') { + document.title = `${pageDetails?.appName} ${license ? '' : '| ToolJet'}`; + return; + } document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`; } From 52191c9625bba42c1f229a86620a0a2905994784 Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:25:35 +0530 Subject: [PATCH 76/88] Adding dockerfile change for cloud and ee file (#12987) * Adding dockerfile change for cloud and ee file * fix EOF --- docker/cloud/cloud-entrypoint.sh | 31 ++++++ docker/cloud/cloud-production.Dockerfile | 121 +++++++++++++++++++++++ docker/cloud/cloud-server.Dockerfile | 9 +- docker/ee/ee-entrypoint.sh | 109 ++++++++++++++++++++ docker/ee/ee-production.Dockerfile | 77 ++++++++++++++- 5 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 docker/cloud/cloud-entrypoint.sh create mode 100644 docker/cloud/cloud-production.Dockerfile diff --git a/docker/cloud/cloud-entrypoint.sh b/docker/cloud/cloud-entrypoint.sh new file mode 100644 index 0000000000..26df38cb5c --- /dev/null +++ b/docker/cloud/cloud-entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +npm cache clean --force + +if [ -d "./server/dist" ]; then + SETUP_CMD='npm run cloud:setup:prod' +else + SETUP_CMD='npm run cloud:setup' +fi + +npm cache clean --force + +if [ -f "./.env" ]; then + declare $(grep -v '^#' ./.env | xargs) +fi + +if [ -z "$DATABASE_URL" ]; then + ./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- $SETUP_CMD +else + PG_HOST=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $6}') + PG_PORT=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $7}') + + if [ -z "$DATABASE_PORT" ]; then + DATABASE_PORT="5432" + fi + + ./server/scripts/wait-for-it.sh "$PG_HOST:$PG_PORT" --strict --timeout=300 -- $SETUP_CMD +fi + +exec "$@" diff --git a/docker/cloud/cloud-production.Dockerfile b/docker/cloud/cloud-production.Dockerfile new file mode 100644 index 0000000000..a2e36bedb3 --- /dev/null +++ b/docker/cloud/cloud-production.Dockerfile @@ -0,0 +1,121 @@ +FROM node:18.18.2-buster AS builder + +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=4096" + +RUN npm i -g npm@9.8.1 +RUN mkdir -p /app +# RUN npm cache clean --force + +WORKDIR /app + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM debian:11 + +RUN apt-get update -yq \ + && apt-get install curl gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt-get clean -y + + +RUN curl -O https://nodejs.org/dist/v18.18.2/node-v18.18.2-linux-x64.tar.xz \ + && tar -xf node-v18.18.2-linux-x64.tar.xz \ + && mv node-v18.18.2-linux-x64 /usr/local/lib/nodejs \ + && echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \ + && /bin/bash -c "source /etc/profile.d/nodejs.sh" \ + && rm node-v18.18.2-linux-x64.tar.xz +ENV PATH=/usr/local/lib/nodejs/bin:$PATH + +ENV NODE_ENV=production +ENV NODE_OPTIONS="--max-old-space-size=4096" +RUN apt-get update && \ + apt-get install -y postgresql-client freetds-dev libaio1 wget && \ + apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes && \ + apt-get -y autoremove && \ + apt-get -y autoclean + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle + +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + + +WORKDIR / + +RUN mkdir -p /app + +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json + +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json + +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +COPY --from=builder /app/server/dist ./app/server/dist + +COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh + +# Define non-sudo user +RUN useradd --create-home --home-dir /home/appuser appuser \ + && chown -R appuser:0 /app \ + && chown -R appuser:0 /home/appuser \ + && chmod u+x /app \ + && chmod -R g=u /app + +# Set npm cache directory +ENV npm_config_cache /home/appuser/.npm + +ENV HOME=/home/appuser + +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + +USER appuser + +WORKDIR /app +# Dependencies for scripts outside nestjs +RUN npm install dotenv@10.0.0 joi@17.4.1 + +ENTRYPOINT ["./server/cloud-entrypoint.sh"] diff --git a/docker/cloud/cloud-server.Dockerfile b/docker/cloud/cloud-server.Dockerfile index cc9fd4fce3..96a9fe0f01 100644 --- a/docker/cloud/cloud-server.Dockerfile +++ b/docker/cloud/cloud-server.Dockerfile @@ -91,12 +91,13 @@ COPY --from=builder /app/plugins/package.json ./app/plugins/package.json # copy server build COPY --from=builder /app/server/package.json ./app/server/package.json COPY --from=builder /app/server/.version ./app/server/.version -COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh COPY --from=builder /app/server/node_modules ./app/server/node_modules COPY --from=builder /app/server/templates ./app/server/templates COPY --from=builder /app/server/scripts ./app/server/scripts COPY --from=builder /app/server/dist ./app/server/dist +COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh + # Define non-sudo user RUN useradd --create-home --home-dir /home/appuser appuser \ && chown -R appuser:0 /app \ @@ -108,10 +109,14 @@ RUN useradd --create-home --home-dir /home/appuser appuser \ ENV npm_config_cache /home/appuser/.npm ENV HOME=/home/appuser + +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + USER appuser WORKDIR /app # Dependencies for scripts outside nestjs RUN npm install dotenv@10.0.0 joi@17.4.1 -ENTRYPOINT ["./server/entrypoint.sh"] +ENTRYPOINT ["./server/cloud-entrypoint.sh"] diff --git a/docker/ee/ee-entrypoint.sh b/docker/ee/ee-entrypoint.sh index ac4b0bafd2..f9319e16be 100755 --- a/docker/ee/ee-entrypoint.sh +++ b/docker/ee/ee-entrypoint.sh @@ -42,6 +42,115 @@ else echo "Using external PostgREST at $PGRST_HOST." fi +# Neo4j configuration +# ---------------------------------- +# Default Neo4j environment values +# ---------------------------------- +export NEO4J_USER=${NEO4J_USER:-"neo4j"} +export NEO4J_PASSWORD=${NEO4J_PASSWORD:-"appaqvyvRLbeukhFE"} +export NEO4J_AUTH=${NEO4J_AUTH:-"neo4j/appaqvyvRLbeukhFE"} +export NEO4J_URI=${NEO4J_URI:-"bolt://localhost:7687"} +export NEO4J_PLUGINS=${NEO4J_PLUGINS:-'["apoc"]'} +export NEO4J_AUTH + +# Extract username and password from NEO4J_AUTH if set +if [ -n "$NEO4J_AUTH" ]; then + # Extract username and password from NEO4J_AUTH (format: username/password) + NEO4J_USERNAME=$(echo "$NEO4J_AUTH" | cut -d'/' -f1) + NEO4J_PASSWORD=$(echo "$NEO4J_AUTH" | cut -d'/' -f2) + + # Export these for application use + export NEO4J_USERNAME + export NEO4J_PASSWORD + + echo "Neo4j authentication configured with username: $NEO4J_USERNAME" +else + echo "NEO4J_AUTH not set, using default authentication" +fi + +# Check if Neo4j is already initialized and set password if necessary +if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then + echo "Setting Neo4j initial password..." + + # Ensure Neo4j is not running before setting the initial password + neo4j stop || true + + # Set the initial password using the correct command format for Neo4j 5.x + NEO4J_ADMIN_CMD=$(which neo4j-admin) + NEO4J_VERSION=$(neo4j --version | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -n 1) + echo "Detected Neo4j version: $NEO4J_VERSION" + + # Use version-specific command format + MAJOR_VERSION=$(echo $NEO4J_VERSION | cut -d. -f1) + if [ "$MAJOR_VERSION" -ge "5" ]; then + # For Neo4j 5.x and higher + echo "Using Neo4j 5.x+ password command format" + $NEO4J_ADMIN_CMD dbms set-initial-password "$NEO4J_PASSWORD" --require-password-change=false >/dev/null 2>&1 || { + echo "Warning: Could not set Neo4j password, it may already be set" + } + else + # For Neo4j 4.x and lower + echo "Using Neo4j 4.x password command format" >/dev/null 2>&1 + $NEO4J_ADMIN_CMD set-initial-password "$NEO4J_PASSWORD" >/dev/null 2>&1 || { + echo "Warning: Could not set Neo4j password, it may already be set" + } + fi +fi + +# Update Neo4j configuration +echo "Configuring Neo4j..." +cat > /etc/neo4j/neo4j.conf << EOF +# Neo4j configuration +dbms.security.auth_enabled=true +server.bolt.enabled=true +server.bolt.listen_address=0.0.0.0:7687 +server.directories.data=/var/lib/neo4j/data +server.directories.logs=/var/log/neo4j +initial.dbms.default_database=neo4j +server.directories.plugins=/var/lib/neo4j/plugins +server.directories.import=/var/lib/neo4j/import + +# APOC Settings +dbms.security.procedures.unrestricted=apoc.* +dbms.security.procedures.allowlist=apoc.*,algo.*,gds.* +EOF + +if [ -w "$NEO4J_LOG_DIR" ]; then + chmod -R 770 "$NEO4J_LOG_DIR" || echo "Warning: Could not set log directory permissions" >/dev/null 2>&1 +fi + +# Start Neo4j +echo "Starting Neo4j service..." +neo4j console >/dev/null 2>&1 & + +# Add a wait for Neo4j to be ready with more robust checking +echo "Waiting for Neo4j to be ready..." +NEO4J_READY=false +for i in {1..60}; do + # First try standard status check + if neo4j status >/dev/null 2>&1; then + echo "Neo4j is ready (via status check)" + NEO4J_READY=true + break + fi + + # Also try connecting to the bolt port as a fallback + if command -v nc >/dev/null 2>&1; then + if nc -z localhost 7687 >/dev/null 2>&1; then + echo "Neo4j is ready (port 7687 is open)" + NEO4J_READY=true + break + fi + fi + + echo "Waiting for Neo4j to start... ($i/60)" + sleep 2 +done + +if [ "$NEO4J_READY" = false ]; then + echo "WARNING: Neo4j may not be fully started yet, but continuing..." +fi + # Check WORKLOW_WORKER and skip SETUP_CMD if true if [ "${WORKFLOW_WORKER}" == "true" ]; then echo "WORKFLOW_WORKER is set to true. Running worker process." diff --git a/docker/ee/ee-production.Dockerfile b/docker/ee/ee-production.Dockerfile index e611643f30..e7405f0994 100644 --- a/docker/ee/ee-production.Dockerfile +++ b/docker/ee/ee-production.Dockerfile @@ -60,7 +60,7 @@ RUN npm --prefix server run build FROM debian:11 RUN apt-get update -yq \ - && apt-get install curl gnupg zip -yq \ + && apt-get install curl wget gnupg zip -yq \ && apt-get install -yq build-essential \ && apt -y install redis \ && apt-get clean -y @@ -80,13 +80,29 @@ RUN echo "[supervisord]\n" \ "nodaemon=true\n" \ "\n" \ "[program:postgrest]\n" \ - "command=/bin/postgrest \n" \ + "command=/bin/postgrest\n" \ "autostart=true\n" \ "autorestart=true\n" \ "stdout_logfile=/dev/stdout\n" \ "stderr_logfile=/dev/stderr\n" \ "stdout_logfile_maxbytes=0\n" \ "stderr_logfile_maxbytes=0\n" \ + "\n" \ + "[program:neo4j]\n" \ + "command=neo4j console\n" \ + "autostart=true\n" \ + "autorestart=unexpected\n" \ + "startsecs=30\n" \ + "startretries=999\n" \ + "priority=90\n" \ + "exitcodes=0,1,2\n" \ + "stopsignal=SIGTERM\n" \ + "stopasgroup=true\n" \ + "killasgroup=true\n" \ + "redirect_stderr=true\n" \ + "stdout_logfile=/var/log/neo4j/neo4j.log\n" \ + "stdout_logfile_backups=10\n" \ + "stderr_capture_maxbytes=20MB\n" \ "\n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf # Create a wrapper for PostgREST to prefix its logs @@ -114,6 +130,48 @@ RUN apt-get update && \ apt-get -y autoremove && \ apt-get -y autoclean +# Install Neo4j +RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \ + echo "deb https://debian.neo4j.com stable 5" > /etc/apt/sources.list.d/neo4j.list && \ + apt-get update && \ + apt-get install -y neo4j=1:5.26.6 && \ + apt-mark hold neo4j && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the necessary Neo4j environment variables +ENV NEO4J_HOME=/opt/neo4j +ENV NEO4J_CONF=/etc/neo4j +ENV NEO4J_DATA=/var/lib/neo4j/data +ENV NEO4J_LOG=/var/log/neo4j +ENV NEO4J_PLUGIN=/var/lib/neo4j/plugins +ENV NEO4J_IMPORT=/var/lib/neo4j/import + +# Create the necessary directories for Neo4j +RUN mkdir -p /data/db /data/logs /data/plugins +RUN mkdir -p /opt/neo4j/plugins + +# Configure APOC plugin for Neo4j +ENV NEO4J_dbms_active_plugins=apoc + +# Download and install APOC plugin for Neo4j 5.x (BEFORE creating user) +RUN mkdir -p /var/lib/neo4j/plugins && \ + wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar && \ + # Try to download extended version + (wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-extended.jar || \ + wget -P /var/lib/neo4j/plugins https://neo4j-contrib.github.io/neo4j-apoc-procedures/5.26.6/apoc-5.26.6-extended.jar || \ + echo "Extended JAR not available, continuing with core only") + +# Configure Neo4j with APOC +RUN echo "dbms.security.procedures.unrestricted=apoc.*" >> /etc/neo4j/neo4j.conf && \ + echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" >> /etc/neo4j/neo4j.conf && \ + echo "dbms.directories.plugins=/var/lib/neo4j/plugins" >> /etc/neo4j/neo4j.conf + +# Configure Neo4j to use authentication +RUN if [ -f "/etc/neo4j/neo4j.conf" ]; then \ + sed -i '/dbms.security.auth_enabled/d' /etc/neo4j/neo4j.conf && \ + echo "dbms.security.auth_enabled=true" >> /etc/neo4j/neo4j.conf; \ +fi + # Install Instantclient Basic Light Oracle and Dependencies WORKDIR /opt/oracle @@ -149,6 +207,7 @@ COPY --from=builder /app/server/node_modules ./app/server/node_modules COPY --from=builder /app/server/templates ./app/server/templates COPY --from=builder /app/server/scripts ./app/server/scripts COPY --from=builder /app/server/dist ./app/server/dist +COPY --from=builder /app/server/src/assets ./app/server/src/assets COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh @@ -161,14 +220,21 @@ RUN useradd --create-home --home-dir /home/appuser appuser \ && chmod -R g=u /app \ && chmod -R g=u /home -# Create directory /home/appuser and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \ + chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \ + chmod -R 770 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \ + chmod -R 644 /var/lib/neo4j/plugins/*.jar && \ + chown -R appuser:0 /var/lib/neo4j/plugins && \ + chmod 755 /var/lib/neo4j/plugins + +# Create directory /home/appuser and set ownership to appuser RUN mkdir -p /home/appuser \ && chown -R appuser:0 /home/appuser \ && chmod g+s /home/appuser \ && chmod -R g=u /home/appuser \ && npm cache clean --force -# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser RUN mkdir -p /tmp/.npm/npm-cache/ \ && chown -R appuser:0 /tmp/.npm/npm-cache/ \ && chmod g+s /tmp/.npm/npm-cache/ \ @@ -206,6 +272,9 @@ RUN mkdir -p /var/lib/postgrest /var/log/postgrest /etc/postgrest \ ENV HOME=/home/appuser +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + # Switch back to appuser USER appuser From d638ef60aa279ca726e42f138ad72eddeb93066a Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 9 Jun 2025 10:19:36 +0530 Subject: [PATCH 77/88] Revert "chore: Added an alert for error" This reverts commit d8d41de924b13424b7cbc1cc8926064173555abf. --- frontend/src/Editor/ErrorBoundary.jsx | 3 --- frontend/src/_ui/ErrorBoundary/index.jsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/frontend/src/Editor/ErrorBoundary.jsx b/frontend/src/Editor/ErrorBoundary.jsx index 87570ec750..678bda4c9f 100644 --- a/frontend/src/Editor/ErrorBoundary.jsx +++ b/frontend/src/Editor/ErrorBoundary.jsx @@ -15,9 +15,6 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - alert( - `An error occurred: ${JSON.stringify(error)} and the error info is ${JSON.stringify(JSON.stringify(errorInfo))}` - ); console.log('error--- 2--- ', error, errorInfo); } diff --git a/frontend/src/_ui/ErrorBoundary/index.jsx b/frontend/src/_ui/ErrorBoundary/index.jsx index 2d3a64fae9..61bd339227 100644 --- a/frontend/src/_ui/ErrorBoundary/index.jsx +++ b/frontend/src/_ui/ErrorBoundary/index.jsx @@ -15,9 +15,6 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - alert( - `An error occurred: ${JSON.stringify(error)} and the error info is ${JSON.stringify(JSON.stringify(errorInfo))}` - ); console.log('error--- 1--- ', error, errorInfo); } From 295c97c1209cf76bbd6188a1c84689111e41e525 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 9 Jun 2025 10:19:52 +0530 Subject: [PATCH 78/88] Revert "chore: enabled console logs" This reverts commit 0d358e441dc4ed4c569cc28480517379d0e44c2c. --- frontend/src/Editor/ErrorBoundary.jsx | 2 +- frontend/src/_ui/ErrorBoundary/index.jsx | 2 +- frontend/webpack.config.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/ErrorBoundary.jsx b/frontend/src/Editor/ErrorBoundary.jsx index 678bda4c9f..4ec47921c3 100644 --- a/frontend/src/Editor/ErrorBoundary.jsx +++ b/frontend/src/Editor/ErrorBoundary.jsx @@ -15,7 +15,7 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - console.log('error--- 2--- ', error, errorInfo); + console.log(error, errorInfo); } render() { diff --git a/frontend/src/_ui/ErrorBoundary/index.jsx b/frontend/src/_ui/ErrorBoundary/index.jsx index 61bd339227..4ec47921c3 100644 --- a/frontend/src/_ui/ErrorBoundary/index.jsx +++ b/frontend/src/_ui/ErrorBoundary/index.jsx @@ -15,7 +15,7 @@ class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service - console.log('error--- 1--- ', error, errorInfo); + console.log(error, errorInfo); } render() { diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 133cc7f6f7..986c7011b1 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -89,7 +89,7 @@ module.exports = { keep_fnames: true, compress: { drop_debugger: true, - drop_console: false, + drop_console: true, }, }, parallel: environment === 'production', From 9165682c678b2dde400383b6d1d93dd71905e540 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Mon, 9 Jun 2025 10:31:37 +0530 Subject: [PATCH 79/88] chore: update submodule references --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index aa3c4f603f..064c4c149a 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit aa3c4f603f549337fc88a772a6a31e18eaf38701 +Subproject commit 064c4c149ab095ea9e6b6f992b45ae5a3f190673 diff --git a/server/ee b/server/ee index f70ac83c38..b58301bdd8 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit f70ac83c38e0a8b44aeb2a0fb2059690eb5e2f46 +Subproject commit b58301bdd81d035f491debec0a55c7fc342c4f12 From 24fc71ea348b5b7fbb194f7d466098ef814ef9e6 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Mon, 9 Jun 2025 15:34:30 +0530 Subject: [PATCH 80/88] Fixed inspector breaking for duplicate component name along with minor css issues (#12990) --- .../AppBuilder/AppCanvas/appCanvasUtils.js | 1 + .../CustomJSONViewer/Components/Row.jsx | 2 +- .../LeftSidebarInspector/HiddenOptions.jsx | 2 +- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 33 ++++++++++++++++++- .../LeftSidebarInspector.jsx | 31 +++++++++++++---- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 26 ++++++++++----- .../LeftSidebarInspector/TreeViewHeader.jsx | 2 +- .../LeftSidebarInspector/styles.scss | 8 +++++ .../LeftSidebar/LeftSidebarInspector/utils.js | 5 +-- frontend/src/_styles/components.scss | 2 ++ frontend/src/_styles/left-sidebar.scss | 10 +++++- .../src/_ui/Icon/solidIcons/RemoveFolder.jsx | 28 ++++++++++++++++ frontend/src/_ui/Icon/solidIcons/index.js | 3 ++ 13 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index cfec0ab180..a138b759cc 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -195,6 +195,7 @@ export function addChildrenWidgetsToParent(componentType, parentId, currentLayou component: { ...componentData, parent: _parent, + name: widgetName, }, layouts: { [currentLayout]: { diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index 2e8abe7c0d..94bf363706 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -96,7 +96,7 @@ const Row = ({ label, value, level = 1, absolutePath, iconsList, darkMode }) => { - copyToClipboard(absolutePath); + copyToClipboard(`{{${absolutePath}}}`, false); }} className="copy-to-clipboard json-viewer-action-icon" > diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index efa3bae7a2..05c4333549 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -17,7 +17,7 @@ export const HiddenOptions = (props) => { }; const copyPath = () => { - generalActionsFiltered[0].dispatchAction(data?.selectedNodePath); + generalActionsFiltered[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false); }; const copyValue = () => { diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 120276bfc0..3829f2187a 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -9,6 +9,36 @@ import { v4 as uuidv4 } from 'uuid'; import InputComponent from '@/components/ui/Input/Index'; import { isEmpty } from 'lodash'; +const ensureUniqueIds = (node, parentId = '') => { + if (!node) return node; + + const seenIds = new Set(); + const processNode = (currentNode, currentParentId = '') => { + if (!currentNode) return currentNode; + + const newChildren = currentNode.children?.map((child, index) => { + const baseId = child.id; + let uniqueId = baseId; + let counter = 1; + + while (seenIds.has(uniqueId)) { + uniqueId = `${baseId}_${counter}`; + counter++; + } + + seenIds.add(uniqueId); + return processNode({ ...child, id: uniqueId }, uniqueId); + }); + + return { + ...currentNode, + children: newChildren, + }; + }; + + return processNode(node, parentId); +}; + const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { const searchValue = useStore((state) => state.inspectorSearchValue, shallow); const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); @@ -81,7 +111,8 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths return uuidv4(); }, [JSON.stringify(data), selectedNodePath]); - const flattendedData = flattenTree(data); + const processedData = useMemo(() => ensureUniqueIds(data), [data]); + const flattendedData = flattenTree(processedData); const backFn = () => { setSelectedNodePath(null); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index a5bb77966c..6b8e52ce4b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -9,6 +9,7 @@ import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; import { Button as ButtonComponent } from '@/components/ui/Button/Button'; import { formatInspectorDataMisc, formatInspectorQueryData } from './utils'; +import ErrorBoundary from '@/_ui/ErrorBoundary'; import './styles.scss'; @@ -121,6 +122,22 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType } }); } + const addNoDataChild = (data) => { + const types = data.children; + types.forEach((type) => { + if (type.children.length === 0) { + type.children.push({ + id: `empty-${type.metadata.type}`, + name: `No ${type.metadata.type} found`, + children: [], + metadata: { noData: true }, + }); + } + }); + }; + + addNoDataChild(jsontreeData); + return jsontreeData; }, [ sortedComponents, @@ -154,12 +171,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }
- + + +
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 3f4c23c315..081737f3f5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -124,20 +124,28 @@ export const Node = (props) => {
onSelect(props)} className={cx('node-content', { - 'node-content-hoverable': level !== 1, + 'node-content-hoverable': level !== 1 && !metadata.noData, 'node-content-active': actionClicked, })} > {nodeIcon &&
{nodeIcon}
}
- - - + {metadata.noData && ( +
+ + {element.name} +
+ )} + {!metadata.noData && ( + + + + )}
{ }; const copyPath = () => { - generalActions[0].dispatchAction(data?.selectedNodePath); + generalActions[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false); }; const copyValue = () => { diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss index 162bbdce06..6a2c981600 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss @@ -277,4 +277,12 @@ } } } +} + +.node-label-no-data { + display: flex; + align-items: center; + gap: 4px; + color: var(--text-placeholder, #858C94); + } \ No newline at end of file diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 203b5ea298..87ee5306a2 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -84,8 +84,9 @@ export const extractComponentName = (path) => { } }; -export const copyToClipboard = (data) => { +export const copyToClipboard = (data, includeQuotes = true) => { const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); - navigator.clipboard.writeText(stringified); + const finalText = includeQuotes ? stringified : stringified.slice(1, -1); + navigator.clipboard.writeText(finalText); return toast.success('Copied to the clipboard', { position: 'top-center' }); }; diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 176aca62b3..b823e7623c 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -122,6 +122,8 @@ $btn-dark-color: #FFFFFF; .page-selector-panel-body { padding: 4px; border-right: 1px solid #DFE3E6; + padding-left:16px; + padding-right:16px; &.dark-theme { border-right: 1px solid var(--slate7); diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index 1e76904563..6a0311b0c4 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -182,9 +182,17 @@ } } +.page { + .leftsidebar-panel-header { + margin-bottom: 8px; + } + +} + .debugger { .leftsidebar-panel-header { border-bottom: none; + margin-bottom: 8px; } .text-slate-12 { @@ -207,7 +215,7 @@ } .nav-item .nav-link { - background-color: var(--slate3) !important; + background-color: var(--base) !important; color: var(--slate11) !important; } diff --git a/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx new file mode 100644 index 0000000000..dd97454175 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx @@ -0,0 +1,28 @@ +import React from 'react'; + +const RemoveFolder = ({ width = '14', fill = '#6A727C', className = '', viewBox = '0 0 14 14' }) => ( + + + + + + + + + + +); + +export default RemoveFolder; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 743c2a6e9c..0e7192edaf 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -239,6 +239,7 @@ import EnterpriseCrown from './EnterrpiseCrown.jsx'; import FileCode from './FileCode.jsx'; import Corners from './Corners.jsx'; import Moon from './Moon.jsx'; +import RemoveFolder from './RemoveFolder.jsx'; const Icon = (props) => { switch (props.name) { @@ -504,6 +505,8 @@ const Icon = (props) => { return ; case 'remove01': return ; + case 'removefolder': + return ; case 'removerectangle': return ; case 'rightarrrow': From a233a79b01e529472dc04436edfdc64140ede40c Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:20:51 +0530 Subject: [PATCH 81/88] fix: crash issue on dropping components on CE (#12999) --- frontend/src/AppBuilder/AppCanvas/Container.jsx | 3 ++- .../RightSideBar/ComponentManagerTab/DragLayer.jsx | 3 ++- .../RightSideBar/Inspector/EventManager.jsx | 13 +++++++------ .../src/AppBuilder/_stores/slices/eventsSlice.js | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index e31252d7ce..b90eb55380 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -28,6 +28,7 @@ import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; +import { noop } from 'lodash'; //TODO: Revisit the logic of height (dropRef) @@ -69,7 +70,7 @@ export const Container = React.memo( const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); - const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 55d6a41ce8..0c6f7327af 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -8,10 +8,11 @@ import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { noop } from 'lodash'; export const DragLayer = ({ index, component, isModuleTab = false }) => { const { isModuleEditor } = useModuleContext(); - const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index cdde36aa69..a5f5bb2202 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -54,7 +54,7 @@ export const EventManager = ({ const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup ); - const moduleInputDummyQueries = useStore((state) => state.getModuleInputDummyQueries(), shallow); + const moduleInputDummyQueries = useStore((state) => state?.getModuleInputDummyQueries?.(), shallow) || {}; const dataQueries = useStore((state) => { const queries = state.dataQuery?.queries?.modules?.canvas || []; @@ -437,8 +437,8 @@ export const EventManager = ({ const newParams = params.length > 0 ? params.map((paramOfParamList) => { - return paramOfParamList.handle === param.handle ? newParam : paramOfParamList; - }) + return paramOfParamList.handle === param.handle ? newParam : paramOfParamList; + }) : [newParam]; return handlerChanged(index, 'componentSpecificActionParams', newParams); @@ -462,7 +462,7 @@ export const EventManager = ({ const queries = dataQueries.filter((qry) => isQueryRunnable(qry)).map((qry) => ({ name: qry.name, value: qry.id })); const moduleInputs = Object.entries(moduleInputDummyQueries).map(([key, value]) => ({ name: value, value: key })); return [...moduleInputs, ...queries]; - } + }; const formatGroupLabel = (data) => { if (data.label === 'run-action') return; return ( @@ -1010,8 +1010,9 @@ export const EventManager = ({
) : (
({ } // Check and replace the module input dummy queries with the linked query id /* Logic starts here */ - const moduleInputDummyQueries = get()?.getModuleInputDummyQueries() || {}; + const moduleInputDummyQueries = get()?.getModuleInputDummyQueries?.() || {}; let updatedQueryId = queryId, updatedQueryName = queryName, updatedModuleId = moduleId; From f45d9a2840db91ad715b5c12b28fe2dcea233702 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:47:20 +0530 Subject: [PATCH 82/88] fix: Loader was not hidden even after the appDef response. Fallback added for all module slice functions (#13000) --- frontend/src/AppBuilder/_hooks/useAppData.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index f1331e5768..8b8851eb3b 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -13,7 +13,7 @@ import { } from '@/_services'; import useStore from '@/AppBuilder/_stores/store'; import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore'; -import { camelCase, cloneDeep, isEmpty, kebabCase, mapKeys, rest } from 'lodash'; +import { camelCase, cloneDeep, isEmpty, kebabCase, mapKeys, noop, rest } from 'lodash'; import { usePrevious } from '@dnd-kit/utilities'; import { deepCamelCase } from '@/_helpers/appUtils'; import { useEventActions } from '../_stores/slices/eventsSlice'; @@ -124,11 +124,11 @@ const useAppData = ( const selectedVersion = useStore((state) => state.selectedVersion); const setIsPublicAccess = useStore((state) => state.setIsPublicAccess); - const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow); - const setModulesList = useStore((state) => state.setModulesList, shallow); - const setModuleDefinition = useStore((state) => state.setModuleDefinition); - const getModuleDefinition = useStore((state) => state.getModuleDefinition); - const deleteModuleDefinition = useStore((state) => state.deleteModuleDefinition); + const setModulesIsLoading = useStore((state) => state?.setModulesIsLoading ?? noop, shallow); + const setModulesList = useStore((state) => state?.setModulesList ?? noop, shallow); + const setModuleDefinition = useStore((state) => state?.setModuleDefinition ?? noop); + const getModuleDefinition = useStore((state) => state?.getModuleDefinition ?? noop); + const deleteModuleDefinition = useStore((state) => state?.deleteModuleDefinition ?? noop); const themeAccess = useThemeAccess(); From 162efc43a07f2daff1f7dce9a0c303c9def1c079 Mon Sep 17 00:00:00 2001 From: emidhun Date: Tue, 10 Jun 2025 18:07:04 +0530 Subject: [PATCH 83/88] Fix automation inspector items --- .../commonTestcases/newSuits/appTitle.cy.js | 2 +- .../newSuits/componentsBasics/button.cy.js | 11 +-- .../newSuits/componentsBasics/checkbox.cy.js | 8 +- .../componentsBasics/dropdown.skip.js | 4 +- .../componentsBasics/globalActions.skip.js | 6 +- .../componentsBasics/multiselect.skip.js | 4 +- .../componentsBasics/numberInput.cy.js | 8 +- .../componentsBasics/passwordInput.skip.js | 4 +- .../newSuits/componentsBasics/textInput.cy.js | 26 +++--- .../componentsBasics/toggleSwitch.skip.js | 4 +- .../newSuits/globalSetingsHappyPath.cy.js | 2 +- .../newSuits/inspectorHappypath.cy.js | 24 ++--- ...HappyPath.cy.js => runjsHappyPath.skip.js} | 12 +-- ...HappyPath.cy.js => runpyHappyPath.skip.js} | 10 +- .../cypress/support/utils/dataSource.js | 2 +- .../cypress/support/utils/inspector.js | 93 ++++++++----------- 16 files changed, 104 insertions(+), 116 deletions(-) rename cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/{runjsHappyPath.cy.js => runjsHappyPath.skip.js} (96%) rename cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/{runpyHappyPath.cy.js => runpyHappyPath.skip.js} (97%) diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js index e98190bc81..51c3e00ef1 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js @@ -13,7 +13,7 @@ describe("Editor title", () => { afterEach(() => { cy.apiDeleteApp(); }); - it("should verify titles", () => { + it.skip("should verify titles", () => { cy.url().should("include", "/tooljets-workspace"); cy.title().should("eq", "Dashboard | ToolJet"); // cy.title().should("eq", "ToolJet"); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js index 263825a0a1..56ff165f57 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Button Component Tests', () => { @@ -75,22 +75,21 @@ describe('Button Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Button-App`); cy.openApp(); - cy.dragAndDropWidget("Button", 50, 50); + cy.dragAndDropWidget("Button", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); it('should verify all the exposed values on inspector', () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); - openNode("components"); - openAndVerifyNode("button1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("button1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); - it.skip('should verify all the events from the button', () => { + it('should verify all the events from the button', () => { const events = [ { event: "On hover", message: "On hover Event" }, { event: "On Click", message: "On Click Event" }, diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js index 5ff7f869fe..521ac89867 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Checkbox Component Tests', () => { @@ -83,7 +83,7 @@ describe('Checkbox Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Checkbox-App`); cy.openApp(); - cy.dragAndDropWidget("Checkbox", 50, 50); + cy.dragAndDropWidget("Checkbox", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); @@ -92,8 +92,8 @@ describe('Checkbox Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("checkbox1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("checkbox1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js index 53dcf30a54..d9ee3d8b56 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Dropdown Component Tests', () => { @@ -101,7 +101,7 @@ describe('Dropdown Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("dropdown1", exposedValues, verifyValue); + openAndVerifyNode("dropdown1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js index 08a84e5fc3..a892251502 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js @@ -15,7 +15,7 @@ import { deleteDownloadsFolder } from "Support/utils/common"; import { resizeQueryPanel } from "Support/utils/dataSource"; -import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; @@ -49,11 +49,11 @@ describe("Global Actions", () => { verifyNodeData("variables", "Object", "1 entry "); openNode("variables", 0); - verifyValue("var", "String", `"test"`); + verifyNodeData("var", "String", `"test"`); openNode("page"); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageTest"`); + verifyNodeData("pageVar", "String", `"pageTest"`); addInputOnQueryField( "runjs", diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js index a917ef77e6..0097c7d738 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Multiselect Component Tests', () => { @@ -104,7 +104,7 @@ describe('Multiselect Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("multiselect1", exposedValues, verifyValue); + openAndVerifyNode("multiselect1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js index dddf1931a6..93aff8c0cb 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Number Input Component Tests', () => { @@ -86,7 +86,7 @@ describe('Number Input Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Numberinput-App`); cy.openApp(); - cy.dragAndDropWidget("Number Input", 50, 50); + cy.dragAndDropWidget("Number Input", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); @@ -95,8 +95,8 @@ describe('Number Input Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("numberinput1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("numberinput1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js index edfd8f04ef..5d1b377a88 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Password Input Component Tests', () => { @@ -95,7 +95,7 @@ describe('Password Input Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("passwordinput1", exposedValues, verifyValue); + openAndVerifyNode("passwordinput1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js index d7b277d193..e571cb11ec 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Text Input Component Tests', () => { @@ -27,14 +27,14 @@ describe('Text Input Component Tests', () => { "key": "setBlur", "type": "Function" }, - { - "key": "disable", - "type": "Function" - }, - { - "key": "visibility", - "type": "Function" - }, + // { + // "key": "disable", + // "type": "Function" + // }, + // { + // "key": "visibility", + // "type": "Function" + // }, { "key": "setVisibility", "type": "Function" @@ -94,17 +94,17 @@ describe('Text Input Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Textinput-App`); cy.openApp(); - cy.dragAndDropWidget("Text Input", 50, 50); + cy.dragAndDropWidget("Text Input", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); - it.skip('should verify all the exposed values on inspector', () => { + it('should verify all the exposed values on inspector', () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("textinput1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("textinput1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js index 3c97812ec9..9fc577adc1 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('ToggleSwitch Component Tests', () => { @@ -88,7 +88,7 @@ describe('ToggleSwitch Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("toggleswitch1", exposedValues, verifyValue); + openAndVerifyNode("toggleswitch1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js index 3802f068ed..e86f32c55e 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js @@ -10,7 +10,7 @@ import { selectColourFromColourPicker, verifyWidgetColorCss, } from "Support/utils/commonWidget"; -import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector"; +// import { verifyNodeData, openNode, verifyNodeData } from "Support/utils/inspector"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { commonText, diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js index a6b6a1406a..1eec4a4e55 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js @@ -3,7 +3,7 @@ import { commonWidgetSelector } from "Selectors/common"; import { multipageSelector } from "Selectors/multipage"; import { addSupportCSAData, selectEvent } from "Support/utils/events"; import { createNewVersion } from "Support/utils/exportImport"; -import { deleteComponentFromInspector, openNode, verifyNodeData, verifyValue, verifyNodes, openAndVerifyNode } from "Support/utils/inspector"; +import { deleteComponentFromInspector, openNode, verifyNodeData, verifyNodes, openAndVerifyNode } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; import { navigateToCreateNewVersionModal } from "Support/utils/version"; import testData from "Fixtures/inspectorItems.json"; @@ -20,15 +20,15 @@ describe("Editor- Inspector", () => { cy.viewport(1800, 1800); }); - it("should verify the values of inspector", () => { + it.skip("should verify the values of inspector", () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openAndVerifyNode("globals", testData.globalsNodes, verifyNodeData); - openAndVerifyNode("currentUser", testData.currentUserNodes, verifyValue); - openAndVerifyNode("theme", testData.themeNodes, verifyValue); - openAndVerifyNode("mode", testData.modeNodes, verifyValue); - openAndVerifyNode("urlparams", testData.urlparamsNode, verifyValue); + openAndVerifyNode("currentUser", testData.currentUserNodes, verifyNodeData); + openAndVerifyNode("theme", testData.themeNodes, verifyNodeData); + openAndVerifyNode("mode", testData.modeNodes, verifyNodeData); + openAndVerifyNode("urlparams", testData.urlparamsNode, verifyNodeData); if (Cypress.env("environment") !== "Community") { const ssoUserInfoNode = '[data-cy="inspector-node-ssouserinfo"]'; @@ -39,7 +39,7 @@ describe("Editor- Inspector", () => { openNode("theme"); openNode("environment"); - verifyValue("name", "String", `"development"`); + verifyNodeData("name", "String", `"development"`); cy.get(`${inspectorNodeId} > .node-key`).should("have.text", "id"); } @@ -92,7 +92,7 @@ describe("Editor- Inspector", () => { cy.get(commonWidgetSelector.draggableWidget("button3")).click(); cy.get(commonWidgetSelector.sidebarinspector).click(); - openAndVerifyNode("variables", testData.variablesNodes, verifyValue); + openAndVerifyNode("variables", testData.variablesNodes, verifyNodeData); cy.forceClickOnCanvas() cy.wait(500) @@ -101,9 +101,9 @@ describe("Editor- Inspector", () => { // openNode("page"); - openAndVerifyNode("page", testData.testPageNodes, verifyValue); + openAndVerifyNode("page", testData.testPageNodes, verifyNodeData); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageVar"`); + verifyNodeData("pageVar", "String", `"pageVar"`); openAndVerifyNode("components", testData.componentsNodes, verifyNodeData); @@ -111,10 +111,10 @@ describe("Editor- Inspector", () => { cy.get(commonWidgetSelector.draggableWidget("button1")).click(); cy.get(commonWidgetSelector.sidebarinspector).click(); - openAndVerifyNode("page", testData.pageNodes, verifyValue); + openAndVerifyNode("page", testData.pageNodes, verifyNodeData); openNode("globals"); openNode("urlparams"); - verifyValue("key", "String", `"value"`); + verifyNodeData("key", "String", `"value"`); cy.get(`[data-cy="inspector-node-key"] > .mx-1`) .realHover() diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js index b8c54b63d9..1edc529c84 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js @@ -23,7 +23,7 @@ import { resizeQueryPanel, verifypreview } from "Support/utils/dataSource"; -import { openNode, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; describe("RunJS", () => { beforeEach(() => { @@ -42,15 +42,15 @@ describe("RunJS", () => { selectQueryFromLandingPage("runjs", "JavaScript"); addInputOnQueryField("runjs", "return true"); - query("preview"); + query("run"); verifypreview("raw", "true"); query("run"); cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openNode("queries"); openNode("runjs1"); - verifyValue("data", "Boolean", "true"); - verifyValue("rawData", "Boolean", "true"); + verifyNodeData("data", "Boolean", "true"); + verifyNodeData("rawData", "Boolean", "true"); cy.apiDeleteApp(); }); @@ -60,11 +60,11 @@ describe("RunJS", () => { selectQueryFromLandingPage("runjs", "JavaScript"); addInputOnQueryField("runjs", "return [page.handle,page.name]"); - query("preview"); + query("run"); verifypreview("raw", `["home","Home"]`); addInputOnQueryField("runjs", "return globals.theme"); - query("preview"); + query("run"); verifypreview("raw", `{"name":"light"}`); // addInputOnQueryField("runjs", "return globals.currentUser"); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js index ddd489f76c..2018367552 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js @@ -24,7 +24,7 @@ import { resizeQueryPanel, verifypreview } from "Support/utils/dataSource"; -import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; @@ -54,8 +54,8 @@ describe("runpy", () => { cy.get(".tooltip-inner").invoke("hide"); openNode("queries"); openNode("runpy1"); - verifyValue("data", "Boolean", "true"); - verifyValue("rawData", "Boolean", "true"); + verifyNodeData("data", "Boolean", "true"); + verifyNodeData("rawData", "Boolean", "true"); cy.apiDeleteApp(); }); @@ -76,11 +76,11 @@ actions.setPageVariable('pageVar', 'pageTest')` verifyNodeData("variables", "Object", "1 entry "); openNode("variables", 0); - verifyValue("var", "String", `"test"`); + verifyNodeData("var", "String", `"test"`); openNode("page"); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageTest"`); + verifyNodeData("pageVar", "String", `"pageTest"`); addInputOnQueryField( "runpy", diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index 819a807a5f..e480fce14d 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -25,7 +25,7 @@ export const query = (operation) => { }; export const verifypreview = (type, data) => { - cy.get(`[data-cy="preview-tab-${type}"]`).click(); + cy.get(`[data-cy="preview-tab-${type}"]`, { timeout: 15000 }).click(); cy.get(`[data-cy="preview-${type}-data-container"]`).verifyVisibleElement( "contain.text", data, diff --git a/cypress-tests/cypress/support/utils/inspector.js b/cypress-tests/cypress/support/utils/inspector.js index a5c6ba5286..67f9896fa9 100644 --- a/cypress-tests/cypress/support/utils/inspector.js +++ b/cypress-tests/cypress/support/utils/inspector.js @@ -1,60 +1,49 @@ -export const verifyNodeData = (node, type, children, index = 0) => { - cy.get( - `[data-cy="inspector-node-${node.toLowerCase()}"] > .node-length-color` - ) - .eq(index) - .realHover() - .verifyVisibleElement("have.text", `${children}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("have.text", node); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-type`) - .eq(index) - .verifyVisibleElement("have.text", type); -}; +import { commonWidgetSelector } from "Selectors/common"; -export const openNode = (node, index = 0, time = 1000) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .click(); - cy.wait(time); -}; - -export const verifyValue = (node, type, children, index = 0) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-2`) - .eq(index) - .realHover() - .verifyVisibleElement("contain.text", `${children}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("contain.text", node); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`) - .eq(index) - .verifyVisibleElement("contain.text", type); -}; -export const deleteComponentFromInspector = (node) => { - cy.get('[data-cy="inspector-node-components"] > .node-key').click(); - cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click(); -}; - -export const verifyfunctions = (node, type, index = 0) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .fs-10`) - .eq(index) - .realHover() - .verifyVisibleElement("contain.text", `${type}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("contain.text", node); - // cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`) - // .eq(index) - // .verifyVisibleElement("contain.text", type); +export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => { + openStateFromComponent(nodeName); + verifyNodes(nodes, verificationFunction); }; export const verifyNodes = (nodes, verificationFunction) => { nodes.forEach(node => verificationFunction(node.key, node.type, node.value)); }; -export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => { - openNode(nodeName); - verifyNodes(nodes, verificationFunction); +export const openNode = (node, index = 0, time = 1000) => { + cy.get(`[data-cy="inspector-${node.toLowerCase()}-expand-button"]`, { timeout: time }) + .eq(index) + .click(); +}; + +export const openStateFromComponent = (widgetName) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)) + .realHover() + .realHover(); + + cy.get(commonWidgetSelector.draggableWidget(widgetName)) + .realHover() + .then(() => { + cy.get(`[data-cy="${widgetName}-inspect-button"]`) + .realHover({ position: "topRight" }) + .last() + .realClick(); + }); +} + +export const verifyNodeData = (node, type, value, index = 0) => { + cy.get( + `[data-cy="inspector-${node.toLowerCase()}-label"]` + ) + .eq(index) + .realHover() + .verifyVisibleElement("have.text", `${node}`); + + cy.get(`[data-cy="inspector-${node.toLowerCase()}-value"]`) + .eq(index) + .verifyVisibleElement("have.text", type == 'Function' ? 'function' : value); +}; + +export const deleteComponentFromInspector = (node) => { + cy.get('[data-cy="inspector-menu-icon"]').click(); + cy.get(`[data-cy="inspector-delete-component-action"`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click(); }; \ No newline at end of file From 0414abb29c8558f5c25bcfc204bc26ed027d738a Mon Sep 17 00:00:00 2001 From: emidhun Date: Wed, 11 Jun 2025 13:31:44 +0530 Subject: [PATCH 84/88] Fix automation failures on action --- .../newSuits/queries/chainingOfQueries.cy.js | 8 ++++++-- cypress-tests/cypress/support/utils/commonWidget.js | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) 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 258e4ab94a..3784b6dcec 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 @@ -104,6 +104,7 @@ describe("Chaining of queries", () => { cy.wait(1000) cy.get('[data-cy="query-tab-setup"]').click(); + cy.wait(1500); openEditorSidebar(buttonText.defaultWidgetName); selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0); cy.wait(500); @@ -122,7 +123,7 @@ describe("Chaining of queries", () => { // cy.verifyToastMessage(commonSelectors.toastMessage, "Hello World"); }); - it.skip("should verify query duplication", () => { + it("should verify query duplication", () => { const data = {}; let dsName = fake.companyName; @@ -146,6 +147,7 @@ describe("Chaining of queries", () => { chainQuery("runjs", "runpy"); addSuccessNotification("runjs"); + cy.wait(1500); openEditorSidebar(buttonText.defaultWidgetName); selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0); cy.wait(500); @@ -170,6 +172,8 @@ describe("Chaining of queries", () => { "have.text", "runjs_copy " ); + + cy.get('[data-cy="query-tab-settings"]').click(); cy.get('[data-cy="notification-on-success-toggle-switch"]').should( "have.value", "on" @@ -184,7 +188,7 @@ describe("Chaining of queries", () => { }); cy.get( `[data-cy="action-selection"] > .select-search > .react-select__control > .react-select__value-container > ` - ).should("have.text", "Run Query"); + ).should("have.text", "Run query"); cy.get('[data-cy="query-selection-field"]').should("have.text", "runpy"); }); }); diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index c48cd18917..b34ff0b48e 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -31,8 +31,11 @@ export const verifyAndModifyParameter = (paramName, value) => { export const openEditorSidebar = (widgetName = "") => { cy.hideTooltip(); - cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover() - cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click(); + + cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover().then(() => { + cy.wait(1000); + cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click(); + }) }; export const verifyAndModifyToggleFx = ( From 1d15cddbfd8fe5dccd550c4aa78a5e633fe7e05e Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:00:38 +0530 Subject: [PATCH 85/88] FX toggle not appearing on hover (#13007) --- frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 7177dc4202..9c85e0bf43 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -525,7 +525,7 @@ const DynamicEditorBridge = (props) => { }, [component, fxActive]); const renderFx = () => { - if (paramType === 'query' || (paramLabel !== 'Type' && isFxNotRequired === undefined)) { + if (paramType === 'query' || !(paramLabel !== 'Type' && isFxNotRequired === undefined)) { return null; } return ( From 6bdc165ba2756c496905fbf8d3524f33feca0383 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 11 Jun 2025 15:29:18 +0530 Subject: [PATCH 86/88] fix: Passed the correct `app` value to the preview query and autoLayout --- frontend/src/AppBuilder/_stores/slices/componentsSlice.js | 3 ++- frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 7e74a6a006..9075dbede2 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1527,7 +1527,8 @@ export const createComponentsSlice = (set, get) => ({ }, turnOffAutoComputeLayout: async (moduleId = 'canvas') => { - const { app, getCurrentPageId, currentVersionId } = get(); + const { appStore, getCurrentPageId, currentVersionId } = get(); + const app = appStore.modules[moduleId].app; const currentPageId = getCurrentPageId(moduleId); set( (state) => { diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 7344f54aec..f33f67c66e 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -539,7 +539,7 @@ export const createQueryPanelSlice = (set, get) => ({ }, previewQuery: (query, calledFromQuery = false, userSuppliedParameters = {}, moduleId = 'canvas') => { - const { eventsSlice, queryPanel, app, currentVersionId, selectedEnvironment } = get(); + const { eventsSlice, queryPanel, appStore, currentVersionId, selectedEnvironment } = get(); const { queryPreviewData, setPreviewLoading, @@ -563,6 +563,8 @@ export const createQueryPanelSlice = (set, get) => ({ let parameters = userSuppliedParameters; + const app = appStore.modules[moduleId].app; + // passing current env through props only for querymanager const { environmentId } = app; const currentAppEnvironmentId = selectedEnvironment?.id || ''; From 4b05e48ce1f50cdff2bf54c04d7d20cb80cfcb84 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 11 Jun 2025 16:42:39 +0530 Subject: [PATCH 87/88] fix: pass the correct moduleId while setting the editor loading --- frontend/src/AppBuilder/_hooks/useAppData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 8b8851eb3b..14608a95b8 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -664,7 +664,7 @@ const useAppData = ( setQueryMapping(moduleId); initDependencyGraph(moduleId); - setEditorLoading(false, false); + setEditorLoading(false, moduleId); }); } }, [selectedEnvironment?.id, currentVersionId, moduleMode, moduleId]); From 3ce5032b7b1a9bd4680219c9a4f866e1033870aa Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 13:26:30 +0530 Subject: [PATCH 88/88] Update ee-frontend submodule reference --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 064c4c149a..d7fef4465f 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 064c4c149ab095ea9e6b6f992b45ae5a3f190673 +Subproject commit d7fef4465f17a4d01a8e5b53f1bf659e30068698