Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FW][FIX] figure,chart: Enforce unicity of figure ids #2166

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 54 additions & 12 deletions 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 } from "../helpers/index";
import { getItemId, toXC, toZone, UuidGenerator } from "../helpers/index";
import { StateUpdateMessage } from "../types/collaborative/transport_service";
import {
CoreCommand,
Expand Down Expand Up @@ -54,17 +54,7 @@ export function load(data?: any, verboseImport?: boolean): WorkbookData {
data = migrate(data);
}
}

// sanity check: try to fix missing fields/corrupted state by providing
// sensible default values
data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
data.sheets = data.sheets.map((s, i) =>
Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s)
);

if (data.sheets.length === 0) {
data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
}
data = repairData(data);
return data;
}

Expand All @@ -84,6 +74,7 @@ function migrate(data: any): WorkbookData {
for (let i = index; i < MIGRATIONS.length; i++) {
data = MIGRATIONS[i].applyMigration(data);
}

return data;
}

Expand Down Expand Up @@ -316,6 +307,56 @@ const MIGRATIONS: Migration[] = [
},
];

/**
* This function is used to repair faulty data independently of the migration.
*/
export function repairData(data: Partial<WorkbookData>): Partial<WorkbookData> {
data = forceUnicityOfFigure(data);
data = setDefaults(data);
return data;
}

/**
* Force the unicity of figure ids accross sheets
*/
function forceUnicityOfFigure(data: Partial<WorkbookData>): Partial<WorkbookData> {
if (data.uniqueFigureIds) {
return data;
}
const figureIds = new Set();
const uuidGenerator = new UuidGenerator();
for (const sheet of data.sheets || []) {
for (const figure of sheet.figures || []) {
if (figureIds.has(figure.id)) {
figure.id += uuidGenerator.uuidv4();
}
figureIds.add(figure.id);
}
}

data.uniqueFigureIds = true;
return data;
}

/**
* sanity check: try to fix missing fields/corrupted state by providing
* sensible default values
*/
function setDefaults(data: Partial<WorkbookData>): Partial<WorkbookData> {
data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
data.sheets = data.sheets
? data.sheets.map((s, i) =>
Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s)
)
: [];

if (data.sheets.length === 0) {
data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
}

return data;
}

/**
* The goal of this function is to repair corrupted/wrong initial messages caused by
* a bug.
Expand Down Expand Up @@ -460,6 +501,7 @@ export function createEmptyWorkbookData(sheetName = "Sheet1"): WorkbookData {
formats: {},
borders: {},
revisionId: DEFAULT_REVISION_ID,
uniqueFigureIds: true,
};
return data;
}
Expand Down
51 changes: 41 additions & 10 deletions src/plugins/core/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
Command,
CommandResult,
CoreCommand,
CreateChartCommand,
ExcelWorkbookData,
Figure,
FigureData,
Pixel,
UID,
UpdateChartCommand,
WorkbookData,
} from "../../types/index";
import { CorePlugin } from "../core_plugin";
Expand Down Expand Up @@ -44,8 +46,8 @@ export class ChartPlugin extends CorePlugin<ChartState> implements ChartState {
readonly charts: Record<UID, AbstractChart | undefined> = {};

private createChart = chartFactory(this.getters);
private validateChartDefinition = (definition: ChartDefinition) =>
validateChartDefinition(this, definition);
private validateChartDefinition = (cmd: CreateChartCommand | UpdateChartCommand) =>
validateChartDefinition(this, cmd.definition);

adaptRanges(applyChange: ApplyRangeChange) {
for (const [chartId, chart] of Object.entries(this.charts)) {
Expand All @@ -60,8 +62,15 @@ export class ChartPlugin extends CorePlugin<ChartState> implements ChartState {
allowDispatch(cmd: Command) {
switch (cmd.type) {
case "CREATE_CHART":
return this.checkValidations(
cmd,
this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate)
);
case "UPDATE_CHART":
return this.validateChartDefinition(cmd.definition);
return this.checkValidations(
cmd,
this.chainValidations(this.validateChartDefinition, this.checkChartExists)
);
default:
return CommandResult.Success;
}
Expand All @@ -71,10 +80,10 @@ export class ChartPlugin extends CorePlugin<ChartState> implements ChartState {
switch (cmd.type) {
case "CREATE_CHART":
this.addFigure(cmd.id, cmd.sheetId, cmd.position, cmd.size);
this.addChart(cmd.id, cmd.sheetId, cmd.definition);
this.addChart(cmd.id, cmd.definition);
break;
case "UPDATE_CHART": {
this.addChart(cmd.id, cmd.sheetId, cmd.definition);
this.addChart(cmd.id, cmd.definition);
break;
}
case "DUPLICATE_SHEET": {
Expand Down Expand Up @@ -170,10 +179,17 @@ export class ChartPlugin extends CorePlugin<ChartState> implements ChartState {
for (let sheet of data.sheets) {
// TODO This code is false, if two plugins want ot insert figures on the sheet, it will crash !
const sheetFigures = this.getters.getFigures(sheet.id);
const figures = sheetFigures as FigureData<any>[];
for (let figure of figures) {
const figures: FigureData<any>[] = [];
for (let sheetFigure of sheetFigures) {
const figure = sheetFigure as FigureData<any>;
if (figure && figure.tag === "chart") {
figure.data = this.getChartDefinition(figure.id);
const data = this.charts[figure.id]?.getDefinition();
if (data) {
figure.data = data;
figures.push(figure);
}
} else {
figures.push(figure);
}
}
sheet.figures = figures;
Expand Down Expand Up @@ -234,7 +250,22 @@ export class ChartPlugin extends CorePlugin<ChartState> implements ChartState {
* Add a chart in the local state. If a chart already exists, this chart is
* replaced
*/
private addChart(id: UID, sheetId: UID, definition: ChartDefinition) {
this.history.update("charts", id, this.createChart(id, definition, sheetId));
private addChart(id: UID, definition: ChartDefinition) {
const sheetId = this.getters.getFigureSheetId(id);
if (sheetId) {
this.history.update("charts", id, this.createChart(id, definition, sheetId));
}
}

private checkChartDuplicate(cmd: CreateChartCommand): CommandResult {
return this.getters.getFigureSheetId(cmd.id)
? CommandResult.DuplicatedChartId
: CommandResult.Success;
}

private checkChartExists(cmd: UpdateChartCommand): CommandResult {
return this.getters.getFigureSheetId(cmd.id)
? CommandResult.Success
: CommandResult.ChartDoesNotExist;
}
}
8 changes: 7 additions & 1 deletion src/plugins/core/figures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface FigureState {
}

export class FigurePlugin extends CorePlugin<FigureState> implements FigureState {
static getters = ["getFigures", "getFigure"] as const;
static getters = ["getFigures", "getFigure", "getFigureSheetId"] as const;
readonly figures: {
[sheet: string]: Record<UID, Figure | undefined> | undefined;
} = {};
Expand Down Expand Up @@ -116,6 +116,12 @@ export class FigurePlugin extends CorePlugin<FigureState> implements FigureState
return this.figures[sheetId]?.[figureId];
}

getFigureSheetId(figureId: string): UID | undefined {
return Object.keys(this.figures).find(
(sheetId) => this.figures[sheetId]?.[figureId] !== undefined
);
}

// ---------------------------------------------------------------------------
// Import/Export
// ---------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,8 @@ export const enum CommandResult {
NonContinuousTargets,
DuplicatedFigureId,
InvalidSelectionStep,
DuplicatedChartId,
ChartDoesNotExist,
}

export interface CommandHandler<T> {
Expand Down
1 change: 1 addition & 0 deletions src/types/workbook_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface WorkbookData {
borders: { [key: number]: Border };
entities: { [key: string]: { [key: string]: any } };
revisionId: UID;
uniqueFigureIds: boolean;
}

export interface ExcelWorkbookData extends WorkbookData {
Expand Down
Loading