ToolJet/frontend/src/TooljetDatabase/Menu/CellEditMenu/CellHinterWrapper.jsx
Manish Kushare e1d7fbb2bb Feature: Add JSON datatype to ToolJet Database (#2492)
* feat: jsonb datatype support in tooljet database inprogress

* feat: jsonb support added for tooljet database column operations as well as create and edit table

* feat: added support to query JSONB values in JOIN operation

* added dto validation for join operation

* Added json data type in tjdb

* single line editor bug fixed

* Basic UI implementation for create table drawer for jsonb column type

* removed the console

* Added the sanitization for json default value in the dto

* Added jsonb svg for jsonb column type

* Updated UI for created column form

* Updated UI for edit column form

* Change the UI for create row

* Updated UI for edit row form

* Show dummy {...} value on table cell

* Setting up the codehinter in tjdb dashboard

* Created codehinter wrapper for tjdb table celljsonb data type

* Codehineter for tjdb cell

* Made changes in tjdb column and row drawers

* removed unwanted code

* Added maximum height for codehinter wrapper in each drawers

Avoided keydown event in create column drawer

* Set max height to codehinter for tjdb table cell

* Added jsonb path option for list row operation [rebase]

* Added filtering out column with json datatype in udpate rows operation

* Added filter option with jsonpath in delete row operation

* Made changes for join tables

* added json path in jon filter condition

* fix: parsing the jsonb default values in view table api

* Made on change and initial value value changes for all tjdb dashboard changes

* updated intial value and component name for codehinter in all drawers for tooljetdb

* Table cell edit codehinter initial value

* Updated codehinter onchange and initial value

* Added json path field for join sort section

* TJDB query manager updates for jsonb column type

* Tjdb dashboard bug fixes

* fix: joins jsonbpath expression can be sent without single quotes

* Added error validation for JSON in codehinter

* Removed console

* Added codehinter wrapper for tjdb cell

* Made default functional

* Made set to null functionality working

* Toggle functionality for default value

* Toggle null functionality

* Clean code

* create row form added handle disable input click

* cutom-footer css and add new data css

* Fixed tooltip

* Updated tooltip for join codehinters in query manager

* fix: jsonb column values validation in server side

* Made the initial value empty string if value is undefined

* active tab in edit row form bug

* Error state in tjdb hinter inside cell

* code mirror breaks, on the initial render

* Added placeholder and adjusted icon size in dropdown

* Fixed: Opening the cell with keyboard nav shows ‘enter to save’.

* bug fixed

* json icon alignment in the dropdown fixed

* In create and edit table, codehinter text is vertcially centered aligned

* Create row and update row info message for jsonb column type fixed

* SHowing popover when clicked on the jsonb cell

* Showing unique constraint in disable state for jsonb

* bugfix: bulkupload in tjdb for jsonb datatype should accept different json format

* Bug fixed for cellhinter and row form

* Avoided flickering in column form

* removed console

* zindex issue for codehinter in react portal for table cell

* Single line editor file changes removed

* row form bug fixed

* single quotes string escaped as it needs to be inserted as default value in JSONB column

* Bug fixed

* Edit and create row active tab bug fixed

* If value is empty, then dont show error in the codehinter

* fix: error handling for invalid jsonpath in join expression

* Handled null and Null edge cases for edit and create row

* Removed console

* Bug fix for save chages button in edit and create row drawer

* Error toast message for invalid syntax

* removed debounce time

* Copied all query manager and codehinter changes from editor to appbuilder directory

* Updated imports of codehinter in tjdb forms inside dashboard

* Discarded all changes made to editor directory

* Removed console

* Fixed flickering effect in the edit and create row drawer in TJDB dashboard

* Added focused state to codehinter in tjdb drawer, updated font size for the same

* Updated error message

* bug fixed : Pop up icon not visible inside codehinter

* Fixed : border issue in tjdb codehinter

* bugfix: array is not allowed to insert in JSONB column (#2734)

* Error message in tjdb drawers

* Error state for table schema

* Error message for cell hinter wrapper

* fixed : space between {} and table name in popup

* Spacing issue in create and update column UI in query manager fixed

* Show tooltip in fk column dropdown

* Bug fixed: edit drawer on first render cutom value is always empty

* Bug fixed

* Reverting react portal changes

* bug fixed for error copyright

* z index issue fixed for tableschema

* reverting back the changes for table form from last commit

* Bug fix: Flickering issue in table schema for codehinter input

* Bug fixed : toggle bwtween null and default value

* Handled spacing between the component name in react portal

* added new schema definition as we have introduced jsonb datatype default values can have Object and Array

* fix: removed the support for error code mapping for undefined function error  because database has different error text for it based on the scenario like jsonb aggregates etc

---------

Co-authored-by: Ganesh Kumar <ganesh8056234@gmail.com>
Co-authored-by: Ganesh Kumar <40178541+ganesh8056@users.noreply.github.com>
Co-authored-by: Akshay <akshaysasidharan93@gmail.com>
2024-12-04 13:49:24 +05:30

265 lines
8.4 KiB
JavaScript

import React, { useRef, useState } from 'react';
import CodeHinter from '@/AppBuilder/CodeEditor';
import { resolveReferences } from '@/AppBuilder/CodeEditor/utils';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import cx from 'classnames';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import { Popover } from 'react-bootstrap';
import _ from 'lodash';
const transformvalue = (value = '') => {
if (typeof value !== 'string') {
return JSON.stringify(value);
}
return value;
};
export const CellHinterWrapper = ({
isNotNull,
defaultValue,
selectedValue,
setSelectedValue,
saveFunction,
isEditCell,
columnDetails,
close,
closePopover,
show,
previousCellValue,
}) => {
const initialValueRef = useRef(selectedValue);
const defaultValueRef = useRef(defaultValue);
const [shouldUpdateToDefaultVal, setShouldUpdateDefaultVal] = useState(false);
const [shouldUpdateToNullVal, setShouldUpdateNullVal] = useState(false);
const [disabledSaveButton, setDisabledSaveButton] = useState(false);
const handleInputError = (bool = false) => {
setDisabledSaveButton(bool);
};
const handleKeyDown = (e) => {
e.stopPropagation();
if (e.key === 'Escape') {
const event = new Event('click', { bubbles: true, cancelable: true });
document.body.dispatchEvent(event);
}
};
const darkMode = localStorage.getItem('darkMode') === 'true';
const tranformedValue = (rawValue) => {
if (!rawValue) {
return JSON.stringify(rawValue);
}
const [_, __, resolvedValue] = resolveReferences(`{{${rawValue}}}`);
return resolvedValue;
};
const SaveChangesSection = () => {
const handleNullToggle = (value) => {
setShouldUpdateNullVal(false);
if (value) {
setSelectedValue(null);
shouldUpdateToDefaultVal && setShouldUpdateDefaultVal(false);
} else {
setSelectedValue(tranformedValue(previousCellValue));
}
setShouldUpdateNullVal(value);
};
const handleDefaultToggle = (value) => {
setShouldUpdateDefaultVal(false);
if (value) {
setSelectedValue(defaultValue);
shouldUpdateToNullVal && setShouldUpdateNullVal(false);
defaultValueRef.current = defaultValue;
} else {
const val = tranformedValue(previousCellValue);
defaultValueRef.current = val;
setSelectedValue(val);
}
setShouldUpdateDefaultVal(true);
};
return (
<div className="d-flex justify-content-between align-items-center">
<div className="d-flex flex-column align-items-start gap-1">
{
<div className="d-flex align-items-center gap-1">
<div className={`fw-500 tjdbCellMenuShortcutsInfo`} id="enterbutton">
<SolidIcon name="enterbutton" />
</div>
<div className={`fw-400 tjdbCellMenuShortcutsText`}>Press Enter to go on next line</div>
</div>
}
<div className="d-flex align-items-center gap-1">
<div className={`fw-500 tjdbCellMenuShortcutsInfo`} id="escbutton">
Esc
</div>
<div className={`fw-400 tjdbCellMenuShortcutsText`}>Discard Changes</div>
</div>
</div>
<div className="d-flex flex-column align-items-end gap-1">
{isNotNull === false && (
<div className="d-flex align-items-center gap-2">
<div className="d-flex flex-column">
<span style={{ width: 'auto' }} className="fw-400 fs-12">
Set to null
</span>
</div>
<div>
<label className={`form-switch`}>
<input
className="form-check-input"
type="checkbox"
checked={selectedValue === null}
onChange={(e) => handleNullToggle(e.target.checked)}
/>
</label>
</div>
</div>
)}
{defaultValue !== null && (
<div className="d-flex align-items-center gap-2">
<div className="d-flex flex-column">
<span style={{ width: 'auto' }} className="fw-400 fs-12">
Set to default
</span>
</div>
<div>
<label className={`form-switch`}>
<input
className="form-check-input"
type="checkbox"
checked={_.isEqual(selectedValue, defaultValue)}
onChange={(e) => handleDefaultToggle(e.target.checked)}
/>
</label>
</div>
</div>
)}
</div>
</div>
);
};
const handleCancel = () => {
const event = new Event('click', { bubbles: true, cancelable: true });
document.body.dispatchEvent(event);
setShouldUpdateDefaultVal(false);
setShouldUpdateNullVal(false);
};
const handleSave = (e) => {
if (e) {
e.stopPropagation();
}
saveFunction(selectedValue);
const event = new Event('click', { bubbles: true, cancelable: true });
document.body.dispatchEvent(event);
setShouldUpdateDefaultVal(false);
setShouldUpdateNullVal(false);
defaultValueRef.current = defaultValue;
};
const SaveChangesFooter = () => {
return (
<div className={cx('d-flex align-items-center', 'justify-content-end')}>
<div className="d-flex" style={{ gap: '8px' }}>
<ButtonSolid onClick={handleCancel} variant="tertiary" size="sm" className="fs-12 p-2">
Cancel
</ButtonSolid>
<ButtonSolid
onClick={(e) => handleSave(e)}
disabled={disabledSaveButton}
variant="primary"
size="sm"
className="fs-12 p-2"
>
Save
</ButtonSolid>
</div>
</div>
);
};
const popover = (
<Popover className={`${darkMode && 'dark-theme'} tjdb-table-cell-edit-popover jsonb-popover`}>
{disabledSaveButton && (
<Popover.Header className="tjdb-cell-hinter-invalid-syntax-header">
<div className="d-flex align-items-center">
<span className="d-flex mx-2">
{' '}
<SolidIcon name="warning" width="16px" fill={'var(--tomato9)'} />
</span>
<span>Invalid JSON syntax</span>
</div>
</Popover.Header>
)}
<Popover.Body className={`${darkMode && 'dark-theme'}`} onClick={(e) => e.stopPropagation()}>
<div className={`d-flex flex-column gap-3`}>
<SaveChangesSection />
<SaveChangesFooter />
</div>
</Popover.Body>
</Popover>
);
const customFooter = () => {
return (
<div
className={`tjdb-dashboard-codehinter custom-footer ${darkMode && 'dark-theme'} `}
tabIndex="0"
onClick={(e) => e.stopPropagation()}
>
{disabledSaveButton && (
<div className="tjdb-cell-hinter-invalid-syntax-header">
<div className="d-flex align-items-center">
<span className="d-flex mx-2">
{' '}
<SolidIcon name="warning" width="16px" fill={'var(--tomato9)'} />
</span>
<span>Invalid JSON syntax</span>
</div>
</div>
)}
<div className="main-body d-flex flex-column gap-3">
<SaveChangesSection />
<SaveChangesFooter />
</div>
</div>
);
};
return (
<OverlayTrigger trigger="click" placement="bottom-start" rootclose overlay={popover}>
<div className="tjdb-dashboard-codehinter-wrapper-cell" onKeyDown={handleKeyDown}>
<CodeHinter
type="tjdbHinter"
inEditor={false}
initialValue={initialValueRef.current ? transformvalue(initialValueRef.current) : ''}
lang="javascript"
onChange={(value) => {
const [_, __, resolvedValue] = resolveReferences(`{{${value}}}`);
setSelectedValue(resolvedValue);
}}
enablePreview={false}
footerComponent={customFooter}
componentName={`{} ${columnDetails.Header}`}
errorCallback={handleInputError}
defaultValue={defaultValueRef.current}
reset={shouldUpdateToDefaultVal}
shouldUpdateToNullVal={shouldUpdateToNullVal}
columnName={columnDetails?.Header}
/>
</div>
</OverlayTrigger>
);
};