From 2211d9062893e3875903ae1e0f337f8bc69297d3 Mon Sep 17 00:00:00 2001 From: gsmithun4 Date: Tue, 19 Dec 2023 10:23:32 +0530 Subject: [PATCH 01/18] bump version --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index f34083e034..a5f3e61bdc 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.26.1 +2.27.0 diff --git a/frontend/.version b/frontend/.version index f34083e034..a5f3e61bdc 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -2.26.1 +2.27.0 diff --git a/server/.version b/server/.version index f34083e034..a5f3e61bdc 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -2.26.1 +2.27.0 From 7fea77875638e8a498154045a124b3376fa01374 Mon Sep 17 00:00:00 2001 From: Muhsin Shah C P Date: Tue, 19 Dec 2023 10:26:27 +0530 Subject: [PATCH 02/18] [Imp] Auto-removing query params from redirect urls (While login to the application) (#8128) * add: added query params to logout urls * add: added support for sso login * fix: page handle not updated after rename --- frontend/src/Editor/Editor.jsx | 24 ++++++++++++------- frontend/src/LoginPage/LoginPage.jsx | 7 +++--- frontend/src/_components/PrivateRoute.jsx | 2 +- frontend/src/_helpers/routes.js | 24 +++++++++++++------ .../src/_services/authentication.service.js | 2 +- 5 files changed, 38 insertions(+), 21 deletions(-) diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 5c58c6508a..352b21bf68 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -66,6 +66,7 @@ import EditorSelecto from './EditorSelecto'; import { diff } from 'deep-object-diff'; import useDebouncedArrowKeyPress from '@/_hooks/useDebouncedArrowKeyPress'; +import { getQueryParams } from '@/_helpers/routes'; import RightSidebarTabManager from './RightSidebarTabManager'; import { shallow } from 'zustand/shallow'; @@ -1367,11 +1368,21 @@ const EditorComponent = (props) => { useCurrentStateStore.getState().actions.setCurrentState({ globals, page }); }; + const navigateToPage = (queryParams = [], handle) => { + const appId = useAppDataStore.getState()?.appId; + const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&'); + + props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, { + state: { + isSwitchingPage: true, + }, + }); + }; + const switchPage = (pageId, queryParams = []) => { // This are fetched from store to handle runQueriesOnAppLoad const currentPageId = useEditorStore.getState().currentPageId; const appDefinition = useEditorStore.getState().appDefinition; - const appId = useAppDataStore.getState()?.appId; const pageHandle = getCurrentState().pageHandle; if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) { @@ -1381,13 +1392,7 @@ const EditorComponent = (props) => { if (!name || !handle) return; const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition)); - const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&'); - - props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, { - state: { - isSwitchingPage: true, - }, - }); + navigateToPage(queryParams, handle); const page = { id: pageId, @@ -1590,6 +1595,9 @@ const EditorComponent = (props) => { appDefinitionChanged(newDefinition, { pageDefinitionChanged: true, }); + + const queryParams = getQueryParams(); + navigateToPage(Object.entries(queryParams), newHandle); }; const updateOnSortingPages = (newSortedPages) => { diff --git a/frontend/src/LoginPage/LoginPage.jsx b/frontend/src/LoginPage/LoginPage.jsx index 35ce167961..26d8dc1383 100644 --- a/frontend/src/LoginPage/LoginPage.jsx +++ b/frontend/src/LoginPage/LoginPage.jsx @@ -139,9 +139,11 @@ class LoginPageComponent extends React.Component { setRedirectUrlToCookie() { // Page is loaded inside an iframe const iframe = window !== window.top; + const redirectPath = getRedirectTo( + iframe ? new URL(window.location.href).searchParams : new URL(location.href).searchParams + ); if (iframe) { - const redirectPath = getRedirectTo(); window.parent.postMessage( { type: 'redirectTo', @@ -153,9 +155,6 @@ class LoginPageComponent extends React.Component { ); } - const params = iframe ? new URL(window.location.href).searchParams : new URL(location.href).searchParams; - const redirectPath = params.get('redirectTo'); - authenticationService.saveLoginOrganizationId(this.organizationId); authenticationService.saveLoginOrganizationSlug(this.organizationSlug); redirectPath && setCookie('redirectPath', redirectPath, iframe); diff --git a/frontend/src/_components/PrivateRoute.jsx b/frontend/src/_components/PrivateRoute.jsx index 60292f041c..93cf1a81e6 100644 --- a/frontend/src/_components/PrivateRoute.jsx +++ b/frontend/src/_components/PrivateRoute.jsx @@ -95,7 +95,7 @@ export const PrivateRoute = ({ children }) => { { return redirectLoc; }; -export const getRedirectTo = () => { - const params = new URL(window.location.href).searchParams; - return params.get('redirectTo') || '/'; +export const getRedirectTo = (paramObj) => { + const params = paramObj || new URL(window.location.href).searchParams; + let combined = Array.from(params.entries()) + .map((param) => param.join('=')) + .join('&'); + return params.get('redirectTo') ? combined.replace('redirectTo=', '') : '/'; }; export const getPreviewQueryParams = () => { @@ -169,14 +172,21 @@ export const getPreviewQueryParams = () => { }; }; -export const getRedirectToWithParams = () => { +export const getRedirectToWithParams = (shouldAddCustomParams = false) => { const pathname = getPathname(null, true); - const queryParams = pathname.includes('/applications/') ? getPreviewQueryParams() : {}; - const query = !_.isEmpty(queryParams) ? queryString.stringify(queryParams) : ''; - return `${pathname}${!_.isEmpty(query) ? `?${query}` : ''}`; + let query = pathname.includes('/applications/') ? constructQueryParamsInOrder(shouldAddCustomParams) : ''; + return `${pathname}${query}`; }; export const redirectToErrorPage = (errType, queryParams) => { const query = !_.isEmpty(queryParams) ? queryString.stringify(queryParams) : ''; window.location = `${getHostURL()}/error/${errType}${!_.isEmpty(query) ? `?${query}` : ''}`; }; + +/* TODO-reuse: Somewhere in the code we used same logic to construct preview params */ +const constructQueryParamsInOrder = (shouldAddCustomParams = false) => { + const { version, ...rest } = getQueryParams(); + const queryStr = shouldAddCustomParams && !_.isEmpty(rest) ? queryString.stringify(rest) : ''; + const previewParams = `${version ? `?version=${version}` : ''}`; + return `${previewParams}${queryStr ? `${previewParams ? '&' : '?'}${queryStr}` : ''}`; +}; diff --git a/frontend/src/_services/authentication.service.js b/frontend/src/_services/authentication.service.js index 22c125c13b..e19b86975d 100644 --- a/frontend/src/_services/authentication.service.js +++ b/frontend/src/_services/authentication.service.js @@ -262,7 +262,7 @@ function logout(avoidRedirection = false) { if (avoidRedirection) { window.location.href = loginPath; } else { - const pathname = getRedirectToWithParams(); + const pathname = getRedirectToWithParams(true); window.location.href = loginPath + `?redirectTo=${`${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}`}`; } }; From 50323bf3fbbdee2ca96213b7f9c7f2fcd28cbbd9 Mon Sep 17 00:00:00 2001 From: Muhsin Shah C P Date: Tue, 19 Dec 2023 10:27:32 +0530 Subject: [PATCH 03/18] [fix] Rendering all apps inside a folder after the app rename. (#8133) * fix: rendering all apps inside a folder after app rename * fix: Breadcrumbs path not contains query params --- frontend/src/HomePage/HomePage.jsx | 2 +- frontend/src/_ui/Breadcrumbs/index.jsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 430f40a6e6..2f91da6bc2 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -156,7 +156,7 @@ class HomePageComponent extends React.Component { _self.setState({ renamingApp: true }); try { await appsService.saveApp(appId, { name: newAppName }); - await this.fetchApps(); + await this.fetchApps(this.state.currentPage, this.state.currentFolder.id); toast.success('App name has been updated!'); _self.setState({ renamingApp: false }); return true; diff --git a/frontend/src/_ui/Breadcrumbs/index.jsx b/frontend/src/_ui/Breadcrumbs/index.jsx index f4293440e1..d2a1898298 100644 --- a/frontend/src/_ui/Breadcrumbs/index.jsx +++ b/frontend/src/_ui/Breadcrumbs/index.jsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import SolidIcon from '../Icon/SolidIcons'; import { BreadCrumbContext } from '../../App/App'; import useBreadcrumbs from 'use-react-router-breadcrumbs'; @@ -7,6 +7,8 @@ import useBreadcrumbs from 'use-react-router-breadcrumbs'; export const Breadcrumbs = ({ darkMode, dataCy }) => { const { sidebarNav } = useContext(BreadCrumbContext); const breadcrumbs = useBreadcrumbs(routes, { excludePaths: ['/'] }); + const location = useLocation(); + const search = location.search || ''; return (
    @@ -17,7 +19,7 @@ export const Breadcrumbs = ({ darkMode, dataCy }) => {

    {breadcrumb}

    {sidebarNav?.length > 0 && }
  1. - + {' '} {sidebarNav} From 27a0099e89f0beeac8f553da010066f5719c9ed7 Mon Sep 17 00:00:00 2001 From: Muhsin Shah C P Date: Tue, 19 Dec 2023 10:28:53 +0530 Subject: [PATCH 04/18] [fix] Sometimes the workspace modal CTA is in disabled state even if the values are correct (#8299) * fix: sometimes the workspace modal CTA is in disabled state even if the values are accepted * fix: erorr message --- .../CreateOrganization.jsx | 126 ++++++++------ .../OrganizationManager/EditOrganization.jsx | 157 +++++++++--------- 2 files changed, 158 insertions(+), 125 deletions(-) diff --git a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx index 52a463ac86..63f5dc22af 100644 --- a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx +++ b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx @@ -9,32 +9,40 @@ import _ from 'lodash'; export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { const [isCreating, setIsCreating] = useState(false); - const [fields, setFields] = useState({ name: { value: '', error: '' }, slug: { value: null, error: '' } }); + const [name, setName] = useState({ value: null, error: '' }); + const [slug, setSlug] = useState({ value: null, error: '' }); const [slugProgress, setSlugProgress] = useState(false); const [workspaceNameProgress, setWorkspaceNameProgress] = useState(false); - const [isDisabled, setDisabled] = useState(true); + const [isNameDisabled, setNameDisabled] = useState(true); + const [isSlugDisabled, setSlugDisabled] = useState(true); const darkMode = localStorage.getItem('darkMode') === 'true'; const { t } = useTranslation(); const createOrganization = () => { let emptyError = false; - const fieldsTemp = fields; - Object.keys(fields).map((key) => { - if (!fields?.[key]?.value?.trim()) { - fieldsTemp[key] = { - error: `Workspace ${key} can't be empty`, - }; + + [name, slug].map((field, index) => { + if (!field?.value?.trim()) { + index === 0 + ? setName({ + ...name, + error: { + error: `Workspace name can't be empty`, + }, + }) + : setSlug({ ...slug, error: `Workspace slug can't be empty` }); emptyError = true; } }); - setFields({ ...fields, ...fieldsTemp }); + const errorFound = !_.isEmpty(name.error) || !_.isEmpty(slug.error); - if (!emptyError && !Object.keys(fields).find((key) => !_.isEmpty(fields[key].error))) { + if (!emptyError && !errorFound) { + const slugValue = slug.value; setIsCreating(true); - organizationService.createOrganization({ name: fields['name'].value, slug: fields['slug'].value }).then( + organizationService.createOrganization({ name: name.value, slug: slugValue }).then( () => { setIsCreating(false); - const newPath = appendWorkspaceId(fields['slug'].value, location.pathname, true); + const newPath = appendWorkspaceId(slugValue, location.pathname, true); window.history.replaceState(null, null, newPath); window.location.reload(); }, @@ -47,13 +55,18 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { }; const handleInputChange = async (value, field) => { - setFields({ - ...fields, - [field]: { - ...fields[field], + if (field === 'slug') { + setSlug({ + ...slug, error: null, - }, - }); + }); + } + if (field === 'name') { + setName({ + ...name, + error: null, + }); + } let error = validateName( value, `Workspace ${field}`, @@ -79,20 +92,22 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { } } - setFields({ - ...fields, - [field]: { - value, - error: error?.errorMsg, - }, - }); + const disabled = !error?.status; + const updatedValue = { + value, + error: error?.errorMsg, + }; - const otherInputErrors = Object.keys(fields).find( - (key) => (key !== field && !_.isEmpty(fields[key].error)) || (key !== field && _.isEmpty(fields[key].value)) - ); - setDisabled(!error?.status || otherInputErrors); - field === 'slug' && setSlugProgress(false); - field === 'name' && setWorkspaceNameProgress(false); + if (field === 'slug') { + setSlug(updatedValue); + setSlugDisabled(disabled); + setSlugProgress(false); + } + if (field === 'name') { + setName(updatedValue); + setNameDisabled(disabled); + setWorkspaceNameProgress(false); + } return; }; @@ -104,16 +119,25 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { }; const closeModal = () => { - setFields({ name: { value: '', error: '' }, slug: { value: null, error: '' } }); + /* reseting states */ + setName({ value: null, error: '' }); + setSlug({ value: null, error: '' }); setShowCreateOrg(false); - setDisabled(true); + setNameDisabled(true); + setSlugDisabled(true); }; - const delayedFieldChange = _.debounce(async (value, field) => { - field === 'name' && setWorkspaceNameProgress(true); - field === 'slug' && setSlugProgress(true); - await handleInputChange(value, field); - }, 500); + const delayedSlugChange = _.debounce(async (value) => { + setSlugProgress(true); + await handleInputChange(value, 'slug'); + }, 300); + + const delayedNameChange = _.debounce(async (value) => { + setWorkspaceNameProgress(true); + await handleInputChange(value, 'name'); + }, 300); + + const isDisabled = isCreating || isNameDisabled || isSlugDisabled || slugProgress || workspaceNameProgress; return ( { type="text" onChange={async (e) => { e.persist(); - await delayedFieldChange(e.target.value, 'name'); + await delayedNameChange(e.target.value); }} - className={`form-control ${fields['name']?.error ? 'is-invalid' : 'is-valid'}`} + className={`form-control ${name?.error ? 'is-invalid' : 'is-valid'}`} placeholder={t('header.organization.workspaceName', 'Workspace name')} disabled={isCreating} onKeyDown={handleKeyDown} @@ -139,9 +163,9 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { data-cy="workspace-name-input-field" autoFocus /> - {fields['name']?.error ? ( + {name?.error ? ( ) : (