Kanban board fixes (#5933)

* class.extend -> es6 class

* fix(kanban): Fix kanban creation from sidebar

Creating custom fields from client side breaks in cases when DDL
queries and normal queries were run simultaneously.
Now, we do the heavy lifting on the server side

Also, moved kanban specific code from list_sidebar to kanban_view

* style: replace function with arrow functions

* style: semicolon
This commit is contained in:
Faris Ansari 2018-08-07 14:41:51 +05:30 committed by Rushabh Mehta
parent bece82d6ab
commit a406e5db68
4 changed files with 204 additions and 196 deletions

View file

@ -8,6 +8,7 @@ import json
from frappe import _
from frappe.model.document import Document
from six import iteritems
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
class KanbanBoard(Document):
@ -36,6 +37,14 @@ def has_permission(doc, ptype, user):
return False
@frappe.whitelist()
def get_kanban_boards(doctype):
'''Get Kanban Boards for doctype to show in List View'''
return frappe.get_list('Kanban Board',
fields=['name', 'filters', 'reference_doctype', 'private'],
filters={ 'reference_doctype': doctype }
)
@frappe.whitelist()
def add_column(board_name, column_title):
'''Adds new column to Kanban Board'''
@ -120,6 +129,14 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
'''Create new KanbanBoard quickly with default options'''
doc = frappe.new_doc('Kanban Board')
if field_name == 'kanban_column':
create_custom_field(doctype, {
'label': 'Kanban Column',
'fieldname': 'kanban_column',
'fieldtype': 'Select',
'hidden': 1
})
meta = frappe.get_meta(doctype)
options = ''

View file

@ -51,7 +51,7 @@ class FormMeta(Meta):
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js",
"__linked_with", "__messages", "__print_formats", "__workflow_docs",
"__form_grid_templates", "__listview_template", "__tree_js",
"__dashboard", "__kanban_boards", "__kanban_column_fields", '__templates',
"__dashboard", "__kanban_column_fields", '__templates',
'__custom_js'):
d[k] = self.get(k)
@ -185,14 +185,8 @@ class FormMeta(Meta):
self.set('__dashboard', self.get_dashboard_data())
def load_kanban_meta(self):
self.load_kanban_boards()
self.load_kanban_column_fields()
def load_kanban_boards(self):
kanban_boards = frappe.get_list(
'Kanban Board', fields=['name', 'filters', 'reference_doctype', 'private'], filters={'reference_doctype': self.name})
self.set("__kanban_boards", kanban_boards, as_value=True)
def load_kanban_column_fields(self):
values = frappe.get_list(
'Kanban Board', fields=['field_name'],

View file

@ -9,14 +9,15 @@ frappe.provide('frappe.views');
// parent
// set_filter = function called on click
frappe.views.ListSidebar = Class.extend({
init: function(opts) {
frappe.views.ListSidebar = class ListSidebar {
constructor(opts) {
$.extend(this, opts);
this.make();
this.get_stats();
this.cat_tags = [];
},
make: function() {
}
make() {
var sidebar_content = frappe.render_template("list_sidebar", { doctype: this.doctype });
this.sidebar = $('<div class="list-sidebar overlay-sidebar hidden-xs hidden-sm"></div>')
@ -36,8 +37,9 @@ frappe.views.ListSidebar = Class.extend({
if (limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) {
this.setup_upgrade_box();
}
},
setup_views: function() {
}
setup_views() {
var show_list_link = false;
if (frappe.views.calendar[this.doctype]) {
@ -85,8 +87,9 @@ frappe.views.ListSidebar = Class.extend({
if (show_list_link) {
this.sidebar.find('.list-link[data-view="List"]').removeClass('hide');
}
},
setup_reports: function() {
}
setup_reports() {
// add reports linked to this doctype to the dropdown
var me = this;
var added = [];
@ -124,167 +127,22 @@ frappe.views.ListSidebar = Class.extend({
// from specially tagged reports
add_reports(frappe.boot.user.all_reports || []);
},
}
setup_list_filter: function() {
setup_list_filter() {
this.list_filter = new ListFilter({
wrapper: this.page.sidebar.find('.list-filters'),
doctype: this.doctype,
list_view: this.list_view
});
},
setup_kanban_boards: function() {
// add kanban boards linked to this doctype to the dropdown
var me = this;
var $dropdown = this.page.sidebar.find('.kanban-dropdown');
var divider = false;
var meta = frappe.get_meta(this.doctype);
var boards = meta && meta.__kanban_boards;
if (!boards) return;
boards.forEach(function(board) {
var route = ["List", board.reference_doctype, "Kanban", board.name].join('/');
if (!divider) {
me.get_divider().appendTo($dropdown);
divider = true;
}
$(`<li><a href="#${route}">
<span>${__(board.name)}</span>
${board.private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
</a></li>`).appendTo($dropdown);
});
$dropdown.find('.new-kanban-board').click(function() {
// frappe.new_doc('Kanban Board', {reference_doctype: me.doctype});
var select_fields = frappe.get_meta(me.doctype)
.fields.filter(function(df) {
return df.fieldtype === 'Select' &&
df.fieldname !== 'kanban_column';
});
var fields = [{
fieldtype: 'Data',
fieldname: 'board_name',
label: __('Kanban Board Name'),
reqd: 1
}];
if(me.doctype === 'Task') {
fields.push({
fieldtype: 'Link',
fieldname: 'project',
label: __('Project'),
options: 'Project'
});
}
if(select_fields.length > 0) {
fields = fields.concat([{
fieldtype: 'Select',
fieldname: 'field_name',
label: __('Columns based on'),
options: select_fields.map(df => df.label).join('\n'),
default: select_fields[0],
depends_on: 'eval:doc.custom_column===0'
},
{
fieldtype: 'Check',
fieldname: 'custom_column',
label: __('Custom Column'),
default: 0
}]);
setup_kanban_boards() {
const $dropdown = this.page.sidebar.find('.kanban-dropdown');
frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown);
}
if(['Note', 'ToDo'].includes(me.doctype)) {
fields[0].description = __('This Kanban Board will be private');
}
var d = new frappe.ui.Dialog({
title: __('New Kanban Board'),
fields: fields,
primary_action_label: __('Save'),
primary_action: function(values) {
var custom_column = values.custom_column !== undefined ?
values.custom_column : 1;
var field_name;
if (custom_column) {
field_name = 'kanban_column';
} else {
if (!values.field_name) {
frappe.throw(__('Please select Columns Based On'));
}
var field_name =
select_fields
.find(df => df.label === values.field_name)
.fieldname;
}
me.add_custom_column_field(custom_column)
.then(function(custom_column) {
return me.make_kanban_board(values.board_name, field_name, values.project);
})
.then(function() {
d.hide();
}, function(err) {
frappe.msgprint(err);
});
}
});
d.show();
});
},
add_custom_column_field: function(flag) {
var me = this;
return new Promise(function(resolve, reject) {
if (!flag) resolve(false);
frappe.call({
method: 'frappe.custom.doctype.custom_field.custom_field.add_custom_field',
args: {
doctype: me.doctype,
df: {
label: 'Kanban Column',
fieldname: 'kanban_column',
fieldtype: 'Select',
hidden: 1
}
}
}).success(function() {
resolve(true);
}).error(function(err) {
reject(err);
});
});
},
make_kanban_board: function(board_name, field_name, project) {
var me = this;
return frappe.call({
method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
args: {
doctype: me.doctype,
board_name,
field_name,
project
},
callback: function(r) {
var kb = r.message;
if (kb.filters) {
frappe.provide('frappe.kanban_filters');
frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
}
frappe.set_route(
'List',
me.doctype,
'Kanban',
kb.kanban_board_name
);
}
});
},
setup_calendar_view: function() {
setup_calendar_view() {
const doctype = this.doctype;
frappe.db.get_list('Calendar View', {
@ -322,8 +180,9 @@ frappe.views.ListSidebar = Class.extend({
$link_calendar.removeClass('hide');
$link_calendar.html(dropdown_html);
});
},
setup_email_inbox: function() {
}
setup_email_inbox() {
// get active email account for the user and add in dropdown
if (this.doctype != "Communication")
return;
@ -352,13 +211,15 @@ frappe.views.ListSidebar = Class.extend({
$dropdown.find('.new-email-account').click(function() {
frappe.new_doc("Email Account");
});
},
setup_assigned_to_me: function() {
}
setup_assigned_to_me() {
this.page.sidebar.find(".assigned-to-me a").on("click", () => {
this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${frappe.session.user}%`);
});
},
setup_upgrade_box: function() {
}
setup_upgrade_box() {
let upgrade_list = $(`<ul class="list-unstyled sidebar-menu"></ul>`).appendTo(this.sidebar);
// Show Renew/Upgrade button,
@ -386,11 +247,13 @@ frappe.views.ListSidebar = Class.extend({
frappe.flags.upgrade_dismissed = 1;
});
}
},
get_cat_tags: function() {
}
get_cat_tags() {
return this.cat_tags;
},
get_stats: function() {
}
get_stats() {
var me = this;
frappe.call({
method: 'frappe.desk.reportview.get_sidebar_stats',
@ -425,8 +288,9 @@ frappe.views.ListSidebar = Class.extend({
}
}
});
},
render_stat: function(field, stat, tags) {
}
render_stat(field, stat, tags) {
var me = this;
var sum = 0;
var stats = [];
@ -485,8 +349,9 @@ frappe.views.ListSidebar = Class.extend({
});
})
.insertBefore(this.sidebar.find(".close-sidebar-button"));
},
set_fieldtype: function(df) {
}
set_fieldtype(df) {
// scrub
if (df.fieldname == "docstatus") {
@ -511,12 +376,14 @@ frappe.views.ListSidebar = Class.extend({
if (df.fieldtype === "Data" && (df.options || "").toLowerCase() === "email") {
df.options = null;
}
},
reload_stats: function() {
}
reload_stats() {
this.sidebar.find(".sidebar-stat").remove();
this.get_stats();
},
get_divider: function() {
}
get_divider() {
return $('<li role="separator" class="divider"></li>');
}
});
};

View file

@ -99,7 +99,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
title_field = frappe.meta.get_field(this.doctype, this.meta.title_field);
}
this.meta.fields.forEach(function(df) {
this.meta.fields.forEach((df) => {
const is_valid_field =
in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype)
&& !df.hidden;
@ -111,9 +111,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
});
// quick entry
var mandatory = meta.fields.filter(function(df) {
return df.reqd && !doc[df.fieldname];
});
var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]);
if (mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) {
quick_entry = true;
@ -136,3 +134,135 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
];
}
};
frappe.views.KanbanView.setup_dropdown_in_sidebar = function(doctype, $dropdown) {
// get kanban boards and append to dropdown
get_kanban_boards()
.then((kanban_boards) => {
if (!kanban_boards) return;
$('<li role="separator" class="divider"></li>').appendTo($dropdown);
kanban_boards.forEach(board => {
const route = ['List', board.reference_doctype, 'Kanban', board.name].join('/');
$(`<li>
<a href="#${route}">
<span>${__(board.name)}</span>
${board.private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
</a>
</li>
`).appendTo($dropdown);
});
});
$dropdown.on('click', '.new-kanban-board', () => {
const dialog = new_kanban_dialog();
dialog.show();
});
function make_kanban_board(board_name, field_name, project) {
return frappe.call({
method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
args: {
doctype,
board_name,
field_name,
project
},
callback: function(r) {
var kb = r.message;
if (kb.filters) {
frappe.provide('frappe.kanban_filters');
frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
}
frappe.set_route('List', doctype, 'Kanban', kb.kanban_board_name);
}
});
}
let dialog = null;
function new_kanban_dialog() {
if (dialog) return dialog;
const fields = get_fields_for_dialog();
dialog = new frappe.ui.Dialog({
title: __('New Kanban Board'),
fields: fields,
primary_action_label: __('Save'),
primary_action(values) {
const custom_column =
values.custom_column !== undefined ?
values.custom_column : 1;
let field_name = custom_column ? 'kanban_column' : values.field_name;
make_kanban_board(values.board_name, field_name, values.project)
.then(() => dialog.hide(), (err) => frappe.msgprint(err));
}
});
return dialog;
}
function get_fields_for_dialog() {
let fields = [{
fieldtype: 'Data',
fieldname: 'board_name',
label: __('Kanban Board Name'),
reqd: 1,
description: ['Note', 'ToDo'].includes(doctype) ?
__('This Kanban Board will be private') : ''
}];
if (doctype === 'Task') {
fields.push({
fieldtype: 'Link',
fieldname: 'project',
label: __('Project'),
options: 'Project'
});
}
const select_fields =
frappe.get_meta(doctype).fields
.filter(df => {
return df.fieldtype === 'Select' &&
df.fieldname !== 'kanban_column';
});
if (select_fields.length > 0) {
fields = fields.concat([{
fieldtype: 'Select',
fieldname: 'field_name',
label: __('Columns based on'),
options: select_fields.map(df => ({label: df.label, value: df.fieldname})),
default: select_fields[0],
depends_on: 'eval:doc.custom_column===0',
reqd: 1
},
{
fieldtype: 'Check',
fieldname: 'custom_column',
label: __('Custom Column'),
default: 0,
onchange() {
const value = this.get_value();
this.layout.set_df_property('field_name', 'reqd', !value);
}
}]);
}
return fields;
}
function get_kanban_boards() {
return frappe.call('frappe.desk.doctype.kanban_board.kanban_board.get_kanban_boards', { doctype })
.then(r => r.message);
}
};