From 8675b3186a6a55d9dbcbb352ddbd45bfbea67d64 Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Thu, 6 Jun 2024 17:19:46 +0530 Subject: [PATCH] Bugfixes: Table crash fixes for LTS / Zindex issue for datepicker (#9984) * fix : Table breaking for nested array of objects * fix : table crash on selecting time only datepicker sing add new row * remove logs * update zindex * Fix: Entity reference mapping and performance issues on codehinter (#10004) * Fix: Entity reference mapping and performance issues on codehinter (#430) * trial * fixes: - entity mapping on version switch - entity mapping on page switch - entity mapping on viewer on page switch * fixes: performance of codehinter for rendering nested json data * trial * fixes: - entity mapping on version switch - entity mapping on page switch - entity mapping on viewer on page switch * Revert "fixes:" This reverts commit 6ca9921731468aca4132c0aff3aeba02845bcd8a. * Revert "trial" This reverts commit 5bfa40c36aeb84abe98c0f2ab2418148772a048d. * fixes: editor crash on resolving workspace variables * fixes: populate JS hints * chore : version bump * version bump --------- Co-authored-by: Arpit --- .version | 2 +- frontend/.version | 2 +- frontend/src/Editor/CodeEditor/PreviewBox.jsx | 32 +++++++--- .../CodeEditor/SingleLineCodeEditor.jsx | 1 + frontend/src/Editor/CodeEditor/utils.js | 41 ++++++++---- .../Editor/Components/Table/Datepicker.jsx | 2 +- frontend/src/Editor/Components/Table/Link.jsx | 2 +- .../src/Editor/Components/Table/String.jsx | 11 ++-- frontend/src/Editor/Components/Table/Tags.jsx | 3 +- frontend/src/Editor/Components/Table/Text.jsx | 2 +- .../Editor/Components/Table/columns/index.jsx | 6 +- frontend/src/Editor/Container.jsx | 1 + frontend/src/Editor/Editor.jsx | 62 ++++++++++++++----- frontend/src/Editor/Viewer.jsx | 25 +++++++- frontend/src/_helpers/editorHelpers.js | 5 +- frontend/src/_stores/currentStateStore.js | 3 +- frontend/src/_stores/resolverStore.js | 2 +- server/.version | 2 +- 18 files changed, 142 insertions(+), 62 deletions(-) diff --git a/.version b/.version index 57c32dea2e..ed23763f45 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.50.3 +2.50.4 diff --git a/frontend/.version b/frontend/.version index 57c32dea2e..ed23763f45 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -2.50.3 +2.50.4 diff --git a/frontend/src/Editor/CodeEditor/PreviewBox.jsx b/frontend/src/Editor/CodeEditor/PreviewBox.jsx index 61ac9dfc27..2a5d0884c5 100644 --- a/frontend/src/Editor/CodeEditor/PreviewBox.jsx +++ b/frontend/src/Editor/CodeEditor/PreviewBox.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { computeCoercion, getCurrentNodeType, resolveReferences } from './utils'; +import { computeCoercion, getCurrentNodeType, hasDeepChildren, resolveReferences } from './utils'; import CodeHinter from '.'; import { copyToClipboard } from '@/_helpers/appUtils'; import { Alert } from '@/_ui/Alert/Alert'; @@ -17,6 +17,7 @@ export const PreviewBox = ({ setErrorStateActive, setErrorMessage, customVariables, + isWorkspaceVariable, }) => { const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); @@ -68,7 +69,7 @@ export const PreviewBox = ({ useEffect(() => { const [valid, _error, newValue, resolvedValue] = resolveReferences(currentValue, validationSchema, customVariables); - if (!validationSchema || isEmpty(validationSchema)) { + if (isWorkspaceVariable || !validationSchema || isEmpty(validationSchema)) { return setResolvedValue(newValue); } @@ -115,6 +116,7 @@ export const PreviewBox = ({ resolvedValue={content} coersionData={coersionData} withValidation={!isEmpty(validationSchema)} + isWorkspaceVariable={isWorkspaceVariable} /> copyToClipboard(error ? error?.value : content)} @@ -125,7 +127,14 @@ export const PreviewBox = ({ ); }; -const RenderResolvedValue = ({ error, previewType, resolvedValue, coersionData, withValidation }) => { +const RenderResolvedValue = ({ + error, + previewType, + resolvedValue, + coersionData, + withValidation, + isWorkspaceVariable, +}) => { const computeCoersionPreview = (resolvedValue, coersionData) => { if (coersionData?.typeBeforeCoercion === coersionData?.typeAfterCoercion) return resolvedValue; @@ -140,12 +149,13 @@ const RenderResolvedValue = ({ error, previewType, resolvedValue, coersionData, return resolvedValue + coersionData?.coercionPreview; }; - const previewValueType = - withValidation || (coersionData && coersionData?.typeBeforeCoercion) - ? `${coersionData?.typeBeforeCoercion} ${ - coersionData?.coercionPreview ? ` → ${coersionData?.typeAfterCoercion}` : '' - }` - : previewType; + const previewValueType = isWorkspaceVariable + ? previewType + : withValidation || (coersionData && coersionData?.typeBeforeCoercion) + ? `${coersionData?.typeBeforeCoercion} ${ + coersionData?.coercionPreview ? ` → ${coersionData?.typeAfterCoercion}` : '' + }` + : previewType; const previewContent = !withValidation ? resolvedValue : computeCoersionPreview(resolvedValue, coersionData); @@ -341,6 +351,8 @@ const PreviewCodeBlock = ({ code, isExpectValue = false }) => { if (showJSONTree) { const darkMode = localStorage.getItem('darkMode') === 'true'; + const hasDeepChild = hasDeepChildren(prettyPrintedJson); + return (
{ enableClipboard={false} rootName={false} theme={darkMode ? 'dark' : 'light'} - groupArraysAfterLength={500} + groupArraysAfterLength={hasDeepChild ? 10 : 100} />
); diff --git a/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx index fad2684660..80f5044c5a 100644 --- a/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx @@ -193,6 +193,7 @@ const EditorInput = ({ class: 'cm-completionInfo-top cm-custom-completion-info', }; }, + maxRenderedOptions: 10, }); const customKeyMaps = [...defaultKeymap, ...completionKeymap]; diff --git a/frontend/src/Editor/CodeEditor/utils.js b/frontend/src/Editor/CodeEditor/utils.js index 24a9bf5b5e..eb4b57c7ca 100644 --- a/frontend/src/Editor/CodeEditor/utils.js +++ b/frontend/src/Editor/CodeEditor/utils.js @@ -101,26 +101,25 @@ const resolveWorkspaceVariables = (query) => { let resolvedStr = query; let error = null; let valid = false; + // Resolve %%object%% const serverRegex = /(%%.+?%%)/g; - const serverMatch = resolvedStr.match(serverRegex)?.[0]; + const serverMatches = resolvedStr.match(serverRegex); - if (serverMatch) { - const code = serverMatch.replace(/%%/g, ''); + if (serverMatches) { + serverMatches.forEach((serverMatch) => { + const code = serverMatch.replace(/%%/g, ''); - if (code.includes('server.')) { - resolvedStr = resolvedStr.replace(serverMatch, 'HiddenEnvironmentVariable'); - error = 'Server variables cannot be resolved in the client.'; - } else { - const [resolvedCode, err] = resolveCode(code); - - if (!resolvedCode) { - error = err ? err : `Cannot resolve ${query}`; + if (code.includes('server.') && !/^server\.[A-Za-z0-9]+$/.test(code)) { + resolvedStr = resolvedStr.replace(serverMatch, 'HiddenEnvironmentVariable'); } else { + const resolvedCode = resolveCode(code); + resolvedStr = resolvedStr.replace(serverMatch, resolvedCode); - valid = true; } - } + }); + + valid = true; } return [valid, error, resolvedStr]; @@ -407,3 +406,19 @@ export const validateComponentProperty = (resolvedValue, validation) => { return validate(resolvedValue, schema, defaultValue, true); }; + +export function hasDeepChildren(obj, currentDepth = 1, maxDepth = 3) { + if (currentDepth > maxDepth) { + return true; + } + + for (const key in obj) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + if (hasDeepChildren(obj[key], currentDepth + 1, maxDepth)) { + return true; + } + } + } + + return false; +} diff --git a/frontend/src/Editor/Components/Table/Datepicker.jsx b/frontend/src/Editor/Components/Table/Datepicker.jsx index 040fdb8df5..b3b5cc9cc8 100644 --- a/frontend/src/Editor/Components/Table/Datepicker.jsx +++ b/frontend/src/Editor/Components/Table/Datepicker.jsx @@ -206,7 +206,7 @@ export const Datepicker = function Datepicker({ selected={date} onChange={(date) => handleDateChange(date)} value={computeDateString(date)} - dateFormat={dateDisplayFormat} + dateFormat={!isDateSelectionEnabled && isTimeChecked ? 'HH:mm' : dateDisplayFormat} customInput={ } diff --git a/frontend/src/Editor/Components/Table/Link.jsx b/frontend/src/Editor/Components/Table/Link.jsx index 0c05ac0800..fac7129917 100644 --- a/frontend/src/Editor/Components/Table/Link.jsx +++ b/frontend/src/Editor/Components/Table/Link.jsx @@ -19,7 +19,7 @@ export const Link = ({ cellValue, linkTarget, underline, underlineColor, linkCol }} rel="noreferrer" > - {displayText ? displayText : cellValue} + {displayText ? displayText : String(cellValue)} ); diff --git a/frontend/src/Editor/Components/Table/String.jsx b/frontend/src/Editor/Components/Table/String.jsx index 95226ef938..95d24bbd1c 100644 --- a/frontend/src/Editor/Components/Table/String.jsx +++ b/frontend/src/Editor/Components/Table/String.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { validateWidget, determineJustifyContentValue } from '@/_helpers/utils'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; -const String = ({ +const StringColumn = ({ isEditable, darkMode, handleCellValueChange, @@ -93,7 +93,7 @@ const String = ({ e.stopPropagation(); }} > - {cellValue} + {String(cellValue)} ); @@ -110,7 +110,7 @@ const String = ({ width: `${containerWidth}px`, }} > - {cellValue} + {String(cellValue)} ); @@ -120,7 +120,6 @@ const String = ({ ref?.current && (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth || ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight); - return ( <> - {cellValue} + {String(cellValue)} ) : ( @@ -177,4 +176,4 @@ const String = ({ ); }; -export default String; +export default StringColumn; diff --git a/frontend/src/Editor/Components/Table/Tags.jsx b/frontend/src/Editor/Components/Table/Tags.jsx index dc13e4c9d9..1050570826 100644 --- a/frontend/src/Editor/Components/Table/Tags.jsx +++ b/frontend/src/Editor/Components/Table/Tags.jsx @@ -4,7 +4,6 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; export const Tags = ({ value, onChange, readOnly, containerWidth = '' }) => { const [showOverlay, setShowOverlay] = useState(false); const [hovered, setHovered] = useState(false); - const elem = document.querySelector('.table-tags-col-container'); useEffect(() => { @@ -45,7 +44,7 @@ export const Tags = ({ value, onChange, readOnly, containerWidth = '' }) => { function renderTag(text) { return ( - {text} + {String(text)} {!readOnly && ( removeTag(text)}> x diff --git a/frontend/src/Editor/Components/Table/Text.jsx b/frontend/src/Editor/Components/Table/Text.jsx index 8ead00ab0a..8790772b50 100644 --- a/frontend/src/Editor/Components/Table/Text.jsx +++ b/frontend/src/Editor/Components/Table/Text.jsx @@ -115,7 +115,7 @@ const Text = ({ }} ref={nonEditableCellValueRef} > - {cellValue} + {String(cellValue)} ); diff --git a/frontend/src/Editor/Components/Table/columns/index.jsx b/frontend/src/Editor/Components/Table/columns/index.jsx index 172f23604e..40526d3b97 100644 --- a/frontend/src/Editor/Components/Table/columns/index.jsx +++ b/frontend/src/Editor/Components/Table/columns/index.jsx @@ -13,7 +13,7 @@ import { Boolean } from '../Boolean'; import { CustomSelect } from '../CustomSelect'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import Text from '../Text'; -import String from '../String'; +import StringColumn from '../String'; export default function generateColumnsData({ columnProperties, @@ -152,7 +152,7 @@ export default function generateColumnsData({ case 'default': { const cellTextColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData }); return ( - - {cellValue} + {String(cellValue)} ); } diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index ff979ca18c..d45f06b37f 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -1070,6 +1070,7 @@ const WidgetWrapper = ({ widgetid={id} style={{ transform: `translate(332px, -134px)`, + zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null, ...styles, }} > diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index ec46785fce..8b858f50fc 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -359,6 +359,19 @@ const EditorComponent = (props) => { () => { const components = appDefinition?.pages?.[currentPageId]?.components || {}; computeComponentState(components); + + const isPageSwitched = useResolveStore.getState().isPageSwitched; + + if (isPageSwitched) { + const currentStateObj = useCurrentStateStore.getState(); + + useResolveStore.getState().actions.addAppSuggestions({ + queries: currentStateObj.queries, + components: currentStateObj.components, + page: currentStateObj.page, + }); + useResolveStore.getState().actions.pageSwitched(false); + } }, // eslint-disable-next-line react-hooks/exhaustive-deps [currentPageId] @@ -744,7 +757,7 @@ const EditorComponent = (props) => { user_id: userId, events, } = appData; - useResolveStore.getState().actions.updateJSHints(); + const startingPageHandle = props.params.pageHandle; setWindowTitle({ page: pageTitles.EDITOR, appName }); useAppVersionStore.getState().actions.updateEditingVersion(editing_version); @@ -765,7 +778,7 @@ const EditorComponent = (props) => { await processNewAppDefinition(appData, startingPageHandle, false, ({ homePageId }) => { handleLowPriorityWork(async () => { - useResolveStore.getState().actions.updateLastUpdatedRefs(['constants']); + useResolveStore.getState().actions.updateLastUpdatedRefs(['constants', 'client']); await useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId); await fetchDataSources(editing_version?.id); commonLowPriorityActions(events, { homePageId }); @@ -781,6 +794,7 @@ const EditorComponent = (props) => { }; const processNewAppDefinition = async (data, startingPageHandle, versionSwitched = false, onComplete) => { + useResolveStore.getState().actions.updateJSHints(); const appDefData = buildAppDefinition(data); const appJson = appDefData; @@ -1440,6 +1454,7 @@ const EditorComponent = (props) => { useCurrentStateStore.getState().actions.setEditorReady(true); const currentComponents = appJson?.pages?.[pageId]?.components; + const currentDataQueries = useDataQueriesStore.getState().dataQueries; const referenceManager = useResolveStore.getState().referenceMapper; @@ -1453,8 +1468,17 @@ const EditorComponent = (props) => { }; } }); + const newDataQueries = currentDataQueries.map((dq) => { + if (!referenceManager.get(dq.id)) { + return { + id: dq.id, + name: dq.name, + }; + } + }); - useResolveStore.getState().actions.addEntitiesToMap(newComponents); + useResolveStore.getState().actions.addEntitiesToMap([...newComponents, ...newDataQueries]); + // useResolveStore.getState().actions.addEntitiesToMap(newDataQueries); }; const updateEntityReferences = (appJson, pageId) => { @@ -1675,11 +1699,11 @@ const EditorComponent = (props) => { switchPage: true, pageId: newPageId, }); - props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${newHandle}`, { - state: { - isSwitchingPage: true, - }, - }); + // props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${newHandle}`, { + // state: { + // isSwitchingPage: true, + // }, + // }); const page = { id: newPageId, @@ -1714,14 +1738,15 @@ const EditorComponent = (props) => { return; } - clearAllQueuedTasks(); + await clearAllQueuedTasks(); + useResolveStore.getState().actions.resetStore(); useEditorStore.getState().actions.setPageProgress(true); useCurrentStateStore.getState().actions.setEditorReady(false); - useResolveStore.getState().actions.resetStore(); // This are fetched from store to handle runQueriesOnAppLoad const currentPageId = useEditorStore.getState().currentPageId; const appDefinition = useEditorStore.getState().appDefinition; - const pageHandle = getCurrentState().pageHandle; + + const pageHandle = useCurrentStateStore.getState().page?.handle; if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) { return; @@ -1730,6 +1755,7 @@ const EditorComponent = (props) => { if (!name || !handle) return; const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition)); + navigateToPage(queryParams, handle); const page = { @@ -1750,9 +1776,6 @@ const EditorComponent = (props) => { await onEditorLoad(appDefinition, pageId, true); updateEntityReferences(appDefinition, pageId); - handleLowPriorityWork(() => { - useResolveStore.getState().actions.updateJSHints(); - }); setCurrentPageId(pageId); @@ -1761,9 +1784,14 @@ const EditorComponent = (props) => { .events.filter((event) => event.target === 'page' && event.sourceId === page.id); handleEvent('onPageLoad', currentPageEvents); - handleLowPriorityWork(() => { - useEditorStore.getState().actions.setPageProgress(false); - }, 100); + handleLowPriorityWork( + () => { + useEditorStore.getState().actions.setPageProgress(false); + useResolveStore.getState().actions.updateJSHints(); + }, + null, + true + ); }; const deletePageRequest = (pageId, isHomePage = false, pageName = '') => { diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index fdba792bd0..041fc16532 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -323,12 +323,14 @@ class ViewerComponent extends React.Component { queryState[query.name] = { ...exposedVariables, ...this.props.currentState.queries[query.name], + id: query.id, }; } else { const dataSourceTypeDetail = DataSourceTypes.find((source) => source.kind === query.kind); queryState[query.name] = { ...dataSourceTypeDetail.exposedVariables, ...this.props.currentState.queries[query.name], + id: query.id, }; } }); @@ -736,7 +738,7 @@ class ViewerComponent extends React.Component { if (currentPageComponents && !_.isEmpty(currentPageComponents)) { const referenceManager = useResolveStore.getState().referenceMapper; - + const currentDataQueries = useDataQueriesStore.getState().dataQueries; const newComponents = Object.keys(currentPageComponents).map((componentId) => { const component = currentPageComponents[componentId]; @@ -747,9 +749,18 @@ class ViewerComponent extends React.Component { }; } }); + const newDataQueries = currentDataQueries.map((dq) => { + if (!referenceManager.get(dq.id)) { + return { + id: dq.id, + name: dq.name, + }; + } + }); try { useResolveStore.getState().actions.addEntitiesToMap(newComponents); + useResolveStore.getState().actions.addEntitiesToMap(newDataQueries); } catch (error) { console.error(error); } @@ -1043,6 +1054,18 @@ const withStore = (Component) => (props) => { flushComponentsToRender(updatedComponentIds); } + React.useEffect(() => { + const currentComponentsDef = appDefinition?.pages?.[currentPageId]?.components || {}; + const currentComponents = Object.keys(currentComponentsDef); + + setTimeout(() => { + if (currentComponents.length > 0) { + batchUpdateComponents(currentComponents); + } + }, 400); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentPageId]); + React.useEffect(() => { if (lastUpdatedRef.length > 0) { const currentComponents = appDefinition?.pages?.[currentPageId]?.components || {}; diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 342f26b1fc..71988fcc74 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -146,7 +146,10 @@ function findReferenceInComponent(node, changedCurrentState) { if (typeof node === 'object') { for (let key in node) { const value = node[key]; - if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) { + if ( + typeof value === 'string' && + ((value.includes('{{') && value.includes('}}')) || value.includes('%%client')) + ) { // Check if the referenced entity is in the state if (changedCurrentState.some((state) => value.includes(state))) { return true; diff --git a/frontend/src/_stores/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index e2e16b123d..3f4d746312 100644 --- a/frontend/src/_stores/currentStateStore.js +++ b/frontend/src/_stores/currentStateStore.js @@ -105,7 +105,7 @@ useCurrentStateStore.subscribe((state) => { () => { useResolveStore.getState().actions.updateAppSuggestions({ queries: state.queries, - components: state.components, + components: !isPageSwitched ? state.components : {}, globals: state.globals, page: state.page, variables: state.variables, @@ -113,7 +113,6 @@ useCurrentStateStore.subscribe((state) => { server: state.server, constants: state.constants, }); - useResolveStore.getState().actions.pageSwitched(false); }, null, isPageSwitched diff --git a/frontend/src/_stores/resolverStore.js b/frontend/src/_stores/resolverStore.js index 14f84c615e..5deb238ac9 100644 --- a/frontend/src/_stores/resolverStore.js +++ b/frontend/src/_stores/resolverStore.js @@ -71,7 +71,7 @@ export const useResolveStore = create( set(() => ({ ...state, storeReady: true })); }, resetStore: () => { - set(() => initialState); + set(() => ({ ...initialState, referenceMapper: new ReferencesBiMap() })); }, pageSwitched: (bool) => set(() => ({ isPageSwitched: bool })), updateAppSuggestions: (refState) => { diff --git a/server/.version b/server/.version index 57c32dea2e..ed23763f45 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -2.50.3 +2.50.4