[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:
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user