From e830968efec87dce3fbc1f42224b631e14e4f9ed Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 10 Sep 2021 14:31:52 +0000 Subject: [PATCH] [REF] web: views: factorize sample data logic This commit factorizes the sample data logic to the Model and useModel hook. That way, nothing as to be done in concrete models anymore. This commit also ensures that searchParams given to the view are a deep copy of what relies in the SearchModel. Before this commit, the PivotModel cloned them in its "load" method. It is now done in the SearchModel for every views. X-original-commit: 0f43b7994b6963644cea5b694ba6b07df7af8989 Part-of: odoo/odoo#76576 --- addons/web/static/src/search/search_model.js | 14 ++-- .../web/static/src/views/graph/graph_model.js | 44 ++++--------- .../web/static/src/views/graph/graph_view.js | 10 +-- .../web/static/src/views/graph/graph_view.xml | 6 +- addons/web/static/src/views/helpers/model.js | 64 +++++++++++++++++-- .../src/views/helpers/standard_view_props.js | 1 + .../web/static/src/views/pivot/pivot_model.js | 20 +----- .../static/src/views/pivot/pivot_renderer.xml | 2 +- .../web/static/src/views/pivot/pivot_view.js | 1 - .../web/static/src/views/pivot/pivot_view.xml | 4 +- addons/web/static/src/views/view.js | 5 +- 11 files changed, 96 insertions(+), 75 deletions(-) diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js index 69f59c20230..0a41a9052da 100644 --- a/addons/web/static/src/search/search_model.js +++ b/addons/web/static/src/search/search_model.js @@ -427,7 +427,7 @@ export class SearchModel extends EventBus { if (!this._context) { this._context = this._getContext(); } - return this._context; + return deepCopy(this._context); } /** @@ -437,7 +437,7 @@ export class SearchModel extends EventBus { if (!this._domain) { this._domain = this._getDomain(); } - return this._domain; + return deepCopy(this._domain); } /** @@ -476,7 +476,7 @@ export class SearchModel extends EventBus { } } } - return this._comparison; + return deepCopy(this._comparison); } get facets() { @@ -506,7 +506,7 @@ export class SearchModel extends EventBus { if (!this._groupBy) { this._groupBy = this._getGroupBy(); } - return this._groupBy; + return deepCopy(this._groupBy); } /** @@ -516,7 +516,7 @@ export class SearchModel extends EventBus { if (!this._orderBy) { this._orderBy = this._getOrderBy(); } - return this._orderBy; + return deepCopy(this._orderBy); } //-------------------------------------------------------------------------- @@ -1851,9 +1851,7 @@ export class SearchModel extends EventBus { case "favorite": case "filter": { //Return a deep copy of the filter/favorite to avoid the view to modify the context - return makeContext( - searchItem.context && JSON.parse(JSON.stringify(searchItem.context)) - ); + return makeContext(searchItem.context && deepCopy(searchItem.context)); } default: { return null; diff --git a/addons/web/static/src/views/graph/graph_model.js b/addons/web/static/src/views/graph/graph_model.js index 6e75041e5c9..909fce47610 100644 --- a/addons/web/static/src/views/graph/graph_model.js +++ b/addons/web/static/src/views/graph/graph_model.js @@ -6,7 +6,6 @@ import { rankInterval } from "@web/search/utils/dates"; import { getGroupBy } from "@web/search/utils/group_by"; import { GROUPABLE_TYPES } from "@web/search/utils/misc"; import { Model } from "@web/views/helpers/model"; -import { buildSampleORM } from "@web/views/helpers/sample_server"; import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils"; export const SEP = " / "; @@ -85,13 +84,8 @@ export class GraphModel extends Model { /** * @override */ - setup(params, services) { - const { orm, user } = services; - this.orm = orm; - this.user = user; - + setup(params) { this.keepLast = new KeepLast(); - this._fakeORM = null; this.initialGroupBy = null; @@ -137,6 +131,13 @@ export class GraphModel extends Model { this._prepareData(); } + /** + * @override + */ + hasData() { + return this.dataPoints.length > 0; + } + /** * Only supposed to be called to change one or several parameters among * "measure", "mode", "order", and "stacked". @@ -162,22 +163,14 @@ export class GraphModel extends Model { //-------------------------------------------------------------------------- /** - * Fetch the data points (possibly sample ones) determined by the metaData. - * This function has several side effects. It can alter metaData - * (e.g. change metaData.useSampleModel value) and set this.metaData and - * this.dataPoints (sometimes this._fakeORM). + * Fetch the data points determined by the metaData. This function has + * several side effects. It can alter this.metaData and set this.dataPoints. * @protected * @param {Object} metaData */ async _fetchDataPoints(metaData) { - let dataPoints = await this.keepLast.add(this._loadDataPoints(metaData)); - if (metaData.useSampleModel && dataPoints.length === 0) { - dataPoints = await this.keepLast.add(this._loadDataPoints(metaData, true)); - } else { - metaData.useSampleModel = false; - } + this.dataPoints = await this.keepLast.add(this._loadDataPoints(metaData)); this.metaData = metaData; - this.dataPoints = dataPoints; } /** @@ -376,21 +369,11 @@ export class GraphModel extends Model { * with an aggregation function, such as my_date:week. * @protected * @param {Object} metaData - * @param {boolean} [sample=false] * @returns {Object[]} */ - async _loadDataPoints(metaData, sample = false) { + async _loadDataPoints(metaData) { const { measure, domains, fields, groupBy, resModel } = metaData; - if (sample && !this._fakeORM) { - this._fakeORM = buildSampleORM( - resModel, - Object.assign({ __count: { type: "integer" } }, fields), - this.user - ); - } - const orm2use = sample ? this._fakeORM : this.orm; - const measures = ["__count"]; if (measure !== "__count") { let { group_operator, type } = fields[measure]; @@ -410,7 +393,7 @@ export class GraphModel extends Model { // for instance [1, "ABC"] [3, "ABC"] should be distinguished. domains.forEach((domain, originIndex) => { proms.push( - orm2use + this.orm .webReadGroup( resModel, domain.arrayRepr, @@ -538,4 +521,3 @@ export class GraphModel extends Model { } } } -GraphModel.services = ["orm", "user"]; diff --git a/addons/web/static/src/views/graph/graph_view.js b/addons/web/static/src/views/graph/graph_view.js index 919fc29a88c..6513da03892 100644 --- a/addons/web/static/src/views/graph/graph_view.js +++ b/addons/web/static/src/views/graph/graph_view.js @@ -30,7 +30,6 @@ const KEYS = [ "resModel", "stacked", "title", - "useSampleModel", ]; export class GraphView extends Component { @@ -38,8 +37,9 @@ export class GraphView extends Component { this.actionService = useService("action"); let modelParams; - if (this.props.state) { - modelParams = this.props.state; + if (this.props.state && this.props.state.metaData) { + // check this (dashboard testss) + modelParams = this.props.state.metaData; } else { const { arch, fields } = this.props; const parser = new this.constructor.ArchParser(); @@ -53,7 +53,9 @@ export class GraphView extends Component { this.model = useModel(this.constructor.Model, modelParams); useSetupView({ - exportLocalState: () => this.model.metaData, + exportLocalState: () => { + return { metaData: this.model.metaData }; + }, saveParams: () => this.saveParams(), }); } diff --git a/addons/web/static/src/views/graph/graph_view.xml b/addons/web/static/src/views/graph/graph_view.xml index e07699eca6f..dcd4f870bca 100644 --- a/addons/web/static/src/views/graph/graph_view.xml +++ b/addons/web/static/src/views/graph/graph_view.xml @@ -46,7 +46,7 @@ -
+
@@ -55,14 +55,14 @@
- + diff --git a/addons/web/static/src/views/helpers/model.js b/addons/web/static/src/views/helpers/model.js index 77d51201fc1..3f0d4c56910 100644 --- a/addons/web/static/src/views/helpers/model.js +++ b/addons/web/static/src/views/helpers/model.js @@ -2,6 +2,8 @@ import { SEARCH_KEYS } from "@web/search/with_search/with_search"; import { useBus, useService } from "@web/core/utils/hooks"; +import { buildSampleORM } from "@web/views/helpers/sample_server"; +import { useSetupView } from "@web/views/helpers/view_hook"; const { core, hooks } = owl; const { EventBus } = core; @@ -19,6 +21,8 @@ export class Model extends EventBus { constructor(env, params, services) { super(); this.env = env; + this.orm = services.orm; + this.useSampleModel = false; // will be set to true by the "useModel" hook if necessary this.setup(params, services); } @@ -31,7 +35,20 @@ export class Model extends EventBus { /** * @param {SearchParams} searchParams */ - load(/* searchParams */) {} + async load(/* searchParams */) {} + + /** + * This function is meant to be overriden by models that want to implement + * the sample data feature. It should return true iff the last loaded state + * actually contains data. If not, another load will be done (if the sample + * feature is enabled) with the orm service substituted by another using the + * SampleServer, to have sample data to display instead of an empty screen. + * + * @returns {boolean} + */ + hasData() { + return true; + } notify() { this.trigger("update"); @@ -68,18 +85,51 @@ export function useModel(ModelClass, params, options = {}) { for (const key of ModelClass.services) { services[key] = useService(key); } + services.orm = services.orm || useService("orm"); + const model = new ModelClass(component.env, params, services); useBus(model, "update", options.onUpdate || component.render); + const globalState = component.props.globalState || {}; + let useSampleModel = Boolean( + "useSampleModel" in globalState + ? globalState.useSampleModel + : component.props.useSampleModel + ); + model.useSampleModel = useSampleModel; + const orm = model.orm; + let sampleORM = globalState.sampleORM; + const user = useService("user"); + async function load(props) { + model.orm = orm; + const searchParams = getSearchParams(props); + await model.load(searchParams); + if (useSampleModel && !model.hasData()) { + sampleORM = + sampleORM || buildSampleORM(component.props.resModel, component.props.fields, user); + model.orm = sampleORM; + await model.load(searchParams); + } else { + useSampleModel = false; + } + model.useSampleModel = useSampleModel; + } onWillStart(() => { - const searchParams = getSearchParams(component.props); - return model.load(searchParams); + return load(component.props); + }); + onWillUpdateProps((nextProps) => { + useSampleModel = false; + return load(nextProps); }); - onWillUpdateProps((nextProps) => { - const searchParams = getSearchParams(nextProps); - searchParams.useSampleModel = false; - return model.load(searchParams); + useSetupView({ + exportGlobalState() { + return { + sampleORM, + useSampleModel: model.useSampleModel, + }; + }, }); + return model; } diff --git a/addons/web/static/src/views/helpers/standard_view_props.js b/addons/web/static/src/views/helpers/standard_view_props.js index ae2ae2063ae..30b10349405 100644 --- a/addons/web/static/src/views/helpers/standard_view_props.js +++ b/addons/web/static/src/views/helpers/standard_view_props.js @@ -29,6 +29,7 @@ export const standardViewProps = { orderBy: { type: Array, elements: String }, useSampleModel: { type: Boolean }, state: { type: Object, optional: 1 }, + globalState: { type: Object, optional: 1 }, resId: { type: Number, optional: 1 }, resIds: { type: Array, optional: 1 }, bannerRoute: { type: String, optional: 1 }, diff --git a/addons/web/static/src/views/pivot/pivot_model.js b/addons/web/static/src/views/pivot/pivot_model.js index 5527587bd46..68a4ef04b6a 100644 --- a/addons/web/static/src/views/pivot/pivot_model.js +++ b/addons/web/static/src/views/pivot/pivot_model.js @@ -6,7 +6,6 @@ import { KeepLast } from "@web/core/utils/concurrency"; import { computeVariation } from "@web/core/utils/numbers"; import { DEFAULT_INTERVAL } from "@web/search/utils/dates"; import { Model } from "@web/views/helpers/model"; -import { buildSampleORM } from "@web/views/helpers/sample_server"; import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils"; /** @@ -338,10 +337,7 @@ export class PivotModel extends Model { * @param {Object|null} [params.metaData.sortedColumn=null] * @param {Object} [params.data] previously exported data */ - setup(params, { orm, user }) { - this.realORM = orm; - this.orm2Use = this.realORM; - this.user = user; + setup(params) { this.keepLast = new KeepLast(); let sortedColumn = params.metaData.sortedColumn || null; @@ -681,8 +677,7 @@ export class PivotModel extends Model { * @param {SearchParams} searchParams */ async load(searchParams) { - this.orm2Use = this.realORM; - this.searchParams = JSON.parse(JSON.stringify(searchParams)); + this.searchParams = searchParams; const activeMeasures = processMeasure(searchParams.context.pivot_measures) || this.metaData.activeMeasures; @@ -954,7 +949,7 @@ export class PivotModel extends Model { const measureSpecs = this._getMeasureSpecs(config); const groupBy = rowGroupBy.concat(colGroupBy); const options = { lazy: false }; - const subGroups = await this.orm2Use.readGroup( + const subGroups = await this.orm.readGroup( config.metaData.resModel, groupDomain, measureSpecs, @@ -1382,14 +1377,6 @@ export class PivotModel extends Model { return this._subdivideGroup(group, divisors.slice(1), config); }) .then(async () => { - if (metaData.useSampleModel && !this._hasData(config.data)) { - const fakeORM = buildSampleORM(metaData.resModel, metaData.fields, this.user); - this.orm2Use = fakeORM; - await this._loadData(config); - } else if (this.orm2Use.rpc.name !== "fakeRPC") { - metaData.useSampleModel = false; - } - // keep folded groups folded after the reload if the structure of the table is the same if (prune && this._hasData(data) && this._hasData(this.data)) { if ( @@ -1653,4 +1640,3 @@ export class PivotModel extends Model { }); } } -PivotModel.services = ["orm", "user"]; diff --git a/addons/web/static/src/views/pivot/pivot_renderer.xml b/addons/web/static/src/views/pivot/pivot_renderer.xml index dafe3249a2f..494cb2f6c63 100644 --- a/addons/web/static/src/views/pivot/pivot_renderer.xml +++ b/addons/web/static/src/views/pivot/pivot_renderer.xml @@ -3,7 +3,7 @@
- +
diff --git a/addons/web/static/src/views/pivot/pivot_view.js b/addons/web/static/src/views/pivot/pivot_view.js index a3c228f53ce..2782205fb32 100644 --- a/addons/web/static/src/views/pivot/pivot_view.js +++ b/addons/web/static/src/views/pivot/pivot_view.js @@ -46,7 +46,6 @@ export class PivotView extends Component { resModel: this.props.resModel, rowGroupBys: archInfo.rowGroupBys, title: this.props.title || archInfo.title || this.env._t("Untitled"), - useSampleModel: Boolean(this.props.useSampleModel), widgets: archInfo.widgets, }; } diff --git a/addons/web/static/src/views/pivot/pivot_view.xml b/addons/web/static/src/views/pivot/pivot_view.xml index 47dacb8515b..f53659259ab 100644 --- a/addons/web/static/src/views/pivot/pivot_view.xml +++ b/addons/web/static/src/views/pivot/pivot_view.xml @@ -17,7 +17,7 @@ -
+
@@ -28,7 +28,7 @@ diff --git a/addons/web/static/src/views/view.js b/addons/web/static/src/views/view.js index 6bcaffcd5fb..345a817ff90 100644 --- a/addons/web/static/src/views/view.js +++ b/addons/web/static/src/views/view.js @@ -222,11 +222,14 @@ export class View extends Component { displayName: this.props.displayName, }, arch, + bannerRoute, fields, resModel, useSampleModel: false, - bannerRoute, }; + if (this.props.globalState) { + viewProps.globalState = this.props.globalState; + } if ("useSampleModel" in this.props) { viewProps.useSampleModel = this.props.useSampleModel;