[FIX] spreadsheet: faulty logic in filters dependencies

The global filters had to be aware of every type of datasources that it
affected (i.e. pivots, lists and charts). This commit introduces the
possibility for any datasource to register its type in an object handled
by the filters, making the latter agnostic of the different possible
kinds.

Task 2992752

closes odoo/odoo#102688

X-original-commit: b7dda98dfd2ee6973e2ca09b8c8ee7e8c43032a8
Related: odoo/enterprise#32526
Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
Signed-off-by: Pierre Rousseau (pro) <pro@odoo.com>
This commit is contained in:
Rémi Rahir
2022-10-07 19:45:13 +02:00
parent 53e9761a41
commit c822f4778f
30 changed files with 1204 additions and 704 deletions
@@ -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,
},
@@ -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 };
@@ -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.");
}
}
}
@@ -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.<string, string>} */
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<string>}
*/
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);
@@ -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");
@@ -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.<string, Chart>} */
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<string>}
*/
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",
];
@@ -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 = [];
@@ -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
@@ -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<any>}
*/
async fetch(resModel, method, args) {
@@ -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 };
@@ -13,21 +13,21 @@
* @property {string} id
* @property {string} label
* @property {string} type "text" | "date" | "relation"
* @property {RangeType} rangeType
* @property {Record<string, FilterMatchingField>} pivotFields
* @property {Record<string, FilterMatchingField>} listFields
* @property {Record<string, FilterMatchingField>} graphFields
* @property {RangeType} [rangeType]
* @property {boolean} [defaultsToCurrentPeriod]
* @property {string|Array<string>|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.<string, GlobalFilter>} */
@@ -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",
];
@@ -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<Field>} 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",
+40
View File
@@ -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);
+5 -9
View File
@@ -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 };
@@ -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<string>} 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.<number, Pivot>} */
/** @type {Object.<string, List>} */
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<import("@spreadsheet/list/list_data_source").ListDataSource>}
* @param {string} id
* @returns {string}
*/
getListFieldMatch(id) {
return this.lists[id].fieldMatching;
}
/**
* @param {string} id
* @returns {Promise<import("@spreadsheet/list/list_data_source").default>}
*/
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",
];
@@ -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<Array<any>>} 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
@@ -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.
@@ -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;
+4 -9
View File
@@ -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 };
@@ -1,6 +1,8 @@
/** @odoo-module */
/**
* @typedef {import("@spreadsheet/global_filters/plugins/global_filters_ui_plugin").FieldMatching} FieldMatching
*
* @typedef {Object} PivotDefinition
* @property {Array<string>} colGroupBys
* @property {Array<string>} 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.<number, Pivot>} */
/** @type {Object.<string, Pivot>} */
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<PivotDataSource>}
*/
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<number>}
* @returns {Array<string>}
*/
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",
];
@@ -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<Array<any>>} 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 = [
@@ -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);
@@ -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);
});
});
@@ -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", {}, () => {
<field name="date" interval="month" type="col"/>
</pivot>`,
});
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", {}, () => {
<field name="date" interval="month" type="row"/>
</pivot>`,
});
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", {}, () => {
</pivot>`,
});
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", {}, () => {
</pivot>`,
});
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", {}, () => {
</pivot>`,
});
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"
);
});
});
@@ -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);
@@ -537,6 +537,7 @@ QUnit.module("spreadsheet > pivot plugin", {}, () => {
},
name: "A pivot",
context: {},
fieldMatching: {},
},
},
};
@@ -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;
@@ -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;
}
@@ -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)
}