run macros in table data editor #68

This commit is contained in:
Jan Prochazka
2021-03-25 19:07:51 +01:00
parent 2afa7a5f58
commit 952bdc4baa
9 changed files with 184 additions and 15 deletions

View File

@@ -19,4 +19,6 @@ export interface MacroDefinition {
export interface MacroSelectedCell { export interface MacroSelectedCell {
column: string; column: string;
row: number; row: number;
rowData: any;
value: any;
} }

View File

@@ -4,6 +4,8 @@ import uuidv1 from 'uuid/v1';
import uuidv4 from 'uuid/v4'; import uuidv4 from 'uuid/v4';
import moment from 'moment'; import moment from 'moment';
import { MacroDefinition, MacroSelectedCell } from './MacroDefinition'; import { MacroDefinition, MacroSelectedCell } from './MacroDefinition';
import { ChangeSet, setChangeSetValue } from './ChangeSet';
import { GridDisplay } from './GridDisplay';
const getMacroFunction = { const getMacroFunction = {
transformValue: code => ` transformValue: code => `
@@ -183,3 +185,55 @@ export function runMacro(
} }
return data; return data;
} }
export function compileMacroFunction(macro: MacroDefinition, errors = []) {
if (!macro) return null;
let func;
try {
func = eval(getMacroFunction[macro.type](macro.code));
return func;
} catch (err) {
errors.push(`Error compiling macro ${macro.name}: ${err.message}`);
return null;
}
}
export function runMacroOnValue(compiledFunc, macroArgs, value, rowIndex, row, column, errors = []) {
if (!compiledFunc) return value;
try {
const res = compiledFunc(value, macroArgs, modules, rowIndex, row, column);
return res;
} catch (err) {
errors.push(`Error processing column ${column} on row ${rowIndex}: ${err.message}`);
return value;
}
}
export function runMacroOnChangeSet(
macro: MacroDefinition,
macroArgs: {},
selectedCells: MacroSelectedCell[],
changeSet: ChangeSet,
display: GridDisplay
): ChangeSet {
const errors = [];
const compiledMacroFunc = compileMacroFunction(macro, errors);
if (!compiledMacroFunc) return null;
let res = changeSet;
for (const cell of selectedCells) {
const definition = display.getChangeSetField(cell.rowData, cell.column, undefined);
const macroResult = runMacroOnValue(
compiledMacroFunc,
macroArgs,
cell.value,
cell.row,
cell.rowData,
cell.column,
errors
);
res = setChangeSetValue(res, definition, macroResult);
}
return res;
}

View File

@@ -3,7 +3,6 @@ import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores'
import registerCommand from './registerCommand'; import registerCommand from './registerCommand';
currentDatabase.subscribe(value => { currentDatabase.subscribe(value => {
console.log('DB', value);
if (!value) return; if (!value) return;
recentDatabases.update(list => { recentDatabases.update(list => {
const res = [ const res = [

View File

@@ -7,8 +7,12 @@ import {
findExistingChangeSetItem, findExistingChangeSetItem,
getChangeSetInsertedRows, getChangeSetInsertedRows,
GridDisplay, GridDisplay,
MacroDefinition,
MacroSelectedCell,
revertChangeSetRowChanges, revertChangeSetRowChanges,
setChangeSetValue, setChangeSetValue,
compileMacroFunction,
runMacroOnValue,
} from 'dbgate-datalib'; } from 'dbgate-datalib';
import Grider, { GriderRowStatus } from './Grider'; import Grider, { GriderRowStatus } from './Grider';
@@ -21,8 +25,18 @@ export default class ChangeSetGrider extends Grider {
private rowStatusCache; private rowStatusCache;
private rowDefinitionsCache; private rowDefinitionsCache;
private batchChangeSet: ChangeSet; private batchChangeSet: ChangeSet;
private _errors = null;
private compiledMacroFunc;
constructor(public sourceRows: any[], public changeSetState, public dispatchChangeSet, public display: GridDisplay) { constructor(
public sourceRows: any[],
public changeSetState,
public dispatchChangeSet,
public display: GridDisplay,
public macro: MacroDefinition,
public macroArgs: {},
public selectedCells: MacroSelectedCell[]
) {
super(); super();
this.changeSet = changeSetState && changeSetState.value; this.changeSet = changeSetState && changeSetState.value;
this.insertedRows = getChangeSetInsertedRows(this.changeSet, display.baseTable); this.insertedRows = getChangeSetInsertedRows(this.changeSet, display.baseTable);
@@ -32,6 +46,11 @@ export default class ChangeSetGrider extends Grider {
this.rowStatusCache = {}; this.rowStatusCache = {};
this.rowDefinitionsCache = {}; this.rowDefinitionsCache = {};
this.batchChangeSet = null; this.batchChangeSet = null;
this.compiledMacroFunc = compileMacroFunction(macro, this._errors);
}
get errors() {
return this._errors;
} }
getRowSource(index: number) { getRowSource(index: number) {
@@ -49,7 +68,11 @@ export default class ChangeSetGrider extends Grider {
const insertedRowIndex = this.getInsertedRowIndex(index); const insertedRowIndex = this.getInsertedRowIndex(index);
const rowDefinition = this.display.getChangeSetRow(row, insertedRowIndex); const rowDefinition = this.display.getChangeSetRow(row, insertedRowIndex);
const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, rowDefinition); const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, rowDefinition);
const rowUpdated = matchedChangeSetItem ? { ...row, ...matchedChangeSetItem.fields } : row; const rowUpdated = matchedChangeSetItem
? { ...row, ...matchedChangeSetItem.fields }
: this.compiledMacroFunc
? { ...row }
: row;
let status = 'regular'; let status = 'regular';
if (matchedChangeSetItem && matchedField == 'updates') status = 'updated'; if (matchedChangeSetItem && matchedField == 'updates') status = 'updated';
if (matchedField == 'deletes') status = 'deleted'; if (matchedField == 'deletes') status = 'deleted';
@@ -59,6 +82,23 @@ export default class ChangeSetGrider extends Grider {
modifiedFields: modifiedFields:
matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null, matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null,
}; };
if (this.compiledMacroFunc) {
for (const cell of this.selectedCells) {
if (cell.row != index) continue;
const newValue = runMacroOnValue(
this.compiledMacroFunc,
this.macroArgs,
rowUpdated[cell.column],
index,
rowUpdated,
cell.column,
this._errors
);
rowUpdated[cell.column] = newValue;
}
}
this.rowDataCache[index] = rowUpdated; this.rowDataCache[index] = rowUpdated;
this.rowStatusCache[index] = rowStatus; this.rowStatusCache[index] = rowStatus;
this.rowDefinitionsCache[index] = rowDefinition; this.rowDefinitionsCache[index] = rowDefinition;

View File

@@ -1,6 +1,15 @@
<script lang="ts"> <script lang="ts">
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
import { runMacroOnChangeSet } from 'dbgate-datalib';
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte'; import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import FormViewFilters from '../formview/FormViewFilters.svelte'; import FormViewFilters from '../formview/FormViewFilters.svelte';
import { extractMacroValuesForMacro } from '../freetable/FreeTableGrid.svelte';
import MacroDetail from '../freetable/MacroDetail.svelte';
import MacroManager from '../freetable/MacroManager.svelte';
import createRef from '../utility/createRef';
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte'; import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte'; import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
import ColumnManager from './ColumnManager.svelte'; import ColumnManager from './ColumnManager.svelte';
@@ -11,13 +20,37 @@
export let formViewComponent; export let formViewComponent;
export let formDisplay; export let formDisplay;
export let display; export let display;
export let changeSetState;
export let dispatchChangeSet;
export let isDetailView = false; export let isDetailView = false;
export let showReferences = false; export let showReferences = false;
export let showMacros;
let selectedCellsPublished = [];
const selectedMacro = writable(null);
setContext('selectedMacro', selectedMacro);
const macroValues = writable({});
setContext('macroValues', macroValues);
let managerSize; let managerSize;
$: isFormView = !!(formDisplay && formDisplay.config && formDisplay.config.isFormView); $: isFormView = !!(formDisplay && formDisplay.config && formDisplay.config.isFormView);
const handleExecuteMacro = () => {
const newChangeSet = runMacroOnChangeSet(
$selectedMacro,
extractMacroValuesForMacro($macroValues, $selectedMacro),
selectedCellsPublished,
changeSetState?.value,
display
);
if (newChangeSet) {
dispatchChangeSet({ type: 'set', value: newChangeSet });
}
$selectedMacro = null;
};
</script> </script>
<HorizontalSplitter initialValue="300px" bind:size={managerSize}> <HorizontalSplitter initialValue="300px" bind:size={managerSize}>
@@ -40,18 +73,36 @@
> >
<ReferenceManager {...$$props} {managerSize} /> <ReferenceManager {...$$props} {managerSize} />
</WidgetColumnBarItem> </WidgetColumnBarItem>
<WidgetColumnBarItem title="Macros" name="macros" skip={!showMacros} collapsed={isDetailView}>
<MacroManager {...$$props} {managerSize} macroCondition={macro => macro.type == 'transformValue'} />
</WidgetColumnBarItem>
</WidgetColumnBar> </WidgetColumnBar>
</div> </div>
<svelte:fragment slot="2"> <svelte:fragment slot="2">
{#if isFormView} <VerticalSplitter initialValue="70%" isSplitter={!!$selectedMacro && !isFormView && showMacros}>
<svelte:component this={formViewComponent} {...$$props} /> <svelte:fragment slot="1">
{:else} {#if isFormView}
<svelte:component <svelte:component this={formViewComponent} {...$$props} />
this={gridCoreComponent} {:else}
{...$$props} <svelte:component
formViewAvailable={!!formViewComponent && !!formDisplay} this={gridCoreComponent}
/> {...$$props}
{/if} formViewAvailable={!!formViewComponent && !!formDisplay}
onSelectionChanged={value => (selectedCellsPublished = value)}
macroValues={extractMacroValuesForMacro($macroValues, $selectedMacro)}
macroPreview={$selectedMacro}
{selectedCellsPublished}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="2">
{#if $selectedMacro}
<MacroDetail onExecute={handleExecuteMacro} />
{/if}
</svelte:fragment>
</VerticalSplitter>
</svelte:fragment> </svelte:fragment>
</HorizontalSplitter> </HorizontalSplitter>

View File

@@ -66,11 +66,26 @@
export let changeSetState; export let changeSetState;
export let dispatchChangeSet; export let dispatchChangeSet;
export let macroPreview;
export let macroValues;
export let selectedCellsPublished;
// export let onChangeGrider = undefined;
let loadedRows = []; let loadedRows = [];
// $: console.log('loadedRows BIND', loadedRows); // $: console.log('loadedRows BIND', loadedRows);
$: grider = new ChangeSetGrider(loadedRows, changeSetState, dispatchChangeSet, display); $: grider = new ChangeSetGrider(
loadedRows,
changeSetState,
dispatchChangeSet,
display,
macroPreview,
macroValues,
selectedCellsPublished
);
// $: console.log('GRIDER', grider); // $: console.log('GRIDER', grider);
// $: if (onChangeGrider) onChangeGrider(grider);
async function handleConfirmSql(sql) { async function handleConfirmSql(sql) {
const resp = await axiosInstance.request({ const resp = await axiosInstance.request({
@@ -162,6 +177,7 @@
onOpenQuery={openQuery} onOpenQuery={openQuery}
onOpenActiveChart={openActiveChart} onOpenActiveChart={openActiveChart}
bind:loadedRows bind:loadedRows
frameSelection={!!macroPreview}
{grider} {grider}
onSave={handleSave} onSave={handleSave}
/> />

View File

@@ -119,6 +119,7 @@
{display} {display}
{formDisplay} {formDisplay}
showReferences showReferences
showMacros
onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null} onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null}
onReferenceClick={value => { onReferenceClick={value => {
if (value && value.referenceId && reference && reference.referenceId == value.referenceId) { if (value && value.referenceId && reference && reference.referenceId == value.referenceId) {

View File

@@ -1,5 +1,5 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
function extractMacroValuesForMacro(macroValues, macro) { export function extractMacroValuesForMacro(macroValues, macro) {
// return {}; // return {};
if (!macro) return {}; if (!macro) return {};
return { return {

View File

@@ -11,11 +11,17 @@
let filter = ''; let filter = '';
export let managerSize; export let managerSize;
export let macroCondition;
</script> </script>
<ManagerInnerContainer width={managerSize}> <ManagerInnerContainer width={managerSize}>
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput placeholder="Search macros" bind:value={filter} /> <SearchInput placeholder="Search macros" bind:value={filter} />
</SearchBoxWrapper> </SearchBoxWrapper>
<AppObjectList list={_.sortBy(macros, 'title')} module={macroAppObject} {filter} groupFunc={data => data.group} /> <AppObjectList
list={_.sortBy(macros, 'title').filter(x => (macroCondition ? macroCondition(x) : true))}
module={macroAppObject}
{filter}
groupFunc={data => data.group}
/>
</ManagerInnerContainer> </ManagerInnerContainer>