diff --git a/addons/spreadsheet/static/src/chart/data_source/graph_data_source.js b/addons/spreadsheet/static/src/chart/data_source/chart_data_source.js similarity index 83% rename from addons/spreadsheet/static/src/chart/data_source/graph_data_source.js rename to addons/spreadsheet/static/src/chart/data_source/chart_data_source.js index e2a503635fb..01c27d6d3c8 100644 --- a/addons/spreadsheet/static/src/chart/data_source/graph_data_source.js +++ b/addons/spreadsheet/static/src/chart/data_source/chart_data_source.js @@ -2,9 +2,9 @@ import { OdooViewsDataSource } from "@spreadsheet/data_sources/odoo_views_data_source"; import { _t } from "@web/core/l10n/translation"; -import { GraphModel } from "@web/views/graph/graph_model"; +import { GraphModel as ChartModel} from "@web/views/graph/graph_model"; -export default class GraphDataSource extends OdooViewsDataSource { +export default class ChartDataSource extends OdooViewsDataSource { /** * @override * @param {Object} services Services (see DataSource) @@ -22,7 +22,7 @@ export default class GraphDataSource extends OdooViewsDataSource { fieldAttrs: {}, ...this._metaData, }; - this._model = new GraphModel( + this._model = new ChartModel( { _t, }, diff --git a/addons/spreadsheet/static/src/chart/odoo_chart/index.js b/addons/spreadsheet/static/src/chart/index.js similarity index 58% rename from addons/spreadsheet/static/src/chart/odoo_chart/index.js rename to addons/spreadsheet/static/src/chart/index.js index 7a09e5ef68a..fb740cdb349 100644 --- a/addons/spreadsheet/static/src/chart/odoo_chart/index.js +++ b/addons/spreadsheet/static/src/chart/index.js @@ -4,11 +4,13 @@ import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; const { chartComponentRegistry } = spreadsheet.registries; const { ChartJsComponent } = spreadsheet.components; -const { invalidateEvaluationCommands, readonlyAllowedCommands } = spreadsheet; chartComponentRegistry.add("odoo_bar", ChartJsComponent); chartComponentRegistry.add("odoo_line", ChartJsComponent); chartComponentRegistry.add("odoo_pie", ChartJsComponent); -invalidateEvaluationCommands.add("ADD_GRAPH_DOMAIN"); -readonlyAllowedCommands.add("ADD_GRAPH_DOMAIN"); +import OdooChartCorePlugin from "./plugins/odoo_chart_core_plugin"; +import ChartOdooMenuPlugin from "./plugins/chart_odoo_menu_plugin"; +import OdooChartUIPlugin from "./plugins/odoo_chart_ui_plugin"; + +export { OdooChartCorePlugin, ChartOdooMenuPlugin, OdooChartUIPlugin }; diff --git a/addons/spreadsheet/static/src/chart/odoo_chart/odoo_chart.js b/addons/spreadsheet/static/src/chart/odoo_chart/odoo_chart.js index 741ca74f1a9..d69752b57d1 100644 --- a/addons/spreadsheet/static/src/chart/odoo_chart/odoo_chart.js +++ b/addons/spreadsheet/static/src/chart/odoo_chart/odoo_chart.js @@ -1,7 +1,7 @@ /** @odoo-module */ import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; -import GraphDataSource from "../data_source/graph_data_source"; +import ChartDataSource from "../data_source/chart_data_source"; const { AbstractChart, CommandResult } = spreadsheet; @@ -123,11 +123,11 @@ export class OdooChart extends AbstractChart { } setDataSource(dataSource) { - if (dataSource instanceof GraphDataSource) { + if (dataSource instanceof ChartDataSource) { this.dataSource = dataSource; } else { - throw new Error("Only GraphDataSources can be added."); + throw new Error("Only ChartDataSources can be added."); } } } diff --git a/addons/spreadsheet/static/src/chart/plugin/odoo_chart_plugin.js b/addons/spreadsheet/static/src/chart/plugin/odoo_chart_plugin.js deleted file mode 100644 index 5a01eeb7595..00000000000 --- a/addons/spreadsheet/static/src/chart/plugin/odoo_chart_plugin.js +++ /dev/null @@ -1,126 +0,0 @@ -/** @odoo-module */ - -import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended"; -import GraphDataSource from "../data_source/graph_data_source"; - -const { CorePlugin } = spreadsheet; -const { corePluginRegistry } = spreadsheet.registries; - -/** - * @typedef {import("@web/views/graph/graph_model").GraphModel} GraphModel - */ - -export default class OdooGraphPlugin extends CorePlugin { - constructor(getters, history, range, dispatch, config, uuidGenerator) { - super(getters, history, range, dispatch, config, uuidGenerator); - this.dataSources = config.dataSources; - - /** @type {Object.} */ - this.graphsDataSources = {}; - } - - /** - * Handle a spreadsheet command - * - * @param {Object} cmd Command - */ - handle(cmd) { - switch (cmd.type) { - case "CREATE_CHART": - switch (cmd.definition.type) { - case "odoo_pie": - case "odoo_bar": - case "odoo_line": - this._addGraphDataSource(cmd.id); - break; - } - break; - case "UPDATE_CHART": { - switch (cmd.definition.type) { - case "odoo_pie": - case "odoo_bar": - case "odoo_line": - this._setGraphDataSource(cmd.id); - break; - } - break; - } - case "ADD_GRAPH_DOMAIN": - this.getters.getGraphDataSource(cmd.id).addDomain(cmd.domain); - } - } - - // ------------------------------------------------------------------------- - // Getters - // ------------------------------------------------------------------------- - - /** - * Get all the odoo chart ids - * @returns {Array} - */ - getOdooChartIds() { - const ids = []; - for (const sheetId of this.getters.getSheetIds()) { - ids.push( - ...this.getters - .getChartIds(sheetId) - .filter((id) => this.getters.getChartType(id).startsWith("odoo_")) - ); - } - return ids; - } - - /** - * @param {string} id - * @returns {GraphDataSource|undefined} - */ - getGraphDataSource(id) { - const dataSourceId = this.graphsDataSources[id]; - return this.dataSources.get(dataSourceId); - } - - import(data) { - for (const sheet of data.sheets) { - if (sheet.figures) { - for (const figure of sheet.figures) { - if (figure.tag === "chart" && figure.data.type.startsWith("odoo_")) { - this._addGraphDataSource(figure.id); - } - } - } - } - } - - // ------------------------------------------------------------------------- - // Private - // ------------------------------------------------------------------------- - - /** - * @param {string} chartId - */ - _addGraphDataSource(chartId) { - const dataSourceId = this.uuidGenerator.uuidv4(); - const graphsDataSources = { ...this.graphsDataSources }; - graphsDataSources[chartId] = dataSourceId; - const definition = this.getters.getChart(chartId).getDefinitionForDataSource(); - if (!this.dataSources.contains(dataSourceId)) { - this.dataSources.add(dataSourceId, GraphDataSource, definition); - } - this.history.update("graphsDataSources", graphsDataSources); - - this._setGraphDataSource(chartId); - } - - /** - * Sets the catasource on the corresponding graph - * @param {string} chartId - */ - _setGraphDataSource(chartId) { - const chart = this.getters.getChart(chartId); - chart.setDataSource(this.getters.getGraphDataSource(chartId)); - } -} - -OdooGraphPlugin.getters = ["getGraphDataSource", "getOdooChartIds"]; - -corePluginRegistry.add("odoo_graph_plugin", OdooGraphPlugin); diff --git a/addons/spreadsheet/static/src/chart/plugin/chart_odoo_menu_plugin.js b/addons/spreadsheet/static/src/chart/plugins/chart_odoo_menu_plugin.js similarity index 92% rename from addons/spreadsheet/static/src/chart/plugin/chart_odoo_menu_plugin.js rename to addons/spreadsheet/static/src/chart/plugins/chart_odoo_menu_plugin.js index 2e154199c9b..02549354b89 100644 --- a/addons/spreadsheet/static/src/chart/plugin/chart_odoo_menu_plugin.js +++ b/addons/spreadsheet/static/src/chart/plugins/chart_odoo_menu_plugin.js @@ -1,7 +1,5 @@ /** @odoo-module */ - import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; -const { corePluginRegistry } = spreadsheet.registries; const { coreTypes } = spreadsheet; /** Plugin that link charts with Odoo menus. It can contain either the Id of the odoo menu, or its xml id. */ @@ -50,5 +48,4 @@ export default class ChartOdooMenuPlugin extends spreadsheet.CorePlugin { ChartOdooMenuPlugin.modes = ["normal", "headless"]; ChartOdooMenuPlugin.getters = ["getChartOdooMenu"]; -corePluginRegistry.add("chartOdooMenuPlugin", ChartOdooMenuPlugin); coreTypes.add("LINK_ODOO_MENU_TO_CHART"); diff --git a/addons/spreadsheet/static/src/chart/plugins/odoo_chart_core_plugin.js b/addons/spreadsheet/static/src/chart/plugins/odoo_chart_core_plugin.js new file mode 100644 index 00000000000..1bcca624533 --- /dev/null +++ b/addons/spreadsheet/static/src/chart/plugins/odoo_chart_core_plugin.js @@ -0,0 +1,244 @@ +/** @odoo-module */ +import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended"; +import ChartDataSource from "../data_source/chart_data_source"; +import { globalFiltersFieldMatchers } from "@spreadsheet/global_filters/plugins/global_filters_core_plugin"; +import { sprintf } from "@web/core/utils/strings"; +import { _t } from "@web/core/l10n/translation"; + +const { CorePlugin } = spreadsheet; + +/** + * @typedef {Object} Chart + * @property {string} dataSourceId + * @property {Object} fieldMatching + */ + +export default class OdooChartCorePlugin extends CorePlugin { + constructor(getters, history, range, dispatch, config, uuidGenerator) { + super(getters, history, range, dispatch, config, uuidGenerator); + this.dataSources = config.dataSources; + + /** @type {Object.} */ + this.charts = {}; + + globalFiltersFieldMatchers["chart"] = { + geIds: () => this.getters.getOdooChartIds(), + getDisplayName: (chartId) => this.getters.getOdooChartDisplayName(chartId), + getTag: async (chartId) => { + const model = await this.getChartDataSource(chartId).getModelLabel(); + return sprintf(_t("Chart - %s"), model); + }, + getFieldMatching: (chartId, filterId) => + this.getOdooChartFieldMatching(chartId, filterId), + waitForReady: () => this.getOdooChartsWaitForReady(), + getModel: (chartId) => + this.getters.getChart(chartId).getDefinitionForDataSource().metaData.resModel, + getFields: (chartId) => this.getChartDataSource(chartId).getFields(), + }; + } + + /** + * Handle a spreadsheet command + * + * @param {Object} cmd Command + */ + handle(cmd) { + switch (cmd.type) { + case "CREATE_CHART": { + switch (cmd.definition.type) { + case "odoo_pie": + case "odoo_bar": + case "odoo_line": + this._addOdooChart(cmd.id); + break; + } + break; + } + case "UPDATE_CHART": { + switch (cmd.definition.type) { + case "odoo_pie": + case "odoo_bar": + case "odoo_line": + this._setChartDataSource(cmd.id); + break; + } + break; + } + case "DELETE_FIGURE": { + const charts = { ...this.charts }; + delete charts[cmd.id]; + this.history.update("charts", charts); + break; + } + case "REMOVE_GLOBAL_FILTER": + this._onFilterDeletion(cmd.id); + break; + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + if (cmd.chart) { + this._setOdooChartFieldMatching(cmd.filter.id, cmd.chart); + } + break; + } + } + + // ------------------------------------------------------------------------- + // Getters + // ------------------------------------------------------------------------- + + /** + * Get all the odoo chart ids + * @returns {Array} + */ + getOdooChartIds() { + const ids = []; + for (const sheetId of this.getters.getSheetIds()) { + ids.push( + ...this.getters + .getChartIds(sheetId) + .filter((id) => this.getters.getChartType(id).startsWith("odoo_")) + ); + } + return ids; + } + + /** + * @param {string} chartId + * @returns {string} + */ + getChartFieldMatch(chartId) { + return this.charts[chartId].fieldMatching; + } + + /** + * @param {string} id + * @returns {ChartDataSource|undefined} + */ + getChartDataSource(id) { + const dataSourceId = this.charts[id].dataSourceId; + return this.dataSources.get(dataSourceId); + } + + /** + * + * @param {string} chartId + * @returns {string} + */ + getOdooChartDisplayName(chartId) { + return this.getters.getChart(chartId).title; + } + + /** + * Import the pivots + * + * @param {Object} data + */ + import(data) { + for (const sheet of data.sheets) { + if (sheet.figures) { + for (const figure of sheet.figures) { + if (figure.tag === "chart" && figure.data.type.startsWith("odoo_")) { + this._addOdooChart(figure.id, figure.data.fieldMatching); + } + } + } + } + } + /** + * Export the pivots + * + * @param {Object} data + */ + export(data) { + for (const sheet of data.sheets) { + if (sheet.figures) { + for (const figure of sheet.figures) { + if (figure.tag === "chart" && figure.data.type.startsWith("odoo_")) { + figure.data.fieldMatching = this.getChartFieldMatch(figure.id); + } + } + } + } + } + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + /** + * + * @return {Promise[]} + */ + getOdooChartsWaitForReady() { + return this.getOdooChartIds().map((chartId) => + this.getChartDataSource(chartId).loadMetadata() + ); + } + + /** + * Get the current pivotFieldMatching of a chart + * + * @param {string} chartId + * @param {string} filterId + */ + getOdooChartFieldMatching(chartId, filterId) { + return this.charts[chartId].fieldMatching[filterId]; + } + + /** + * Sets the current pivotFieldMatching of a chart + * + * @param {string} chartId + * @param {string} filterId + * @param {FieldMatching} fieldMatching + */ + _setOdooChartFieldMatching(filterId, chartFieldMatches) { + const charts = { ...this.charts }; + for (const [chartId, fieldMatch] of Object.entries(chartFieldMatches)) { + charts[chartId].fieldMatching[filterId] = fieldMatch; + } + this.history.update("charts", charts); + } + + _onFilterDeletion(filterId) { + const charts = { ...this.charts }; + for (const chartId in charts) { + this.history.update("charts", chartId, "fieldMatching", filterId, undefined); + } + } + + /** + * @param {string} chartId + * @param {string} dataSourceId + */ + _addOdooChart(chartId, fieldMatching = {}) { + const dataSourceId = this.uuidGenerator.uuidv4(); + const charts = { ...this.charts }; + charts[chartId] = { + dataSourceId, + fieldMatching, + }; + const definition = this.getters.getChart(chartId).getDefinitionForDataSource(); + if (!this.dataSources.contains(dataSourceId)) { + this.dataSources.add(dataSourceId, ChartDataSource, definition); + } + this.history.update("charts", charts); + this._setChartDataSource(chartId); + } + + /** + * Sets the catasource on the corresponding chart + * @param {string} chartId + */ + _setChartDataSource(chartId) { + const chart = this.getters.getChart(chartId); + chart.setDataSource(this.getters.getChartDataSource(chartId)); + } +} + +OdooChartCorePlugin.getters = [ + "getChartDataSource", + "getOdooChartIds", + "getChartFieldMatch", + "getOdooChartDisplayName", + "getOdooChartFieldMatching", +]; diff --git a/addons/spreadsheet/static/src/chart/plugins/odoo_chart_ui_plugin.js b/addons/spreadsheet/static/src/chart/plugins/odoo_chart_ui_plugin.js new file mode 100644 index 00000000000..ee40fbab57e --- /dev/null +++ b/addons/spreadsheet/static/src/chart/plugins/odoo_chart_ui_plugin.js @@ -0,0 +1,70 @@ +/** @odoo-module */ + +import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended"; +import { Domain } from "@web/core/domain"; + +const { UIPlugin } = spreadsheet; + +export default class OdooChartUIPlugin extends UIPlugin { + beforeHandle(cmd) { + switch (cmd.type) { + case "START": + // make sure the domains are correctly set before + // any evaluation + this._addDomains(); + break; + } + } + + /** + * Handle a spreadsheet command + * + * @param {Object} cmd Command + */ + handle(cmd) { + switch (cmd.type) { + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + case "SET_GLOBAL_FILTER_VALUE": + case "CLEAR_GLOBAL_FILTER_VALUE": + this._addDomains(); + break; + } + } + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + /** + * Add an additional domain to a chart + * + * @private + * + * @param {string} chartId chart id + */ + _addDomain(chartId) { + const domainList = []; + for (const [filterId, fieldMatch] of Object.entries( + this.getters.getChartFieldMatch(chartId) + )) { + domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch)); + } + const domain = Domain.combine(domainList, "AND").toString(); + this.getters.getChartDataSource(chartId).addDomain(domain); + } + + /** + * Add an additional domain to all chart + * + * @private + * + */ + _addDomains() { + for (const chartId of this.getters.getOdooChartIds()) { + this._addDomain(chartId); + } + } +} + +OdooChartUIPlugin.getters = []; diff --git a/addons/spreadsheet/static/src/chart/plugin/operational_transform.js b/addons/spreadsheet/static/src/chart/plugins/operational_transform.js similarity index 100% rename from addons/spreadsheet/static/src/chart/plugin/operational_transform.js rename to addons/spreadsheet/static/src/chart/plugins/operational_transform.js diff --git a/addons/spreadsheet/static/src/data_sources/metadata_repository.js b/addons/spreadsheet/static/src/data_sources/metadata_repository.js index 272cace0eb6..ee4f2ee3b5c 100644 --- a/addons/spreadsheet/static/src/data_sources/metadata_repository.js +++ b/addons/spreadsheet/static/src/data_sources/metadata_repository.js @@ -14,6 +14,7 @@ const { EventBus } = owl; * @property {string} type field type * @property {string} string display name * @property {string} [relation] related model technical name (only for relational fields) + * @property {boolean} [searchable] true if a field can be searched in database */ /** * This class is used to provide facilities to fetch some common data. It's diff --git a/addons/spreadsheet/static/src/data_sources/server_data.js b/addons/spreadsheet/static/src/data_sources/server_data.js index 8d56406c646..1190a0f5426 100644 --- a/addons/spreadsheet/static/src/data_sources/server_data.js +++ b/addons/spreadsheet/static/src/data_sources/server_data.js @@ -149,7 +149,7 @@ export class ServerData { * Returns the request result if cached or the associated promise * @param {string} resModel * @param {string} method - * @param {unknown[]} args + * @param {unknown[]} [args] * @returns {Promise} */ async fetch(resModel, method, args) { diff --git a/addons/spreadsheet/static/src/global_filters/index.js b/addons/spreadsheet/static/src/global_filters/index.js index 7ef8fa91302..07e057699f3 100644 --- a/addons/spreadsheet/static/src/global_filters/index.js +++ b/addons/spreadsheet/static/src/global_filters/index.js @@ -1,10 +1,9 @@ /** @odoo-module */ import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; -import "@spreadsheet/pivot"; // filter depends on pivot and lists for its getters -import "@spreadsheet/list"; // filter depends on pivot and lists for its getters + import GlobalFiltersUIPlugin from "./plugins/global_filters_ui_plugin"; -import GlobalFiltersCorePlugin from "./plugins/global_filters_core_plugin"; +import { GlobalFiltersCorePlugin } from "./plugins/global_filters_core_plugin"; const { inverseCommandRegistry } = spreadsheet.registries; function identity(cmd) { @@ -12,10 +11,6 @@ function identity(cmd) { } const { coreTypes, invalidateEvaluationCommands, readonlyAllowedCommands } = spreadsheet; -const { corePluginRegistry, uiPluginRegistry } = spreadsheet.registries; - -corePluginRegistry.add("OdooGlobalFiltersCorePlugin", GlobalFiltersCorePlugin); -uiPluginRegistry.add("OdooGlobalFiltersUIPlugin", GlobalFiltersUIPlugin); coreTypes.add("ADD_GLOBAL_FILTER"); coreTypes.add("EDIT_GLOBAL_FILTER"); @@ -30,6 +25,7 @@ invalidateEvaluationCommands.add("CLEAR_GLOBAL_FILTER_VALUE"); readonlyAllowedCommands.add("SET_GLOBAL_FILTER_VALUE"); readonlyAllowedCommands.add("SET_MANY_GLOBAL_FILTER_VALUE"); readonlyAllowedCommands.add("CLEAR_GLOBAL_FILTER_VALUE"); +readonlyAllowedCommands.add("UPDATE_OBJECT_DOMAINS"); inverseCommandRegistry .add("EDIT_GLOBAL_FILTER", identity) @@ -50,3 +46,5 @@ inverseCommandRegistry }, ]; }); + +export { GlobalFiltersCorePlugin, GlobalFiltersUIPlugin }; diff --git a/addons/spreadsheet/static/src/global_filters/plugins/global_filters_core_plugin.js b/addons/spreadsheet/static/src/global_filters/plugins/global_filters_core_plugin.js index eec2ee6007e..a657dc64f80 100644 --- a/addons/spreadsheet/static/src/global_filters/plugins/global_filters_core_plugin.js +++ b/addons/spreadsheet/static/src/global_filters/plugins/global_filters_core_plugin.js @@ -13,21 +13,21 @@ * @property {string} id * @property {string} label * @property {string} type "text" | "date" | "relation" - * @property {RangeType} rangeType - * @property {Record} pivotFields - * @property {Record} listFields - * @property {Record} graphFields + * @property {RangeType} [rangeType] + * @property {boolean} [defaultsToCurrentPeriod] * @property {string|Array|Object} defaultValue Default Value * @property {number} [modelID] ID of the related model * @property {string} [modelName] Name of the related model */ +export const globalFiltersFieldMatchers = {}; + import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; import CommandResult from "@spreadsheet/o_spreadsheet/cancelled_reason"; import { checkFiltersTypeValueCombination } from "@spreadsheet/global_filters/helpers"; import { _t } from "@web/core/l10n/translation"; -export default class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin { +export class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin { constructor() { super(...arguments); /** @type {Object.} */ @@ -78,15 +78,6 @@ export default class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin { case "REMOVE_GLOBAL_FILTER": this._removeGlobalFilter(cmd.id); break; - case "REMOVE_PIVOT": - this._removeMatchingField("pivot", cmd.pivotId); - break; - case "REMOVE_ODOO_LIST": - this._removeMatchingField("list", cmd.listId); - break; - case "DELETE_FIGURE": - this._removeMatchingField("graph", cmd.id); - break; } } @@ -104,39 +95,6 @@ export default class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin { return this.globalFilters[id]; } - /** - * Get the field name on which the global filter is applied for a pivot - * - * @param {string} filterId Id of the filter - * @param {string} pivotId Id of the pivot - * @returns {FilterMatchingField|undefined} - */ - getGlobalFilterFieldPivot(filterId, pivotId) { - return this.getGlobalFilter(filterId).pivotFields[pivotId]; - } - - /** - * Get the field name on which the global filter is applied for a list - * - * @param {string} filterId Id of the filter - * @param {string} listId Id of the list - * @returns {FilterMatchingField|undefined} - */ - getGlobalFilterFieldList(filterId, listId) { - return this.getGlobalFilter(filterId).listFields[listId]; - } - - /** - * Get the field name on which the global filter is applied for a graph - * - * @param {string} filterId Id of the filter - * @param {string} graphId Id of the Graph - * @returns {FilterMatchingField|undefined} - */ - getGlobalFilterFieldGraph(filterId, graphId) { - return this.getGlobalFilter(filterId).graphFields[graphId]; - } - /** * Get the global filter with the given name * @@ -210,22 +168,6 @@ export default class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin { } } - /** - * @param {"pivot" | "list" | "graph"} dataSourceType - * @param {string} dataSourceId - */ - _removeMatchingField(dataSourceType, dataSourceId) { - for (const filter of this.getGlobalFilters()) { - this.history.update( - "globalFilters", - filter.id, - `${dataSourceType}Fields`, - dataSourceId, - undefined - ); - } - } - // --------------------------------------------------------------------- // Import/Export // --------------------------------------------------------------------- @@ -307,7 +249,4 @@ GlobalFiltersCorePlugin.getters = [ "getGlobalFilters", "getGlobalFilterDefaultValue", "getGlobalFilterLabel", - "getGlobalFilterFieldPivot", - "getGlobalFilterFieldList", - "getGlobalFilterFieldGraph", ]; diff --git a/addons/spreadsheet/static/src/global_filters/plugins/global_filters_ui_plugin.js b/addons/spreadsheet/static/src/global_filters/plugins/global_filters_ui_plugin.js index 7eacc8a8646..9994681f70b 100644 --- a/addons/spreadsheet/static/src/global_filters/plugins/global_filters_ui_plugin.js +++ b/addons/spreadsheet/static/src/global_filters/plugins/global_filters_ui_plugin.js @@ -3,6 +3,12 @@ /** * @typedef {import("@spreadsheet/data_sources/metadata_repository").Field} Field * @typedef {import("./global_filters_core_plugin").GlobalFilter} GlobalFilter + * + * @typedef {Object} FieldMatching + * @property {string} chain + * @property {string} type Type of the last field of the chain + * @property {number} [offset] offset to apply to the field (for date filters) + * */ import { _t } from "@web/core/l10n/translation"; @@ -73,16 +79,6 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { return CommandResult.Success; } - beforeHandle(cmd) { - switch (cmd.type) { - case "START": - // make sure the domains are correctly set before - // any evaluation - this._updateAllDomains(); - break; - } - } - /** * Handle a spreadsheet command * @@ -124,21 +120,31 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { this._clearGlobalFilterValue(cmd.id); break; } - switch (cmd.type) { - case "ADD_GLOBAL_FILTER": - case "EDIT_GLOBAL_FILTER": - case "SET_GLOBAL_FILTER_VALUE": - case "REMOVE_GLOBAL_FILTER": - case "CLEAR_GLOBAL_FILTER_VALUE": - this._updateAllDomains(); - break; - } } // ------------------------------------------------------------------------- // Getters // ------------------------------------------------------------------------- + /** + * @param {string} filterId + * @param {FieldMatching} fieldMatching + * + * @return {Domain} + */ + getGlobalFilterDomain(filterId, fieldMatching) { + /** @type {GlobalFilter} */ + const filter = this.getters.getGlobalFilter(filterId); + switch (filter.type) { + case "text": + return this._getTextDomain(filter, fieldMatching); + case "date": + return this._getDateDomain(filter, fieldMatching); + case "relation": + return this._getRelationDomain(filter, fieldMatching); + } + } + /** * Get the current value of a global filter * @@ -301,32 +307,6 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { this.values[id] = { value, rangeType }; } - /** - * Update the domain of all pivots and all lists - * - * @private - */ - _updateAllDomains() { - for (const pivotId of this.getters.getPivotIds()) { - const domain = this._getComputedDomain((filterId) => - this.getters.getGlobalFilterFieldPivot(filterId, pivotId) - ); - this.dispatch("ADD_PIVOT_DOMAIN", { id: pivotId, domain }); - } - for (const listId of this.getters.getListIds()) { - const domain = this._getComputedDomain((filterId) => - this.getters.getGlobalFilterFieldList(filterId, listId) - ); - this.dispatch("ADD_LIST_DOMAIN", { id: listId, domain }); - } - for (const chartId of this.getters.getOdooChartIds()) { - const domain = this._getComputedDomain((filterId) => - this.getters.getGlobalFilterFieldGraph(filterId, chartId) - ); - this.dispatch("ADD_GRAPH_DOMAIN", { id: chartId, domain }); - } - } - // ------------------------------------------------------------------------- // Private // ------------------------------------------------------------------------- @@ -337,19 +317,19 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { * @private * * @param {GlobalFilter} filter - * @param {Field} fieldDesc + * @param {FieldMatching} fieldMatching * * @returns {Domain|undefined} */ - _getDateDomain(filter, fieldDesc) { + _getDateDomain(filter, fieldMatching) { if (!this.isFilterActive(filter.id)) { return undefined; } let granularity; const value = this.getGlobalFilterValue(filter.id); - const field = fieldDesc.field; - const type = fieldDesc.type; - const offset = fieldDesc.offset || 0; + const field = fieldMatching.chain; + const type = fieldMatching.type; + const offset = fieldMatching.offset || 0; const now = DateTime.local(); if (filter.rangeType === "relative") { @@ -393,16 +373,16 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { * @private * * @param {GlobalFilter} filter - * @param {Field} fieldDesc + * @param {FieldMatching} fieldMatching * * @returns {Domain|undefined} */ - _getTextDomain(filter, fieldDesc) { + _getTextDomain(filter, fieldMatching) { const value = this.getGlobalFilterValue(filter.id); - if (!value) { + if (!value || !fieldMatching.chain) { return undefined; } - const field = fieldDesc.field; + const field = fieldMatching.chain; return new Domain([[field, "ilike", value]]); } @@ -412,56 +392,19 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { * @private * * @param {GlobalFilter} filter - * @param {Field} fieldDesc + * @param {FieldMatching} fieldMatching * * @returns {Domain|undefined} */ - _getRelationDomain(filter, fieldDesc) { + _getRelationDomain(filter, fieldMatching) { const values = this.getGlobalFilterValue(filter.id); - if (!values || values.length === 0) { + if (!values || values.length === 0 || !fieldMatching.chain) { return undefined; } - const field = fieldDesc.field; + const field = fieldMatching.chain; return new Domain([[field, "in", values]]); } - /** - * Get the computed domain of a global filters applied to the field given - * by the callback - * - * @private - * - * @param {Function} getFieldDesc Callback to get the field description - * on which the global filters is applied - * - * @returns {string} - */ - _getComputedDomain(getFieldDesc) { - let domain = new Domain([]); - for (const filter of this.getters.getGlobalFilters()) { - const fieldDesc = getFieldDesc(filter.id); - if (!fieldDesc || !fieldDesc.field) { - continue; - } - let domainToAdd = undefined; - switch (filter.type) { - case "date": - domainToAdd = this._getDateDomain(filter, fieldDesc); - break; - case "text": - domainToAdd = this._getTextDomain(filter, fieldDesc); - break; - case "relation": - domainToAdd = this._getRelationDomain(filter, fieldDesc); - break; - } - if (domainToAdd) { - domain = Domain.and([domain, domainToAdd]); - } - } - return domain.toString(); - } - /** * Adds all active filters (and their values) at the time of export in a dedicated sheet * @@ -508,6 +451,7 @@ export default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin { GlobalFiltersUIPlugin.getters = [ "getFilterDisplayValue", + "getGlobalFilterDomain", "getGlobalFilterValue", "getActiveFilterCount", "isFilterActive", diff --git a/addons/spreadsheet/static/src/index.js b/addons/spreadsheet/static/src/index.js new file mode 100644 index 00000000000..b8463e88c20 --- /dev/null +++ b/addons/spreadsheet/static/src/index.js @@ -0,0 +1,40 @@ +/** @odoo-module */ + +/** + * This file is meant to load the different subparts of the module + * to guarantee their plugins are loaded in the right order + * + * dependency: + * other plugins + * | + * ... + * | + * filters + * /\ \ + * / \ \ + * pivot list Odoo chart + */ + +/** TODO: Introduce a position parameter to the plugin registry in order to load them in a specific order */ +import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; +const { corePluginRegistry, uiPluginRegistry } = spreadsheet.registries; + +import { GlobalFiltersCorePlugin, GlobalFiltersUIPlugin } from "@spreadsheet/global_filters/index"; +import { PivotCorePlugin, PivotUIPlugin } from "@spreadsheet/pivot/index"; // list depends on filter for its getters +import { ListCorePlugin, ListUIPlugin } from "@spreadsheet/list/index"; // pivot depends on filter for its getters +import { + ChartOdooMenuPlugin, + OdooChartCorePlugin, + OdooChartUIPlugin, +} from "@spreadsheet/chart/index"; // Odoochart depends on filter for its getters + +corePluginRegistry.add("OdooGlobalFiltersCorePlugin", GlobalFiltersCorePlugin); +corePluginRegistry.add("OdooPivotCorePlugin", PivotCorePlugin); +corePluginRegistry.add("OdooListCorePlugin", ListCorePlugin); +corePluginRegistry.add("odooChartCorePlugin", OdooChartCorePlugin); +corePluginRegistry.add("chartOdooMenuPlugin", ChartOdooMenuPlugin); + +uiPluginRegistry.add("OdooGlobalFiltersUIPlugin", GlobalFiltersUIPlugin); +uiPluginRegistry.add("OdooPivotUIPlugin", PivotUIPlugin); +uiPluginRegistry.add("OdooListUIPlugin", ListUIPlugin); +uiPluginRegistry.add("odooChartUIPlugin", OdooChartUIPlugin); diff --git a/addons/spreadsheet/static/src/list/index.js b/addons/spreadsheet/static/src/list/index.js index 24a7d48893c..e7c183c6cb6 100644 --- a/addons/spreadsheet/static/src/list/index.js +++ b/addons/spreadsheet/static/src/list/index.js @@ -1,5 +1,4 @@ /** @odoo-module */ - import { _lt } from "@web/core/l10n/translation"; import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; @@ -16,24 +15,19 @@ function identity(cmd) { return [cmd]; } -const { coreTypes, readonlyAllowedCommands, invalidateEvaluationCommands } = spreadsheet; -const { corePluginRegistry, uiPluginRegistry, cellMenuRegistry } = spreadsheet.registries; - -corePluginRegistry.add("OdooListCorePlugin", ListCorePlugin); - -uiPluginRegistry.add("OdooListUIPlugin", ListUIPlugin); +const { coreTypes, invalidateEvaluationCommands } = spreadsheet; +const { cellMenuRegistry } = spreadsheet.registries; coreTypes.add("INSERT_ODOO_LIST"); coreTypes.add("RENAME_ODOO_LIST"); coreTypes.add("REMOVE_ODOO_LIST"); coreTypes.add("RE_INSERT_ODOO_LIST"); coreTypes.add("UPDATE_ODOO_LIST_DOMAIN"); +coreTypes.add("ADD_LIST_DOMAIN"); invalidateEvaluationCommands.add("UPDATE_ODOO_LIST_DOMAIN"); invalidateEvaluationCommands.add("REMOVE_ODOO_LIST"); -readonlyAllowedCommands.add("ADD_LIST_DOMAIN"); - cellMenuRegistry.add("list_see_record", { name: _lt("See record"), sequence: 200, @@ -53,3 +47,5 @@ inverseCommandRegistry .add("RE_INSERT_ODOO_LIST", identity) .add("RENAME_ODOO_LIST", identity) .add("REMOVE_ODOO_LIST", identity); + +export { ListCorePlugin, ListUIPlugin }; diff --git a/addons/spreadsheet/static/src/list/plugins/list_core_plugin.js b/addons/spreadsheet/static/src/list/plugins/list_core_plugin.js index 74e1a5eae15..0c24c16e737 100644 --- a/addons/spreadsheet/static/src/list/plugins/list_core_plugin.js +++ b/addons/spreadsheet/static/src/list/plugins/list_core_plugin.js @@ -6,6 +6,8 @@ import { getMaxObjectId } from "../../helpers/helpers"; import ListDataSource from "../list_data_source"; import { TOP_LEVEL_STYLE } from "../../helpers/constants"; import { _t } from "@web/core/l10n/translation"; +import { globalFiltersFieldMatchers } from "@spreadsheet/global_filters/plugins/global_filters_core_plugin"; +import { sprintf } from "@web/core/utils/strings"; /** * @typedef {Object} ListDefinition @@ -17,15 +19,35 @@ import { _t } from "@web/core/l10n/translation"; * @property {string} name Name of the list * @property {Array} orderBy * + * @typedef {Object} List + * @property {string} id + * @property {string} dataSourceId + * @property {ListDefinition} definition + * @property {Object} fieldMatching + * + * @typedef {import("@spreadsheet/global_filters/plugins/global_filters_ui_plugin").FieldMatching} FieldMatching */ -export default class ListCorePlugin extends spreadsheet.CorePlugin { + +const { CorePlugin } = spreadsheet; + +export default class ListCorePlugin extends CorePlugin { constructor(getters, history, range, dispatch, config, uuidGenerator) { super(getters, history, range, dispatch, config, uuidGenerator); this.dataSources = config.dataSources; this.nextId = 1; - /** @type {Object.} */ + /** @type {Object.} */ this.lists = {}; + + globalFiltersFieldMatchers["list"] = { + geIds: () => this.getters.getListIds(), + getDisplayName: (listId) => this.getters.getListName(listId), + getTag: (listId) => sprintf(_t("List #%s"), listId), + getFieldMatching: (listId, filterId) => this.getListFieldMatching(listId, filterId), + waitForReady: () => this.getListsWaitForReady(), + getModel: (listId) => this.getListDefinition(listId).model, + getFields: (listId) => this.getListDataSource(listId).getFields(), + }; } allowDispatch(cmd) { @@ -114,6 +136,15 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { } break; } + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + if (cmd.list) { + this._setListFieldMatching(cmd.filter.id, cmd.list); + } + break; + case "REMOVE_GLOBAL_FILTER": + this._onFilterDeletion(cmd.id); + break; } } @@ -122,8 +153,8 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { // ------------------------------------------------------------------------- /** - * @param {number} id - * @returns {import("@spreadsheet/list/list_data_source").ListDataSource|undefined} + * @param {string} id + * @returns {import("@spreadsheet/list/list_data_source").default|undefined} */ getListDataSource(id) { const dataSourceId = this.lists[id].dataSourceId; @@ -131,7 +162,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { } /** - * @param {number} id + * @param {string} id * @returns {string} */ getListDisplayName(id) { @@ -139,7 +170,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { } /** - * @param {number} id + * @param {string} id * @returns {string} */ getListName(id) { @@ -147,8 +178,16 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { } /** - * @param {number} id - * @returns {Promise} + * @param {string} id + * @returns {string} + */ + getListFieldMatch(id) { + return this.lists[id].fieldMatching; + } + + /** + * @param {string} id + * @returns {Promise} */ async getAsyncListDataSource(id) { const dataSourceId = this.lists[id].dataSourceId; @@ -175,7 +214,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { } /** - * @param {number} id + * @param {string} id * @returns {ListDefinition} */ getListDefinition(id) { @@ -194,7 +233,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { /** * Check if an id is an id of an existing list * - * @param {number} id Id of the list + * @param {string} id Id of the list * * @returns {boolean} */ @@ -206,12 +245,53 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { // Private // --------------------------------------------------------------------- - _addList(id, definition, dataSourceId, limit) { + /** + * + * @return {Promise[]} + */ + getListsWaitForReady() { + return this.getListIds().map((ListId) => this.getListDataSource(ListId).loadMetadata()); + } + + /** + * Get the current FieldMatching on a list + * + * @param {string} listId + * @param {string} filterId + */ + getListFieldMatching(listId, filterId) { + return this.lists[listId].fieldMatching[filterId]; + } + + /** + * Sets the current FieldMatching on a list + * + * @param {string} listId + * @param {string} filterId + * @param {FieldMatching} fieldMatching + */ + _setListFieldMatching(filterId, listFieldMatches) { + const lists = { ...this.lists }; + for (const [listId, fieldMatch] of Object.entries(listFieldMatches)) { + lists[listId].fieldMatching[filterId] = fieldMatch; + } + this.history.update("lists", lists); + } + + _onFilterDeletion(filterId) { + const lists = { ...this.lists }; + for (const listId in lists) { + this.history.update("lists", listId, "fieldMatching", filterId, undefined); + } + } + + _addList(id, definition, dataSourceId, limit, fieldMatching = {}) { const lists = { ...this.lists }; lists[id] = { id, definition, dataSourceId, + fieldMatching, }; if (!this.dataSources.contains(dataSourceId)) { @@ -338,7 +418,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { }, name: list.name, }; - this._addList(id, definition, this.uuidGenerator.uuidv4(), 0); + this._addList(id, definition, this.uuidGenerator.uuidv4(), 0, list.fieldMatching); } } this.nextId = data.listNextId || getMaxObjectId(this.lists) + 1; @@ -352,6 +432,7 @@ export default class ListCorePlugin extends spreadsheet.CorePlugin { data.lists = {}; for (const id in this.lists) { data.lists[id] = JSON.parse(JSON.stringify(this.getListDefinition(id))); + data.lists[id].fieldMatching = this.lists[id].fieldMatching; } data.listNextId = this.nextId; } @@ -366,4 +447,6 @@ ListCorePlugin.getters = [ "getListName", "getNextListId", "isExistingList", + "getListFieldMatch", + "getListFieldMatching", ]; diff --git a/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js b/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js index b91cdfc2edd..3bda35f5955 100644 --- a/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js +++ b/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js @@ -2,6 +2,7 @@ import spreadsheet from "../../o_spreadsheet/o_spreadsheet_extended"; import { getFirstListFunction } from "../list_helpers"; +import { Domain } from "@web/core/domain"; const { astToFormula } = spreadsheet; @@ -17,6 +18,16 @@ export default class ListUIPlugin extends spreadsheet.UIPlugin { this.env = config.evalContext.env; } + beforeHandle(cmd) { + switch (cmd.type) { + case "START": + // make sure the domains are correctly set before + // any evaluation + this._addDomains(); + break; + } + } + /** * Handle a spreadsheet command * @param {Object} cmd Command @@ -26,15 +37,17 @@ export default class ListUIPlugin extends spreadsheet.UIPlugin { case "SELECT_ODOO_LIST": this._selectList(cmd.listId); break; - case "ADD_LIST_DOMAIN": - this._addDomain(cmd.id, cmd.domain); - break; case "REFRESH_ODOO_LIST": this._refreshOdooList(cmd.listId); break; case "REFRESH_ALL_DATA_SOURCES": this._refreshOdooLists(); break; + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + case "SET_GLOBAL_FILTER_VALUE": + case "CLEAR_GLOBAL_FILTER_VALUE": + this._addDomains(); } } @@ -47,13 +60,32 @@ export default class ListUIPlugin extends spreadsheet.UIPlugin { * * @private * - * @param {string} listId pivot id - * @param {Array>} domain + * @param {string} listId list id + * */ - _addDomain(listId, domain) { + _addDomain(listId) { + const domainList = []; + for (const [filterId, fieldMatch] of Object.entries( + this.getters.getListFieldMatch(listId) + )) { + domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch)); + } + const domain = Domain.combine(domainList, "AND").toString(); this.getters.getListDataSource(listId).addDomain(domain); } + /** + * Add an additional domain to all lists + * + * @private + * + */ + _addDomains() { + for (const listId of this.getters.getListIds()) { + this._addDomain(listId); + } + } + /** * Refresh the cache of a list * @param {string} listId Id of the list diff --git a/addons/spreadsheet/static/src/o_spreadsheet/migration.js b/addons/spreadsheet/static/src/o_spreadsheet/migration.js index ba09a8f3771..0315db35ba1 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/migration.js +++ b/addons/spreadsheet/static/src/o_spreadsheet/migration.js @@ -4,7 +4,7 @@ import spreadsheet from "./o_spreadsheet_extended"; const { load, CorePlugin, tokenize, parse, convertAstNodes, astToFormula } = spreadsheet; const { corePluginRegistry } = spreadsheet.registries; -export const ODOO_VERSION = 4; +export const ODOO_VERSION = 5; const MAP = { PIVOT: "ODOO.PIVOT", @@ -32,6 +32,9 @@ export function migrate(data) { if (version < 4) { _data = migrate3to4(_data); } + if (version < 5) { + _data = migrate4to5(_data); + } return _data; } @@ -130,6 +133,57 @@ function migrate3to4(data) { return data; } +function migrate4to5(data) { + for (const filter of data.globalFilters || []) { + for (const [id, fm] of Object.entries(filter.pivotFields || {})) { + if (!(data.pivots && id in data.pivots)) { + delete filter.pivotFields[id]; + continue; + } + if (!data.pivots[id].fieldMatching) { + data.pivots[id].fieldMatching = {}; + } + data.pivots[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type }; + } + delete filter.pivotFields; + + for (const [id, fm] of Object.entries(filter.listFields || {})) { + if (!(data.lists && id in data.lists)) { + delete filter.listFields[id]; + continue; + } + if (!data.lists[id].fieldMatching) { + data.lists[id].fieldMatching = {}; + } + data.lists[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type }; + } + delete filter.listFields; + + const findFigureFromId = (id) => { + for (const sheet of data.sheets) { + const fig = sheet.figures.find((f) => f.id === id); + if (fig) { + return fig; + } + } + return undefined; + }; + for (const [id, fm] of Object.entries(filter.graphFields || {})) { + const figure = findFigureFromId(id); + if (!figure) { + delete filter.graphFields[id]; + continue; + } + if (!figure.data.fieldMatching) { + figure.data.fieldMatching = {}; + } + figure.data.fieldMatching[filter.id] = { chain: fm.field, type: fm.type }; + } + delete filter.graphFields; + } + return data; +} + /** * Convert pivot formulas days parameters from day/month/year * format to the standard spreadsheet month/day/year format. diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.js index 8687174c962..736467c10d5 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.js +++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.js @@ -4,7 +4,7 @@ const spreadsheet = window.o_spreadsheet; export const initCallbackRegistry = new spreadsheet.Registry(); import { _t } from "@web/core/l10n/translation"; -window.o_spreadsheet.setTranslationMethod(_t); +spreadsheet.setTranslationMethod(_t); // export * from spreadsheet ? export default spreadsheet; diff --git a/addons/spreadsheet/static/src/pivot/index.js b/addons/spreadsheet/static/src/pivot/index.js index dd62f2e9299..15364e820f0 100644 --- a/addons/spreadsheet/static/src/pivot/index.js +++ b/addons/spreadsheet/static/src/pivot/index.js @@ -1,5 +1,4 @@ /** @odoo-module */ - import { _lt } from "@web/core/l10n/translation"; import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; @@ -9,8 +8,8 @@ import PivotUIPlugin from "./plugins/pivot_ui_plugin"; import { SEE_RECORDS_PIVOT, SEE_RECORDS_PIVOT_VISIBLE } from "./pivot_actions"; -const { coreTypes, readonlyAllowedCommands, invalidateEvaluationCommands } = spreadsheet; -const { corePluginRegistry, uiPluginRegistry, cellMenuRegistry } = spreadsheet.registries; +const { coreTypes, invalidateEvaluationCommands } = spreadsheet; +const { cellMenuRegistry } = spreadsheet.registries; const { inverseCommandRegistry } = spreadsheet.registries; @@ -18,10 +17,6 @@ function identity(cmd) { return [cmd]; } -corePluginRegistry.add("OdooPivotCorePlugin", PivotCorePlugin); - -uiPluginRegistry.add("OdooPivotUIPlugin", PivotUIPlugin); - coreTypes.add("INSERT_PIVOT"); coreTypes.add("RENAME_ODOO_PIVOT"); coreTypes.add("REMOVE_PIVOT"); @@ -31,8 +26,6 @@ coreTypes.add("UPDATE_ODOO_PIVOT_DOMAIN"); invalidateEvaluationCommands.add("UPDATE_ODOO_PIVOT_DOMAIN"); invalidateEvaluationCommands.add("REMOVE_PIVOT"); -readonlyAllowedCommands.add("ADD_PIVOT_DOMAIN"); - cellMenuRegistry.add("pivot_see_records", { name: _lt("See records"), sequence: 175, @@ -52,3 +45,5 @@ inverseCommandRegistry .add("REMOVE_PIVOT", identity) .add("UPDATE_ODOO_PIVOT_DOMAIN", identity) .add("RE_INSERT_PIVOT", identity); + +export { PivotCorePlugin, PivotUIPlugin }; diff --git a/addons/spreadsheet/static/src/pivot/plugins/pivot_core_plugin.js b/addons/spreadsheet/static/src/pivot/plugins/pivot_core_plugin.js index 285faee558d..404319697e0 100644 --- a/addons/spreadsheet/static/src/pivot/plugins/pivot_core_plugin.js +++ b/addons/spreadsheet/static/src/pivot/plugins/pivot_core_plugin.js @@ -1,6 +1,8 @@ /** @odoo-module */ /** + * @typedef {import("@spreadsheet/global_filters/plugins/global_filters_ui_plugin").FieldMatching} FieldMatching + * * @typedef {Object} PivotDefinition * @property {Array} colGroupBys * @property {Array} rowGroupBys @@ -13,10 +15,13 @@ * @property {Object | null} sortedColumn * * @typedef {Object} Pivot - * @property {number} id + * @property {string} id * @property {string} dataSourceId * @property {PivotDefinition} definition + * @property {Object} fieldMatching + * */ + import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; import { makePivotFormula } from "../pivot_helpers"; import { getMaxObjectId } from "@spreadsheet/helpers/helpers"; @@ -25,6 +30,8 @@ import PivotDataSource from "../pivot_data_source"; import { SpreadsheetPivotTable } from "../pivot_table"; import CommandResult from "../../o_spreadsheet/cancelled_reason"; import { _t } from "@web/core/l10n/translation"; +import { globalFiltersFieldMatchers } from "@spreadsheet/global_filters/plugins/global_filters_core_plugin"; +import { sprintf } from "@web/core/utils/strings"; const { CorePlugin } = spreadsheet; @@ -34,8 +41,17 @@ export default class PivotCorePlugin extends CorePlugin { this.dataSources = config.dataSources; this.nextId = 1; - /** @type {Object.} */ + /** @type {Object.} */ this.pivots = {}; + globalFiltersFieldMatchers["pivot"] = { + geIds: () => this.getters.getPivotIds(), + getDisplayName: (pivotId) => this.getters.getPivotName(pivotId), + getTag: (pivotId) => sprintf(_t("Pivot #%s"), pivotId), + getFieldMatching: (pivotId, filterId) => this.getPivotFieldMatching(pivotId, filterId), + waitForReady: () => this.getPivotsWaitForReady(), + getModel: (pivotId) => this.getPivotDefinition(pivotId).model, + getFields: (pivotId) => this.getPivotDataSource(pivotId).getFields(), + }; } allowDispatch(cmd) { @@ -53,6 +69,9 @@ export default class PivotCorePlugin extends CorePlugin { return CommandResult.InvalidNextId; } break; + case "REMOVE_GLOBAL_FILTER": + this._removeGlobalFilter(cmd.id); + break; } return CommandResult.Success; } @@ -118,6 +137,15 @@ export default class PivotCorePlugin extends CorePlugin { } break; } + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + if (cmd.pivot) { + this._setPivotFieldMatching(cmd.filter.id, cmd.pivot); + } + break; + case "REMOVE_GLOBAL_FILTER": + this._onFilterDeletion(cmd.id); + break; } } @@ -126,7 +154,7 @@ export default class PivotCorePlugin extends CorePlugin { // ------------------------------------------------------------------------- /** - * @param {number} id + * @param {string} id * @returns {PivotDataSource|undefined} */ getPivotDataSource(id) { @@ -135,7 +163,7 @@ export default class PivotCorePlugin extends CorePlugin { } /** - * @param {number} id + * @param {string} id * @returns {string} */ getPivotDisplayName(id) { @@ -143,7 +171,7 @@ export default class PivotCorePlugin extends CorePlugin { } /** - * @param {number} id + * @param {string} id * @returns {string} */ getPivotName(id) { @@ -151,7 +179,15 @@ export default class PivotCorePlugin extends CorePlugin { } /** - * @param {number} id + * @param {string} id + * @returns {string} + */ + getPivotFieldMatch(id) { + return this.pivots[id].fieldMatching; + } + + /** + * @param {string} id * @returns {Promise} */ async getAsyncPivotDataSource(id) { @@ -170,7 +206,7 @@ export default class PivotCorePlugin extends CorePlugin { } /** - * @param {number} id Id of the pivot + * @param {string} id Id of the pivot * * @returns {PivotDefinition} */ @@ -192,7 +228,7 @@ export default class PivotCorePlugin extends CorePlugin { /** * Retrieve all the pivot ids * - * @returns {Array} + * @returns {Array} */ getPivotIds() { return Object.keys(this.pivots); @@ -209,21 +245,67 @@ export default class PivotCorePlugin extends CorePlugin { return pivotId in this.pivots; } + /** + * Get the current pivotFieldMatching on a pivot + * + * @param {string} pivotId + * @param {string} filterId + */ + getPivotFieldMatching(pivotId, filterId) { + return this.pivots[pivotId].fieldMatching[filterId]; + } + // ------------------------------------------------------------------------- // Private // ------------------------------------------------------------------------- + _removeGlobalFilter(filterId) { + for (const pivotId of this.getters.getPivotIds()) { + this.history.update("pivots", pivotId, "fieldMatching", filterId, undefined); + } + } /** - * @param {number} id + * + * @return {Promise[]} + */ + getPivotsWaitForReady() { + return this.getPivotIds().map((pivotId) => this.getPivotDataSource(pivotId).loadMetadata()); + } + + /** + * Sets the current pivotFieldMatching on a pivot + * + * @param {string} pivotId + * @param {string} filterId + * @param {FieldMatching} fieldMatching + */ + _setPivotFieldMatching(filterId, pivotFieldMatches) { + const pivots = { ...this.pivots }; + for (const [pivotId, fieldMatch] of Object.entries(pivotFieldMatches)) { + pivots[pivotId].fieldMatching[filterId] = fieldMatch; + } + this.history.update("pivots", pivots); + } + + _onFilterDeletion(filterId) { + const pivots = { ...this.pivots }; + for (const pivotId in pivots) { + this.history.update("pivots", pivotId, "fieldMatching", filterId, undefined); + } + } + + /** + * @param {string} id * @param {PivotDefinition} definition * @param {string} dataSourceId */ - _addPivot(id, definition, dataSourceId) { + _addPivot(id, definition, dataSourceId, fieldMatching = {}) { const pivots = { ...this.pivots }; pivots[id] = { id, definition, dataSourceId, + fieldMatching, }; if (!this.dataSources.contains(dataSourceId)) { @@ -235,7 +317,7 @@ export default class PivotCorePlugin extends CorePlugin { /** * @param {string} sheetId * @param {[number, number]} anchor - * @param {number} id + * @param {string} id * @param {SpreadsheetPivotTable} table */ _insertPivot(sheetId, anchor, id, table) { @@ -248,7 +330,7 @@ export default class PivotCorePlugin extends CorePlugin { /** * @param {string} sheetId * @param {[number, number]} anchor - * @param {number} id + * @param {string} id * @param {SpreadsheetPivotTable} table */ _insertColumns(sheetId, anchor, id, table) { @@ -346,7 +428,7 @@ export default class PivotCorePlugin extends CorePlugin { /** * @param {string} sheetId * @param {[number, number]} anchor - * @param {number} id + * @param {string} id * @param {SpreadsheetPivotTable} table */ _insertRows(sheetId, anchor, id, table) { @@ -371,7 +453,7 @@ export default class PivotCorePlugin extends CorePlugin { /** * @param {string} sheetId * @param {[number, number]} anchor - * @param {number} id + * @param {string} id * @param {SpreadsheetPivotTable} table */ _insertBody(sheetId, anchor, id, table) { @@ -446,7 +528,7 @@ export default class PivotCorePlugin extends CorePlugin { }, name: pivot.name, }; - this._addPivot(id, definition, this.uuidGenerator.uuidv4()); + this._addPivot(id, definition, this.uuidGenerator.uuidv4(), pivot.fieldMatching); } } this.nextId = data.pivotNextId || getMaxObjectId(this.pivots) + 1; @@ -461,6 +543,7 @@ export default class PivotCorePlugin extends CorePlugin { for (const id in this.pivots) { data.pivots[id] = JSON.parse(JSON.stringify(this.getPivotDefinition(id))); data.pivots[id].measures = data.pivots[id].measures.map((elt) => ({ field: elt })); + data.pivots[id].fieldMatching = this.pivots[id].fieldMatching; } data.pivotNextId = this.nextId; } @@ -475,4 +558,6 @@ PivotCorePlugin.getters = [ "getAsyncPivotDataSource", "isExistingPivot", "getPivotDataSource", + "getPivotFieldMatch", + "getPivotFieldMatching", ]; diff --git a/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js b/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js index e89558ba8ff..8d4c584ccb6 100644 --- a/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js +++ b/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js @@ -1,9 +1,10 @@ /** @odoo-module */ -import { _t } from "web.core"; +import { _t } from "@web/core/l10n/translation"; import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; import { getFirstPivotFunction } from "../pivot_helpers"; import { FILTER_DATE_OPTION, monthsOptions } from "@spreadsheet/assets_backend/constants"; +import { Domain } from "@web/core/domain"; const { astToFormula } = spreadsheet; const { DateTime } = luxon; @@ -68,6 +69,16 @@ export default class PivotUIPlugin extends spreadsheet.UIPlugin { } } + beforeHandle(cmd) { + switch (cmd.type) { + case "START": + // make sure the domains are correctly set before + // any evaluation + this._addDomains(); + break; + } + } + /** * Handle a spreadsheet command * @param {Object} cmd Command @@ -77,15 +88,17 @@ export default class PivotUIPlugin extends spreadsheet.UIPlugin { case "SELECT_PIVOT": this.selectedPivotId = cmd.pivotId; break; - case "ADD_PIVOT_DOMAIN": - this._addDomain(cmd.id, cmd.domain); - break; case "REFRESH_PIVOT": this._refreshOdooPivot(cmd.id); break; case "REFRESH_ALL_DATA_SOURCES": this._refreshOdooPivots(); break; + case "ADD_GLOBAL_FILTER": + case "EDIT_GLOBAL_FILTER": + case "SET_GLOBAL_FILTER_VALUE": + case "CLEAR_GLOBAL_FILTER_VALUE": + this._addDomains(); } } @@ -203,8 +216,8 @@ export default class PivotUIPlugin extends spreadsheet.UIPlugin { for (const filter of filters) { const dataSource = this.getters.getPivotDataSource(pivotId); const { field, aggregateOperator: time } = dataSource.parseGroupField(argField); - const pivotField = filter.pivotFields[pivotId]; - if (pivotField && pivotField.field === field.name) { + const pivotFieldMatching = this.getters.getPivotFieldMatching(pivotId, filter.id); + if (pivotFieldMatching && pivotFieldMatching.chain === field.name) { let value = dataSource.getPivotHeaderValue(evaluatedArgs.slice(-2)); let transformedValue; const currentValue = this.getters.getGlobalFilterValue(filter.id); @@ -258,7 +271,7 @@ export default class PivotUIPlugin extends spreadsheet.UIPlugin { } /** - * Refresh the cache of all the lists + * Refresh the cache of all the pivots */ _refreshOdooPivots() { for (const pivotId of this.getters.getPivotIds()) { @@ -272,11 +285,29 @@ export default class PivotUIPlugin extends spreadsheet.UIPlugin { * @private * * @param {string} pivotId pivot id - * @param {Array>} domain */ - _addDomain(pivotId, domain) { + _addDomain(pivotId) { + const domainList = []; + for (const [filterId, fieldMatch] of Object.entries( + this.getters.getPivotFieldMatch(pivotId) + )) { + domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch)); + } + const domain = Domain.combine(domainList, "AND").toString(); this.getters.getPivotDataSource(pivotId).addDomain(domain); } + + /** + * Add an additional domain to all pivots + * + * @private + * + */ + _addDomains() { + for (const pivotId of this.getters.getPivotIds()) { + this._addDomain(pivotId); + } + } } PivotUIPlugin.getters = [ diff --git a/addons/spreadsheet/static/tests/charts/model/odoo_chart_plugin_test.js b/addons/spreadsheet/static/tests/charts/model/odoo_chart_plugin_test.js index 35c9ae5f0d3..b0c5ed35e9f 100644 --- a/addons/spreadsheet/static/tests/charts/model/odoo_chart_plugin_test.js +++ b/addons/spreadsheet/static/tests/charts/model/odoo_chart_plugin_test.js @@ -4,7 +4,7 @@ import { OdooBarChart } from "@spreadsheet/chart/odoo_chart/odoo_bar_chart"; import { OdooChart } from "@spreadsheet/chart/odoo_chart/odoo_chart"; import { OdooLineChart } from "@spreadsheet/chart/odoo_chart/odoo_line_chart"; import { nextTick } from "@web/../tests/helpers/utils"; -import { createSpreadsheetWithGraph, insertGraphInSpreadsheet } from "../../utils/chart"; +import { createSpreadsheetWithChart, insertChartInSpreadsheet } from "../../utils/chart"; import { createModelWithDataSource } from "../../utils/model"; import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended"; @@ -12,7 +12,7 @@ const { toZone } = spreadsheet.helpers; QUnit.module("spreadsheet > odoo chart plugin", {}, () => { QUnit.test("Can add an Odoo Bar chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_bar" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" }); const sheetId = model.getters.getActiveSheetId(); assert.strictEqual(model.getters.getChartIds(sheetId).length, 1); const chartId = model.getters.getChartIds(sheetId)[0]; @@ -23,7 +23,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Can add an Odoo Line chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_line" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_line" }); const sheetId = model.getters.getActiveSheetId(); assert.strictEqual(model.getters.getChartIds(sheetId).length, 1); const chartId = model.getters.getChartIds(sheetId)[0]; @@ -34,7 +34,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Can add an Odoo Pie chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_pie" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" }); const sheetId = model.getters.getActiveSheetId(); assert.strictEqual(model.getters.getChartIds(sheetId).length, 1); const chartId = model.getters.getChartIds(sheetId)[0]; @@ -45,14 +45,14 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("A data source is added after a chart creation", async (assert) => { - const { model } = await createSpreadsheetWithGraph(); + const { model } = await createSpreadsheetWithChart(); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; - assert.ok(model.getters.getGraphDataSource(chartId)); + assert.ok(model.getters.getChartDataSource(chartId)); }); QUnit.test("Odoo bar chart runtime loads the data", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ + const { model } = await createSpreadsheetWithChart({ type: "odoo_bar", mockRPC: async function (route, args) { if (args.method === "web_read_group") { @@ -83,7 +83,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Odoo pie chart runtime loads the data", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie", mockRPC: async function (route, args) { if (args.method === "web_read_group") { @@ -114,7 +114,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Odoo line chart runtime loads the data", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ + const { model } = await createSpreadsheetWithChart({ type: "odoo_line", mockRPC: async function (route, args) { if (args.method === "web_read_group") { @@ -148,7 +148,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Changing the chart type does not reload the data", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ + const { model } = await createSpreadsheetWithChart({ type: "odoo_line", mockRPC: async function (route, args) { if (args.method === "web_read_group") { @@ -181,7 +181,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { QUnit.test("Can import/export an Odoo chart", async (assert) => { const model = await createModelWithDataSource(); - insertGraphInSpreadsheet(model, "odoo_line"); + insertChartInSpreadsheet(model, "odoo_line"); const data = model.exportData(); const figures = data.sheets[0].figures; assert.strictEqual(figures.length, 1); @@ -192,28 +192,27 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { const sheetId = m1.getters.getActiveSheetId(); assert.strictEqual(m1.getters.getChartIds(sheetId).length, 1); const chartId = m1.getters.getChartIds(sheetId)[0]; - assert.ok(m1.getters.getGraphDataSource(chartId)); + assert.ok(m1.getters.getChartDataSource(chartId)); assert.strictEqual(m1.getters.getChartRuntime(chartId).chartJsConfig.type, "line"); }); QUnit.test("Can undo/redo an Odoo chart creation", async (assert) => { const model = await createModelWithDataSource(); - insertGraphInSpreadsheet(model, "odoo_line"); + insertChartInSpreadsheet(model, "odoo_line"); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; - assert.ok(model.getters.getGraphDataSource(chartId)); + assert.ok(model.getters.getChartDataSource(chartId)); model.dispatch("REQUEST_UNDO"); - assert.notOk(model.getters.getGraphDataSource(chartId)); assert.strictEqual(model.getters.getChartIds(sheetId).length, 0); model.dispatch("REQUEST_REDO"); - assert.ok(model.getters.getGraphDataSource(chartId)); + assert.ok(model.getters.getChartDataSource(chartId)); assert.strictEqual(model.getters.getChartIds(sheetId).length, 1); }); QUnit.test("charts with no legend", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_pie" }); - insertGraphInSpreadsheet(model, "odoo_bar"); - insertGraphInSpreadsheet(model, "odoo_line"); + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" }); + insertChartInSpreadsheet(model, "odoo_bar"); + insertChartInSpreadsheet(model, "odoo_line"); const sheetId = model.getters.getActiveSheetId(); const [pieChartId, barChartId, lineChartId] = model.getters.getChartIds(sheetId); const pie = model.getters.getChartDefinition(pieChartId); @@ -270,7 +269,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Bar chart with stacked attribute is supported", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_bar" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" }); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; const definition = model.getters.getChartDefinition(chartId); @@ -305,7 +304,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Can copy/paste Odoo chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_pie" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" }); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; model.dispatch("SELECT_FIGURE", { id: chartId }); @@ -327,7 +326,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Can cut/paste Odoo chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_pie" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" }); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; const chartRuntime = model.getters.getChartRuntime(chartId); @@ -345,7 +344,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Duplicating a sheet correctly duplicates Odoo chart", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_bar" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_bar" }); const sheetId = model.getters.getActiveSheetId(); const secondSheetId = "secondSheetId"; const chartId = model.getters.getChartIds(sheetId)[0]; @@ -366,7 +365,7 @@ QUnit.module("spreadsheet > odoo chart plugin", {}, () => { }); QUnit.test("Line chart with stacked attribute is supported", async (assert) => { - const { model } = await createSpreadsheetWithGraph({ type: "odoo_line" }); + const { model } = await createSpreadsheetWithChart({ type: "odoo_line" }); const sheetId = model.getters.getActiveSheetId(); const chartId = model.getters.getChartIds(sheetId)[0]; const definition = model.getters.getChartDefinition(chartId); diff --git a/addons/spreadsheet/static/tests/global_filters/global_filters_chart_test.js b/addons/spreadsheet/static/tests/global_filters/global_filters_chart_test.js index d26fb73d199..a97c33d418f 100644 --- a/addons/spreadsheet/static/tests/global_filters/global_filters_chart_test.js +++ b/addons/spreadsheet/static/tests/global_filters/global_filters_chart_test.js @@ -1,6 +1,7 @@ /** @odoo-module */ -import { createSpreadsheetWithGraph } from "../utils/chart"; +import { globalFiltersFieldMatchers } from "../../src/global_filters/plugins/global_filters_core_plugin"; +import { createSpreadsheetWithChart } from "../utils/chart"; import { addGlobalFilter, setGlobalFilterValue } from "../utils/commands"; async function addChartGlobalFilter(model) { @@ -11,27 +12,28 @@ async function addChartGlobalFilter(model) { label: "Last Year", rangeType: "year", defaultValue: { yearOffset: -1 }, - graphFields: { [chartId]: { field: "date", type: "date" } }, }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter( + model, + { filter }, + { chart: { [chartId]: { chain: "date", type: "date" } } } + ); } QUnit.module("spreadsheet > Global filters chart", {}, () => { QUnit.test("Can add a chart global filter", async function (assert) { - const { model } = await createSpreadsheetWithGraph(); + const { model } = await createSpreadsheetWithChart(); assert.equal(model.getters.getGlobalFilters().length, 0); await addChartGlobalFilter(model); assert.equal(model.getters.getGlobalFilters().length, 1); const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0]; - const computedDomain = model.getters.getGraphDataSource(chartId).getComputedDomain(); + const computedDomain = model.getters.getChartDataSource(chartId).getComputedDomain(); assert.equal(computedDomain.length, 3); assert.equal(computedDomain[0], "&"); }); QUnit.test("Chart is loaded with computed domain", async function (assert) { - assert.expect(3); - - const { model } = await createSpreadsheetWithGraph({ + const { model } = await createSpreadsheetWithChart({ mockRPC: function (route, { model, method, kwargs }) { if (model === "partner" && method === "web_read_group") { assert.strictEqual(kwargs.domain.length, 3); @@ -44,7 +46,7 @@ QUnit.module("spreadsheet > Global filters chart", {}, () => { }); QUnit.test("Chart is impacted by global filter in dashboard mode", async function (assert) { - const { model } = await createSpreadsheetWithGraph(); + const { model } = await createSpreadsheetWithChart(); assert.equal(model.getters.getGlobalFilters().length, 0); const chartId = model.getters.getChartIds(model.getters.getActiveSheetId())[0]; const filter = { @@ -52,43 +54,70 @@ QUnit.module("spreadsheet > Global filters chart", {}, () => { type: "date", label: "Last Year", rangeType: "year", - graphFields: { [chartId]: { field: "date", type: "date" } }, }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter( + model, + { filter }, + { chart: { [chartId]: { chain: "date", type: "date" } } } + ); model.updateMode("dashboard"); - let computedDomain = model.getters.getGraphDataSource(chartId).getComputedDomain(); + let computedDomain = model.getters.getChartDataSource(chartId).getComputedDomain(); assert.equal(computedDomain.length, 0); await setGlobalFilterValue(model, { id: "42", value: { yearOffset: -1 }, }); - computedDomain = model.getters.getGraphDataSource(chartId).getComputedDomain(); + computedDomain = model.getters.getChartDataSource(chartId).getComputedDomain(); assert.equal(computedDomain.length, 3); assert.equal(computedDomain[0], "&"); }); QUnit.test("field matching is removed when chart is deleted", async function (assert) { - const { model } = await createSpreadsheetWithGraph(); + const { model } = await createSpreadsheetWithChart(); await addChartGlobalFilter(model); const [filter] = model.getters.getGlobalFilters(); const [chartId] = model.getters.getChartIds(model.getters.getActiveSheetId()); const matching = { - field: "date", + chain: "date", type: "date", }; - assert.deepEqual(model.getters.getGlobalFilterFieldGraph(filter.id, chartId), matching); + assert.deepEqual(model.getters.getChartFieldMatch(chartId)[filter.id], matching); model.dispatch("DELETE_FIGURE", { sheetId: model.getters.getActiveSheetId(), id: chartId, }); - assert.strictEqual( - model.getters.getGlobalFilterFieldGraph(filter.id, chartId), - undefined, - "it should have removed the field matching with the chart" + assert.deepEqual( + globalFiltersFieldMatchers["chart"].geIds(), + [], + "it should have removed the chart and its fieldMatching and datasource altogether" ); model.dispatch("REQUEST_UNDO"); - assert.deepEqual(model.getters.getGlobalFilterFieldGraph(filter.id, chartId), matching); + assert.deepEqual(model.getters.getChartFieldMatch(chartId)[filter.id], matching); model.dispatch("REQUEST_REDO"); - assert.strictEqual(model.getters.getGlobalFilterFieldGraph(filter.id, chartId), undefined); + assert.deepEqual(globalFiltersFieldMatchers["chart"].geIds(), []); + }); + + QUnit.test("field matching is removed when filter is deleted", async function (assert) { + const { model } = await createSpreadsheetWithChart(); + await addChartGlobalFilter(model); + const [filter] = model.getters.getGlobalFilters(); + const [chartId] = model.getters.getChartIds(model.getters.getActiveSheetId()); + const matching = { + chain: "date", + type: "date", + }; + assert.deepEqual(model.getters.getChartFieldMatch(chartId)[filter.id], matching); + model.dispatch("REMOVE_GLOBAL_FILTER", { + id: filter.id, + }); + assert.deepEqual( + model.getters.getChartFieldMatch(chartId)[filter.id], + undefined, + "it should have removed the chart and its fieldMatching and datasource altogether" + ); + model.dispatch("REQUEST_UNDO"); + assert.deepEqual(model.getters.getChartFieldMatch(chartId)[filter.id], matching); + model.dispatch("REQUEST_REDO"); + assert.deepEqual(model.getters.getChartFieldMatch(chartId)[filter.id], undefined); }); }); diff --git a/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js b/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js index a1833ac6792..c62a2de9c58 100644 --- a/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js +++ b/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js @@ -20,8 +20,8 @@ import { } from "@spreadsheet/../tests/utils/commands"; import { createSpreadsheetWithPivot } from "@spreadsheet/../tests/utils/pivot"; import { - createSpreadsheetWithGraph, - insertGraphInSpreadsheet, + createSpreadsheetWithChart, + insertChartInSpreadsheet, } from "@spreadsheet/../tests/utils/chart"; import { createSpreadsheetWithList } from "@spreadsheet/../tests/utils/list"; import { DataSources } from "@spreadsheet/data_sources/data_sources"; @@ -36,6 +36,14 @@ import { migrate } from "@spreadsheet/o_spreadsheet/migration"; const { Model, DispatchResult } = spreadsheet; +/** + * @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").GlobalFilter} GlobalFilter + * + * @typedef {Object} FilterPayload + * @property {GlobalFilter} filter + */ + +/** @type FilterPayload */ const LAST_YEAR_FILTER = { filter: { id: "42", @@ -43,10 +51,9 @@ const LAST_YEAR_FILTER = { label: "Last Year", rangeType: "year", defaultValue: { yearOffset: -1 }, - pivotFields: { 1: { field: "date", type: "date" } }, - listFields: { 1: { field: "date", type: "date" } }, }, }; +/** @type FilterPayload */ const LAST_YEAR_LEGACY_FILTER = { filter: { id: "41", @@ -54,29 +61,32 @@ const LAST_YEAR_LEGACY_FILTER = { rangeType: "year", label: "Legacy Last Year", defaultValue: { year: "last_year" }, - pivotFields: { 1: { field: "date", type: "date" } }, - listFields: { 1: { field: "date", type: "date" } }, }, }; - +/** @type FilterPayload */ const THIS_YEAR_FILTER = { filter: { + id: "43", type: "date", label: "This Year", rangeType: "year", defaultValue: { yearOffset: 0 }, - pivotFields: { 1: { field: "date", type: "date" } }, - listFields: { 1: { field: "date", type: "date" } }, }, }; +const DEFAULT_FIELD_MATCHINGS = { + 1: { chain: "date", type: "date" }, +}; + QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("Can add a global filter", async function (assert) { assert.expect(4); const { model } = await createSpreadsheetWithPivotAndList(); assert.equal(model.getters.getGlobalFilters().length, 0); - await addGlobalFilter(model, LAST_YEAR_FILTER); + await addGlobalFilter(model, LAST_YEAR_FILTER, { + pivot: DEFAULT_FIELD_MATCHINGS, + }); assert.equal(model.getters.getGlobalFilters().length, 1); const computedDomain = model.getters.getPivotComputedDomain("1"); assert.equal(computedDomain.length, 3); @@ -116,13 +126,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("A global filter with an empty field can be evaluated", async function (assert) { const { model } = await createSpreadsheetWithPivotAndList(); - const fields = { 1: {} }; - const filter = { - ...THIS_YEAR_FILTER.filter, - pivotFields: fields, - listFields: fields, - }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter(model, THIS_YEAR_FILTER); const domain = model.getters.getPivotComputedDomain(1); assert.deepEqual(domain, []); }); @@ -195,9 +199,11 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { assert.expect(8); const { model } = await createSpreadsheetWithPivotAndList(); - await addGlobalFilter(model, { - filter: { ...LAST_YEAR_FILTER.filter, rangeType: "month" }, - }); + await addGlobalFilter( + model, + { filter: { ...LAST_YEAR_FILTER.filter, rangeType: "month" } }, + { pivot: DEFAULT_FIELD_MATCHINGS, list: DEFAULT_FIELD_MATCHINGS } + ); const gf = model.getters.getGlobalFilters()[0]; let result = await setGlobalFilterValue(model, { id: gf.id, @@ -222,14 +228,21 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("Domain of simple date filter", async function (assert) { patchDate(2022, 6, 14, 0, 0, 0); const { model } = await createSpreadsheetWithPivotAndList(); - insertGraphInSpreadsheet(model); + insertChartInSpreadsheet(model); const chartId = model.getters.getOdooChartIds()[0]; - await addGlobalFilter(model, { - filter: { - ...LAST_YEAR_FILTER.filter, - graphFields: { [chartId]: { field: "date", type: "date" } }, + await addGlobalFilter( + model, + { + filter: { + ...LAST_YEAR_FILTER.filter, + }, }, - }); + { + pivot: { 1: { chain: "date", type: "date" } }, + list: { 1: { chain: "date", type: "date" } }, + chart: { [chartId]: { chain: "date", type: "date" } }, + } + ); const pivotDomain = model.getters.getPivotComputedDomain("1"); assert.deepEqual(pivotDomain[0], "&"); assert.deepEqual(pivotDomain[1], ["date", ">=", "2021-01-01"]); @@ -238,20 +251,18 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { assert.deepEqual(listDomain[0], "&"); assert.deepEqual(listDomain[1], ["date", ">=", "2021-01-01"]); assert.deepEqual(listDomain[2], ["date", "<=", "2021-12-31"]); - const graphDomain = model.getters.getGraphDataSource(chartId).getComputedDomain(); - assert.deepEqual(graphDomain[0], "&"); - assert.deepEqual(graphDomain[1], ["date", ">=", "2021-01-01"]); - assert.deepEqual(graphDomain[2], ["date", "<=", "2021-12-31"]); + const chartDomain = model.getters.getChartDataSource(chartId).getComputedDomain(); + assert.deepEqual(chartDomain[0], "&"); + assert.deepEqual(chartDomain[1], ["date", ">=", "2021-01-01"]); + assert.deepEqual(chartDomain[2], ["date", "<=", "2021-12-31"]); }); QUnit.test("Domain of date filter with year offset on pivot field", async function (assert) { patchDate(2022, 6, 14, 0, 0, 0); const { model } = await createSpreadsheetWithPivot(); - const filter = { - ...THIS_YEAR_FILTER.filter, - pivotFields: { 1: { field: "date", type: "date", offset: 1 } }, - }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter(model, THIS_YEAR_FILTER, { + pivot: { 1: { chain: "date", type: "date", offset: 1 } }, + }); const pivotDomain = model.getters.getPivotComputedDomain("1"); assert.deepEqual(pivotDomain[0], "&"); assert.deepEqual(pivotDomain[1], ["date", ">=", "2023-01-01"]); @@ -261,13 +272,19 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("Domain of date filter with quarter offset on list field", async function (assert) { patchDate(2022, 6, 14, 0, 0, 0); const { model } = await createSpreadsheetWithList(); + /** @type GlobalFilter */ const filter = { ...THIS_YEAR_FILTER.filter, rangeType: "quarter", defaultValue: { yearOffset: 0, period: "third_quarter" }, - listFields: { 1: { field: "date", type: "date", offset: 2 } }, }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter( + model, + { filter }, + { + list: { 1: { chain: "date", type: "date", offset: 2 } }, + } + ); const listDomain = model.getters.getListComputedDomain("1"); assert.deepEqual(listDomain[0], "&"); assert.deepEqual(listDomain[1], ["date", ">=", "2023-01-01"]); @@ -276,19 +293,25 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("Domain of date filter with month offset on graph field", async function (assert) { patchDate(2022, 6, 14, 0, 0, 0); - const { model } = await createSpreadsheetWithGraph(); + const { model } = await createSpreadsheetWithChart(); const chartId = model.getters.getOdooChartIds()[0]; + /** @type GlobalFilter */ const filter = { ...THIS_YEAR_FILTER.filter, rangeType: "month", defaultValue: { yearOffset: 0, period: "july" }, - graphFields: { [chartId]: { field: "date", type: "date", offset: -2 } }, }; - await addGlobalFilter(model, { filter }); - const graphDomain = model.getters.getGraphDataSource(chartId).getComputedDomain(); - assert.deepEqual(graphDomain[0], "&"); - assert.deepEqual(graphDomain[1], ["date", ">=", "2022-05-01"]); - assert.deepEqual(graphDomain[2], ["date", "<=", "2022-05-31"]); + await addGlobalFilter( + model, + { filter }, + { + chart: { [chartId]: { chain: "date", type: "date", offset: -2 } }, + } + ); + const chartDomain = model.getters.getChartDataSource(chartId).getComputedDomain(); + assert.deepEqual(chartDomain[0], "&"); + assert.deepEqual(chartDomain[1], ["date", ">=", "2022-05-01"]); + assert.deepEqual(chartDomain[2], ["date", "<=", "2022-05-31"]); }); QUnit.test("Can import/export filters", async function (assert) { @@ -310,6 +333,10 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { model: "partner", rowGroupBys: ["bar"], context: {}, + fieldMatching: { + 41: { type: "date", chain: "date" }, + 42: { type: "date", chain: "date" }, + }, }, }, lists: { @@ -320,6 +347,10 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { model: "partner", orderBy: [], context: {}, + fieldMatching: { + 41: { type: "date", chain: "date" }, + 42: { type: "date", chain: "date" }, + }, }, }, globalFilters: [LAST_YEAR_LEGACY_FILTER.filter, LAST_YEAR_FILTER.filter], @@ -376,19 +407,24 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { assert.expect(1); const { model } = await createSpreadsheetWithPivot(); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - label: "Relation Filter", - pivotFields: { + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + label: "Relation Filter", + }, + }, + { + pivot: { 1: { field: "foo", type: "char", }, }, - }, - }); + } + ); const [filter] = model.getters.getGlobalFilters(); await setGlobalFilterValue(model, { id: filter.id, @@ -524,12 +560,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Text Filter", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); await nextTick(); @@ -554,12 +584,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "date", label: "Date Filter", - pivotFields: { - 1: { - field: "name", - type: "date", - }, - }, }, }); await nextTick(); @@ -671,12 +695,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "date", label: "Cuillère", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); setCellContent( @@ -688,12 +706,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { const newFilter = { type: "date", label: "Interprete", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }; await editGlobalFilter(model, { id: filter.id, filter: newFilter }); assert.equal( @@ -712,12 +724,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Cuillère", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); await setGlobalFilterValue(model, { @@ -739,12 +745,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Cuillère", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); assert.equal(model.getters.getGlobalFilters().length, 1); @@ -763,12 +763,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Cuillère", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); await removeGlobalFilter(model, "42"); @@ -788,12 +782,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Cuillère", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); await editGlobalFilter(model, { @@ -802,12 +790,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "text", label: "Arthouuuuuur", - pivotFields: { - 1: { - field: "name", - type: "char", - }, - }, }, }); assert.equal(model.getters.getGlobalFilters()[0].label, "Arthouuuuuur"); @@ -828,16 +810,19 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { }); assert.equal(getCellValue(model, "A3"), "xphone"); assert.equal(getCellValue(model, "A4"), "xpad"); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - label: "Relation Filter", - modelName: "product", - defaultValue: [41], - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + label: "Relation Filter", + modelName: "product", + defaultValue: [41], + }, }, - }); + { pivot: { 1: { chain: "product_id", type: "many2one" } } } + ); assert.equal(getCellValue(model, "A3"), "xphone"); assert.equal(getCellValue(model, "B3"), ""); assert.equal(getCellValue(model, "A4"), "xpad"); @@ -874,13 +859,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { label: "my filter", defaultValue: {}, rangeType: "year", - pivotFields: { - 1: { field: "date", type: "date" }, - }, - listFields: {}, - fields: { - 1: { field: "date", type: "date" }, - }, }, ], }; @@ -924,6 +902,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { model: "partner", rowGroupBys: ["bar"], context: {}, + fieldMatching: { filterId: { chain: "date", type: "date" } }, }, }, globalFilters: [ @@ -933,13 +912,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { label: "my filter", defaultValue: { yearOffset: 0 }, rangeType: "year", - pivotFields: { - 1: { field: "date", type: "date" }, - }, - listFields: {}, - fields: { - 1: { field: "date", type: "date" }, - }, }, ], }; @@ -1001,9 +973,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { type: "date", rangeType: "month", label: "This month", - pivotFields: { - 1: { field: "date", type: "date" }, - }, defaultValue: {}, // no default value! }, }); @@ -1035,6 +1004,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { model: "partner", rowGroupBys: ["bar"], context: {}, + fieldMatching: {}, }, }, }; @@ -1053,11 +1023,11 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { type: "date", rangeType: "month", label: "This month", - pivotFields: { - 1: { field: "date", type: "date" }, - }, defaultValue: { period: "january" }, }, + pivot: { + 1: { chain: "date", type: "date" }, + }, }); assert.verifySteps([]); model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: "sheet1", sheetIdTo: "sheet2" }); @@ -1092,6 +1062,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { model: "partner", rowGroupBys: ["bar"], context: {}, + fieldMatching: { filterId: { chain: "date", type: "date" } }, }, }, globalFilters: [ @@ -1101,13 +1072,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { label: "my filter", defaultValue: {}, rangeType: "year", - pivotFields: { - 1: { field: "date", type: "date" }, - }, - listFields: {}, - fields: { - 1: { field: "date", type: "date" }, - }, }, ], }; @@ -1124,6 +1088,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "filterId", value: { yearOffset: 0 }, }); + assert.verifySteps([]); model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: "sheet1", sheetIdTo: "sheet2" }); assert.equal(getCellValue(model, "A1"), "Loading..."); @@ -1140,7 +1105,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { const label = "This year"; const defaultValue = "value"; await addGlobalFilter(model, { - filter: { id: "42", type: "text", label, defaultValue, pivotFields: {} }, + filter: { id: "42", type: "text", label, defaultValue }, }); const [filter] = model.getters.getGlobalFilters(); assert.equal(model.getters.getGlobalFilterValue(filter.id), defaultValue); @@ -1195,7 +1160,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "1", type: "date", label, - pivotFields: {}, defaultsToCurrentPeriod: true, rangeType: "year", }, @@ -1214,7 +1178,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "1", type: "date", label, - pivotFields: {}, defaultsToCurrentPeriod: true, rangeType: "month", }, @@ -1234,7 +1197,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "1", type: "date", label, - pivotFields: {}, defaultsToCurrentPeriod: true, rangeType: "quarter", }, @@ -1287,7 +1249,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { type: "date", label, defaultValue, - pivotFields: {}, rangeType: "relative", }, }); @@ -1301,15 +1262,15 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { patchDate(2022, 4, 16, 0, 0, 0); const label = "Last Month"; const { model } = await createSpreadsheetWithPivot(); + /**@type GlobalFilter */ const filter = { id: "42", type: "date", label, defaultValue: "last_week", - pivotFields: { 1: { field: "date", type: "date" } }, rangeType: "relative", }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter(model, { filter }, { pivot: { 1: { chain: "date", type: "date" } } }); let computedDomain = model.getters.getPivotComputedDomain("1"); assert.equal(getDateDomainDurationInDays(computedDomain), 7); assertDateDomainEqual(assert, "date", "2022-05-09", "2022-05-15", computedDomain); @@ -1344,15 +1305,19 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { patchDate(2022, 4, 16, 0, 0, 0); const label = "Last Month"; const { model } = await createSpreadsheetWithPivot(); + /**@type GlobalFilter */ const filter = { id: "42", type: "date", label, defaultValue: "last_week", - pivotFields: { 1: { field: "date", type: "date", offset: -1 } }, rangeType: "relative", }; - await addGlobalFilter(model, { filter }); + await addGlobalFilter( + model, + { filter }, + { pivot: { 1: { chain: "date", type: "date", offset: -1 } } } + ); let computedDomain = model.getters.getPivotComputedDomain("1"); assert.equal(getDateDomainDurationInDays(computedDomain), 7); assertDateDomainEqual(assert, "date", "2022-05-02", "2022-05-08", computedDomain); @@ -1397,7 +1362,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { filter: { id: "42", type: "relation", - pivotFields: { 1: { field: "product_id", type: "many2one" } }, }, }); model.dispatch("SET_MANY_GLOBAL_FILTER_VALUE", { @@ -1418,7 +1382,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { filter: { id: "42", type: "date", - pivotFields: { 1: { field: "date", type: "date" } }, rangeType: "month", }, }); @@ -1444,32 +1407,47 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { `, }); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - label: "relational filter", - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + label: "relational filter", + }, }, - }); - await addGlobalFilter(model, { - filter: { - id: "43", - type: "date", - label: "date filter 1", - pivotFields: { 1: { field: "date", type: "date" } }, - rangeType: "month", + { + pivot: { 1: { chain: "product_id", type: "many2one" } }, + } + ); + await addGlobalFilter( + model, + { + filter: { + id: "43", + type: "date", + label: "date filter 1", + rangeType: "month", + }, }, - }); - await addGlobalFilter(model, { - filter: { - id: "44", - type: "date", - label: "date filter 2", - pivotFields: { 1: { field: "date", type: "date" } }, - rangeType: "year", + { + pivot: { 1: { chain: "date", type: "date" } }, + } + ); + await addGlobalFilter( + model, + { + filter: { + id: "44", + type: "date", + label: "date filter 2", + rangeType: "year", + }, }, - }); + { + pivot: { 1: { chain: "date", type: "date" } }, + } + ); const relationalFilters1 = model.getters.getFiltersMatchingPivot( '=ODOO.PIVOT.HEADER(1,"product_id",37)' ); @@ -1511,23 +1489,33 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { `, }); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - label: "relational filter", - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + label: "relational filter", + }, }, - }); - await addGlobalFilter(model, { - filter: { - id: "43", - type: "date", - label: "date filter 1", - pivotFields: { 1: { field: "date", type: "date" } }, - rangeType: "month", + { + pivot: { 1: { chain: "product_id", type: "many2one" } }, + } + ); + await addGlobalFilter( + model, + { + filter: { + id: "43", + type: "date", + label: "date filter 1", + rangeType: "month", + }, }, - }); + { + pivot: { 1: { chain: "product_id", type: "many2one" } }, + } + ); const filters = model.getters.getFiltersMatchingPivot( '=ODOO.PIVOT.HEADER(1,"date:month","08/2016","product_id","41")' ); @@ -1546,14 +1534,17 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { `, }); setCellContent(model, "B3", '=ODOO.PIVOT(1, "__count", "#product_id", 1)'); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - defaultValue: [], - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + defaultValue: [], + }, }, - }); + { pivot: { 1: { chain: "product_id", type: "many2one" } } } + ); const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); assert.deepEqual(filters, [ { @@ -1575,14 +1566,17 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { `, }); setCellContent(model, "B3", '=ODOO.PIVOT(1, "probability", "#product_id", 1)'); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - defaultValue: [], - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + defaultValue: [], + }, }, - }); + { pivot: { 1: { chain: "product_id", type: "many2one" } } } + ); const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); assert.deepEqual(filters, [ { @@ -1608,7 +1602,6 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { id: "42", type: "relation", defaultValue: [], - pivotFields: {}, }, }); const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); @@ -1627,14 +1620,17 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { `, }); setCellContent(model, "B3", '=ODOO.PIVOT(1, "probability")'); - await addGlobalFilter(model, { - filter: { - id: "42", - type: "relation", - defaultValue: [], - pivotFields: { 1: { field: "product_id", type: "many2one" } }, + await addGlobalFilter( + model, + { + filter: { + id: "42", + type: "relation", + defaultValue: [], + }, }, - }); + { pivot: { 1: { chain: "product_id", type: "many2one" } } } + ); const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); assert.deepEqual(filters, []); } @@ -1642,37 +1638,76 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("field matching is removed when pivot is deleted", async function (assert) { const { model } = await createSpreadsheetWithPivot(); - await addGlobalFilter(model, LAST_YEAR_FILTER); + await addGlobalFilter(model, LAST_YEAR_FILTER, { pivot: DEFAULT_FIELD_MATCHINGS }); const [pivotId] = model.getters.getPivotIds(); const [filter] = model.getters.getGlobalFilters(); const matching = { - field: "date", + chain: "date", type: "date", }; - assert.deepEqual(model.getters.getGlobalFilterFieldPivot(filter.id, pivotId), matching); + assert.deepEqual(model.getters.getPivotFieldMatching(pivotId, filter.id), matching); model.dispatch("REMOVE_PIVOT", { pivotId }); - assert.strictEqual(model.getters.getGlobalFilterFieldPivot(filter.id, pivotId), undefined); + assert.throws( + () => model.getters.getPivotFieldMatching(pivotId, filter.id), + "Pivot does not exist" + ); model.dispatch("REQUEST_UNDO"); - assert.deepEqual(model.getters.getGlobalFilterFieldPivot(filter.id, pivotId), matching); + assert.deepEqual(model.getters.getPivotFieldMatching(pivotId, filter.id), matching); model.dispatch("REQUEST_REDO"); - assert.strictEqual(model.getters.getGlobalFilterFieldPivot(filter.id, pivotId), undefined); + assert.throws( + () => model.getters.getPivotFieldMatching(pivotId, filter.id), + "Pivot does not exist" + ); }); QUnit.test("field matching is removed when list is deleted", async function (assert) { const { model } = await createSpreadsheetWithList(); - await addGlobalFilter(model, LAST_YEAR_FILTER); + await addGlobalFilter(model, LAST_YEAR_FILTER, { list: DEFAULT_FIELD_MATCHINGS }); const [listId] = model.getters.getListIds(); const [filter] = model.getters.getGlobalFilters(); const matching = { - field: "date", + chain: "date", type: "date", }; - assert.deepEqual(model.getters.getGlobalFilterFieldList(filter.id, listId), matching); + assert.deepEqual(model.getters.getListFieldMatching(listId, filter.id), matching); model.dispatch("REMOVE_ODOO_LIST", { listId }); - assert.strictEqual(model.getters.getGlobalFilterFieldList(filter.id, listId), undefined); + assert.throws( + () => model.getters.getListFieldMatching(listId, filter.id), + "List does not exist" + ); model.dispatch("REQUEST_UNDO"); - assert.deepEqual(model.getters.getGlobalFilterFieldList(filter.id, listId), matching); + assert.deepEqual(model.getters.getListFieldMatching(listId, filter.id), matching); model.dispatch("REQUEST_REDO"); - assert.strictEqual(model.getters.getGlobalFilterFieldList(filter.id, listId), undefined); + assert.throws( + () => model.getters.getListFieldMatching(listId, filter.id), + "List does not exist" + ); + }); + + QUnit.test("field matching is removed when an Odoo chart is deleted", async function (assert) { + const { model } = await createSpreadsheetWithChart({ type: "odoo_pie" }); + const sheetId = model.getters.getActiveSheetId(); + const [chartId] = model.getters.getChartIds(sheetId); + await addGlobalFilter(model, LAST_YEAR_FILTER, { + chart: { [chartId]: { chain: "date", type: "date" } }, + }); + const [filter] = model.getters.getGlobalFilters(); + const matching = { + chain: "date", + type: "date", + }; + assert.deepEqual(model.getters.getOdooChartFieldMatching(chartId, filter.id), matching); + model.dispatch("DELETE_FIGURE", { id: chartId, sheetId }); + assert.throws( + () => model.getters.getOdooChartFieldMatching(chartId, filter.id), + "Chart does not exist" + ); + model.dispatch("REQUEST_UNDO"); + assert.deepEqual(model.getters.getOdooChartFieldMatching(chartId, filter.id), matching); + model.dispatch("REQUEST_REDO"); + assert.throws( + () => model.getters.getOdooChartFieldMatching(chartId, filter.id), + "Chart does not exist" + ); }); }); diff --git a/addons/spreadsheet/static/tests/migrations/migrations.js b/addons/spreadsheet/static/tests/migrations/migrations.js index 146974c38d1..d73c66fa5dc 100644 --- a/addons/spreadsheet/static/tests/migrations/migrations.js +++ b/addons/spreadsheet/static/tests/migrations/migrations.js @@ -68,7 +68,7 @@ QUnit.test("Global filters: pivot fields is correctly added", (assert) => { const data = { globalFilters: [ { - id: "1", + id: "Filter1", type: "relation", label: "Relation Filter", fields: { @@ -79,12 +79,18 @@ QUnit.test("Global filters: pivot fields is correctly added", (assert) => { }, }, ], + pivots: { + 1: { + name: "test", + }, + }, }; const migratedData = migrate(data); const filter = migratedData.globalFilters[0]; - assert.deepEqual(filter.pivotFields, { - 1: { - field: "foo", + const pivot = migratedData.pivots["1"]; + assert.deepEqual(pivot.fieldMatching, { + Filter1: { + chain: "foo", type: "char", }, }); @@ -121,49 +127,6 @@ QUnit.test("Global filters: date is correctly migrated", (assert) => { assert.deepEqual(f3.defaultValue, { yearOffset: 0 }); }); -QUnit.test("Global filters: list and graph fields are added", (assert) => { - const data = { - globalFilters: [ - { - id: "1", - type: "relation", - }, - { - id: "1", - type: "relation", - listFields: { - 1: { - field: "foo", - type: "char", - }, - }, - graphFields: { - 1: { - field: "foo", - type: "char", - }, - }, - }, - ], - }; - const migratedData = migrate(data); - const [f1, f2] = migratedData.globalFilters; - assert.deepEqual(f1.listFields, {}); - assert.deepEqual(f1.graphFields, {}); - assert.deepEqual(f2.listFields, { - 1: { - field: "foo", - type: "char", - }, - }); - assert.deepEqual(f2.graphFields, { - 1: { - field: "foo", - type: "char", - }, - }); -}); - QUnit.test("List name default is model name", (assert) => { const data = { lists: { @@ -200,6 +163,69 @@ QUnit.test("Pivot name default is model name", (assert) => { assert.strictEqual(migratedData.pivots["2"].name, "Model"); }); +QUnit.test("fieldMatchings are moved from filters to their respective datasources", (assert) => { + const data = { + globalFilters: [ + { + id: "Filter", + label: "MyFilter1", + type: "relation", + listFields: { + 1: { + field: "parent_id", + type: "many2one", + }, + }, + pivotFields: { + 1: { + field: "parent_id", + type: "many2one", + }, + }, + graphFields: { + fig1: { + field: "parent_id", + type: "many2one", + }, + }, + }, + ], + pivots: { + 1: { + name: "Name", + }, + }, + lists: { + 1: { + name: "Name", + }, + }, + sheets: [ + { + figures: [ + { + id: "fig1", + tag: "chart", + data: { + type: "odoo_bar", + }, + }, + ], + }, + ], + }; + const migratedData = migrate(data); + assert.deepEqual(migratedData.pivots["1"].fieldMatching, { + Filter: { chain: "parent_id", type: "many2one" }, + }); + assert.deepEqual(migratedData.lists["1"].fieldMatching, { + Filter: { chain: "parent_id", type: "many2one" }, + }); + assert.deepEqual(migratedData.sheets[0].figures[0].data.fieldMatching, { + Filter: { chain: "parent_id", type: "many2one" }, + }); +}); + QUnit.test("Odoo version is exported", (assert) => { const model = new Model(); assert.strictEqual(model.exportData().odooVersion, ODOO_VERSION); diff --git a/addons/spreadsheet/static/tests/pivots/model/pivot_plugin_test.js b/addons/spreadsheet/static/tests/pivots/model/pivot_plugin_test.js index d628dcd322b..9e7d4ac1676 100644 --- a/addons/spreadsheet/static/tests/pivots/model/pivot_plugin_test.js +++ b/addons/spreadsheet/static/tests/pivots/model/pivot_plugin_test.js @@ -537,6 +537,7 @@ QUnit.module("spreadsheet > pivot plugin", {}, () => { }, name: "A pivot", context: {}, + fieldMatching: {}, }, }, }; diff --git a/addons/spreadsheet/static/tests/utils/chart.js b/addons/spreadsheet/static/tests/utils/chart.js index ff925a4297d..aa8c0957e0d 100644 --- a/addons/spreadsheet/static/tests/utils/chart.js +++ b/addons/spreadsheet/static/tests/utils/chart.js @@ -11,7 +11,7 @@ const uuidGenerator = new spreadsheet.helpers.UuidGenerator(); * * @param {Model} model */ -export function insertGraphInSpreadsheet(model, type = "odoo_bar") { +export function insertChartInSpreadsheet(model, type = "odoo_bar") { const definition = getChartDefinition(type); model.dispatch("CREATE_CHART", { sheetId: model.getters.getActiveSheetId(), @@ -31,12 +31,12 @@ export function insertGraphInSpreadsheet(model, type = "odoo_bar") { * * @returns { Promise<{ model: Model, env: Object }>} */ -export async function createSpreadsheetWithGraph(params = {}) { +export async function createSpreadsheetWithChart(params = {}) { const model = await createModelWithDataSource({ mockRPC: params.mockRPC, }); - insertGraphInSpreadsheet(model, params.type); + insertChartInSpreadsheet(model, params.type); const env = model.config.evalContext.env; env.model = model; diff --git a/addons/spreadsheet/static/tests/utils/commands.js b/addons/spreadsheet/static/tests/utils/commands.js index f107b22755b..acfd84ce9c0 100644 --- a/addons/spreadsheet/static/tests/utils/commands.js +++ b/addons/spreadsheet/static/tests/utils/commands.js @@ -5,6 +5,10 @@ import { waitForDataSourcesLoaded } from "@spreadsheet/../tests/utils/model"; const { toCartesian, toZone } = spreadsheet.helpers; +/** + * @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").GlobalFilter} GlobalFilter + */ + /** * Select a cell */ @@ -15,9 +19,11 @@ export function selectCell(model, xc) { /** * Add a global filter and ensure the data sources are completely reloaded + * @param {Model} model + * @param {{filter: GlobalFilter}} filter */ -export async function addGlobalFilter(model, filter) { - const result = model.dispatch("ADD_GLOBAL_FILTER", filter); +export async function addGlobalFilter(model, filter, fieldMatchings = {}) { + const result = model.dispatch("ADD_GLOBAL_FILTER", { ...filter, ...fieldMatchings }); await waitForDataSourcesLoaded(model); return result; } diff --git a/addons/spreadsheet/tests/validate_spreadsheet_data.py b/addons/spreadsheet/tests/validate_spreadsheet_data.py index ab62d966d7d..8c2d064d261 100644 --- a/addons/spreadsheet/tests/validate_spreadsheet_data.py +++ b/addons/spreadsheet/tests/validate_spreadsheet_data.py @@ -41,7 +41,7 @@ def odoo_view_links(data): urls looks like odoo://view/{... view data...} """ return [ - json.loads(url[len(odoo_view_link_prefix) :]) + json.loads(url[len(odoo_view_link_prefix):]) for url in links_urls(data) if url.startswith(odoo_view_link_prefix) ] @@ -120,17 +120,36 @@ def filter_fields(data): """return all field names used in global filter definitions""" fields_by_model = defaultdict(set) charts = odoo_charts(data) - for filter_definition in data["globalFilters"]: - for pivot_id, matching in filter_definition.get("pivotFields", dict()).items(): - model = data["pivots"][pivot_id]["model"] - fields_by_model[model].add(matching["field"]) - for list_id, matching in filter_definition.get("listFields", dict()).items(): - model = data["lists"][list_id]["model"] - fields_by_model[model].add(matching["field"]) - for chart_id, matching in filter_definition.get("graphFields", dict()).items(): - chart = next((chart for chart in charts if chart["id"] == chart_id), None) + odoo_version = data.get("odooVersion", 1) + if odoo_version < 5: + for filter_definition in data.get("globalFilters", []): + for pivot_id, matching in filter_definition.get("pivotFields", dict()).items(): + model = data["pivots"][pivot_id]["model"] + fields_by_model[model].add(matching["field"]) + for list_id, matching in filter_definition.get("listFields", dict()).items(): + model = data["lists"][list_id]["model"] + fields_by_model[model].add(matching["field"]) + for chart_id, matching in filter_definition.get("graphFields", dict()).items(): + chart = next((chart for chart in charts if chart["id"] == chart_id), None) + model = chart["metaData"]["resModel"] + fields_by_model[model].add(matching["field"]) + else: + for pivot in data["pivots"].values(): + model = pivot.model + field = pivot.get("fieldMatching", {}).get("chain") + if field: + fields_by_model[model].add(field) + for _list in data["lists"].values(): + model = _list.model + field = _list.get("fieldMatching", {}).get("chain") + if field: + fields_by_model[model].add(field) + for chart in charts: model = chart["metaData"]["resModel"] - fields_by_model[model].add(matching["field"]) + field = chart.get("fieldMatching", {}).get("chain") + if field: + fields_by_model[model].add(field) + return dict(fields_by_model) @@ -164,7 +183,7 @@ def fields_in_spreadsheet(data): def xml_ids_in_spreadsheet(data): return set(data.get("chartOdooMenusReferences", {}).values()) | { - url[len(xml_id_url_prefix) :] + url[len(xml_id_url_prefix):] for url in links_urls(data) if url.startswith(xml_id_url_prefix) }