mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Feature/automatic columns generation (Task ID: CU-2vjwn7r) (#4149)
* Remove unnecessary setting of columnSizes in table inpector * Rough implementation of autogeneration of table columns * Use custom mapping for autogeneration of table columns * Use setTimeout to prevent race conditions while storing autogenerated columns * Autogenerate columns only on new tables * Do not generate columns that were deleted previously * Autogenerate columns only in edit mode * Re-render Inspector whenever app definition is updated * Refactor table column auto-generation * Remove unnecessary console.log * Add Number data type support for table column auto-generation * Remove unnecessary console.log * Ensure that column key is stored in deletion history if it is available
This commit is contained in:
parent
0e7a4e6b38
commit
c1adcc9e2c
7 changed files with 86 additions and 14 deletions
|
|
@ -309,6 +309,10 @@ export const Box = function Box({
|
|||
dataQueries={dataQueries}
|
||||
variablesExposedForPreview={variablesExposedForPreview}
|
||||
exposeToCodeHinter={exposeToCodeHinter}
|
||||
setProperty={(property, value) => {
|
||||
paramUpdated(id, property, { value });
|
||||
}}
|
||||
mode={mode}
|
||||
></ComponentToRender>
|
||||
) : (
|
||||
<div className="m-1" style={{ height: '76px', width: '76px', marginLeft: '18px' }}>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { reducer, reducerActions, initialState } from './reducer';
|
|||
import customFilter from './custom-filter';
|
||||
import generateColumnsData from './columns';
|
||||
import generateActionsData from './columns/actions';
|
||||
import autogenerateColumns from './columns/autogenerateColumns';
|
||||
import IndeterminateCheckbox from './IndeterminateCheckbox';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as XLSX from 'xlsx/xlsx.mjs';
|
||||
|
|
@ -52,6 +53,8 @@ export function Table({
|
|||
properties,
|
||||
variablesExposedForPreview,
|
||||
exposeToCodeHinter,
|
||||
setProperty,
|
||||
mode,
|
||||
exposedVariables,
|
||||
}) {
|
||||
const {
|
||||
|
|
@ -214,7 +217,6 @@ export function Table({
|
|||
if (currentState) {
|
||||
tableData = resolveReferences(component.definition.properties.data.value, currentState, []);
|
||||
if (!Array.isArray(tableData)) tableData = [];
|
||||
console.log('resolved param', tableData);
|
||||
}
|
||||
|
||||
tableData = tableData || [];
|
||||
|
|
@ -286,6 +288,17 @@ export function Table({
|
|||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length != 0 && component.definition.properties.autogenerateColumns.value && mode === 'edit') {
|
||||
autogenerateColumns(
|
||||
tableData,
|
||||
component.definition.properties.columns.value,
|
||||
component.definition.properties?.columnDeletionHistory?.value ?? [],
|
||||
setProperty
|
||||
);
|
||||
}
|
||||
}, [JSON.stringify(tableData)]);
|
||||
|
||||
const computedStyles = {
|
||||
// width: `${width}px`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import _ from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default function autogenerateColumns(tableData, existingColumns, columnDeletionHistory, setProperty) {
|
||||
const firstRow = tableData?.[0] ?? {};
|
||||
|
||||
const keysOfTableData = Object.keys(firstRow);
|
||||
|
||||
const keysOfExistingColumns = existingColumns.map((column) => column.key || column.name);
|
||||
|
||||
const keysFromWhichNewColumnsShouldBeGenerated = _.difference(keysOfTableData, [
|
||||
...keysOfExistingColumns,
|
||||
...columnDeletionHistory,
|
||||
]);
|
||||
|
||||
const keysAndDataTypesToGenerateNewColumns = keysFromWhichNewColumnsShouldBeGenerated?.map((key) => [
|
||||
key,
|
||||
typeof firstRow[key],
|
||||
]);
|
||||
|
||||
const keysOfExistingColumnsThatNeedToPersist = existingColumns
|
||||
.filter((column) => !column.autogenerated || keysOfTableData.includes(column.key || column.name))
|
||||
.map((column) => column.key || column.name);
|
||||
|
||||
const generatedColumns = keysAndDataTypesToGenerateNewColumns.map(([key, dataType]) => ({
|
||||
id: uuidv4(),
|
||||
name: key,
|
||||
key: key,
|
||||
columnType: convertDataTypeToColumnType(dataType),
|
||||
autogenerated: true,
|
||||
}));
|
||||
|
||||
const finalKeys = [...keysFromWhichNewColumnsShouldBeGenerated, ...keysOfExistingColumnsThatNeedToPersist];
|
||||
const finalColumns = [...existingColumns, ...generatedColumns].filter((column) =>
|
||||
finalKeys.includes(column.key || column.name)
|
||||
);
|
||||
|
||||
setTimeout(() => setProperty('columns', finalColumns), 10);
|
||||
}
|
||||
|
||||
const dataTypeToColumnTypeMapping = {
|
||||
string: 'string',
|
||||
number: 'number',
|
||||
};
|
||||
|
||||
const convertDataTypeToColumnType = (dataType) => {
|
||||
if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType];
|
||||
else return 'default';
|
||||
};
|
||||
|
|
@ -62,6 +62,7 @@ import { EditorContextWrapper } from './Context/EditorContextWrapper';
|
|||
// eslint-disable-next-line import/no-unresolved
|
||||
import Selecto from 'react-selecto';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
|
@ -551,7 +552,7 @@ class EditorComponent extends React.Component {
|
|||
},
|
||||
this.handleAddPatch
|
||||
);
|
||||
this.setState({ isSaving: true, appDefinition: newDefinition }, () => {
|
||||
this.setState({ isSaving: true, appDefinition: newDefinition, appDefinitionLocalVersion: uuid() }, () => {
|
||||
if (!opts.skipAutoSave) this.autoSave();
|
||||
});
|
||||
computeComponentState(this, newDefinition.components);
|
||||
|
|
@ -1666,6 +1667,7 @@ class EditorComponent extends React.Component {
|
|||
apps={apps}
|
||||
darkMode={this.props.darkMode}
|
||||
handleEditorEscapeKeyPress={this.handleEditorEscapeKeyPress}
|
||||
appDefinitionLocalVersion={this.state.appDefinitionLocalVersion}
|
||||
></Inspector>
|
||||
) : (
|
||||
<center className="mt-5 p-2">
|
||||
|
|
|
|||
|
|
@ -684,16 +684,7 @@ class TableComponent extends React.Component {
|
|||
onColumnItemChange = (index, item, value) => {
|
||||
const columns = this.props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
if (item === 'name') {
|
||||
const columnSizes = this.props.component.component.definition.properties.columnSizes;
|
||||
if (columnSizes) {
|
||||
const newColumnSizes = JSON.parse(JSON.stringify(columnSizes));
|
||||
if (newColumnSizes[column.name]) {
|
||||
newColumnSizes[value] = newColumnSizes[column.name];
|
||||
this.props.paramUpdated({ name: 'columnSizes' }, null, newColumnSizes, 'properties');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column[item] = value;
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
|
|
@ -703,8 +694,16 @@ class TableComponent extends React.Component {
|
|||
removeColumn = (index) => {
|
||||
const columns = this.props.component.component.definition.properties.columns;
|
||||
const newValue = columns.value;
|
||||
newValue.splice(index, 1);
|
||||
const removedColumns = newValue.splice(index, 1);
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties');
|
||||
|
||||
const existingcolumnDeletionHistory =
|
||||
this.props.component.component.definition.properties.columnDeletionHistory?.value ?? [];
|
||||
const newcolumnDeletionHistory = [
|
||||
...existingcolumnDeletionHistory,
|
||||
...removedColumns.map((column) => column.key || column.name),
|
||||
];
|
||||
this.props.paramUpdated({ name: 'columnDeletionHistory' }, 'value', newcolumnDeletionHistory, 'properties');
|
||||
};
|
||||
|
||||
getPopoverFieldSource = (column, field) =>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const Inspector = ({
|
|||
switchSidebarTab,
|
||||
removeComponent,
|
||||
handleEditorEscapeKeyPress,
|
||||
appDefinitionLocalVersion,
|
||||
}) => {
|
||||
const component = {
|
||||
id: selectedComponentId,
|
||||
|
|
@ -389,7 +390,7 @@ export const Inspector = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="inspector">
|
||||
<div className="inspector" key={appDefinitionLocalVersion}>
|
||||
<ConfirmDialog
|
||||
show={showWidgetDeleteConfirmation}
|
||||
message={'Widget will be deleted, do you want to continue?'}
|
||||
|
|
|
|||
|
|
@ -346,19 +346,23 @@ export const widgets = [
|
|||
displaySearchBox: { value: '{{true}}' },
|
||||
showDownloadButton: { value: '{{true}}' },
|
||||
showFilterButton: { value: '{{true}}' },
|
||||
autogenerateColumns: { value: true },
|
||||
columns: {
|
||||
value: [
|
||||
{
|
||||
name: 'id',
|
||||
id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737',
|
||||
autogenerated: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a',
|
||||
autogenerated: true,
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f',
|
||||
autogenerated: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue