Skip to content

Commit f8677dc

Browse files
committed
[FIX] CorePlugins: Prevent dispatch during adaptRange
The method `adaptRange` was originally designed for each plugin to update their own ranges, whithout having to manually react to the different commands that alter the sheets structure. We have abused the system when we introduced the spreadsheet pivots to allow the modification of a pivot dataset (which is stored in PivotCorePlugin) from another entry point `SpreadsheetPivotCorePlugin`) by dispatching an UPDATE_PIVOT command but there are some side effects that we did not account for, the dispatched command is forwarded to EVERY other plugin. Meaning that during an adaptRange, which should only concern a specific plugin, we end up notifying a change to the whole core/coreview stack. This revision removes the access to 'dispatch' during the handling of `adaptRange` so we ensure that the changes are kept locally. closes #7633 Task: 5380747 X-original-commit: 53c92a2 Signed-off-by: Lucas Lefèvre (lul) <[email protected]> Signed-off-by: Rémi Rahir (rar) <[email protected]>
1 parent 6fe9f42 commit f8677dc

File tree

5 files changed

+87
-56
lines changed

5 files changed

+87
-56
lines changed

packages/o-spreadsheet-engine/src/helpers/pivot/pivot_registry.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Registry } from "../../registry";
22
import { CoreGetters } from "../../types/core_getters";
33
import { Getters } from "../../types/getters";
4-
import { UID } from "../../types/misc";
4+
import { ApplyRangeChange, UID } from "../../types/misc";
55
import { ModelConfig } from "../../types/model";
66
import { PivotCoreDefinition, PivotField, PivotFields } from "../../types/pivot";
77
import { Pivot } from "../../types/pivot_runtime";
8+
import { Range } from "../../types/range";
89
import { PivotRuntimeDefinition } from "./pivot_runtime_definition";
910
import { SpreadsheetPivotRuntimeDefinition } from "./spreadsheet_pivot/runtime_definition_spreadsheet_pivot";
1011
import { SpreadsheetPivot } from "./spreadsheet_pivot/spreadsheet_pivot";
@@ -36,6 +37,11 @@ export interface PivotRegistryItem {
3637
isGroupable: (field: PivotField) => boolean;
3738
canHaveCustomGroup: (field: PivotField) => boolean;
3839
isPivotUnused: (getters: Getters, pivotId: UID) => boolean;
40+
adaptRanges?: (
41+
getters: CoreGetters,
42+
definition: PivotCoreDefinition,
43+
applyChange: ApplyRangeChange
44+
) => PivotCoreDefinition;
3945
}
4046

4147
export const pivotRegistry = new Registry<PivotRegistryItem>();
@@ -60,4 +66,40 @@ pivotRegistry.add("SPREADSHEET", {
6066
isGroupable: () => true,
6167
canHaveCustomGroup: (field: PivotField) => field.type === "char" && !field.isCustomField,
6268
isPivotUnused: () => true,
69+
adaptRanges: (getters, definition, applyChange) => {
70+
if (definition.type !== "SPREADSHEET" || !definition.dataSet) {
71+
return definition;
72+
}
73+
const { sheetId, zone } = definition.dataSet;
74+
const range = getters.getRangeFromZone(sheetId, zone);
75+
const adaptedRange = adaptPivotRange(range, applyChange);
76+
77+
if (adaptedRange === range) {
78+
return definition;
79+
}
80+
81+
const dataSet = adaptedRange && {
82+
sheetId: adaptedRange.sheetId,
83+
zone: adaptedRange.zone,
84+
};
85+
return { ...definition, dataSet };
86+
},
6387
});
88+
89+
function adaptPivotRange(
90+
range: Range | undefined,
91+
applyChange: ApplyRangeChange
92+
): Range | undefined {
93+
if (!range) {
94+
return undefined;
95+
}
96+
const change = applyChange(range);
97+
switch (change.changeType) {
98+
case "NONE":
99+
return range;
100+
case "REMOVE":
101+
return undefined;
102+
default:
103+
return change.range;
104+
}
105+
}

packages/o-spreadsheet-engine/src/plugins/core/pivot.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { compile } from "../../formulas/compiler";
22
import { deepCopy, deepEquals } from "../../helpers/misc";
33
import { createPivotFormula, getMaxObjectId } from "../../helpers/pivot/pivot_helpers";
4+
import { pivotRegistry } from "../../helpers/pivot/pivot_registry";
45
import { SpreadsheetPivotTable } from "../../helpers/pivot/table_spreadsheet_pivot";
56
import { CommandResult, CoreCommand } from "../../types/commands";
67
import { CellPosition, UID } from "../../types/misc";
@@ -141,6 +142,18 @@ export class PivotCorePlugin extends CorePlugin<CoreState> implements CoreState
141142
}
142143

143144
adaptRanges(applyChange: ApplyRangeChange) {
145+
for (const pivotId in this.pivots) {
146+
const definition = deepCopy(this.pivots[pivotId]?.definition);
147+
if (!definition) {
148+
continue;
149+
}
150+
const newDefinition = pivotRegistry
151+
.get(definition.type)
152+
?.adaptRanges?.(this.getters, definition, applyChange);
153+
if (newDefinition && !deepEquals(definition, newDefinition)) {
154+
this.history.update("pivots", pivotId, "definition", newDefinition);
155+
}
156+
}
144157
for (const sheetId in this.compiledMeasureFormulas) {
145158
for (const formulaString in this.compiledMeasureFormulas[sheetId]) {
146159
const compiledFormula = this.compiledMeasureFormulas[sheetId][formulaString];
@@ -325,19 +338,19 @@ export class PivotCorePlugin extends CorePlugin<CoreState> implements CoreState
325338
if (!pivot) {
326339
continue;
327340
}
328-
const def = deepCopy(pivot.definition);
329341

330-
for (const measure of def.measures) {
342+
for (const measure of pivot.definition.measures) {
331343
if (measure.computedBy?.formula === formulaString) {
332-
const measureIndex = def.measures.indexOf(measure);
333-
if (measureIndex !== -1) {
334-
def.measures[measureIndex].computedBy = {
335-
formula: newFormulaString,
336-
sheetId,
337-
};
338-
}
339-
340-
this.dispatch("UPDATE_PIVOT", { pivotId, pivot: def });
344+
const measureIndex = pivot.definition.measures.indexOf(measure);
345+
this.history.update(
346+
"pivots",
347+
pivotId,
348+
"definition",
349+
"measures",
350+
measureIndex,
351+
"computedBy",
352+
{ formula: newFormulaString, sheetId }
353+
);
341354
}
342355
}
343356
}

packages/o-spreadsheet-engine/src/plugins/core/range.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { Range, RangeData, RangeStringOptions } from "../../types/range";
3232
export class RangeAdapter implements CommandHandler<CoreCommand> {
3333
private getters: CoreGetters;
3434
private providers: Array<RangeProvider["adaptRanges"]> = [];
35+
private isAdaptingRanges: boolean = false;
3536
constructor(getters: CoreGetters) {
3637
this.getters = getters;
3738
}
@@ -66,6 +67,9 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
6667
beforeHandle(command: Command) {}
6768

6869
handle(cmd: CoreCommand) {
70+
if (this.isAdaptingRanges) {
71+
throw new Error("Plugins cannot dispatch commands during adaptRanges phase");
72+
}
6973
const rangeAdapter = getRangeAdapter(cmd);
7074
if (rangeAdapter?.applyChange) {
7175
this.executeOnAllRanges(
@@ -99,10 +103,12 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
99103
sheetId: UID,
100104
sheetName: AdaptSheetName
101105
) {
106+
this.isAdaptingRanges = true;
102107
const func = this.verifyRangeRemoved(adaptRange);
103108
for (const provider of this.providers) {
104109
provider(func, sheetId, sheetName);
105110
}
111+
this.isAdaptingRanges = false;
106112
}
107113

108114
/**

packages/o-spreadsheet-engine/src/plugins/core/spreadsheet_pivot.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,8 @@
11
import { isZoneValid } from "../../helpers/zones";
22
import { CommandResult, CoreCommand } from "../../types/commands";
3-
import { ApplyRangeChange } from "../../types/misc";
43
import { PivotCoreDefinition } from "../../types/pivot";
5-
import { Range } from "../../types/range";
64
import { CorePlugin } from "../core_plugin";
75

8-
function adaptPivotRange(
9-
range: Range | undefined,
10-
applyChange: ApplyRangeChange
11-
): Range | undefined {
12-
if (!range) {
13-
return undefined;
14-
}
15-
const change = applyChange(range);
16-
switch (change.changeType) {
17-
case "NONE":
18-
return range;
19-
case "REMOVE":
20-
return undefined;
21-
default:
22-
return change.range;
23-
}
24-
}
25-
266
export class SpreadsheetPivotCorePlugin extends CorePlugin {
277
allowDispatch(cmd: CoreCommand) {
288
switch (cmd.type) {
@@ -34,30 +14,6 @@ export class SpreadsheetPivotCorePlugin extends CorePlugin {
3414
return CommandResult.Success;
3515
}
3616

37-
adaptRanges(applyChange: ApplyRangeChange) {
38-
for (const pivotId of this.getters.getPivotIds()) {
39-
const definition = this.getters.getPivotCoreDefinition(pivotId);
40-
if (definition.type !== "SPREADSHEET") {
41-
continue;
42-
}
43-
if (definition.dataSet) {
44-
const { sheetId, zone } = definition.dataSet;
45-
const range = this.getters.getRangeFromZone(sheetId, zone);
46-
const adaptedRange = adaptPivotRange(range, applyChange);
47-
48-
if (adaptedRange === range) {
49-
return;
50-
}
51-
52-
const dataSet = adaptedRange && {
53-
sheetId: adaptedRange.sheetId,
54-
zone: adaptedRange.zone,
55-
};
56-
this.dispatch("UPDATE_PIVOT", { pivotId, pivot: { ...definition, dataSet } });
57-
}
58-
}
59-
}
60-
6117
private checkDataSetValidity(definition: PivotCoreDefinition) {
6218
if (definition.type === "SPREADSHEET" && definition.dataSet) {
6319
const { zone, sheetId } = definition.dataSet;

tests/range_plugin.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,3 +744,17 @@ test.each([
744744
const adaptedRange = model.getters.createAdaptedRanges([range], 1, 1, sheetId);
745745
expect(model.getters.getRangeString(adaptedRange[0], sheetId)).toBe(expected);
746746
});
747+
748+
test("Plugins cannot dispatch a command during adaptRanges", () => {
749+
class PluginDispatchInAdaptRanges extends CorePlugin {
750+
adaptRanges() {
751+
this.dispatch("DELETE_SHEET", { sheetId: "s1", sheetName: "coucou" });
752+
}
753+
}
754+
addTestPlugin(corePluginRegistry, PluginDispatchInAdaptRanges);
755+
756+
const model = new Model({});
757+
expect(() => {
758+
addColumns(model, "before", "A", 1);
759+
}).toThrowError("Plugins cannot dispatch commands during adaptRanges phase");
760+
});

0 commit comments

Comments
 (0)