Skip to content

Commit d17a974

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 #7603 Task: 5380747 Signed-off-by: Lucas Lefèvre (lul) <[email protected]>
1 parent 7c2da49 commit d17a974

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) => !["datetime", "boolean"].includes(field.type),
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,
@@ -132,6 +133,18 @@ export class PivotCorePlugin extends CorePlugin<CoreState> implements CoreState
132133
}
133134

134135
adaptRanges(applyChange: ApplyRangeChange) {
136+
for (const pivotId in this.pivots) {
137+
const definition = deepCopy(this.pivots[pivotId]?.definition);
138+
if (!definition) {
139+
continue;
140+
}
141+
const newDefinition = pivotRegistry
142+
.get(definition.type)
143+
?.adaptRanges?.(this.getters, definition, applyChange);
144+
if (newDefinition && !deepEquals(definition, newDefinition)) {
145+
this.history.update("pivots", pivotId, "definition", newDefinition);
146+
}
147+
}
135148
for (const sheetId in this.compiledMeasureFormulas) {
136149
for (const formulaString in this.compiledMeasureFormulas[sheetId]) {
137150
const compiledFormula = this.compiledMeasureFormulas[sheetId][formulaString];
@@ -314,19 +327,19 @@ export class PivotCorePlugin extends CorePlugin<CoreState> implements CoreState
314327
if (!pivot) {
315328
continue;
316329
}
317-
const def = deepCopy(pivot.definition);
318330

319-
for (const measure of def.measures) {
331+
for (const measure of pivot.definition.measures) {
320332
if (measure.computedBy?.formula === formulaString) {
321-
const measureIndex = def.measures.indexOf(measure);
322-
if (measureIndex !== -1) {
323-
def.measures[measureIndex].computedBy = {
324-
formula: newFormulaString,
325-
sheetId,
326-
};
327-
}
328-
329-
this.dispatch("UPDATE_PIVOT", { pivotId, pivot: def });
333+
const measureIndex = pivot.definition.measures.indexOf(measure);
334+
this.history.update(
335+
"pivots",
336+
pivotId,
337+
"definition",
338+
"measures",
339+
measureIndex,
340+
"computedBy",
341+
{ formula: newFormulaString, sheetId }
342+
);
330343
}
331344
}
332345
}

src/plugins/core/range.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface RangeStringOptions {
4242
export class RangeAdapter implements CommandHandler<CoreCommand> {
4343
private getters: CoreGetters;
4444
private providers: Array<RangeProvider["adaptRanges"]> = [];
45+
private isAdaptingRanges: boolean = false;
4546
constructor(getters: CoreGetters) {
4647
this.getters = getters;
4748
}
@@ -73,6 +74,9 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
7374
beforeHandle(command: Command) {}
7475

7576
handle(cmd: Command) {
77+
if (this.isAdaptingRanges) {
78+
throw new Error("Plugins cannot dispatch commands during adaptRanges phase");
79+
}
7680
switch (cmd.type) {
7781
case "REMOVE_COLUMNS_ROWS": {
7882
let start: "left" | "top" = cmd.dimension === "COL" ? "left" : "top";
@@ -243,10 +247,12 @@ export class RangeAdapter implements CommandHandler<CoreCommand> {
243247
}
244248

245249
private executeOnAllRanges(adaptRange: ApplyRangeChange, sheetId?: UID) {
250+
this.isAdaptingRanges = true;
246251
const func = this.verifyRangeRemoved(adaptRange);
247252
for (const provider of this.providers) {
248253
provider(func, sheetId);
249254
}
255+
this.isAdaptingRanges = false;
250256
}
251257

252258
/**

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" });
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)