[IMP] web: allow kanban folded columns examples

Will add the ability to make grouped kanban column examples
with an initial folded status.

**Usage**
Add your kanban column examples with the following options:

```js
import { registry } from "@web/core/registry";

const kanbanExamplesRegistry = registry.category("kanban_examples");
const myExamples = "my-examples";

kanbanExamplesRegistry.add(myExamples, {
  // ...
  allowedGroupBys: ["stage_id"],
  foldField: "fold_state",
  examples: [
    // ...
    { columns: ["To Do"], foldedColumns: ["Done"], /* ... */ },
  ],
});
```

In your arch description:
```xml
<kanban examples="my-examples" ...
```

Your python model should look something like this:
```python
from odoo import fields, models

class Project(models.Model):
  _name = 'project.project'
  stage_id = fields.Many2one('project.project.stage')
  # ...

class ProjectStage(models.Model):
  _name = 'project.project.stage'
  _fold_name = 'fold_state'  # optional, default is 'fold'
  fold_state = fields.Boolean()
  # ...
```

closes odoo/odoo#115909

Taskid: 3246042
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Bruno Boi
2023-03-29 09:33:35 +02:00
parent d2575b50eb
commit 4e974e5977
6 changed files with 94 additions and 20 deletions
@@ -9,10 +9,15 @@ import { Component, useRef } from "@odoo/owl";
const random = (min, max) => Math.floor(Math.random() * (max - min) + min);
class KanbanExamplesNotebookTemplate extends Component {
static defaultProps = {
columns: [],
foldedColumns: [],
};
setup() {
this.columns = [];
const hasBullet = this.props.bullets && this.props.bullets.length;
for (const title of this.props.columns) {
const allColumns = [...this.props.columns, ...this.props.foldedColumns];
for (const title of allColumns) {
const col = { title, records: [] };
this.columns.push(col);
for (let i = 0; i < random(1, 5); i++) {
@@ -76,9 +76,14 @@ export class KanbanColumnQuickCreate extends Component {
applyExamplesText:
this.props.exampleData.applyExamplesText || this.env._t("Use This For My Kanban"),
applyExamples: (index) => {
for (const groupName of this.props.exampleData.examples[index].columns) {
const { examples, foldField } = this.props.exampleData;
const { columns, foldedColumns = [] } = examples[index];
for (const groupName of columns) {
this.props.onValidate(groupName);
}
for (const groupName of foldedColumns) {
this.props.onValidate(groupName, { [foldField]: true }, true);
}
},
});
}
@@ -51,13 +51,17 @@ export function canQuickCreate(list) {
}
function validateColumnQuickCreateExamples(data) {
const { allowedGroupBys = [], examples = [] } = data;
const { allowedGroupBys = [], examples = [], foldField = "" } = data;
if (!allowedGroupBys.length) {
throw new Error("The example data must contain an array of allowed groupbys");
}
if (!examples.length) {
throw new Error("The example data must contain an array of examples");
}
const someHasFoldedColumns = examples.some(({ foldedColumns = [] }) => foldedColumns.length);
if (!foldField && someHasFoldedColumns) {
throw new Error("The example data must contain a fold field if there are folded columns");
}
}
export class KanbanRenderer extends Component {
@@ -116,7 +116,7 @@
<KanbanColumnQuickCreate
folded="state.columnQuickCreateIsFolded"
onFoldChange="folded => state.columnQuickCreateIsFolded = props.list.groups.length > 0 and folded"
onValidate="name => props.list.createGroup(name)"
onValidate="props.list.createGroup.bind(props.list)"
exampleData="exampleData"
groupByField="props.list.groupByField"
/>
+11 -11
View File
@@ -2272,8 +2272,8 @@ export class DynamicGroupList extends DynamicList {
/**
* @see {_createGroup}
*/
async createGroup(groupName) {
await this.model.mutex.exec(() => this._createGroup(groupName));
async createGroup(groupName, groupData, isFolded) {
await this.model.mutex.exec(() => this._createGroup(groupName, groupData, isFolded));
}
/**
@@ -2413,27 +2413,27 @@ export class DynamicGroupList extends DynamicList {
/**
* @param {string} groupName
* @param {{ [relatedFieldName: string]: any }} [groupData={}]
* @param {boolean} [isFolded=false]
* @returns {Promise<Group>}
*/
async _createGroup(groupName) {
const [id, displayName] = await this.model.orm.call(
this.groupByField.relation,
"name_create",
[groupName],
{ context: this.context }
);
async _createGroup(groupName, groupData = {}, isFolded = false) {
groupData = { ...groupData, name: groupName };
const [id] = await this.model.orm.create(this.groupByField.relation, [groupData], {
context: this.context,
});
const [lastGroup] = this.groups.slice(-1);
const group = this.model.createDataPoint("group", {
...this.commonGroupParams,
count: 0,
value: id,
displayName,
displayName: groupName,
aggregates: {},
groupByField: this.groupByField,
groupDomain: Domain.and([this.domain, [[this.groupByField.name, "=", id]]]).toList(),
isFolded,
rawContext: this.rawContext,
});
group.isFolded = false;
this.addGroup(group);
if (lastGroup) {
@@ -2249,7 +2249,7 @@ QUnit.module("Views", (hooks) => {
"web_search_read", // initial search_read (second column)
"get_views", // get form view
"onchange", // quick create
"create", // should perform a name_create to create the record
"create", // should perform a create to create the record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
@@ -4292,7 +4292,7 @@ QUnit.module("Views", (hooks) => {
"</kanban>",
groupBy: ["product_id"],
async mockRPC(route, { method, model }) {
if (method === "name_create" && model === "product") {
if (method === "create" && model === "product") {
await prom;
}
},
@@ -5687,7 +5687,7 @@ QUnit.module("Views", (hooks) => {
"</kanban>",
groupBy: ["product_id"],
async mockRPC(route, { method }) {
if (method === "name_create" || route === "/web/dataset/resequence") {
if (method === "create" || route === "/web/dataset/resequence") {
assert.step(method || route);
}
},
@@ -5735,7 +5735,7 @@ QUnit.module("Views", (hooks) => {
"o_column_folded",
"the created column should not be folded"
);
assert.verifySteps(["name_create", "/web/dataset/resequence"]);
assert.verifySteps(["create", "/web/dataset/resequence"]);
// fold and unfold the created column, and check that no RPCs are done (as there are no records)
const clickColumnAction = await toggleColumnActions(2);
@@ -6124,7 +6124,7 @@ QUnit.module("Views", (hooks) => {
);
assert.deepEqual(resequencedIDs, [4, 3], "moved column should be resequenced accordingly");
assert.verifySteps(["name_create"]);
assert.verifySteps(["create"]);
});
QUnit.test("create a column, delete it and create another one", async (assert) => {
@@ -6552,6 +6552,66 @@ QUnit.module("Views", (hooks) => {
);
});
QUnit.test("quick create column and examples: with folded columns", async (assert) => {
serverData.models.partner.records = [];
serverData.models.product.fields.folded = { string: "Folded", type: "boolean" };
serviceRegistry.add("dialog", dialogService, { force: true });
registry.category("kanban_examples").add("test", {
allowedGroupBys: ["product_id"],
foldField: "folded",
examples: [
{
name: "A first example",
columns: ["not folded"],
foldedColumns: ["folded"],
description: "A weak description.",
},
],
});
const expectedGroups = ['[[{"name":"not folded"}]]', '[[{"folded":true,"name":"folded"}]]'];
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: /* xml */ `
<kanban examples="test">
<field name="product_id"/>
<templates><t t-name="kanban-box">
<div><field name="foo"/></div>
</t></templates>
</kanban>
`,
groupBy: ["product_id"],
mockRPC(route, args) {
if (args?.method === "create") {
assert.step(`${args.method} (model: ${args.model})`);
assert.strictEqual(JSON.stringify(args.args), expectedGroups.shift());
}
},
});
// the quick create should already be unfolded as there are no records
assert.containsOnce(target, ".o_column_quick_create .o_quick_create_unfolded");
// click to see the examples
await click(target, ".o_column_quick_create .o_kanban_examples");
// apply the examples
assert.verifySteps([]);
await click(document.body, ".modal .modal-footer .btn.btn-primary");
assert.verifySteps(["create (model: product)", "create (model: product)"]);
// the applied examples should be visible
assert.containsN(target, ".o_kanban_group", 2);
assert.containsOnce(target, ".o_kanban_group:not(.o_column_folded)");
assert.containsOnce(target, ".o_kanban_group.o_column_folded");
assert.deepEqual(
[...target.querySelectorAll(".o_kanban_group")].map((e) => e.textContent),
["not folded", "folded (0)"]
);
});
QUnit.test("quick create column's apply button's display text", async (assert) => {
serviceRegistry.add("dialog", dialogService, { force: true });
const applyExamplesText = "Use This For My Test";