From dda026a00f56b2b764fb755ea1e94a8bd061780b Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Thu, 7 Nov 2024 18:27:51 +0530 Subject: [PATCH 01/30] fix: page settings on public app --- 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 bb745f2568..bc33e7bba9 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -193,7 +193,7 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId } ); setPages(pages, moduleId); - setPageSettings(deepCamelCase(appData?.editing_version?.page_settings)); + setPageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)); // set starting page as homepage initially let startingPage = appData.pages.find((page) => page.id === homePageId); From 38bffb450b10de16c6be3bc76b8c72d1d1b90ea8 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Thu, 14 Nov 2024 17:28:49 +0530 Subject: [PATCH 02/30] button to submit in form mapping fix on version creation --- .../src/services/app_import_export.service.ts | 6 ++++- server/src/services/apps.service.ts | 15 ++++++++---- server/src/services/page.service.ts | 24 ++++++++++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index 12e63d8458..1b7eee534e 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { isEmpty } from 'lodash'; +import { isEmpty, set } from 'lodash'; import { App } from 'src/entities/app.entity'; import { AppEnvironment } from 'src/entities/app_environments.entity'; import { AppVersion } from 'src/entities/app_version.entity'; @@ -809,6 +809,10 @@ export class AppImportExportService { const newComponent = new Component(); let parentId = component.parent ? component.parent : null; + if (component?.properties?.buttonToSubmit) { + const newButtonToSubmitValue = newComponentIdsMap[component?.properties?.buttonToSubmit?.value]; + if (newButtonToSubmitValue) set(component, 'properties.buttonToSubmit.value', newButtonToSubmitValue); + } const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, pageComponents, parentId, true); diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index e63622b675..65cd02c410 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -28,7 +28,7 @@ import { EventHandler, Target } from 'src/entities/event_handler.entity'; import { VersionReleaseDto } from '@dto/version-release.dto'; import { findAllEntityReferences, isValidUUID, updateEntityReferences } from 'src/helpers/import_export.helpers'; -import { isEmpty } from 'lodash'; +import { isEmpty, set } from 'lodash'; import { AppBase } from 'src/entities/app_base.entity'; import { LayoutDimensionUnits } from 'src/helpers/components.helper'; import { AbilityService } from './permissions-ability.service'; @@ -532,9 +532,9 @@ export class AppsService { let homePageId = prevHomePagePage; - const newComponents = []; + let newComponents = []; const newComponentLayouts = []; - const oldComponentToNewComponentMapping = {}; + let oldComponentToNewComponentMapping = {}; const oldPageToNewPageMapping = {}; const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => { @@ -660,9 +660,14 @@ export class AppsService { await manager.save(newEvent); }); }); - newComponents.forEach((component) => { let parentId = component.parent ? component.parent : null; + // re establish mapping relationship + if (component?.properties?.buttonToSubmit) { + const newButtonToSubmitValue = + oldComponentToNewComponentMapping[component?.properties?.buttonToSubmit?.value]; + if (newButtonToSubmitValue) set(component, 'properties.buttonToSubmit.value', newButtonToSubmitValue); + } if (!parentId) return; @@ -688,6 +693,8 @@ export class AppsService { await manager.save(newComponents); await manager.save(newComponentLayouts); + newComponents = []; + oldComponentToNewComponentMapping = {}; } await manager.update(AppVersion, { id: appVersion.id }, { homePageId }); diff --git a/server/src/services/page.service.ts b/server/src/services/page.service.ts index 81f2eddfe1..e67420c4f2 100644 --- a/server/src/services/page.service.ts +++ b/server/src/services/page.service.ts @@ -14,6 +14,7 @@ import { EventHandler } from 'src/entities/event_handler.entity'; import { findAllEntityReferences, isValidUUID, updateEntityReferences } from 'src/helpers/import_export.helpers'; import { isEmpty } from 'class-validator'; import { PageHelperService } from '@apps/services/pages/service.helper'; +import * as _ from 'lodash'; @Injectable() export class PageService { @@ -101,19 +102,25 @@ export class PageService { const componentsIdMap = {}; // Clone components + // array to store maapings and update them later with path + const mappingsToUpdate = []; const clonedComponents = await Promise.all( pageComponents.map(async (component) => { const clonedComponent = { ...component, id: undefined, pageId: clonePageId }; const newComponent = await manager.save(manager.create(Component, clonedComponent)); - componentsIdMap[component.id] = newComponent.id; const componentLayouts = await manager.find(Layout, { where: { componentId: component.id } }); + if (component?.properties?.buttonToSubmit?.value) { + mappingsToUpdate.push({ + component: newComponent, + pathToUpdate: 'properties.buttonToSubmit.value', + }); + } const clonedLayouts = componentLayouts.map((layout) => ({ ...layout, id: undefined, componentId: newComponent.id, })); - // Clone component events const clonedComponentEvents = await this.eventHandlerService.findAllEventsWithSourceId(component.id); const clonedEvents = clonedComponentEvents.map((event) => { @@ -150,7 +157,18 @@ export class PageService { return newComponent; }) ); - + // re estabilish mappings + await Promise.all( + mappingsToUpdate.map((itemToUpdate) => { + const { component, pathToUpdate: path } = itemToUpdate; + const oldId = _.get(component, path); + const newId = componentsIdMap[oldId]; + if (newId) { + _.set(component, path, newId); + } + manager.save(component); + }) + ); // Clone events await Promise.all( pageEvents.map(async (event) => { From eee6cc8b55e45c35b55f92a94db6abe61c00d6cb Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Thu, 14 Nov 2024 17:29:54 +0530 Subject: [PATCH 03/30] Fix: remove events and exposed variables from component definition to avoid backend Component entity error --- .../src/AppBuilder/_stores/slices/componentsSlice.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index f7c64475c6..1b316a9652 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1236,8 +1236,16 @@ export const createComponentsSlice = (set, get) => ({ ); } + const oldComponent = get().modules[moduleId].pages[currentPageIndex].components[componentId].component; + const { events, exposedVariables, ...filteredDefinition } = oldComponent.definition || {}; + const diff = { - [componentId]: { component: get().modules[moduleId].pages[currentPageIndex].components[componentId].component }, + [componentId]: { + component: { + ...oldComponent, + definition: filteredDefinition, + }, + }, }; if (saveAfterAction) { From 057e651a7a5fa36243e01befb94e7790476dc539 Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Thu, 14 Nov 2024 18:00:30 +0530 Subject: [PATCH 04/30] fix: page settings not available on imported apps --- server/src/services/app_import_export.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index bcb95dfddd..2ef1d4b02a 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -517,6 +517,7 @@ export class AppImportExportService { autoComputeLayout: page.autoComputeLayout || false, isPageGroup: page.isPageGroup || false, pageGroupIndex: page.pageGroupIndex || null, + icon: page.icon || null, }); const pageCreated = await transactionalEntityManager.save(newPage); @@ -784,6 +785,7 @@ export class AppImportExportService { disabled: page.disabled || false, hidden: page.hidden || false, autoComputeLayout: page.autoComputeLayout || false, + icon: page.icon || null, }); const pageCreated = await manager.save(newPage); From b4d2cacff9d19418a141cd8dbf54c99ad1788a24 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Fri, 15 Nov 2024 11:12:42 +0530 Subject: [PATCH 05/30] bump version for release --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index e7bb755881..99ba0ddace 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.0.1-ce-lts +3.0.2-ce-lts diff --git a/frontend/.version b/frontend/.version index e7bb755881..99ba0ddace 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.0.1-ce-lts +3.0.2-ce-lts diff --git a/server/.version b/server/.version index e7bb755881..99ba0ddace 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.0.1-ce-lts +3.0.2-ce-lts From f57186419e1b52694c868c79b51447c7746c8127 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 15 Nov 2024 12:22:51 +0530 Subject: [PATCH 06/30] Fixed onCellValueChanged event being triggered before save changes button appears --- frontend/src/AppBuilder/Widgets/Table/Table.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 97fe46d94f..986edca4f5 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -237,7 +237,8 @@ export const Table = React.memo( const changesToBeSavedAndExposed = { dataUpdates: newDataUpdates, changeSet: newChangeset }; mergeToTableDetails(changesToBeSavedAndExposed); setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData }); - fireEvent('onCellValueChanged'); + // Need to add a timeout here as changes are happening in the next render + setTimeout(() => fireEvent('onCellValueChanged'), 0); return; } From 1376debde50fa5c0e00003e01a079ff7b5af2368 Mon Sep 17 00:00:00 2001 From: Ganesh Kumar Date: Fri, 15 Nov 2024 16:17:42 +0530 Subject: [PATCH 07/30] feat: statement timeout can be configured from env for postgresql mysql mssql plugins --- plugins/packages/mssql/lib/index.ts | 12 ++++++++---- plugins/packages/mysql/lib/index.ts | 12 ++++++++---- plugins/packages/postgresql/lib/index.ts | 17 ++++++++++------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/plugins/packages/mssql/lib/index.ts b/plugins/packages/mssql/lib/index.ts index e9864a608c..84263a93a2 100644 --- a/plugins/packages/mssql/lib/index.ts +++ b/plugins/packages/mssql/lib/index.ts @@ -10,12 +10,16 @@ import { import { SourceOptions, QueryOptions } from './types'; import { isEmpty } from '@tooljet-plugins/common'; -const STATEMENT_TIMEOUT = 10000; - export default class MssqlQueryService implements QueryService { private static _instance: MssqlQueryService; + private STATEMENT_TIMEOUT; constructor() { + this.STATEMENT_TIMEOUT = + process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT && !isNaN(Number(process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT)) + ? Number(process.env.PLUGINS_SQL_DB_STATEMENT_TIMEOUT) + : 120000; + if (MssqlQueryService._instance) { return MssqlQueryService._instance; } @@ -84,13 +88,13 @@ export default class MssqlQueryService implements QueryService { private async executeQuery(knexInstance: Knex, query: string, sanitizedQueryParams: Record = {}) { if (isEmpty(query)) throw new Error('Query is empty'); - const result = await knexInstance.raw(query, sanitizedQueryParams).timeout(STATEMENT_TIMEOUT); + const result = await knexInstance.raw(query, sanitizedQueryParams).timeout(this.STATEMENT_TIMEOUT); return result; } async testConnection(sourceOptions: SourceOptions): Promise { const knexInstance = await this.getConnection(sourceOptions, {}, false); - await knexInstance.raw('select @@version;').timeout(STATEMENT_TIMEOUT); + await knexInstance.raw('select @@version;').timeout(this.STATEMENT_TIMEOUT); knexInstance.destroy(); return { diff --git a/plugins/packages/mysql/lib/index.ts b/plugins/packages/mysql/lib/index.ts index 9fc9e9afac..8a4509a894 100644 --- a/plugins/packages/mysql/lib/index.ts +++ b/plugins/packages/mysql/lib/index.ts @@ -10,12 +10,16 @@ import { import { SourceOptions, QueryOptions } from './types'; import { isEmpty } from '@tooljet-plugins/common'; -const STATEMENT_TIMEOUT = 10000; - export default class MysqlQueryService implements QueryService { private static _instance: MysqlQueryService; + private STATEMENT_TIMEOUT; constructor() { + this.STATEMENT_TIMEOUT = + process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT && !isNaN(Number(process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT)) + ? Number(process.env.PLUGINS_SQL_DB_STATEMENT_TIMEOUT) + : 120000; + if (MysqlQueryService._instance) { return MysqlQueryService._instance; } @@ -51,7 +55,7 @@ export default class MysqlQueryService implements QueryService { async testConnection(sourceOptions: SourceOptions): Promise { const knexInstance = await this.getConnection(sourceOptions, {}, false); - await knexInstance.raw('select @@version;').timeout(STATEMENT_TIMEOUT); + await knexInstance.raw('select @@version;').timeout(this.STATEMENT_TIMEOUT); knexInstance.destroy(); return { status: 'ok' }; } @@ -77,7 +81,7 @@ export default class MysqlQueryService implements QueryService { private async executeQuery(knexInstance: Knex, query: string, sanitizedQueryParams: Record = {}) { if (isEmpty(query)) throw new Error('Query is empty'); - const result = await knexInstance.raw(query, sanitizedQueryParams).timeout(STATEMENT_TIMEOUT); + const result = await knexInstance.raw(query, sanitizedQueryParams).timeout(this.STATEMENT_TIMEOUT); return result; } diff --git a/plugins/packages/postgresql/lib/index.ts b/plugins/packages/postgresql/lib/index.ts index 78a5b9320e..fcbf2d736b 100644 --- a/plugins/packages/postgresql/lib/index.ts +++ b/plugins/packages/postgresql/lib/index.ts @@ -10,12 +10,16 @@ import { SourceOptions, QueryOptions } from './types'; import knex, { Knex } from 'knex'; import { isEmpty } from '@tooljet-plugins/common'; -const STATEMENT_TIMEOUT = 10000; - export default class PostgresqlQueryService implements QueryService { private static _instance: PostgresqlQueryService; + private STATEMENT_TIMEOUT; constructor() { + this.STATEMENT_TIMEOUT = + process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT && !isNaN(Number(process.env?.PLUGINS_SQL_DB_STATEMENT_TIMEOUT)) + ? Number(process.env.PLUGINS_SQL_DB_STATEMENT_TIMEOUT) + : 120000; + if (PostgresqlQueryService._instance) { return PostgresqlQueryService._instance; } @@ -51,7 +55,7 @@ export default class PostgresqlQueryService implements QueryService { async testConnection(sourceOptions: SourceOptions): Promise { const knexInstance = await this.getConnection(sourceOptions, {}, false); - await knexInstance.raw('SELECT version();').timeout(STATEMENT_TIMEOUT); + await knexInstance.raw('SELECT version();').timeout(this.STATEMENT_TIMEOUT); return { status: 'ok' }; } @@ -75,9 +79,7 @@ export default class PostgresqlQueryService implements QueryService { private async executeQuery(knexInstance: Knex, query: string, sanitizedQueryParams: Record = {}) { if (isEmpty(query)) throw new Error('Query is empty'); - - const { rows } = await knexInstance.raw(query, sanitizedQueryParams).timeout(STATEMENT_TIMEOUT); - + const { rows } = await knexInstance.raw(query, sanitizedQueryParams); return rows; } @@ -100,6 +102,7 @@ export default class PostgresqlQueryService implements QueryService { password: sourceOptions.password, port: sourceOptions.port, ssl: this.getSslConfig(sourceOptions), + statement_timeout: this.STATEMENT_TIMEOUT, }; } else if (sourceOptions.connection_type === 'string' && sourceOptions.connection_string) { connectionConfig = { @@ -111,7 +114,7 @@ export default class PostgresqlQueryService implements QueryService { client: 'pg', connection: connectionConfig, pool: { min: 0, max: 10, acquireTimeoutMillis: 10000 }, - acquireConnectionTimeout: 10000, + acquireConnectionTimeout: 60000, ...this.connectionOptions(sourceOptions), }; From 98eccc5390829d79b2cb5d254de489a6367ec684 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 15 Nov 2024 20:24:53 +0530 Subject: [PATCH 08/30] Fixed default calendar view not updating view on reload --- .../src/AppBuilder/Widgets/Calendar/Calendar.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Calendar/Calendar.jsx b/frontend/src/AppBuilder/Widgets/Calendar/Calendar.jsx index 080a51c47c..b7957d9ba0 100644 --- a/frontend/src/AppBuilder/Widgets/Calendar/Calendar.jsx +++ b/frontend/src/AppBuilder/Widgets/Calendar/Calendar.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Calendar as ReactCalendar, momentLocalizer } from 'react-big-calendar'; import moment from 'moment'; import 'react-big-calendar/lib/css/react-big-calendar.css'; @@ -54,7 +54,7 @@ export const Calendar = function ({ const [currentDate, setCurrentDate] = useState(defaultDate); const [eventPopoverOptions, setEventPopoverOptions] = useState({ show: false }); - const [defaultView, setDefaultValue] = useState(allowedCalendarViews[0]); + const isInitialRender = useRef(true); const eventPropGetter = (event) => { const backgroundColor = event.color; @@ -100,10 +100,10 @@ export const Calendar = function ({ const view = allowedCalendarViews.includes(properties.defaultView) ? properties.defaultView : allowedCalendarViews[0]; - if (currentView !== view) { - setDefaultValue(view); + if (currentView !== view || isInitialRender.current) { setExposedVariable('currentView', view); setCurrentView(view); + isInitialRender.current = false; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [properties.defaultView]); @@ -145,10 +145,9 @@ export const Calendar = function ({ endAccessor="end" style={style} views={allowedCalendarViews} - defaultView={defaultView} - view={defaultView} + defaultView={properties.defaultView || allowedCalendarViews[0]} + view={currentView} onView={(view) => { - setDefaultValue(view); setExposedVariable('currentView', view); setCurrentView(view); fireEvent('onCalendarViewChange'); From 3913716ce080bc3a189d490e29361dfbd7fcc474 Mon Sep 17 00:00:00 2001 From: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:55:49 +0530 Subject: [PATCH 09/30] Disable signup changes (#11311) * minor fixes * removed signup disabled guard from workspace level login * minor change in the flag * console removed * minor fix * fixed ws level sigup * fixed ws level sigup --- .../onboarding/pages/SignupPage/SignupPage.jsx | 11 +++++++---- .../SignupPage/components/SignupForm/SignupForm.jsx | 2 ++ server/src/controllers/app.controller.ts | 2 -- server/src/services/auth.service.ts | 2 +- server/src/services/organizations.service.ts | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx b/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx index d411d7018e..fa0b4038f7 100644 --- a/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx +++ b/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx @@ -38,13 +38,15 @@ const SignupPage = ({ configs, organizationId }) => { }); }, []); - const handleSignup = (formData, onSuccess = () => {}, onFaluire = () => {}) => { + const handleSignup = (formData, onSuccess = () => {}, onFailure = () => {}) => { const { email, name, password } = formData; - if (organizationToken) { authenticationService .activateAccountWithToken(email, password, organizationToken) - .then((response) => onInvitedUserSignUpSuccess(response, navigate)) + .then((response) => { + onInvitedUserSignUpSuccess(response, navigate); + onSuccess(); + }) .catch((errorObj) => { let errorMessage; const isThereAnyErrorsArray = errorObj?.error?.length && typeof errorObj?.error?.[0] === 'string'; @@ -54,6 +56,7 @@ const SignupPage = ({ configs, organizationId }) => { errorMessage = errorObj?.error?.error; } errorMessage && toast.error(errorMessage); + onFailure(); }); } else { authenticationService @@ -69,7 +72,7 @@ const SignupPage = ({ configs, organizationId }) => { toast.error(e?.error || 'Something went wrong!', { position: 'top-center', }); - onFaluire(); + onFailure(); }); } }; diff --git a/frontend/src/modules/onboarding/pages/SignupPage/components/SignupForm/SignupForm.jsx b/frontend/src/modules/onboarding/pages/SignupPage/components/SignupForm/SignupForm.jsx index 40eb34c61c..52e2d587cb 100644 --- a/frontend/src/modules/onboarding/pages/SignupPage/components/SignupForm/SignupForm.jsx +++ b/frontend/src/modules/onboarding/pages/SignupPage/components/SignupForm/SignupForm.jsx @@ -118,9 +118,11 @@ const SignupForm = ({ onSubmit( formData, () => { + // Success callback setIsLoading(false); }, () => { + // Error callback setIsLoading(false); } ); diff --git a/server/src/controllers/app.controller.ts b/server/src/controllers/app.controller.ts index 02f3534498..05f88d4ac2 100644 --- a/server/src/controllers/app.controller.ts +++ b/server/src/controllers/app.controller.ts @@ -73,7 +73,6 @@ export class AppController { return await this.authService.validateInvitedUserSession(user, invitedUser, tokens); } - @UseGuards(SignupDisableGuard) @UseGuards(FirstUserSignupDisableGuard) @Post('activate-account-with-token') async activateAccountWithToken( @@ -164,7 +163,6 @@ export class AppController { return await this.authService.acceptOrganizationInvite(response, user, acceptInviteDto); } - @UseGuards(SignupDisableGuard) @UseGuards(FirstUserSignupDisableGuard) @Post('signup') async signup(@Body() appSignUpDto: AppSignupDto, @Res({ passthrough: true }) response: Response) { diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 863c417a65..5bcbbfc996 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -347,7 +347,7 @@ export class AuthService { return dbTransactionWrap(async (manager: EntityManager) => { // Check if the configs allows user signups - if (this.configService.get('DISABLE_SIGNUPS') === 'true') { + if (!organizationId && this.configService.get('DISABLE_SIGNUPS') === 'true') { throw new NotAcceptableException(); } diff --git a/server/src/services/organizations.service.ts b/server/src/services/organizations.service.ts index 0a539bd381..4101b9e1ef 100644 --- a/server/src/services/organizations.service.ts +++ b/server/src/services/organizations.service.ts @@ -146,7 +146,7 @@ export class OrganizationsService { enable_sign_up: this.configService.get('DISABLE_SIGNUPS') !== 'true', enabled: true, }, - enableSignUp: this.configService.get('SSO_DISABLE_SIGNUPS') !== 'true', + enableSignUp: this.configService.get('DISABLE_SIGNUPS') !== 'true', }; } From fc8e737602932cce70eb225caf8bfbf893a2a7a5 Mon Sep 17 00:00:00 2001 From: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:56:16 +0530 Subject: [PATCH 10/30] Fixed copywriting for new Env modal (#11350) Co-authored-by: gsmithun4 --- frontend/src/AppBuilder/Header/CreateVersionModal.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index cb3fc653c2..e7284767d1 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -201,8 +201,9 @@ export const CreateVersion = ({ showCreateAppVersion, setShowCreateAppVersion }) width: '100%', }} > + {/* EE - change to development */}
- The new version will be created in development environment + The new version will be created in production environment
From 813ea9e31c90b8daee9897236842da5dc580ce85 Mon Sep 17 00:00:00 2001 From: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:09:29 +0530 Subject: [PATCH 11/30] Workspace constant Page UI changes (#11339) --- .../ManageOrgConstants/ManageOrgConstants.jsx | 297 +++++++++--------- frontend/src/_styles/theme.scss | 7 +- 2 files changed, 158 insertions(+), 146 deletions(-) diff --git a/frontend/src/ManageOrgConstants/ManageOrgConstants.jsx b/frontend/src/ManageOrgConstants/ManageOrgConstants.jsx index b6548197bd..3d40ce168e 100644 --- a/frontend/src/ManageOrgConstants/ManageOrgConstants.jsx +++ b/frontend/src/ManageOrgConstants/ManageOrgConstants.jsx @@ -17,6 +17,7 @@ import { BreadCrumbContext } from '@/App'; import './ConstantFormStyle.scss'; import { Constants, redirectToWorkspace } from '@/_helpers/utils'; import { SearchBox } from '@/_components/SearchBox'; +import { OrganizationList } from '@/_components/OrganizationManager/List'; const MODES = Object.freeze({ CREATE: 'create', EDIT: 'edit', @@ -429,156 +430,166 @@ const ManageOrgConstantsComponent = ({ darkMode }) => { /> )} -
-
- {capitalize(activeTabEnvironment?.name)} ({globalCount + secretCount}) -
-
- {canCreateVariable() && ( - { - setMode(() => MODES.CREATE); - setIsManageVarDrawerOpen(() => true); - }} - className="add-new-constant-button" - customStyles={{ minWidth: '200px', height: '32px' }} - disabled={isManageVarDrawerOpen} - > - + Create new constant - - )} -
-
-
-
-
-
-
-
- - -
-
- -
+
+
+
+ +
+ +
+ +
+
+
+
+ {capitalize(activeTabEnvironment?.name)} ({globalCount + secretCount}) +
+
+ {canCreateVariable() && ( + { + setMode(() => MODES.CREATE); + setIsManageVarDrawerOpen(() => true); + }} + className="add-new-constant-button" + customStyles={{ minWidth: '200px', height: '32px' }} + disabled={isManageVarDrawerOpen} + > + + Create new constant + + )}
-
-
-
-
-
- -
-
- {activeTab === Constants.Global ? ( - <> - To resolve a global workspace constant use{' '} - {'{{constants.access_token}}'} - - ) : ( - <> - To resolve a secret workspace constant use{' '} - {'{{secrets.access_token}}'} - - )} -
- -
- -
+
+
+
+
+
+ +
- -
-
-
- - {(activeTab === Constants.Global && globalCount > 0) || - (activeTab === Constants.Secret && secretCount > 0) ? ( -
- - 0} - /> -
- ) : ( - + - )} +
+
+
+ +
+
+ +
+
+ {activeTab === Constants.Global ? ( + <> + To resolve a global workspace constant use{' '} + + {'{{constants.access_token}}'} + + + ) : ( + <> + To resolve a secret workspace constant use{' '} + {'{{secrets.access_token}}'} + + )} +
+ +
+ +
+
+
+
+ +
+
+ {(activeTab === Constants.Global && globalCount > 0) || + (activeTab === Constants.Secret && secretCount > 0) ? ( +
+ + 0} + /> +
+ ) : ( + + )} +
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 3c59855152..1b145788b4 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -15569,9 +15569,10 @@ color: var(--text-default); background-color: var(--page-default); height: calc(100vh - 64px); display: flex; - align-items: center; - justify-content: center; - padding-top: 1.5rem; + //Uncomment for EE + // align-items: center; + // justify-content: center; + // padding-top: 1.5rem; } .blank-page-wrapper { From 6bdb8082397f20f2c073f88750a1c40e2d143dfc Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 18 Nov 2024 13:16:47 +0530 Subject: [PATCH 12/30] Fix submit button event not triggered when dynamic schema is selected --- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 6 ------ frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index 38571c8c35..eeec8760fc 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -19,7 +19,6 @@ export const Form = function Form(props) { component, width, height, - removeComponent, styles, setExposedVariable, setExposedVariables, @@ -28,11 +27,6 @@ export const Form = function Form(props) { properties, resetComponent = () => {}, dataCy, - paramUpdated, - currentLayout, - mode, - getContainerProps, - containerProps, } = props; const childComponents = useStore((state) => state.getChildComponents(id), shallow); const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles; diff --git a/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx b/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx index bf4fc459c3..b5bfa9e4c3 100644 --- a/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/RenderSchema.jsx @@ -50,6 +50,7 @@ const RenderSchema = ({ component, parent, id, onOptionChange, onOptionsChange, darkMode={darkMode} fireEvent={fireEvent} formId={formId} + id={id} /> ); }; From 6d3b076546ad8ad07652c2968cb5abaf80738b9f Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Mon, 18 Nov 2024 13:36:33 +0530 Subject: [PATCH 13/30] Fixed unable to move calendar on changing view mode --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index f5603de836..3fd34d530f 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -596,7 +596,7 @@ export default function Grid({ gridWidth, currentLayout }) { isDragOnTableORCalendar = tableElem.contains(e.inputEvent.target); } if (box?.component?.component === 'Calendar') { - const calenderElem = e.target.querySelector('.rbc-month-view'); + const calenderElem = e.target.querySelector('.calendar-widget'); isDragOnTableORCalendar = calenderElem.contains(e.inputEvent.target); } From f805116c9e6a7e6446b02031338ea162610cab47 Mon Sep 17 00:00:00 2001 From: Adish M Date: Tue, 12 Nov 2024 19:58:41 +0530 Subject: [PATCH 14/30] Fixes in render for v3 --- .github/workflows/render-preview-deploy.yml | 276 +++++++++++++++++++- .github/workflows/review-path-deploy.yml | 6 +- 2 files changed, 277 insertions(+), 5 deletions(-) diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index f10dff0651..330e650366 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -59,7 +59,7 @@ jobs: }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}" + "value": "${{ env.PR_NUMBER }}-tj-db" }, { "key": "TOOLJET_DB_HOST", @@ -87,7 +87,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}" + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-tjdb" }, { "key": "PGRST_HOST", @@ -342,3 +342,275 @@ jobs: } catch (e) { console.log(e) } + + recreate-review-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'recreate-review-app' }} + runs-on: ubuntu-latest + + steps: + - name: Delete service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request DELETE \ + --url https://api.render.com/v1/services/$SERVICE_ID \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'destroy-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-review-app' + }) + } catch (e) { + console.log(e) + } + + - name: Install PostgreSQL client + run: | + sudo apt update + sudo apt install postgresql-client -y + + - name: Wait after installing PostgreSQL + run: sleep 25 + + - name: Drop PostgreSQL PR database + env: + PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} + PGPORT: 5432 + PGUSER: ${{ secrets.RENDER_DS_PG_USER }} + PGDATABASE: ${{ env.PR_NUMBER }} + run: | + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then + echo "Database $PGDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" + else + echo "Database $PGDATABASE does not exist." + fi + + - name: Create deployment + id: create-deployment + run: | + export RESPONSE=$(curl --request POST \ + --url https://api.render.com/v1/services \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header 'Authorization: Bearer ${{ secrets.RENDER_API_KEY }}' \ + --data ' + { + "autoDeploy": "yes", + "branch": "${{ env.BRANCH_NAME }}", + "name": "ToolJet PR #${{ env.PR_NUMBER }}", + "notifyOnFail": "default", + "ownerId": "tea-caeo4bj19n072h3dddc0", + "repo": "https://${{ secrets.RENDER_GITHUB_PAT }}@github.com/ToolJet/tj-ee", + "slug": "tooljet-pr-${{ env.PR_NUMBER }}", + "suspended": "not_suspended", + "suspenders": [], + "type": "web_service", + "envVars": [ + { + "key": "PG_HOST", + "value": "${{ secrets.RENDER_PG_HOST }}" + }, + { + "key": "PG_PORT", + "value": "5432" + }, + { + "key": "PG_USER", + "value": "${{ secrets.RENDER_PG_USER }}" + }, + { + "key": "PG_PASS", + "value": "${{ secrets.RENDER_PG_PASS }}" + }, + { + "key": "PG_DB", + "value": "${{ env.PR_NUMBER }}" + }, + { + "key": "ENABLE_TOOLJET_DB", + "value": "true" + }, + { + "key": "TOOLJET_DB", + "value": "${{ env.PR_NUMBER }}-tj-db" + }, + { + "key": "TOOLJET_DB_HOST", + "value": "${{ secrets.RENDER_PG_HOST }}" + }, + { + "key": "TOOLJET_DB_USER", + "value": "${{ secrets.RENDER_PG_USER }}" + }, + { + "key": "TOOLJET_DB_PASS", + "value": "${{ secrets.RENDER_PG_PASS }}" + }, + { + "key": "TOOLJET_DB_PORT", + "value": "5432" + }, + { + "key": "PGRST_DB_PRE_CONFIG", + "value": "postgrest.pre_config" + }, + { + "key": "PGRST_DB_URI", + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-tjdb" + }, + { + "key": "PGRST_HOST", + "value": "127.0.0.1:3000" + }, + { + "key": "PGRST_JWT_SECRET", + "value": "r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" + }, + { + "key": "PGRST_LOG_LEVEL", + "value": "info" + }, + { + "key": "PORT", + "value": "80" + }, + { + "key": "TOOLJET_HOST", + "value": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + }, + { + "key": "DISABLE_TOOLJET_TELEMETRY", + "value": "true" + }, + { + "key": "SMTP_ADDRESS", + "value": "smtp.mailtrap.io" + }, + { + "key": "SMTP_DOMAIN", + "value": "smtp.mailtrap.io" + }, + { + "key": "SMTP_PORT", + "value": "2525" + }, + { + "key": "SMTP_USERNAME", + "value": "${{ secrets.RENDER_SMTP_USERNAME }}" + }, + { + "key": "SMTP_PASSWORD", + "value": "${{ secrets.RENDER_SMTP_PASSWORD }}" + }, + { + "key": "REDIS_HOST", + "value": "${{ secrets.RENDER_REDIS_HOST }}" + }, + { + "key": "REDIS_PORT", + "value": "${{ secrets.RENDER_REDIS_PORT }}" + }, + { + "key": "LICENSE_KEY", + "value": "${{ secrets.RENDER_LICENSE_KEY }}" + }, + { + "key": "ENABLE_MARKETPLACE_FEATURE", + "value": "true" + }, + { + "key": "TOOLJET_MARKETPLACE_URL", + "value": "${{ secrets.MARKETPLACE_BUCKET }}" + } + ], + "serviceDetails": { + "disk": null, + "env": "docker", + "envSpecificDetails": { + "dockerCommand": "", + "dockerContext": "./", + "dockerfilePath": "./docker/preview.Dockerfile" + }, + "healthCheckPath": "/api/health", + "numInstances": 1, + "openPorts": [{ + "port": 80, + "protocol": "TCP" + }], + "plan": "starter", + "pullRequestPreviewsEnabled": "no", + "region": "oregon", + "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + } + }') + + echo "response: $RESPONSE" + export SERVICE_ID=$(echo $RESPONSE | jq -r '.service.id') + echo "SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + + - name: Comment deployment URL + uses: actions/github-script@v5 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' + }) + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'create-review-app' + }) + } catch (e) { + console.log(e) + } + + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-review-app'] + }) diff --git a/.github/workflows/review-path-deploy.yml b/.github/workflows/review-path-deploy.yml index f45d90aa47..8d3bed14e3 100644 --- a/.github/workflows/review-path-deploy.yml +++ b/.github/workflows/review-path-deploy.yml @@ -59,7 +59,7 @@ jobs: }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}" + "value": "${{ env.PR_NUMBER }}-tjdb" }, { "key": "TOOLJET_DB_HOST", @@ -68,7 +68,7 @@ jobs: { "key": "TOOLJET_DB_USER", "value": "${{ secrets.RENDER_PG_USER }}" - }, + }, { "key": "TOOLJET_DB_PASS", "value": "${{ secrets.RENDER_PG_PASS }}" @@ -87,7 +87,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}" + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-tjdb" }, { "key": "PGRST_HOST", From 244452879329c318977ad73b9a666d7c394f20fe Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Mon, 18 Nov 2024 17:03:48 +0530 Subject: [PATCH 15/30] Merge conflict resolved --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 3fd34d530f..a8a10caf55 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -596,7 +596,10 @@ export default function Grid({ gridWidth, currentLayout }) { isDragOnTableORCalendar = tableElem.contains(e.inputEvent.target); } if (box?.component?.component === 'Calendar') { - const calenderElem = e.target.querySelector('.calendar-widget'); + const calenderElem = + e.target.querySelector('.rbc-month-view') || + e.target.querySelector('.rbc-time-view') || + e.target.querySelector('.rbc-day-view'); isDragOnTableORCalendar = calenderElem.contains(e.inputEvent.target); } From 56d0ae908aafcfcd0cfa6d43a287555890b2ad83 Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Mon, 18 Nov 2024 18:13:59 +0530 Subject: [PATCH 16/30] add changes for table --- .../src/AppBuilder/_stores/slices/componentsSlice.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 1b316a9652..7fb9650075 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1192,8 +1192,16 @@ export const createComponentsSlice = (set, get) => ({ 'setComponentProperty' ); + const oldComponent = get().modules[moduleId].pages[currentPageIndex].components[componentId].component; + const { events, exposedVariables, ...filteredDefinition } = oldComponent.definition || {}; + const diff = { - [componentId]: { component: get().modules[moduleId].pages[currentPageIndex].components[componentId].component }, + [componentId]: { + component: { + ...oldComponent, + definition: filteredDefinition, + }, + }, }; if (saveAfterAction) { From 8d4b8aa56a4aff8aa381078744ec3a6049f19529 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Fri, 15 Nov 2024 11:46:34 +0530 Subject: [PATCH 17/30] form button to submit mapping issue on version creation and import export --- server/package.json | 2 + server/src/helpers/import_export.helpers.ts | 474 +++++++++++++++--- .../src/services/app_import_export.service.ts | 18 +- server/src/services/apps.service.ts | 22 +- server/src/services/page.service.ts | 10 +- 5 files changed, 408 insertions(+), 118 deletions(-) diff --git a/server/package.json b/server/package.json index 745a0e9610..4f66f7de3d 100644 --- a/server/package.json +++ b/server/package.json @@ -53,6 +53,8 @@ "@sentry/tracing": "6.17.6", "@tooljet/plugins": "../plugins", "@types/express-serve-static-core": "^4.19.5", + "acorn": "^8.13.0", + "acorn-walk": "^8.3.4", "bcrypt": "^5.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", diff --git a/server/src/helpers/import_export.helpers.ts b/server/src/helpers/import_export.helpers.ts index 2c98071159..b7aa926d34 100644 --- a/server/src/helpers/import_export.helpers.ts +++ b/server/src/helpers/import_export.helpers.ts @@ -1,39 +1,40 @@ +import * as acorn from 'acorn'; +import * as walk from 'acorn-walk'; + +function findExpression(input) { + const matches = []; + let startIdx = -1; + let braceCount = 0; + + for (let i = 0; i < input.length; i++) { + if (input[i] === '{' && input[i + 1] === '{' && braceCount === 0) { + startIdx = i; + braceCount = 2; + i++; // Skip the second '{' + } else if (input[i] === '{' && braceCount > 0) { + braceCount++; + } else if (input[i] === '}' && braceCount > 0) { + braceCount--; + if (braceCount === 0 && startIdx !== -1) { + matches.push({ + fullMatch: input.slice(startIdx, i + 1), + expression: input.slice(startIdx + 2, i - 1).trim(), + index: startIdx, + }); + startIdx = -1; + } + } + } + + return matches; +} + export function updateEntityReferences(node, resourceMapping: Record = {}) { if (typeof node === 'object') { for (const key in node) { let value = node[key]; if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) { - const referenceExists = value; - - if (referenceExists) { - const matches = value.match(/{{(.*?)}}/g); - // gett all references {{entityName}} - if (matches) { - matches.forEach((match) => { - // remove curly braces and extract the entity "component.entityName.something" - const ref = match.slice(2, -2).trim(); - const entityName = ref.split('.')[1]; - - if (resourceMapping[entityName]) { - const newValue = value.replace(entityName, resourceMapping[entityName]); - - node[key] = newValue; - value = newValue; - } - }); - } else { - // kept this logic for fallback, although it should not be needed - const ref = value.replace('{{', '').replace('}}', ''); - - const entityName = ref.split('.')[1]; - - if (resourceMapping[entityName]) { - const newValue = value.replace(entityName, resourceMapping[entityName]); - - node[key] = newValue; - } - } - } + node[key] = extractAndReplaceReferencesFromString(value, resourceMapping, resourceMapping)?.valueWithId; } else if (typeof value === 'object') { value = updateEntityReferences(value, resourceMapping); } @@ -43,53 +44,372 @@ export function updateEntityReferences(node, resourceMapping: Record { - const ref = match.slice(2, -2).trim(); // Remove {{ and }} - const entityName = ref.split('.')[1]; - if (entityName && !allRefs.includes(entityName)) { - allRefs.push(entityName); - } - }); - } else { - // kept this logic for fallback, although it should not be needed - const ref = value.replace('{{', '').replace('}}', ''); - - const entityName = ref.split('.')[1]; - - allRefs.push(entityName); - } - } - } else if (typeof value === 'object') { - findAllEntityReferences(value, allRefs); - } - } - } - return allRefs; -} - export function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); } + +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*)/; + if (!regexForQuickCheck.test(input)) { + return { + allRefs: [], + valueWithId: input, + valueWithBrackets: input, + }; + } + + const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants)\b/; + const expressionRegex = /{{(.*?)}}/gs; + const results = []; + let lastIndex = 0; + let replacedString = ''; + let bracketNotationString = ''; + + // Precompile the UUID regex + const uuidRegex = + /\b(components|queries)(\??\.|\??\.?\[['"]?)([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(['"]?\])?/g; + + let match; + if (input.startsWith('{{{') && input.endsWith('}}}')) { + input = input.replace(/\{\{(.*)\}\}/, '{{($1)}}'); + const matches = findExpression(input); + for (const match of matches) { + const { fullMatch, expression, index } = match; + + // Check if the expression contains relevant keywords + if (!relevantKeywords.test(expression)) { + replacedString += input.slice(lastIndex, index); + bracketNotationString += input.slice(lastIndex, index); + replacedString += fullMatch; + bracketNotationString += fullMatch; + lastIndex = index + fullMatch.length; + continue; + } + + try { + const { processedExpression, uuidMappings } = preprocessExpression( + expression, + uuidRegex, + componentIdNameMapping, + queryIdNameMapping + ); + const parsedResult = parseExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + uuidMappings + ); + + replacedString += input.slice(lastIndex, index); + bracketNotationString += input.slice(lastIndex, index); + + const replacedExpression = replaceIdsInExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + false, + uuidMappings + ); + const bracketNotationExpression = replaceIdsInExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + true, + uuidMappings + ); + + replacedString += `{{${replacedExpression}}}`; + bracketNotationString += `{{${bracketNotationExpression}}}`; + + results.push({ + allRefs: parsedResult.references, + valueWithId: `{{${replacedExpression}}}`, + valueWithBrackets: `{{${bracketNotationExpression}}}`, + }); + } catch (error) { + replacedString += fullMatch; + bracketNotationString += fullMatch; + results.push({ + allRefs: [], + valueWithId: fullMatch, + valueWithBrackets: fullMatch, + }); + } + + lastIndex = index + fullMatch.length; + } + + replacedString += input.slice(lastIndex); + bracketNotationString += input.slice(lastIndex); + // remove the parentheses that were added + + return { + valueWithId: `{{${replacedString.slice(3, -3)}}}`, + valueWithBrackets: `{{${bracketNotationString.slice(3, -3)}}}`, + allRefs: results.flatMap((r) => r.allRefs), + }; + } + while ((match = expressionRegex.exec(input)) !== null) { + const fullMatch = match[0]; + const expression = match[1].trim(); + + // Check if the expression contains relevant keywords + if (!relevantKeywords.test(expression)) { + replacedString += input.slice(lastIndex, match.index); + bracketNotationString += input.slice(lastIndex, match.index); + replacedString += fullMatch; + bracketNotationString += fullMatch; + lastIndex = match.index + fullMatch.length; + continue; + } + + try { + const { processedExpression, uuidMappings } = preprocessExpression( + expression, + uuidRegex, + componentIdNameMapping, + queryIdNameMapping + ); + const parsedResult = parseExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + uuidMappings + ); + + replacedString += input.slice(lastIndex, match.index); + bracketNotationString += input.slice(lastIndex, match.index); + + const replacedExpression = replaceIdsInExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + false, + uuidMappings + ); + const bracketNotationExpression = replaceIdsInExpression( + processedExpression, + componentIdNameMapping, + queryIdNameMapping, + true, + uuidMappings + ); + + replacedString += `{{${replacedExpression}}}`; + bracketNotationString += `{{${bracketNotationExpression}}}`; + + results.push({ + allRefs: parsedResult.references, + valueWithId: `{{${replacedExpression}}}`, + valueWithBrackets: `{{${bracketNotationExpression}}}`, + }); + } catch (error) { + replacedString += fullMatch; + bracketNotationString += fullMatch; + results.push({ + allRefs: [], + valueWithId: fullMatch, + valueWithBrackets: fullMatch, + }); + } + + lastIndex = match.index + fullMatch.length; + } + + replacedString += input.slice(lastIndex); + bracketNotationString += input.slice(lastIndex); + + return { + allRefs: results.flatMap((r) => r.allRefs), + valueWithId: replacedString, + valueWithBrackets: bracketNotationString, + }; +} + +function preprocessExpression(expression, uuidRegex, componentIdNameMapping, queryIdNameMapping) { + const uuidMappings = {}; + let placeholderCounter = 0; + + const processedExpression = expression.replace(uuidRegex, (match, p1, p2, p3, p4) => { + const placeholder = `__UUID_PLACEHOLDER_${placeholderCounter}__`; + uuidMappings[placeholder] = (p1 === 'components' ? componentIdNameMapping[p3] : queryIdNameMapping[p3]) || p3; + placeholderCounter++; + return `${p1}${p2}${placeholder}${p4 || ''}`; + }); + + return { processedExpression, uuidMappings }; +} + +function replaceIdsInExpression( + expression, + componentIdNameMapping, + queryIdNameMapping, + useBracketNotation, + uuidMappings +) { + try { + const ast = acorn.parse(expression, { ecmaVersion: 2020 }); + const replacements = []; + + walk.simple(ast, { + MemberExpression(node) { + if ( + node.object.type === 'Identifier' && + (node.object.name === 'components' || node.object.name === 'queries') + ) { + const isComponent = node.object.name === 'components'; + const mapping = isComponent ? componentIdNameMapping : queryIdNameMapping; + + if (node.property.type === 'Identifier') { + const name = node.property.name; + const nameWithOptionalCheck = node.optional + ? useBracketNotation + ? `${node.object.name}?.` + : `${node.object.name}?` + : `${node.object.name}`; + if (mapping[name] || name.startsWith('__UUID_PLACEHOLDER_')) { + const start = node.start; + const end = node.end; + let replacement; + if (name.startsWith('__UUID_PLACEHOLDER_')) { + const actualName = uuidMappings[name]; + replacement = useBracketNotation + ? `${nameWithOptionalCheck}["${actualName}"]` + : `${nameWithOptionalCheck}.${actualName}`; + } else { + replacement = useBracketNotation + ? `${nameWithOptionalCheck}["${mapping[name]}"]` + : `${nameWithOptionalCheck}.${mapping[name]}`; + } + replacements.push({ start, end, replacement }); + } + } else if (node.property.type === 'Literal') { + const name = node.property.value as string; + const nameWithOptionalCheck = node.optional ? `${node.object.name}?.` : `${node.object.name}`; + if (mapping[name] || name.startsWith('__UUID_PLACEHOLDER_')) { + const start = node.start; + const end = node.end; + let replacement; + if (name.startsWith('__UUID_PLACEHOLDER_')) { + const actualName = uuidMappings[name]; + replacement = `${nameWithOptionalCheck}["${actualName}"]`; + } else { + replacement = `${nameWithOptionalCheck}["${mapping[name]}"]`; + } + replacements.push({ start, end, replacement }); + } + } + } + }, + }); + + if (replacements.length === 0) return expression; + + replacements.sort((a, b) => b.start - a.start); + + let result = expression; + for (const { start, end, replacement } of replacements) { + result = result.slice(0, start) + replacement + result.slice(end); + } + + return result; + } catch (error) { + return expression; + } +} + +function parseExpression(expression, componentIdNameMapping, queryIdNameMapping, uuidMappings) { + try { + const ast = acorn.parse(expression, { ecmaVersion: 2020 }); + const references = []; + const validRootObjects = { + components: true, + queries: true, + variables: true, + globals: true, + page: true, + }; + walk.simple(ast, { + MemberExpression: handleMemberExpression, + }); + + // eslint-disable-next-line no-inner-declarations + function handleMemberExpression(node) { + const reference = extractPath(node); + if (reference) references.push(reference); + } + + // eslint-disable-next-line no-inner-declarations + function extractPath(node) { + const path = []; + let current = node; + let rootObject = ''; + + while (current) { + if (current.type === 'Identifier') { + path.unshift(current.name); + if (validRootObjects[current.name]) { + rootObject = current.name; + break; + } + } else if (current.type === 'MemberExpression' || current.type === 'OptionalMemberExpression') { + if (current.computed) { + if ( + current.property.type === 'Literal' && + (typeof current.property.value === 'string' || typeof current.property.value === 'number') + ) { + path.unshift(current.property.value.toString()); + } else { + break; + } + } else { + path.unshift(current.property.name); + } + } else { + break; + } + current = current.object; + } + + if ( + (rootObject && (rootObject === 'queries' || rootObject === 'components') && path.length >= 3) || + ((rootObject === 'variables' || rootObject === 'globals') && path.length === 2) || + (rootObject === 'page' && path.length === 3) + ) { + return createReferenceObject(rootObject, path, uuidMappings, componentIdNameMapping, queryIdNameMapping); + } + return null; + } + + return { references }; + } catch (error) { + console.log(error); + return { references: [] }; + } +} + +function createReferenceObject(entityType, path, uuidMappings, componentIdNameMapping, queryIdNameMapping) { + let entityNameOrId, entityKey; + + if (entityType === 'components' || entityType === 'queries') { + entityNameOrId = path[1]; + entityKey = path[2]; + + if (entityNameOrId.startsWith('__UUID_PLACEHOLDER_')) { + entityNameOrId = uuidMappings[entityNameOrId]; + } else { + const mapping = entityType === 'components' ? componentIdNameMapping : queryIdNameMapping; + entityNameOrId = mapping[entityNameOrId] || entityNameOrId; + } + } else if (entityType === 'variables' || entityType === 'globals') { + entityKey = path[1]; + } else if (entityType === 'page') { + entityNameOrId = path[1]; + entityKey = path[2]; + } + + return { entityType, entityNameOrId, entityKey }; +} diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index 0cf6c7f83b..e799047435 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -30,7 +30,7 @@ import { Component } from 'src/entities/component.entity'; import { Layout } from 'src/entities/layout.entity'; import { EventHandler, Target } from 'src/entities/event_handler.entity'; import { v4 as uuid } from 'uuid'; -import { findAllEntityReferences, isValidUUID, updateEntityReferences } from 'src/helpers/import_export.helpers'; +import { updateEntityReferences } from 'src/helpers/import_export.helpers'; interface AppResourceMappings { defaultDataSourceIdMapping: Record; dataQueryMapping: Record; @@ -290,13 +290,7 @@ export class AppImportExportService { .getMany(); const toUpdateComponents = components.filter((component) => { - const entityReferencesInComponentDefinitions = findAllEntityReferences(component, []).filter( - (entity) => entity && isValidUUID(entity) - ); - - if (entityReferencesInComponentDefinitions.length > 0) { - return updateEntityReferences(component, mappings); - } + return updateEntityReferences(component, mappings); }); if (!isEmpty(toUpdateComponents)) { @@ -312,13 +306,7 @@ export class AppImportExportService { .getMany(); const toUpdateDataQueries = dataQueries.filter((dataQuery) => { - const entityReferencesInQueryOptions = findAllEntityReferences(dataQuery, []).filter( - (entity) => entity && isValidUUID(entity) - ); - - if (entityReferencesInQueryOptions.length > 0) { - return updateEntityReferences(dataQuery, mappings); - } + return updateEntityReferences(dataQuery, mappings); }); if (!isEmpty(toUpdateDataQueries)) { diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index 65cd02c410..ca10a6ff50 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -27,7 +27,7 @@ import { Component } from 'src/entities/component.entity'; import { EventHandler, Target } from 'src/entities/event_handler.entity'; import { VersionReleaseDto } from '@dto/version-release.dto'; -import { findAllEntityReferences, isValidUUID, updateEntityReferences } from 'src/helpers/import_export.helpers'; +import { updateEntityReferences } from 'src/helpers/import_export.helpers'; import { isEmpty, set } from 'lodash'; import { AppBase } from 'src/entities/app_base.entity'; import { LayoutDimensionUnits } from 'src/helpers/components.helper'; @@ -435,13 +435,7 @@ export class AppsService { .getMany(); const toUpdateComponents = components.filter((component) => { - const entityReferencesInComponentDefinitions = findAllEntityReferences(component, []).filter( - (entity) => entity && isValidUUID(entity) - ); - - if (entityReferencesInComponentDefinitions.length > 0) { - return updateEntityReferences(component, mappings); - } + return updateEntityReferences(component, mappings); }); if (!isEmpty(toUpdateComponents)) { @@ -457,13 +451,7 @@ export class AppsService { .getMany(); const toUpdateDataQueries = dataQueries.filter((dataQuery) => { - const entityReferencesInQueryOptions = findAllEntityReferences(dataQuery, []).filter( - (entity) => entity && isValidUUID(entity) - ); - - if (entityReferencesInQueryOptions.length > 0) { - return updateEntityReferences(dataQuery, mappings); - } + return updateEntityReferences(dataQuery, mappings); }); if (!isEmpty(toUpdateDataQueries)) { @@ -532,7 +520,7 @@ export class AppsService { let homePageId = prevHomePagePage; - let newComponents = []; + const newComponents = []; const newComponentLayouts = []; let oldComponentToNewComponentMapping = {}; const oldPageToNewPageMapping = {}; @@ -693,8 +681,6 @@ export class AppsService { await manager.save(newComponents); await manager.save(newComponentLayouts); - newComponents = []; - oldComponentToNewComponentMapping = {}; } await manager.update(AppVersion, { id: appVersion.id }, { homePageId }); diff --git a/server/src/services/page.service.ts b/server/src/services/page.service.ts index e67420c4f2..84a0db5ed5 100644 --- a/server/src/services/page.service.ts +++ b/server/src/services/page.service.ts @@ -11,7 +11,7 @@ import { EventsService } from './events_handler.service'; import { Component } from 'src/entities/component.entity'; import { Layout } from 'src/entities/layout.entity'; import { EventHandler } from 'src/entities/event_handler.entity'; -import { findAllEntityReferences, isValidUUID, updateEntityReferences } from 'src/helpers/import_export.helpers'; +import { updateEntityReferences } from 'src/helpers/import_export.helpers'; import { isEmpty } from 'class-validator'; import { PageHelperService } from '@apps/services/pages/service.helper'; import * as _ from 'lodash'; @@ -243,13 +243,7 @@ export class PageService { } const toUpdateComponents = clonedComponents.filter((component) => { - const entityReferencesInComponentDefinitions = findAllEntityReferences(component, []).filter( - (entity) => entity && isValidUUID(entity) - ); - - if (entityReferencesInComponentDefinitions.length > 0) { - return updateEntityReferences(component, componentsIdMap); - } + return updateEntityReferences(component, componentsIdMap); }); if (!isEmpty(toUpdateComponents)) { From bb928f2031a85649dc245b7e4f7e5738ab3523fa Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Mon, 18 Nov 2024 12:39:36 +0530 Subject: [PATCH 18/30] fix bug breaking hash map --- server/src/services/apps.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index ca10a6ff50..cd683968b8 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -522,7 +522,7 @@ export class AppsService { const newComponents = []; const newComponentLayouts = []; - let oldComponentToNewComponentMapping = {}; + const oldComponentToNewComponentMapping = {}; const oldPageToNewPageMapping = {}; const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => { From 022e475b6d8d7a402dcc9df32719950e812725f0 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Mon, 18 Nov 2024 12:48:52 +0530 Subject: [PATCH 19/30] use string manipulation instead of regex --- frontend/src/AppBuilder/_stores/ast.js | 3 ++- server/src/helpers/import_export.helpers.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/ast.js b/frontend/src/AppBuilder/_stores/ast.js index f735228974..106298588f 100644 --- a/frontend/src/AppBuilder/_stores/ast.js +++ b/frontend/src/AppBuilder/_stores/ast.js @@ -54,7 +54,8 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp let match; if (input.startsWith('{{{') && input.endsWith('}}}')) { - input = input.replace(/\{\{(.*)\}\}/, '{{($1)}}'); + const inputContent = input.slice(3, -3); + input = `{{({${inputContent}})}}`; const matches = findExpression(input); for (const match of matches) { const { fullMatch, expression, index } = match; diff --git a/server/src/helpers/import_export.helpers.ts b/server/src/helpers/import_export.helpers.ts index b7aa926d34..1f1bc57b7e 100644 --- a/server/src/helpers/import_export.helpers.ts +++ b/server/src/helpers/import_export.helpers.ts @@ -74,7 +74,8 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp let match; if (input.startsWith('{{{') && input.endsWith('}}}')) { - input = input.replace(/\{\{(.*)\}\}/, '{{($1)}}'); + const inputContent = input.slice(3, -3); + input = `{{({${inputContent}})}}`; const matches = findExpression(input); for (const match of matches) { const { fullMatch, expression, index } = match; From 6a2b76c5b22ce984b956053a8e90a7a7aae35efd Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 19 Nov 2024 12:10:33 +0530 Subject: [PATCH 20/30] Fix request, response, responseHeaders from resolved query not being part of dependency graph --- frontend/src/AppBuilder/_stores/slices/resolvedSlice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 4c29226864..063fa026b0 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -183,7 +183,7 @@ export const createResolvedSlice = (set, get) => ({ ); Object.entries(details).forEach(([key, value]) => { - if (['isLoading', 'data', 'rawData'].includes(key)) { + if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders'].includes(key)) { if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`); } }); From 8a3f89a19c8fff310f3be646d0fa290eb91e0f54 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 19 Nov 2024 16:55:47 +0530 Subject: [PATCH 21/30] Add request, response,responseHeaders outside of metadata --- frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js | 5 ++++- frontend/src/AppBuilder/_stores/slices/resolvedSlice.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 5e96052f6f..eaae513bb7 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -441,7 +441,10 @@ export const createQueryPanelSlice = (set, get) => ({ isLoading: false, data: finalData, rawData, - metadata: data.metadata, + metadata: data?.metadata, + request: data?.request, + response: data?.response, + responseHeaders: data?.responseHeaders, }); resolve({ status: 'ok', data: finalData }); diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 063fa026b0..dc6eb9cecc 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -183,7 +183,7 @@ export const createResolvedSlice = (set, get) => ({ ); Object.entries(details).forEach(([key, value]) => { - if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders'].includes(key)) { + if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders', 'metadata'].includes(key)) { if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`); } }); From 1cebfe039a4bd7746693c3d70fbcf4f7485b630c Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 19 Nov 2024 17:50:31 +0530 Subject: [PATCH 22/30] fix --- frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index eaae513bb7..c989df9051 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -442,9 +442,8 @@ export const createQueryPanelSlice = (set, get) => ({ data: finalData, rawData, metadata: data?.metadata, - request: data?.request, - response: data?.response, - responseHeaders: data?.responseHeaders, + request: data?.metadata?.request, + response: data?.metadata?.response, }); resolve({ status: 'ok', data: finalData }); From 57f75626d1d24b413c0e69b760e3d743f6b41306 Mon Sep 17 00:00:00 2001 From: Adish M <44204658+adishM98@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:43:28 +0530 Subject: [PATCH 23/30] Fix/tjdb render preview lts 3.0 (#11368) * Fix for TJDB in render preview app lts-3.0 branch * removed marketplace env --- .github/workflows/render-preview-deploy.yml | 38 ++++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index 330e650366..9abca231dd 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -59,7 +59,7 @@ jobs: }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}-tj-db" + "value": "${{ env.PR_NUMBER }}-tjdb" }, { "key": "TOOLJET_DB_HOST", @@ -68,7 +68,7 @@ jobs: { "key": "TOOLJET_DB_USER", "value": "${{ secrets.RENDER_PG_USER }}" - }, + }, { "key": "TOOLJET_DB_PASS", "value": "${{ secrets.RENDER_PG_PASS }}" @@ -77,10 +77,6 @@ jobs: "key": "TOOLJET_DB_PORT", "value": "5432" }, - { - "key": "TOOLJET_DB_STATEMENT_TIMEOUT", - "value": "60000" - }, { "key": "PGRST_DB_PRE_CONFIG", "value": "postgrest.pre_config" @@ -258,12 +254,13 @@ jobs: - name: Wait after installing PostgreSQL run: sleep 25 - - name: Drop PostgreSQL PR database + - name: Drop PostgreSQL PR databases env: PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} PGPORT: 5432 PGUSER: ${{ secrets.RENDER_DS_PG_USER }} PGDATABASE: ${{ env.PR_NUMBER }} + PGTJBDATABASE: ${{ env.PR_NUMBER }}-tjdb run: | if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then echo "Database $PGDATABASE exists, deleting..." @@ -272,6 +269,13 @@ jobs: echo "Database $PGDATABASE does not exist." fi + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + echo "Database $PGTJBDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + else + echo "Database $PGTJBDATABASE does not exist." + fi + suspend-review-app: if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-review-app' }} runs-on: ubuntu-latest @@ -405,12 +409,13 @@ jobs: - name: Wait after installing PostgreSQL run: sleep 25 - - name: Drop PostgreSQL PR database + - name: Drop PostgreSQL PR databases env: PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} PGPORT: 5432 PGUSER: ${{ secrets.RENDER_DS_PG_USER }} PGDATABASE: ${{ env.PR_NUMBER }} + PGTJBDATABASE: ${{ env.PR_NUMBER }}-tjdb run: | if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then echo "Database $PGDATABASE exists, deleting..." @@ -419,6 +424,13 @@ jobs: echo "Database $PGDATABASE does not exist." fi + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + echo "Database $PGTJBDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + else + echo "Database $PGTJBDATABASE does not exist." + fi + - name: Create deployment id: create-deployment run: | @@ -460,13 +472,9 @@ jobs: "key": "PG_DB", "value": "${{ env.PR_NUMBER }}" }, - { - "key": "ENABLE_TOOLJET_DB", - "value": "true" - }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}-tj-db" + "value": "${{ env.PR_NUMBER }}-tjdb" }, { "key": "TOOLJET_DB_HOST", @@ -548,10 +556,6 @@ jobs: "key": "LICENSE_KEY", "value": "${{ secrets.RENDER_LICENSE_KEY }}" }, - { - "key": "ENABLE_MARKETPLACE_FEATURE", - "value": "true" - }, { "key": "TOOLJET_MARKETPLACE_URL", "value": "${{ secrets.MARKETPLACE_BUCKET }}" From caaae7ae74b41ac8b074ae90834cfa67dcb25d44 Mon Sep 17 00:00:00 2001 From: gsmithun4 Date: Wed, 20 Nov 2024 14:56:57 +0530 Subject: [PATCH 24/30] bump version --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts diff --git a/frontend/.version b/frontend/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts diff --git a/server/.version b/server/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts From d29d0c730e23f4c1bb0b615104482101188f5fe5 Mon Sep 17 00:00:00 2001 From: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:58:58 +0530 Subject: [PATCH 25/30] Local data source discontinued changes (#11344) * basic setup changes * minor changes * minor ui fix * remove parameter check and minor refactoring * bugfix * variable changes * css changes * removed consoles * removed consoles * pr review changes * documentation link changes * minor css changes --- frontend/assets/images/icons/info-icon.svg | 5 ++ .../Components/QueryManagerBody.jsx | 29 +++++++++- .../NotificationBanner/NotificationBanner.jsx | 49 +++++++++++++++++ .../_components/NotificationBanner/index.js | 1 + .../NotificationBanner/resources/styles.scss | 55 +++++++++++++++++++ 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 frontend/assets/images/icons/info-icon.svg create mode 100644 frontend/src/_components/NotificationBanner/NotificationBanner.jsx create mode 100644 frontend/src/_components/NotificationBanner/index.js create mode 100644 frontend/src/_components/NotificationBanner/resources/styles.scss diff --git a/frontend/assets/images/icons/info-icon.svg b/frontend/assets/images/icons/info-icon.svg new file mode 100644 index 0000000000..66528cbddb --- /dev/null +++ b/frontend/assets/images/icons/info-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx index 2a7be4a37d..c891ba4c95 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx @@ -19,6 +19,7 @@ import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; import useStore from '@/AppBuilder/_stores/store'; import { EventManager } from '@/AppBuilder/RightSideBar/Inspector/EventManager'; +import NotificationBanner from '@/_components/NotificationBanner'; export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) => { const { t } = useTranslation(); @@ -30,6 +31,7 @@ export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) = const selectedDataSource = useStore((state) => state.queryPanel.selectedDataSource); const changeDataQuery = useStore((state) => state.dataQuery.changeDataQuery); const updateDataQuery = useStore((state) => state.dataQuery.updateDataQuery); + const [showLocalDataSourceDeprecationBanner, setshowLocalDataSourceDeprecationBanner] = useState(false); const [dataSourceMeta, setDataSourceMeta] = useState(null); /* - Added the below line to cause re-rendering when the query is switched @@ -280,7 +282,7 @@ export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) = return ( <>
- {selectedQuery && ( + {selectedQuery && !showLocalDataSourceDeprecationBanner && ( ); }; + useEffect(() => { + const staticDataSources = ['runjs', 'runpy', 'tooljetdb']; + // added specific check for rest api - as it is a part of both : default and global data sources + const showDeprecationBanner = + selectedDataSource == null && + selectedQuery && + !staticDataSources.includes(selectedDataSource?.kind) && + (selectedDataSource?.kind !== 'restapi' || selectedDataSource?.type !== 'default'); + + if (showDeprecationBanner) { + setshowLocalDataSourceDeprecationBanner(true); + } else { + setshowLocalDataSourceDeprecationBanner(false); + } + }, [selectedDataSource, selectedQuery]); // if (selectedQueryId !== selectedQuery?.id) return; const hasPermissions = @@ -318,7 +335,6 @@ export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) = canReadDataSource(selectedQuery?.data_source_id) || canDeleteDataSource() : true; - return (
{selectedDataSource === null || !selectedQuery ? ( - renderDataSourcesList() + showLocalDataSourceDeprecationBanner ? ( + <> + + {renderChangeDataSource()} + + ) : ( + renderDataSourcesList() + ) ) : ( <> {selectedQuery?.data_source_id && activeTab === 1 && renderChangeDataSource()} diff --git a/frontend/src/_components/NotificationBanner/NotificationBanner.jsx b/frontend/src/_components/NotificationBanner/NotificationBanner.jsx new file mode 100644 index 0000000000..8b61b37f1d --- /dev/null +++ b/frontend/src/_components/NotificationBanner/NotificationBanner.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Alert } from '@/_ui/Alert/Alert'; +import './resources/styles.scss'; + +const DEFAULT_CONFIG = { + docsLink: ' https://docs.tooljet.com/docs/data-sources/local-data-sources-migration', +}; + +const DEFAULT_MESSAGES = { + prefix: 'This query is connected to a local data source which has been', + highlightedText: 'discontinued', + middle: 'Please create a global data source connection to reconnect your query.', + suffix: 'to know more.', + linkText: 'Read documentation', +}; + +const NotificationBanner = ({ + docsLink, + customMessage, + darkMode = false, + highlightedText = DEFAULT_MESSAGES.highlightedText, + highlightedClassName = 'highlighted-text', + enhanceDisabledVisibility = false, +}) => { + const currentDocsLink = docsLink || DEFAULT_CONFIG.docsLink; + + const bannerMessage = customMessage || ( + <> + {DEFAULT_MESSAGES.prefix} {highlightedText}.{' '} + {DEFAULT_MESSAGES.middle}{' '} + + {DEFAULT_MESSAGES.linkText} + {' '} + {DEFAULT_MESSAGES.suffix} + + ); + + return ( +
+ +
{bannerMessage}
+
+
+ ); +}; + +export default NotificationBanner; + +// To Do later: Expand this component properly to make it generic notification component diff --git a/frontend/src/_components/NotificationBanner/index.js b/frontend/src/_components/NotificationBanner/index.js new file mode 100644 index 0000000000..e762ccced6 --- /dev/null +++ b/frontend/src/_components/NotificationBanner/index.js @@ -0,0 +1 @@ +export { default } from './NotificationBanner'; diff --git a/frontend/src/_components/NotificationBanner/resources/styles.scss b/frontend/src/_components/NotificationBanner/resources/styles.scss new file mode 100644 index 0000000000..7c8d644933 --- /dev/null +++ b/frontend/src/_components/NotificationBanner/resources/styles.scss @@ -0,0 +1,55 @@ +.notification-banner { + display: flex; + padding: var(--3, 6px) var(--6, 12px); + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 32px; + flex: 1 0 0; + border-radius: var(--3, 6px); + background: var(--background-warning-weak, #FAEFE7); + border: none !important; + outline: none !important; + opacity: 1; + + img { + margin-top: -5px !important; + } +} + +@media screen and (min-width: 1200px) { + .notification-banner { + justify-content: center !important; + align-items: center !important; + } + + &>div { + justify-content: center !important; + } + +} + +.notification-content { + color: var(--text-default, #1B1F24); + font-family: "IBM Plex Sans"; + font-size: 11px; + font-style: normal; + font-weight: 400; + line-height: 16px; + + &.disabled { + font-weight: 700 !important; + opacity: 1 !important; + } +} + +.highlighted-text { + font-weight: 600 !important; + font-style: bold; +} + +.documentation-link { + font-weight: 400 !important; + color: var(--primary, #3E63DD) !important; + text-decoration-line: underline !important; +} \ No newline at end of file From 994e1a8c2fae9853e41f68205ab01b5008184e6f Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 21 Nov 2024 10:56:20 +0530 Subject: [PATCH 26/30] Add support for CSA setValue in codeeditor widget --- .../AppBuilder/WidgetManager/widgets/codeEditor.js | 7 +++++++ frontend/src/Editor/Components/CodeEditor.jsx | 12 +++++++++++- server/src/helpers/widget-config/codeEditor.js | 7 +++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/codeEditor.js b/frontend/src/AppBuilder/WidgetManager/widgets/codeEditor.js index d7ff03d9a8..eb76891af0 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/codeEditor.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/codeEditor.js @@ -67,6 +67,13 @@ export const codeEditorConfig = { exposedVariables: { value: '', }, + actions: [ + { + handle: 'setValue', + displayName: 'Set value', + params: [{ handle: 'setValue', defaultValue: '' }], + }, + ], definition: { others: { showOnDesktop: { value: '{{true}}' }, diff --git a/frontend/src/Editor/Components/CodeEditor.jsx b/frontend/src/Editor/Components/CodeEditor.jsx index 50038c0645..e12e4f10ff 100644 --- a/frontend/src/Editor/Components/CodeEditor.jsx +++ b/frontend/src/Editor/Components/CodeEditor.jsx @@ -1,5 +1,5 @@ /* eslint-disable import/no-unresolved */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { okaidia } from '@uiw/codemirror-theme-okaidia'; import { githubLight } from '@uiw/codemirror-theme-github'; @@ -51,6 +51,16 @@ export const CodeEditor = ({ id, height, darkMode, properties, styles, setExpose return height || 'auto'; }, [height]); + useEffect(() => { + const _setValue = (value) => { + if (typeof value === 'string') { + codeChanged(value); + } + }; + setExposedVariable('setValue', _setValue); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
Date: Thu, 21 Nov 2024 11:23:25 +0530 Subject: [PATCH 27/30] Bump version --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts diff --git a/frontend/.version b/frontend/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts diff --git a/server/.version b/server/.version index 99ba0ddace..09ce5a7d5a 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.0.2-ce-lts +3.0.3-ce-lts From be7d461816ba0a57116cbdeef51df26a933f5730 Mon Sep 17 00:00:00 2001 From: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:26:22 +0530 Subject: [PATCH 28/30] CE Subpath pending issues (#11352) * subpath fix * subpath fix * minor fix * minor fix * ee-cherrypicked --- .../OrganizationManager/CreateOrganization.jsx | 2 +- server/src/helpers/utils.helper.ts | 10 ++++++++++ server/src/main.ts | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx index 82bd046ae1..ef256014dd 100644 --- a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx +++ b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx @@ -148,9 +148,9 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { setSlug({ value: defaultValue, error: '' }); const checkWorkspaceUniqueness = async () => { + sluginput.current.value = defaultValue; try { await organizationService.checkWorkspaceUniqueness(null, defaultValue); - sluginput.current.value = defaultValue; } catch (errResponse) { let error = { status: false, diff --git a/server/src/helpers/utils.helper.ts b/server/src/helpers/utils.helper.ts index 72c64c0c37..41dde11284 100644 --- a/server/src/helpers/utils.helper.ts +++ b/server/src/helpers/utils.helper.ts @@ -424,3 +424,13 @@ export function mergeDeep(target, source, seen = new WeakMap()) { return target; } +export const getSubpath = () => { + const subpath = process.env.SUB_PATH || ''; + // Ensure subpath starts and ends with slashes + if (subpath) { + if (!subpath.startsWith('/') || !subpath.endsWith('/')) { + throw new Error('SUB_PATH must start and end with a slash'); + } + } + return subpath; +}; \ No newline at end of file diff --git a/server/src/main.ts b/server/src/main.ts index 0efcbbfd18..f7a129ff86 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -13,6 +13,7 @@ import { bootstrap as globalAgentBootstrap } from 'global-agent'; import { join } from 'path'; import * as helmet from 'helmet'; import * as express from 'express'; +import { getSubpath } from '@helpers/utils.helper'; const fs = require('fs'); @@ -100,7 +101,15 @@ function setSecurityHeaders(app, configService) { app.use((req, res, next) => { res.setHeader('Permissions-Policy', 'geolocation=(self), camera=(), microphone=()'); - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + + const subpath = getSubpath(); + const path = req.path.replace(subpath, subpath ? '/' : ''); + if (path.startsWith('/api/')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + } else { + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + } + return next(); }); } From 3f681c1bdee8a327f9d4d26434a82242c7a2e640 Mon Sep 17 00:00:00 2001 From: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:06:33 +0530 Subject: [PATCH 29/30] Integration and Workspace Constants Page UI Fixes (#11377) * ui fixes ws constants and integration page * ws const css --- frontend/src/ManageOrgConstants/ConstantFormStyle.scss | 2 +- frontend/src/ManageOrgConstants/ConstantTable.jsx | 6 +++--- frontend/src/ManageOrgConstants/ManageOrgConstants.jsx | 2 +- frontend/src/_styles/theme.scss | 7 ++++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/ManageOrgConstants/ConstantFormStyle.scss b/frontend/src/ManageOrgConstants/ConstantFormStyle.scss index 2984295cfa..3390b70e8a 100644 --- a/frontend/src/ManageOrgConstants/ConstantFormStyle.scss +++ b/frontend/src/ManageOrgConstants/ConstantFormStyle.scss @@ -51,7 +51,7 @@ border: 1px solid #e9ece; box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); overflow: hidden; - width: 920px; + // width: 920px; //Add it for EE height: 620px; padding: 20px; border-radius: 4px; diff --git a/frontend/src/ManageOrgConstants/ConstantTable.jsx b/frontend/src/ManageOrgConstants/ConstantTable.jsx index 38039a12d3..cf78cb598b 100644 --- a/frontend/src/ManageOrgConstants/ConstantTable.jsx +++ b/frontend/src/ManageOrgConstants/ConstantTable.jsx @@ -90,7 +90,7 @@ const ConstantTable = ({ {constants.map((constant) => ( - + - + {canUpdateDeleteConstant && ( - +
{
-
+
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 1b145788b4..07895c1392 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -7864,7 +7864,7 @@ tbody { .marketplace-page-sidebar { height: calc(100vh - 64px); - max-width: 288px; + max-width: 272px; background-color: var(--page-default); border-right: 1px solid var(--slate5) !important; display: grid !important; @@ -12830,6 +12830,11 @@ color: var(--text-default); padding: 16px; padding-top: 0px; padding-bottom: 0px; + + .p-3-constants{ + padding: 1rem !important; + padding-left: 0px !important; + } } .card-footer { From 1f73c11289378f76d942553636a969d744afde1a Mon Sep 17 00:00:00 2001 From: gsmithun4 Date: Thu, 21 Nov 2024 20:14:08 +0530 Subject: [PATCH 30/30] bump version --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 09ce5a7d5a..2ae831adb3 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.0.3-ce-lts +3.0.4-ce-lts diff --git a/frontend/.version b/frontend/.version index 09ce5a7d5a..2ae831adb3 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.0.3-ce-lts +3.0.4-ce-lts diff --git a/server/.version b/server/.version index 09ce5a7d5a..2ae831adb3 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.0.3-ce-lts +3.0.4-ce-lts