diff --git a/addons/board/static/src/add_to_board/add_to_board.js b/addons/board/static/src/add_to_board/add_to_board.js index 69c28cc4eda..72dc4a0e88f 100644 --- a/addons/board/static/src/add_to_board/add_to_board.js +++ b/addons/board/static/src/add_to_board/add_to_board.js @@ -38,11 +38,10 @@ export class AddToBoard extends Component { action, displayName, domain, - comparison, context, groupBy, orderedBy, - view + view, } = this.env.searchModel; // Retrieves view context @@ -57,8 +56,9 @@ export class AddToBoard extends Component { ...viewContext, }; - if (comparison) { - contextToSave.comparison = comparison; + const detailedComparison = this.env.searchModel.getFullComparison(); + if (detailedComparison) { + contextToSave.comparison = detailedComparison; } const result = await this.rpc("/board/add_to_dashboard", { diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js index c6d69f4bb18..ab6fd443245 100644 --- a/addons/web/static/src/search/search_model.js +++ b/addons/web/static/src/search/search_model.js @@ -20,6 +20,28 @@ import { deepCopy } from "@web/core/utils/objects"; const { DateTime } = luxon; const EventBus = owl.core.EventBus; +/** + * @typedef {Object} ComparisonDomain + * @property {DomainListRepr} arrayRepr + * @property {string} description + */ + +/** + * @typedef {Object} Comparison + * @property {ComparisonDomain[]} domains + * @property {string} [fieldName] + */ + +/** + * @typedef {Object} SearchParams + * @property {Comparison | null} comparison + * @property {Context} context + * @property {DomainListRepr} domain + * @property {string[]} groupBy + * @property {string[]} orderBy + * @property {boolean} [useSampleModel] to remove? + */ + /** @todo rework doc */ // interface SectionCommon { // check optional keys // color: string; @@ -170,10 +192,10 @@ export class SearchModel extends EventBus { * * @param {Object} [config.action={id:false,views:[]}] * @param {boolean} [config.activateFavorite=true] + * @param {Object | null} [config.comparison] * @param {Object} [config.context={}] * @param {String} [config.displayName=""] * @param {Array} [config.domain=[]] - * @param {Array} [config.domains] * @param {Array} [config.dynamicFilters=[]] * @param {string[]} [config.groupBy=[]] * @param {boolean} [config.loadIrFilters=false] @@ -197,17 +219,17 @@ export class SearchModel extends EventBus { this.view = view || { id: false }; // used to avoid useless recomputations + // this._comparison is left undefined (we have to distinguish it from null) this._context = null; this._domain = null; - this._domains = null; this._groupBy = null; this._orderBy = null; - const { context, domain, domains, groupBy, orderBy } = config; + const { comparison, context, domain, groupBy, orderBy } = config; + this.globalComparison = comparison; this.globalContext = context || {}; this.globalDomain = domain || []; - this.globalDomains = domains; // rework that part this.globalGroupBy = groupBy || []; this.globalOrderBy = orderBy || []; @@ -362,25 +384,25 @@ export class SearchModel extends EventBus { /** * @param {Object} [config={}] + * @param {Object | null} [config.comparison] * @param {Object} [config.context={}] * @param {Array} [config.domain=[]] - * @param {Object[]} [config.domains] * @param {string[]} [config.groupBy=[]] * @param {string[]} [config.orderBy=[]] */ async reload(config = {}) { // used to avoid useless recomputations + delete this._comparison; this._context = null; this._domain = null; - this._domains = null; this._groupBy = null; this._orderBy = null; - const { context, domain, domains, groupBy, orderBy } = config; + const { comparison, context, domain, groupBy, orderBy } = config; this.globalContext = context || {}; this.globalDomain = domain || []; - this.globalDomains = domains; // how to use this? + this.globalComparison = comparison; this.globalGroupBy = groupBy || []; this.globalOrderBy = orderBy || []; @@ -398,13 +420,6 @@ export class SearchModel extends EventBus { return [...this.sections.values()].filter((s) => s.type === "category"); } - /** - * @returns {Object} - */ - get comparison() { - return this._getComparison(); - } - /** * @returns {Context} should be imported from context.js? */ @@ -425,12 +440,18 @@ export class SearchModel extends EventBus { return this._domain; } - get domains() { - if (!this._domains) { - if (this.globalDomains) { - this._domains = this.globalDomains; + /** + * @returns {Comparison} + */ + get comparison() { + if (!this.searchMenuTypes.has("comparison")) { + return null; + } + if (this._comparison === undefined) { + if (this.globalComparison) { + this._comparison = this.globalComparison; } else { - const comparison = this._getComparison(); + const comparison = this.getFullComparison(); if (comparison) { const { fieldName, @@ -439,7 +460,7 @@ export class SearchModel extends EventBus { comparisonRange, comparisonRangeDescription, } = comparison; - this._domains = [ + const domains = [ { arrayRepr: Domain.and([this.domain, range]).toList(), description: rangeDescription, @@ -449,13 +470,13 @@ export class SearchModel extends EventBus { description: comparisonRangeDescription, }, ]; - this._domains.fieldName = fieldName; // bad but for now okay + this._comparison = { domains, fieldName }; } else { - this._domains = [{ arrayRepr: this.domain, description: null }]; + this._comparison = null; } } } - return this._domains; + return this._comparison; } get facets() { @@ -688,6 +709,57 @@ export class SearchModel extends EventBus { return sortBy(Object.values(copy), (part) => part.groupId); } + getFullComparison() { + let searchItem = null; + for (const queryElem of this.query.slice().reverse()) { + const item = this.searchItems[queryElem.searchItemId]; + if (item.type === "comparison") { + searchItem = item; + break; + } else if (item.type === "favorite" && item.comparison) { + searchItem = item; + break; + } + } + if (!searchItem) { + return null; + } else if (searchItem.type === "favorite") { + return searchItem.comparison; + } + const { dateFilterId, comparisonOptionId } = searchItem; + const { fieldName, fieldType, description: dateFilterDescription } = this.searchItems[ + dateFilterId + ]; + const selectedGeneratorIds = this._getSelectedGeneratorIds(dateFilterId); + // compute range and range description + const { domain: range, description: rangeDescription } = constructDateDomain( + this.referenceMoment, + fieldName, + fieldType, + selectedGeneratorIds + ); + // compute comparisonRange and comparisonRange description + const { + domain: comparisonRange, + description: comparisonRangeDescription, + } = constructDateDomain( + this.referenceMoment, + fieldName, + fieldType, + selectedGeneratorIds, + comparisonOptionId + ); + return { + comparisonId: comparisonOptionId, + fieldName, + fieldDescription: dateFilterDescription, + range: range.toList(), + rangeDescription, + comparisonRange: comparisonRange.toList(), + comparisonRangeDescription, + }; + } + getIrFilterValues(params) { const { irFilter } = this._getIrFilterDescription(params); return irFilter; @@ -1264,57 +1336,6 @@ export class SearchModel extends EventBus { return domain; } - _getComparison() { - let searchItem = null; - for (const queryElem of this.query.slice().reverse()) { - const item = this.searchItems[queryElem.searchItemId]; - if (item.type === "comparison") { - searchItem = item; - break; - } else if (item.type === "favorite" && item.comparison) { - searchItem = item; - break; - } - } - if (!searchItem) { - return null; - } else if (searchItem.type === "favorite") { - return searchItem.comparison; - } - const { dateFilterId, comparisonOptionId } = searchItem; - const { fieldName, fieldType, description: dateFilterDescription } = this.searchItems[ - dateFilterId - ]; - const selectedGeneratorIds = this._getSelectedGeneratorIds(dateFilterId); - // compute range and range description - const { domain: range, description: rangeDescription } = constructDateDomain( - this.referenceMoment, - fieldName, - fieldType, - selectedGeneratorIds - ); - // compute comparisonRange and comparisonRange description - const { - domain: comparisonRange, - description: comparisonRangeDescription, - } = constructDateDomain( - this.referenceMoment, - fieldName, - fieldType, - selectedGeneratorIds, - comparisonOptionId - ); - return { - comparisonId: comparisonOptionId, - fieldName, - fieldDescription: dateFilterDescription, - range: range.toList(), - rangeDescription, - comparisonRange: comparisonRange.toList(), - comparisonRangeDescription, - }; - } - /** * Construct a single context from the contexts of * filters of type 'filter', 'favorite', and 'field'. @@ -1736,7 +1757,7 @@ export class SearchModel extends EventBus { } const rawDomain = this._getDomain({ raw: true, withGlobal: false }); const groupBys = this._getGroupBy(); - const comparison = this._getComparison(); + const comparison = this.getFullComparison(); const orderBy = saveParams.orderBy ? saveParams.orderBy : this._getOrderBy() || []; const userId = isShared ? false : this.userService.userId; @@ -1996,9 +2017,9 @@ export class SearchModel extends EventBus { if (this.blockNotification) { return; } + delete this._comparison; this._context = null; this._domain = null; - this._domains = null; this._groupBy = null; this._orderBy = null; diff --git a/addons/web/static/src/search/with_search/with_search.js b/addons/web/static/src/search/with_search/with_search.js index f4342a50652..7c138a1f949 100644 --- a/addons/web/static/src/search/with_search/with_search.js +++ b/addons/web/static/src/search/with_search/with_search.js @@ -7,7 +7,7 @@ import { CallbackRecorder, useSetupAction } from "@web/webclient/actions/action_ const { Component, hooks } = owl; const { useSubEnv } = hooks; -export const SEARCH_KEYS = ["context", "domain", "domains", "groupBy", "orderBy"]; +export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"]; const OTHER_SEARCH_KEYS = ["irFilters", "searchViewArch", "searchViewFields", "searchViewId"]; export class WithSearch extends Component { @@ -63,7 +63,7 @@ export class WithSearch extends Component { async willUpdateProps(nextProps) { const config = {}; for (const key of SEARCH_KEYS) { - if (nextProps[key]) { + if (nextProps[key] !== undefined) { config[key] = nextProps[key]; } } @@ -122,9 +122,10 @@ WithSearch.props = { display: { type: Object, optional: true }, // search query elements + comparison: { validate: () => true, optional: true }, // fix problem with validation with type: [Object, null] + // Issue OWL: https://github.com/odoo/owl/issues/910 context: { type: Object, optional: true }, domain: { type: Array, element: [String, Array], optional: true }, - domains: { type: Array, element: Object, optional: true }, groupBy: { type: Array, element: String, optional: true }, orderBy: { type: Array, element: String, optional: true }, diff --git a/addons/web/static/src/views/graph/graph_model.js b/addons/web/static/src/views/graph/graph_model.js index 07fddc83516..6e75041e5c9 100644 --- a/addons/web/static/src/views/graph/graph_model.js +++ b/addons/web/static/src/views/graph/graph_model.js @@ -11,6 +11,10 @@ import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils" export const SEP = " / "; +/** + * @typedef {import("@web/search/search_model").SearchParams} SearchParams + */ + class DateClasses { // We view the param "array" as a matrix of values and undefined. // An equivalence class is formed of defined values of a column. @@ -100,15 +104,17 @@ export class GraphModel extends Model { //-------------------------------------------------------------------------- /** - * @param {Object} [searchParams] - * @param {Object} [searchParams.context] - * @param {Object[]} [searchParams.domains] - * @param {string[]} [searchParams.groupBy] + * @param {SearchParams} searchParams */ async load(searchParams) { - const { context, domains, groupBy } = searchParams; - const metaData = Object.assign({}, this.metaData, { context, domains }); - + const { comparison, domain, context, groupBy } = searchParams; + const metaData = Object.assign({}, this.metaData, { context }); + if (comparison) { + metaData.domains = comparison.domains; + metaData.comparisonField = comparison.fieldName; + } else { + metaData.domains = [{ arrayRepr: domain, description: null }]; + } metaData.measure = context.graph_measure || metaData.measure; metaData.mode = context.graph_mode || metaData.mode; @@ -183,10 +189,10 @@ export class GraphModel extends Model { * @returns {Object} */ _getData(dataPoints) { - const { domains, groupBy, mode } = this.metaData; + const { comparisonField, groupBy, mode } = this.metaData; let identify = false; - if (domains.length && groupBy.length && groupBy[0].fieldName === domains.fieldName) { + if (comparisonField && groupBy.length && groupBy[0].fieldName === comparisonField) { identify = true; } const dateClasses = identify ? this._getDateClasses(dataPoints) : null; diff --git a/addons/web/static/src/views/graph/graph_view.xml b/addons/web/static/src/views/graph/graph_view.xml index 4a72f77dc7c..7d4f36f6ae5 100644 --- a/addons/web/static/src/views/graph/graph_view.xml +++ b/addons/web/static/src/views/graph/graph_view.xml @@ -31,7 +31,7 @@ t-att-class="{ active: model.metaData.stacked }" /> -