Skip to content

Commit b95f7ec

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 #7631 Task: 5380747 X-original-commit: f445897 Signed-off-by: Lucas Lefèvre (lul) <[email protected]> Signed-off-by: Rémi Rahir (rar) <[email protected]>
1 parent d137668 commit b95f7ec

File tree

5 files changed

+87
-61
lines changed

5 files changed

+87
-61
lines changed

src/helpers/pivot/pivot_registry.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ModelConfig } from "../../model";
22
import { Registry } from "../../registries/registry";
3-
import { CoreGetters, Getters } from "../../types";
3+
import { ApplyRangeChange, CoreGetters, Getters, Range } from "../../types";
44
import { PivotCoreDefinition, PivotField, PivotFields } from "../../types/pivot";
55
import { Pivot } from "../../types/pivot_runtime";
66
import { PivotRuntimeDefinition } from "./pivot_runtime_definition";
@@ -32,6 +32,11 @@ export interface PivotRegistryItem {
3232
datetimeGranularities: string[];
3333
isMeasureCandidate: (field: PivotField) => boolean;
3434
isGroupable: (field: PivotField) => boolean;
35+
adaptRanges?: (
36+
getters: CoreGetters,
37+
definition: PivotCoreDefinition,
38+
applyChange: ApplyRangeChange
39+
) => PivotCoreDefinition;
3540
}
3641

3742
export const pivotRegistry = new Registry<PivotRegistryItem>();
@@ -54,4 +59,40 @@ pivotRegistry.add("SPREADSHEET", {
5459
datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
5560
isMeasureCandidate: (field: PivotField) => field.type !== "boolean",
5661
isGroupable: () => true,
62+
adaptRanges: (getters, definition, applyChange) => {
63+
if (definition.type !== "SPREADSHEET" || !definition.dataSet) {
64+
return definition;
65+
}
66+
const { sheetId, zone } = definition.dataSet;
67+
const range = getters.getRangeFromZone(sheetId, zone);
68+
const adaptedRange = adaptPivotRange(range, applyChange);
69+
70+
if (adaptedRange === range) {
71+
return definition;
72+
}
73+
74+
const dataSet = adaptedRange && {
75+
sheetId: adaptedRange.sheetId,
76+
zone: adaptedRange.zone,
77+
};
78+
return { ...definition, dataSet };
79+
},
5780
});
81+
82+
function adaptPivotRange(
83+
range: Range | undefined,
84+
applyChange: ApplyRangeChange
85+
): Range | undefined {
86+
if (!range) {
87+
return undefined;
88+
}
89+
const change = applyChange(range);
90+
switch (change.changeType) {
91+
case "NONE":
92+
return range;
93+
case "REMOVE":
94+
return undefined;
95+
default:
96+
return change.range;
97+
}
98+
}

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";
22
import { deepCopy, deepEquals } from "../../helpers";
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 {
67
ApplyRangeChange,
@@ -140,6 +141,18 @@ export class PivotCorePlugin extends CorePlugin<CoreState> implements CoreState
140141
}
141142

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

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

src/plugins/core/range.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
export class RangeAdapter implements CommandHandler<CoreCommand> {
3939
private getters: CoreGetters;
4040
private providers: Array<RangeProvider["adaptRanges"]> = [];
41+
private isAdaptingRanges: boolean = false;
4142
constructor(getters: CoreGetters) {
4243
this.getters = getters;
4344
}
@@ -72,6 +73,9 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
7273
beforeHandle(command: Command) {}
7374

7475
handle(cmd: CoreCommand) {
76+
if (this.isAdaptingRanges) {
77+
throw new Error("Plugins cannot dispatch commands during adaptRanges phase");
78+
}
7579
const rangeAdapter = getRangeAdapter(cmd);
7680
if (rangeAdapter?.applyChange) {
7781
this.executeOnAllRanges(
@@ -105,10 +109,12 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
105109
sheetId: UID,
106110
sheetName: AdaptSheetName
107111
) {
112+
this.isAdaptingRanges = true;
108113
const func = this.verifyRangeRemoved(adaptRange);
109114
for (const provider of this.providers) {
110115
provider(func, sheetId, sheetName);
111116
}
117+
this.isAdaptingRanges = false;
112118
}
113119

114120
/**

src/plugins/core/spreadsheet_pivot.ts

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
11
import { isZoneValid } from "../../helpers";
2-
import {
3-
ApplyRangeChange,
4-
CommandResult,
5-
CoreCommand,
6-
PivotCoreDefinition,
7-
Range,
8-
} from "../../types";
2+
import { CommandResult, CoreCommand, PivotCoreDefinition } from "../../types";
93
import { CorePlugin } from "../core_plugin";
104

11-
function adaptPivotRange(
12-
range: Range | undefined,
13-
applyChange: ApplyRangeChange
14-
): Range | undefined {
15-
if (!range) {
16-
return undefined;
17-
}
18-
const change = applyChange(range);
19-
switch (change.changeType) {
20-
case "NONE":
21-
return range;
22-
case "REMOVE":
23-
return undefined;
24-
default:
25-
return change.range;
26-
}
27-
}
28-
295
export class SpreadsheetPivotCorePlugin extends CorePlugin {
306
allowDispatch(cmd: CoreCommand) {
317
switch (cmd.type) {
@@ -37,30 +13,6 @@ export class SpreadsheetPivotCorePlugin extends CorePlugin {
3713
return CommandResult.Success;
3814
}
3915

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

tests/range_plugin.test.ts

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

0 commit comments

Comments
 (0)