Skip to content

Commit

Permalink
[FIX] DataFilter: Fix overlapping filters
Browse files Browse the repository at this point in the history
The fix proposed in PR #2631 was incomplete. While it prevented the
*new* duplication of filter tables on overlapping zones (due to a mismatch
inthe plugin internal mapping) it didn't fix the filters already
duplicated and overlapping.

This revision adds a new migration to clear the duplicated filters based
on the knowledge of how the bug could occur.

Before #2631, when duplicating a sheet containing a filter, the filter
plugin would:
- Deep copy the entry of `this.tables[cmd.sheetIdFrom]` - an Object
  mapping the id of a `FilterTable` with the corresponding
  `FilterTable` object
- create a new entry for `cmd.sheetIdTo` in `this.tables` with the deep
  copy

However, deep copy would create a new `FilterTable` object with a new
generated id (type uuid) which would mismatch with the internal mapping
of the copied object.

e.g.
```typescript
// in sheet1
table1 = {id1: filter1 instanceof FilterTable}
id1 === filter1.id

// in sheet2
table2 = deepCopy(table1)
table2 ===  {id1: filter2 instanceof FilterTable}
id1 !== filter2.id
```

When trying to then alter the copied sheet structure and such
modification altered the filters, the implementation did not account for
such situation and would end up duplicating the filter table in `sheet2`,
thus creating a sheet with filters with overlapping zones.

E.g.

``` typescript
tables["sheet2"] = {id1: filter2}
filter2.zone === "A1:A10"

deleteRow(5)

tables["sheet2"] = {id1: filter2, id3:filter3}
id1 !== filter2.id
id3 === filter3.id

// overlap in filte zones
filter2.zone === "A1:A10"
filter3.zone === "A1:A9"
```

Since Javascript guarantee `Object` property insertion order upon
iteration (for equivalent objects [1]), we can safely say that in such
situation the latest key of the object `tables["sheet2"]` is the truth
and we can delete the other keyslinked to filters with overlapping
zones.

One of the side-effects of these overlapping tables was a crash in the
owl component `FilterIconsOverlay` due to some duplicated keys. As such,
a regression test was added to cover this case.

[1] https://262.ecma-international.org/10.0/

closes #3634

Task: 3728009
X-original-commit: 5a5c475
Signed-off-by: Lucas Lefèvre (lul) <[email protected]>
Signed-off-by: Rémi Rahir (rar) <[email protected]>
  • Loading branch information
rrahir committed Feb 7, 2024
1 parent b2452e3 commit 0a2a454
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 2 deletions.
30 changes: 29 additions & 1 deletion src/migrations/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
FORBIDDEN_IN_EXCEL_REGEX,
FORMULA_REF_IDENTIFIER,
} from "../constants";
import { getItemId, toXC, toZone, UuidGenerator } from "../helpers/index";
import { getItemId, overlap, toXC, toZone, UuidGenerator, zoneToXc } from "../helpers/index";
import { isValidLocale } from "../helpers/locale";
import { StateUpdateMessage } from "../types/collaborative/transport_service";
import {
Expand All @@ -16,6 +16,7 @@ import {
SheetData,
UID,
WorkbookData,
Zone,
} from "../types/index";
import { XlsxReader } from "../xlsx/xlsx_reader";
import { normalizeV9 } from "./legacy_tools";
Expand Down Expand Up @@ -306,6 +307,33 @@ const MIGRATIONS: Migration[] = [
return data;
},
},
{
description: "Fix datafilter duplication",
from: 12,
to: 12.5,
applyMigration(data: any): any {
for (let sheet of data.sheets || []) {
let knownDataFilterZones: Zone[] = [];
for (let filterTable of sheet.filterTables || []) {
const zone = toZone(filterTable.range);
// See commit message for the details
const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) =>
overlap(knownZone, zone)
);
if (intersectZoneIndex !== -1) {
knownDataFilterZones[intersectZoneIndex] = zone;
} else {
knownDataFilterZones.push(zone);
}
}

sheet.filterTables = knownDataFilterZones.map((zone) => ({
range: zoneToXc(zone),
}));
}
return data;
},
},
{
description: "Change Border description structure",
from: 12,
Expand Down
19 changes: 19 additions & 0 deletions tests/components/filter_icon_overlay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Model } from "../../src";
import { mountSpreadsheet } from "../test_helpers/helpers";

describe("Filter Icon Overlay component", () => {
test("Overlapping filters are overwritten bythe latest inserted", async () => {
const model = new Model({
version: 12,
sheets: [
{
name: "Sheet1",
id: "sh1",
filterTables: [{ range: "A2:B3" }, { range: "A2:C4" }],
},
],
});
const { fixture } = await mountSpreadsheet({ model });
expect(fixture.querySelectorAll(".o-filter-icon").length).toBe(3);
});
});
16 changes: 15 additions & 1 deletion tests/plugins/import_export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("data", () => {
});

describe("Migrations", () => {
test("Can upgrade from 1 to 12", () => {
test("Can upgrade from 1 to 13", () => {
const model = new Model({
version: 1,
sheets: [
Expand Down Expand Up @@ -403,6 +403,20 @@ describe("Migrations", () => {
});
});

test("migrate version 12.5: Fix Overlapping datafilters", () => {
const model = new Model({
version: 12,
sheets: [
{
id: "1",
filterTables: [{ range: "A1:B2" }, { range: "A1:C2" }],
},
],
});
const data = model.exportData();
expect(data.sheets[0].filterTables).toEqual([{ range: "A1:C2" }]);
});

test("migrate version 14: set locale of spreadsheet to en_US", () => {
let model = new Model({ version: 13 });
let data = model.exportData();
Expand Down

0 comments on commit 0a2a454

Please sign in to comment.