Merge branch 'develop' into group-by
This commit is contained in:
commit
42a7d0d527
145 changed files with 11212 additions and 7270 deletions
|
|
@ -137,6 +137,7 @@
|
|||
"Cypress": true,
|
||||
"cy": true,
|
||||
"it": true,
|
||||
"expect": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true
|
||||
|
|
|
|||
38
cypress/integration/control_rating.js
Normal file
38
cypress/integration/control_rating.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
context('Rating Control', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
});
|
||||
|
||||
it('click on the star rating to record value', () => {
|
||||
cy.visit('/desk');
|
||||
cy.dialog('Rating', [{
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Rating',
|
||||
}]).as('dialog');
|
||||
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
.first()
|
||||
.click()
|
||||
.should('have.class', 'star-click');
|
||||
cy.get('@dialog').then(dialog => {
|
||||
var value = dialog.get_value('rate');
|
||||
expect(value).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('hover on the star', () => {
|
||||
cy.visit('/desk');
|
||||
cy.dialog('Rating', [{
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Rating',
|
||||
}]);
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
.first()
|
||||
.invoke('trigger', 'mouseenter')
|
||||
.should('have.class', 'star-hover')
|
||||
.invoke('trigger', 'mouseleave')
|
||||
.should('not.have.class', 'star-hover');
|
||||
});
|
||||
});
|
||||
|
|
@ -61,3 +61,17 @@ Cypress.Commands.add('new_form', (doctype) => {
|
|||
Cypress.Commands.add('go_to_list', (doctype) => {
|
||||
cy.visit(`/desk#List/${doctype}/List`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('dialog', (title, fields) => {
|
||||
cy.window().then(win => {
|
||||
var d = new win.frappe.ui.Dialog({
|
||||
title: title,
|
||||
fields: fields,
|
||||
primary_action: function(){
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
return d;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if sys.version[0] == '2':
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '11.1.16'
|
||||
__version__ = '11.1.20'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
@ -188,6 +188,9 @@ def connect(site=None, db_name=None):
|
|||
local.db = get_db(user=db_name or local.conf.db_name)
|
||||
set_user("Administrator")
|
||||
|
||||
for hook in get_hooks("connect") or []:
|
||||
get_attr(hook)()
|
||||
|
||||
def connect_read_only():
|
||||
from frappe.database import get_db
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions
|
|||
from frappe.translate import get_lang_dict
|
||||
from frappe.email.inbox import get_email_accounts
|
||||
from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
|
||||
def get_bootinfo():
|
||||
"""build and return boot info"""
|
||||
|
|
@ -78,6 +79,7 @@ def get_bootinfo():
|
|||
bootinfo.gsuite_enabled = get_gsuite_status()
|
||||
bootinfo.success_action = get_success_action()
|
||||
bootinfo.update(get_email_accounts(user=frappe.session.user))
|
||||
bootinfo.energy_points_enabled = is_energy_point_enabled()
|
||||
|
||||
return bootinfo
|
||||
|
||||
|
|
@ -98,7 +100,6 @@ def load_conf_settings(bootinfo):
|
|||
def load_desktop_icons(bootinfo):
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
|
||||
bootinfo.home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings','')
|
||||
|
||||
def get_allowed_pages():
|
||||
return get_user_pages_or_reports('Page')
|
||||
|
|
@ -112,22 +113,25 @@ def get_user_pages_or_reports(parent):
|
|||
column = get_column(parent)
|
||||
|
||||
# get pages or reports set on custom role
|
||||
custom_roles = frappe.db.sql("""
|
||||
pages_with_custom_roles = frappe.db.sql("""
|
||||
select
|
||||
`tabCustom Role`.{field} as name,
|
||||
`tabCustom Role`.modified,
|
||||
`tabCustom Role`.ref_doctype
|
||||
from `tabCustom Role`, `tabHas Role`
|
||||
`tabCustom Role`.ref_doctype,
|
||||
{column}
|
||||
from `tabCustom Role`, `tabHas Role`, `tab{parent}`
|
||||
where
|
||||
`tabHas Role`.parent = `tabCustom Role`.name
|
||||
and `tab{parent}`.name = `tabCustom Role`.{field}
|
||||
and `tabCustom Role`.{field} is not null
|
||||
and `tabHas Role`.role in ({roles})
|
||||
""".format(field=parent.lower(), roles = ', '.join(['%s']*len(roles))), roles, as_dict=1)
|
||||
""".format(field=parent.lower(), parent=parent, column=column,
|
||||
roles = ', '.join(['%s']*len(roles))), roles, as_dict=1)
|
||||
|
||||
for p in custom_roles:
|
||||
has_role[p.name] = {"modified":p.modified, "title": p.name, "ref_doctype": p.ref_doctype}
|
||||
for p in pages_with_custom_roles:
|
||||
has_role[p.name] = {"modified":p.modified, "title": p.title, "ref_doctype": p.ref_doctype}
|
||||
|
||||
standard_roles = frappe.db.sql("""
|
||||
pages_with_standard_roles = frappe.db.sql("""
|
||||
select distinct
|
||||
`tab{parent}`.name as name,
|
||||
`tab{parent}`.modified,
|
||||
|
|
@ -144,7 +148,7 @@ def get_user_pages_or_reports(parent):
|
|||
field=parent.lower(), condition="and `tabReport`.disabled=0" if parent == "Report" else ""),
|
||||
roles, as_dict=True)
|
||||
|
||||
for p in standard_roles:
|
||||
for p in pages_with_standard_roles:
|
||||
if p.name not in has_role:
|
||||
has_role[p.name] = {"modified":p.modified, "title": p.title}
|
||||
if parent == "Report":
|
||||
|
|
|
|||
|
|
@ -117,7 +117,10 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
os.unlink(target)
|
||||
else:
|
||||
shutil.rmtree(target)
|
||||
os.symlink(source, target)
|
||||
try:
|
||||
os.symlink(source, target)
|
||||
except OSError:
|
||||
print('Cannot link {} to {}'.format(source, target))
|
||||
else:
|
||||
# warnings.warn('Source {source} does not exist.'.format(source = source))
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -99,4 +99,15 @@ def get_data():
|
|||
'idx': 15,
|
||||
"description": "Build your profile and share posts with other users."
|
||||
},
|
||||
{
|
||||
"module_name": 'leaderboard',
|
||||
"category": "Places",
|
||||
"label": _('Leaderboard'),
|
||||
"icon": "octicon octicon-list-ordered",
|
||||
"type": "link",
|
||||
"link": "#social/users",
|
||||
"color": '#FF4136',
|
||||
'standard': 1,
|
||||
'idx': 16
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -63,14 +63,21 @@ def get_reference_addresses_and_contact(reference_doctype, reference_name):
|
|||
contacts = details.get("contact", [])
|
||||
if not any([addresses, contacts]):
|
||||
result = [reference_name]
|
||||
result.extend(add_blank_columns_for("Contact"))
|
||||
result.extend(add_blank_columns_for("Address"))
|
||||
result.extend(add_blank_columns_for("Contact"))
|
||||
data.append(result)
|
||||
else:
|
||||
result = [reference_name]
|
||||
result.extend(list(addresses) or add_blank_columns_for("Address"))
|
||||
result.extend(list(contacts) or add_blank_columns_for("Contact"))
|
||||
data.append(result)
|
||||
addresses = list(map(list, addresses))
|
||||
contacts = list(map(list, contacts))
|
||||
|
||||
max_length = max(len(addresses), len(contacts))
|
||||
for idx in range(0, max_length):
|
||||
result = [reference_name]
|
||||
|
||||
result.extend(addresses[idx] if idx < len(addresses) else add_blank_columns_for("Address"))
|
||||
result.extend(contacts[idx] if idx < len(contacts) else add_blank_columns_for("Contact"))
|
||||
|
||||
data.append(result)
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -82,8 +89,12 @@ def get_reference_details(reference_doctype, doctype, reference_list, reference_
|
|||
fields = ["`tabDynamic Link`.link_name"] + field_map.get(doctype, [])
|
||||
|
||||
records = frappe.get_list(doctype, filters=filters, fields=fields, as_list=True)
|
||||
temp_records = list()
|
||||
|
||||
for d in records:
|
||||
reference_details[d[0]][frappe.scrub(doctype)] = d[1:]
|
||||
temp_records.append(d[1:])
|
||||
|
||||
reference_details[reference_list[0]][frappe.scrub(doctype)] = temp_records
|
||||
return reference_details
|
||||
|
||||
def add_blank_columns_for(doctype):
|
||||
|
|
|
|||
|
|
@ -94,14 +94,14 @@ def create_linked_contact(link_list):
|
|||
|
||||
class TestAddressesAndContacts(unittest.TestCase):
|
||||
def test_get_data(self):
|
||||
linked_docs = [get_custom_doc_for_address_and_contacts(), get_custom_doc_for_address_and_contacts(), get_custom_doc_for_address_and_contacts()]
|
||||
linked_docs = [get_custom_doc_for_address_and_contacts()]
|
||||
links_list = [item.name for item in linked_docs]
|
||||
create_linked_contact(links_list)
|
||||
create_linked_address(links_list)
|
||||
report_data = get_data({"reference_doctype": "Test Custom Doctype"})
|
||||
for link in links_list:
|
||||
for idx, link in enumerate(links_list):
|
||||
test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '+91 0000000000', None, 'test_contact@example.com', 1]
|
||||
self.assertIn(test_item, report_data)
|
||||
self.assertListEqual(test_item, report_data[idx])
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
frappe.db.rollback()
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False,
|
|||
|
||||
def export_json(doctype, path, filters=None, or_filters=None, name=None, order_by="creation asc"):
|
||||
def post_process(out):
|
||||
del_keys = ('parent', 'parentfield', 'parenttype', 'modified_by', 'creation', 'owner', 'idx')
|
||||
del_keys = ('modified_by', 'creation', 'owner', 'idx')
|
||||
for doc in out:
|
||||
for key in del_keys:
|
||||
if key in doc:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@ frappe.ui.form.on('User', {
|
|||
before_load: function(frm) {
|
||||
var update_tz_select = function(user_language) {
|
||||
frm.set_df_property("time_zone", "options", [""].concat(frappe.all_timezones));
|
||||
}
|
||||
};
|
||||
|
||||
if(!frappe.all_timezones) {
|
||||
frappe.call({
|
||||
|
|
@ -25,7 +25,7 @@ frappe.ui.form.on('User', {
|
|||
args: {
|
||||
role_profile: frm.doc.role_profile_name
|
||||
},
|
||||
callback: function (data) {
|
||||
callback: function(data) {
|
||||
frm.set_value("roles", []);
|
||||
$.each(data.message || [], function(i, v){
|
||||
var d = frm.add_child("roles");
|
||||
|
|
@ -60,6 +60,7 @@ frappe.ui.form.on('User', {
|
|||
frm.reload_doc();
|
||||
return;
|
||||
}
|
||||
|
||||
if(doc.name===frappe.session.user && !doc.__unsaved
|
||||
&& frappe.all_timezones
|
||||
&& (doc.language || frappe.boot.user.language)
|
||||
|
|
@ -78,7 +79,7 @@ frappe.ui.form.on('User', {
|
|||
"user": doc.name
|
||||
};
|
||||
frappe.set_route('List', 'User Permission');
|
||||
}, __("Permissions"))
|
||||
}, __("Permissions"));
|
||||
|
||||
frm.add_custom_button(__('View Permitted Documents'),
|
||||
() => frappe.set_route('query-report', 'Permitted Documents For User',
|
||||
|
|
@ -93,7 +94,7 @@ frappe.ui.form.on('User', {
|
|||
args: {
|
||||
"user": frm.doc.name
|
||||
}
|
||||
})
|
||||
});
|
||||
}, __("Password"));
|
||||
|
||||
frm.add_custom_button(__("Reset OTP Secret"), function() {
|
||||
|
|
@ -102,7 +103,7 @@ frappe.ui.form.on('User', {
|
|||
args: {
|
||||
"user": frm.doc.name
|
||||
}
|
||||
})
|
||||
});
|
||||
}, __("Password"));
|
||||
|
||||
frm.trigger('enabled');
|
||||
|
|
@ -130,8 +131,8 @@ frappe.ui.form.on('User', {
|
|||
}
|
||||
if (!found){
|
||||
frm.add_custom_button(__("Create User Email"), function() {
|
||||
frm.events.create_user_email(frm)
|
||||
})
|
||||
frm.events.create_user_email(frm);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +147,7 @@ frappe.ui.form.on('User', {
|
|||
},
|
||||
validate: function(frm) {
|
||||
if(frm.roles_editor) {
|
||||
frm.roles_editor.set_roles_in_table()
|
||||
frm.roles_editor.set_roles_in_table();
|
||||
}
|
||||
},
|
||||
enabled: function(frm) {
|
||||
|
|
@ -173,18 +174,18 @@ frappe.ui.form.on('User', {
|
|||
"awaiting_password": 1,
|
||||
"enable_incoming": 1
|
||||
};
|
||||
frappe.model.with_doctype("Email Account", function (doc) {
|
||||
frappe.model.with_doctype("Email Account", function(doc) {
|
||||
var doc = frappe.model.get_new_doc("Email Account");
|
||||
frappe.route_flags.linked_user = frm.doc.name;
|
||||
frappe.route_flags.delete_user_from_locals = true;
|
||||
frappe.set_route("Form", "Email Account", doc.name);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
frappe.route_flags.create_user_account = frm.doc.name;
|
||||
frappe.set_route("Form", "Email Account", r.message[0]["name"]);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
generate_keys: function(frm){
|
||||
frappe.call({
|
||||
|
|
@ -197,9 +198,9 @@ frappe.ui.form.on('User', {
|
|||
frappe.msgprint(__("Save API Secret: ") + r.message.api_secret);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function has_access_to_edit_user() {
|
||||
return has_common(frappe.user_roles, get_roles_for_editing_user());
|
||||
|
|
@ -239,10 +240,14 @@ frappe.ModuleEditor = Class.extend({
|
|||
var module = $(this).attr('data-module');
|
||||
if($(this).prop("checked")) {
|
||||
// remove from block_modules
|
||||
me.frm.doc.block_modules = $.map(me.frm.doc.block_modules || [], function(d) { if(d.module != module){ return d } });
|
||||
me.frm.doc.block_modules = $.map(me.frm.doc.block_modules || [], function(d) {
|
||||
if (d.module != module) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
me.frm.add_child("block_modules", {"module": module});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1086,4 +1086,4 @@ def generate_keys(user):
|
|||
user_details.save()
|
||||
|
||||
return {"api_secret": api_secret}
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
0
frappe/core/page/dashboard/__init__.py
Normal file
0
frappe/core/page/dashboard/__init__.py
Normal file
27
frappe/core/page/dashboard/dashboard.css
Normal file
27
frappe/core/page/dashboard/dashboard.css
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.chart-container {
|
||||
border: 1px solid #d1d8dd;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.chart-actions {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 54px;
|
||||
}
|
||||
|
||||
.chart-column-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.last-synced-text {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
right: 60px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dashboard-graph {
|
||||
padding-top: 15px;
|
||||
}
|
||||
234
frappe/core/page/dashboard/dashboard.js
Normal file
234
frappe/core/page/dashboard/dashboard.js
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
|
||||
frappe.pages['dashboard'].on_page_load = function(wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __("Dashboard"),
|
||||
single_column: true
|
||||
});
|
||||
|
||||
frappe.dashboard = new Dashboard(wrapper);
|
||||
$(wrapper).bind('show', function() {
|
||||
frappe.dashboard.show();
|
||||
});
|
||||
};
|
||||
|
||||
class Dashboard {
|
||||
constructor(wrapper) {
|
||||
this.wrapper = $(wrapper);
|
||||
$(`<div class="dashboard">
|
||||
<div class="dashboard-graph row"></div>
|
||||
</div>`).appendTo(this.wrapper.find(".page-content").empty());
|
||||
this.container = this.wrapper.find(".dashboard-graph");
|
||||
this.page = wrapper.page;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.route = frappe.get_route();
|
||||
const current_dashboard_name = this.route.slice(-1)[0];
|
||||
|
||||
if(this.dashboard_name !== current_dashboard_name) {
|
||||
this.dashboard_name = current_dashboard_name;
|
||||
this.page.set_title(this.dashboard_name);
|
||||
this.set_dropdown();
|
||||
this.container.empty();
|
||||
this.refresh();
|
||||
}
|
||||
this.charts = {};
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.get_dashboard_doc().then((doc) => {
|
||||
this.dashboard_doc = doc;
|
||||
this.charts = this.dashboard_doc.charts;
|
||||
|
||||
this.charts.map((chart) => {
|
||||
let chart_container = $("<div></div>");
|
||||
chart_container.appendTo(this.container);
|
||||
|
||||
frappe.model.with_doc("Dashboard Chart", chart.chart).then( chart_doc => {
|
||||
let dashboard_chart = new DashboardChart(chart_doc, chart_container);
|
||||
dashboard_chart.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_dashboard_doc() {
|
||||
return frappe.model.with_doc('Dashboard', this.dashboard_name);
|
||||
}
|
||||
|
||||
set_dropdown() {
|
||||
this.page.clear_menu();
|
||||
frappe.db.get_list("Dashboard").then(dashboards => {
|
||||
dashboards.map(dashboard => {
|
||||
let name = dashboard.name;
|
||||
if(name != this.dashboard_name){
|
||||
this.page.add_menu_item(name, () => frappe.set_route("dashboard", name));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardChart {
|
||||
constructor(chart_doc, chart_container) {
|
||||
this.chart_doc = chart_doc;
|
||||
this.container = chart_container;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.get_settings().then(() => {
|
||||
this.prepare_chart_object();
|
||||
this.prepare_container();
|
||||
this.prepare_chart_actions();
|
||||
this.fetch(this.filters).then((data) => {
|
||||
this.update_last_synced();
|
||||
this.data = data;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
prepare_container() {
|
||||
const column_width_map = {
|
||||
"Half": "6",
|
||||
"Full": "12",
|
||||
};
|
||||
let columns = column_width_map[this.chart_doc.width];
|
||||
this.chart_container = $(`<div class="col-sm-${columns} chart-column-container">
|
||||
<div class="chart-wrapper"></div>
|
||||
</div>`);
|
||||
this.chart_container.appendTo(this.container);
|
||||
|
||||
let last_synced_text = $(`<span class="text-muted last-synced-text"></span>`);
|
||||
last_synced_text.prependTo(this.chart_container);
|
||||
}
|
||||
|
||||
prepare_chart_actions() {
|
||||
let actions = [
|
||||
{
|
||||
label: __("Set Filters"),
|
||||
action: "set-filters",
|
||||
handler: this.create_set_filters_dialog.bind(this)
|
||||
},
|
||||
{
|
||||
label: __("Force Refresh"),
|
||||
action: "force-refresh",
|
||||
handler: () => {
|
||||
this.fetch(this.filters, true).then(data => {
|
||||
this.update_chart_object();
|
||||
this.data = data;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
this.set_chart_actions(actions);
|
||||
}
|
||||
|
||||
set_chart_actions(actions) {
|
||||
this.chart_actions = $(`<div class="chart-actions btn-group dropdown pull-right">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"> <button class="btn btn-default btn-xs"><span class="caret"></span></button>
|
||||
</a>
|
||||
<ul class="dropdown-menu" style="max-height: 300px; overflow-y: auto;">
|
||||
${actions.map(action => `<li><a data-action="${action.action}">${action.label}</a></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`);
|
||||
|
||||
this.chart_actions.find("a[data-action]").each((i, o) => {
|
||||
const action = o.dataset.action;
|
||||
$(o).click(actions.find(a => a.action === action));
|
||||
});
|
||||
this.chart_actions.prependTo(this.chart_container);
|
||||
}
|
||||
|
||||
fetch(filters, refresh=false) {
|
||||
return frappe.xcall(
|
||||
this.settings.method_path,
|
||||
{
|
||||
chart_name: this.chart_doc.name,
|
||||
filters: filters,
|
||||
refresh: refresh ? 1 : 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const chart_type_map = {
|
||||
"Line": "line",
|
||||
"Bar": "bar",
|
||||
};
|
||||
let chart_args = {
|
||||
title: this.chart_doc.chart_name.bold(),
|
||||
data: this.data,
|
||||
type: chart_type_map[this.chart_doc.type],
|
||||
colors: [this.chart_doc.color || "light-blue"],
|
||||
axisOptions: {
|
||||
xIsSeries: this.settings.is_time_series
|
||||
},
|
||||
};
|
||||
if(!this.chart) {
|
||||
this.chart = new Chart(this.chart_container.find(".chart-wrapper")[0], chart_args);
|
||||
} else {
|
||||
this.chart.update(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
update_last_synced() {
|
||||
let last_synced_text = __("Last synced {0}", [comment_when(this.chart_doc.last_synced_on)]);
|
||||
this.container.find(".last-synced-text").html(last_synced_text);
|
||||
}
|
||||
|
||||
update_chart_object() {
|
||||
frappe.db.get_doc("Dashboard Chart", this.chart_doc.name).then(doc => {
|
||||
this.chart_doc = doc;
|
||||
this.prepare_chart_object();
|
||||
this.update_last_synced();
|
||||
});
|
||||
}
|
||||
|
||||
prepare_chart_object() {
|
||||
this.filters = JSON.parse(this.chart_doc.filters_json || '{}');
|
||||
}
|
||||
|
||||
get_settings() {
|
||||
return new Promise(resolve => frappe.db.get_value("Dashboard Chart Source", this.chart_doc.source, "config", e => {
|
||||
this.settings = JSON.parse(e.config);
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
create_set_filters_dialog() {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Set Filters'),
|
||||
fields: this.settings.filters
|
||||
});
|
||||
d.set_values(this.filters);
|
||||
d.show();
|
||||
|
||||
const set_filters = () => {
|
||||
const values = d.get_values();
|
||||
if (!Object.entries(this.filters).map(e => values[e[0]] === e[1]).every(Boolean)) {
|
||||
frappe.db.set_value("Dashboard Chart", this.chart_doc.name, "filters_json", JSON.stringify(values)).then(() => {
|
||||
this.fetch(values, true).then(data => {
|
||||
this.update_chart_object();
|
||||
this.data = data;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
}
|
||||
d.hide();
|
||||
};
|
||||
|
||||
this.settings.filters.map(field => field.onchange = e => {
|
||||
if(e) {
|
||||
d.set_primary_action(__('Save Filters'), set_filters);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
19
frappe/core/page/dashboard/dashboard.json
Normal file
19
frappe/core/page/dashboard/dashboard.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2019-01-08 19:19:48.073410",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2019-01-08 19:19:48.073410",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "dashboard",
|
||||
"owner": "Administrator",
|
||||
"page_name": "Dashboard",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Dashboard"
|
||||
}
|
||||
32
frappe/core/page/dashboard/dashboard.py
Normal file
32
frappe/core/page/dashboard/dashboard.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
import frappe
|
||||
|
||||
|
||||
def cache_source(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
filters = json.loads(kwargs.get("filters", "{}"))
|
||||
chart_name = kwargs.get("chart_name")
|
||||
cache_key = json.dumps({
|
||||
"name": chart_name,
|
||||
"filters": filters
|
||||
}, default=str)
|
||||
if kwargs.get("refresh"):
|
||||
results = generate_and_cache_results(chart_name, function, filters, cache_key)
|
||||
else:
|
||||
cached_results = frappe.cache().get_value(cache_key)
|
||||
if cached_results:
|
||||
results = json.loads(frappe.safe_decode(cached_results))
|
||||
else:
|
||||
results = generate_and_cache_results(chart_name, function, filters, cache_key)
|
||||
return results
|
||||
return wrapper
|
||||
|
||||
|
||||
def generate_and_cache_results(chart_name, function, filters, cache_key):
|
||||
results = function(frappe._dict(filters))
|
||||
frappe.cache().set_value(cache_key, json.dumps(results, default=str))
|
||||
frappe.db.set_value("Dashboard Chart", chart_name, "last_synced_on", frappe.utils.now())
|
||||
return results
|
||||
|
|
@ -11,6 +11,7 @@ from frappe.core.doctype.doctype.doctype import (clear_permissions_cache,
|
|||
validate_permissions_for_doctype)
|
||||
from frappe.permissions import (reset_perms, get_linked_doctypes, get_all_perms,
|
||||
setup_custom_perms, add_permission, update_permission_property)
|
||||
from frappe.utils.user import get_users_with_role as _get_user_with_role
|
||||
|
||||
not_allowed_in_permission_manager = ["DocType", "Patch Log", "Module Def", "Transaction Log"]
|
||||
|
||||
|
|
@ -101,13 +102,7 @@ def reset(doctype):
|
|||
@frappe.whitelist()
|
||||
def get_users_with_role(role):
|
||||
frappe.only_for("System Manager")
|
||||
|
||||
return [p[0] for p in frappe.db.sql("""select distinct tabUser.name
|
||||
from `tabHas Role`, tabUser where
|
||||
`tabHas Role`.role=%s
|
||||
and tabUser.name != "Administrator"
|
||||
and `tabHas Role`.parent = tabUser.name
|
||||
and tabUser.enabled=1""", role)]
|
||||
_get_user_with_role(role)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_standard_permissions(doctype):
|
||||
|
|
|
|||
|
|
@ -46,10 +46,6 @@
|
|||
<div class="usage-info-section" style="margin: 30px;">
|
||||
<h4>{{ __("Space") }}</h4>
|
||||
|
||||
{% var database_percent = ((limits.space_usage.database_size / limits.space) * 100); %}
|
||||
{% var files_percent = ((limits.space_usage.files_size / limits.space) * 100); %}
|
||||
{% var backup_percent = ((limits.space_usage.backup_size / limits.space) * 100); %}
|
||||
|
||||
<div class="progress" style="margin-bottom: 0;">
|
||||
<div class="progress-bar" style="width: {%= database_percent %}%; background-color: #5e64ff"></div>
|
||||
<div class="progress-bar" style="width: {%= files_percent %}%; background-color: #743ee2"></div>
|
||||
|
|
@ -66,12 +62,7 @@
|
|||
{{ __("Backup Size:") }} {%= limits.space_usage.backup_size %} MB
|
||||
</span>
|
||||
|
||||
<p>
|
||||
<span class="{%= ((limits.space - limits.space_usage.total) > 50) ? "" : "text-warning" %}">
|
||||
<b>{%= flt(limits.space - limits.space_usage.total, 2) %} MB</b></span>
|
||||
available out of
|
||||
<span><b>{%= limits.space %} MB</b></span>
|
||||
</p>
|
||||
<p>{{ usage_message }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,33 @@ frappe.pages['usage-info'].on_page_load = function(wrapper) {
|
|||
return;
|
||||
}
|
||||
|
||||
$(frappe.render_template("usage_info", usage_info)).appendTo(page.main);
|
||||
let limits = usage_info.limits;
|
||||
let database_percent = (limits.space_usage.database_size / limits.space) * 100;
|
||||
let files_percent = (limits.space_usage.files_size / limits.space) * 100;
|
||||
let backup_percent = (limits.space_usage.backup_size / limits.space) * 100;
|
||||
|
||||
let total_consumed = database_percent + files_percent + backup_percent;
|
||||
|
||||
let last_part = backup_percent;
|
||||
if (total_consumed > 100) {
|
||||
last_part = backup_percent - (total_consumed - 100);
|
||||
}
|
||||
backup_percent = last_part;
|
||||
|
||||
let usage_message = '';
|
||||
if (limits.space_usage.total > limits.space) {
|
||||
usage_message = __('You have used up all of the space allotted to you. Please buy more space in your subscription.');
|
||||
} else {
|
||||
let available = flt(limits.space - limits.space_usage.total, 2);
|
||||
usage_message = __('{0} available out of {1}', [(available + ' MB').bold(), (limits.space + ' MB').bold()]);
|
||||
}
|
||||
|
||||
$(frappe.render_template("usage_info", Object.assign(usage_info, {
|
||||
database_percent,
|
||||
files_percent,
|
||||
backup_percent,
|
||||
usage_message
|
||||
}))).appendTo(page.main);
|
||||
|
||||
var btn_text = usage_info.limits.users == 1 ? __("Upgrade") : __("Renew / Upgrade");
|
||||
$(page.main).find('.btn-primary').html(btn_text).on('click', () => {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -879,6 +879,10 @@ class Database(object):
|
|||
# implemented in specific class
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def is_column_missing(e):
|
||||
return frappe.db.is_missing_column(e)
|
||||
|
||||
def get_descendants(self, doctype, name):
|
||||
'''Return descendants of the current record'''
|
||||
node_location_indexes = self.get_value(doctype, name, ('lft', 'rgt'))
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class MariaDBDatabase(Database):
|
|||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Rating': ('int', '1'),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
'Attach': ('text', ''),
|
||||
'Attach Image': ('text', ''),
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class PostgresDatabase(Database):
|
|||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Rating': ('smallint', None),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
'Attach': ('text', ''),
|
||||
'Attach Image': ('text', ''),
|
||||
|
|
|
|||
0
frappe/desk/doctype/dashboard/__init__.py
Normal file
0
frappe/desk/doctype/dashboard/__init__.py
Normal file
8
frappe/desk/doctype/dashboard/dashboard.js
Normal file
8
frappe/desk/doctype/dashboard/dashboard.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Dashboard', {
|
||||
refresh: function(frm) {
|
||||
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
|
||||
}
|
||||
});
|
||||
132
frappe/desk/doctype/dashboard/dashboard.json
Normal file
132
frappe/desk/doctype/dashboard/dashboard.json
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:dashboard_name",
|
||||
"beta": 0,
|
||||
"creation": "2019-01-10 12:54:40.938705",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "dashboard_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Dashboard Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "charts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Charts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Dashboard Chart Link",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-12 15:01:12.183208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "dashboard_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
9
frappe/desk/doctype/dashboard/dashboard.py
Normal file
9
frappe/desk/doctype/dashboard/dashboard.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Dashboard(Document):
|
||||
pass
|
||||
9
frappe/desk/doctype/dashboard/test_dashboard.py
Normal file
9
frappe/desk/doctype/dashboard/test_dashboard.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestDashboard(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/dashboard_chart/__init__.py
Normal file
0
frappe/desk/doctype/dashboard_chart/__init__.py
Normal file
69
frappe/desk/doctype/dashboard_chart/dashboard_chart.js
Normal file
69
frappe/desk/doctype/dashboard_chart/dashboard_chart.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Dashboard Chart', {
|
||||
refresh: function(frm) {
|
||||
frm.set_df_property("filters_section", "hidden", 1);
|
||||
frm.trigger("show_filters");
|
||||
},
|
||||
|
||||
source: function(frm) {
|
||||
frm.trigger("show_filters");
|
||||
},
|
||||
|
||||
show_filters: function(frm) {
|
||||
if (frm.doc.source) {
|
||||
if (frm.filters) {
|
||||
frm.trigger('render_filters_table');
|
||||
} else {
|
||||
frappe.db.get_value("Dashboard Chart Source", frm.doc.source, "config", e => {
|
||||
frm.filters = JSON.parse(e.config).filters;
|
||||
frm.trigger('render_filters_table');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render_filters_table: function(frm) {
|
||||
frm.set_df_property("filters_section", "hidden", 0);
|
||||
let fields = frm.filters;
|
||||
|
||||
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
|
||||
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${__('Filter')}</th>
|
||||
<th>${__('Value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>`).appendTo(wrapper);
|
||||
$(`<p class="text-muted small">${__("Click table to edit")}</p>`).appendTo(wrapper);
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || '{}');
|
||||
fields.map( f => {
|
||||
const filter_row = $(`<tr><td>${f.label}</td><td>${filters[f.fieldname] || ""}</td></tr>`);
|
||||
table.find('tbody').append(filter_row);
|
||||
});
|
||||
|
||||
table.on('click', () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Set Filters'),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
let values = this.get_values();
|
||||
if(values) {
|
||||
this.hide();
|
||||
frm.set_value('filters_json', JSON.stringify(values));
|
||||
frm.trigger('show_filters');
|
||||
}
|
||||
},
|
||||
primary_action_label: "Set"
|
||||
});
|
||||
dialog.show();
|
||||
dialog.set_values(filters);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
432
frappe/desk/doctype/dashboard_chart/dashboard_chart.json
Normal file
432
frappe/desk/doctype/dashboard_chart/dashboard_chart.json
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:chart_name",
|
||||
"beta": 0,
|
||||
"creation": "2019-01-10 12:28:06.282875",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "chart_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Chart Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "source",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Chart Source",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Dashboard Chart Source",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Filters",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "filters_json",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Filters JSON",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "JSON",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "chart_options_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Chart Options",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Line\nBar",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Width",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Half\nFull",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Color",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "last_synced_on",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Last Synced On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-18 11:05:51.316816",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
9
frappe/desk/doctype/dashboard_chart/dashboard_chart.py
Normal file
9
frappe/desk/doctype/dashboard_chart/dashboard_chart.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DashboardChart(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestDashboardChart(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/dashboard_chart_link/__init__.py
Normal file
0
frappe/desk/doctype/dashboard_chart_link/__init__.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2019-03-12 15:00:57.052684",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "chart",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Chart",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Dashboard Chart",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-12 15:01:31.639414",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart Link",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DashboardChartLink(Document):
|
||||
pass
|
||||
0
frappe/desk/doctype/dashboard_chart_source/__init__.py
Normal file
0
frappe/desk/doctype/dashboard_chart_source/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Dashboard Chart Source', {
|
||||
});
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:source_name",
|
||||
"beta": 0,
|
||||
"creation": "2019-02-06 07:55:29.579840",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "source_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Source Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Module",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Module Def",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "config",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Config",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "JSON",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-15 14:57:17.696656",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart Source",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
|
||||
class DashboardChartSource(Document):
|
||||
def validate(self):
|
||||
if frappe.session.user != "Administrator":
|
||||
frappe.throw(_("Only Administrator is allowed to create Dashboard Chart Sources"))
|
||||
|
||||
def on_update(self):
|
||||
export_to_files(record_list=[[self.doctype, self.name]], record_module=self.module, create_init=True)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestDashboardChartSource(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -102,6 +102,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
"shared": frappe.share.get_users(doc.doctype, doc.name),
|
||||
"rating": get_feedback_rating(doc.doctype, doc.name),
|
||||
"views": get_view_logs(doc.doctype, doc.name),
|
||||
"energy_point_logs": get_point_logs(doc.doctype, doc.name),
|
||||
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user),
|
||||
"document_follow_enabled": frappe.db.get_value("User", frappe.session.user, "document_follow_notify")
|
||||
}
|
||||
|
|
@ -136,6 +137,13 @@ def get_comments(doctype, name):
|
|||
|
||||
return comments
|
||||
|
||||
def get_point_logs(doctype, docname):
|
||||
return frappe.db.get_all('Energy Point Log', filters={
|
||||
'reference_doctype': doctype,
|
||||
'reference_name': docname,
|
||||
'type': ['!=', 'Review']
|
||||
}, fields=['*'])
|
||||
|
||||
def _get_communications(doctype, name, start=0, limit=20):
|
||||
communications = get_communication_data(doctype, name, start, limit)
|
||||
for c in communications:
|
||||
|
|
|
|||
|
|
@ -26,8 +26,27 @@ def set_list_settings(doctype, values):
|
|||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_assignments_and_count():
|
||||
user_list = frappe.get_list("User", filters={"user_type": "System User"})
|
||||
assignment_data = sorted([{"count":frappe.db.count('ToDo', filters = {'reference_type': 'Issue', 'owner': user['name'], 'status': 'Open'}),
|
||||
"name": user['name']} for user in user_list], key=lambda k: k['count'], reverse = True)
|
||||
return assignment_data
|
||||
def get_user_assignments_and_count(doctype, current_filters):
|
||||
|
||||
subquery_condition = ''
|
||||
if current_filters:
|
||||
# get the subquery
|
||||
subquery = frappe.get_all(doctype,
|
||||
filters=current_filters, return_query = True)
|
||||
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery)
|
||||
|
||||
todo_list = frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
|
||||
from
|
||||
`tabToDo`, `tabUser`
|
||||
where
|
||||
`tabToDo`.status='Open' and
|
||||
`tabToDo`.owner = `tabUser`.name and
|
||||
`tabUser`.user_type = 'System User'
|
||||
{subquery_condition}
|
||||
group by
|
||||
`tabToDo`.owner
|
||||
order by
|
||||
count desc
|
||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
|
||||
|
||||
return todo_list
|
||||
|
|
@ -302,30 +302,30 @@ def get_links(app, module):
|
|||
link_names.append(item.get("label"))
|
||||
return link_names
|
||||
|
||||
@frappe.whitelist()
|
||||
def hide_modules_from_desktop(modules):
|
||||
modules = frappe.parse_json(modules)
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
|
||||
home_settings['hidden_modules'] = modules
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
return home_settings
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_desk_section_settings(desk_section, new_settings):
|
||||
def update_links_for_module(module_name, links):
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
if home_settings:
|
||||
home_settings = json.loads(home_settings)
|
||||
else:
|
||||
return {}
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
|
||||
new_settings = json.loads(new_settings)
|
||||
home_settings.setdefault('links', {})
|
||||
home_settings['links'].setdefault(module_name, None)
|
||||
home_settings['links'][module_name] = links
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
for module, data in new_settings.items():
|
||||
if data.get("links"):
|
||||
data["links"] = get_module_link_items_from_list(data["app"], module, data.get("links"))
|
||||
data.pop("app", None)
|
||||
|
||||
home_settings[desk_section] = new_settings
|
||||
settings_json_str = json.dumps(home_settings)
|
||||
# # This didn't work
|
||||
# frappe.db.set_value("User", frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
frappe.db.sql("""update tabUser set home_settings = %s""", (settings_json_str), debug=True)
|
||||
frappe.db.commit()
|
||||
|
||||
return new_settings
|
||||
return home_settings
|
||||
|
||||
|
||||
def get_module_link_items_from_list(app, module, list_of_link_names):
|
||||
|
|
|
|||
|
|
@ -275,14 +275,14 @@ def build_xlsx_data(columns, data, visible_idx):
|
|||
if i in visible_idx:
|
||||
row_data = []
|
||||
|
||||
if isinstance(row, list):
|
||||
row_data = row
|
||||
elif isinstance(row, dict) and row:
|
||||
if isinstance(row, dict) and row:
|
||||
for idx in range(len(data.columns)):
|
||||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
|
||||
row_data.append(row.get(fieldname, row.get(label, "")))
|
||||
else:
|
||||
row_data = row
|
||||
|
||||
result.append(row_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -81,3 +81,4 @@ class DocumentLockedError(ValidationError): pass
|
|||
class CircularLinkingError(ValidationError): pass
|
||||
class SecurityException(Exception): pass
|
||||
class InvalidColumnName(ValidationError): pass
|
||||
class IncompatibleApp(ValidationError): pass
|
||||
|
|
|
|||
|
|
@ -2691,8 +2691,11 @@
|
|||
},
|
||||
"Yemen": {
|
||||
"code": "ye",
|
||||
"currency": "YER",
|
||||
"currency_fraction": "Fils",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_name": "Yemeni Rial",
|
||||
"currency_symbol": "\ufdfc",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@ doc_events = {
|
|||
"frappe.desk.notifications.clear_doctype_notifications",
|
||||
"frappe.core.doctype.activity_log.feed.update_feed",
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.apply"
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.apply",
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points"
|
||||
],
|
||||
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
|
||||
"on_cancel": [
|
||||
|
|
@ -129,13 +130,12 @@ doc_events = {
|
|||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions"
|
||||
],
|
||||
"on_change": [
|
||||
"frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request",
|
||||
"frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request"
|
||||
]
|
||||
},
|
||||
"Email Group Member": {
|
||||
"validate": "frappe.email.doctype.email_group.email_group.restrict_email_group"
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
scheduler_events = {
|
||||
|
|
@ -174,8 +174,8 @@ scheduler_events = {
|
|||
"frappe.core.doctype.feedback_request.feedback_request.delete_feedback_request",
|
||||
"frappe.core.doctype.activity_log.activity_log.clear_authentication_logs",
|
||||
"frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record",
|
||||
"frappe.desk.form.document_follow.send_daily_updates"
|
||||
|
||||
"frappe.desk.form.document_follow.send_daily_updates",
|
||||
"frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points"
|
||||
],
|
||||
"daily_long": [
|
||||
"frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily",
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ def create_gsuite_doc(doctype, docname, gs_template=None):
|
|||
json_data = doc.as_dict()
|
||||
filename = templ.document_name.format(**json_data)
|
||||
|
||||
r = run_gsuite_script('new', filename, templ.template_id, templ.destination_id, json_data)
|
||||
response = run_gsuite_script('new', filename, templ.template_id, templ.destination_id, json_data)
|
||||
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": r['url'],
|
||||
"file_url": response['url'],
|
||||
"file_name": filename,
|
||||
"attached_to_doctype": doctype,
|
||||
"attached_to_name": docname,
|
||||
|
|
@ -36,15 +36,15 @@ def create_gsuite_doc(doctype, docname, gs_template=None):
|
|||
|
||||
comment = frappe.get_doc(doctype, docname).add_comment("Attachment",
|
||||
_("added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
|
||||
"icon": ' <i class="fa fa-lock text-warning"></i>' if filedata.is_private else "",
|
||||
"file_url": filedata.file_url.replace("#", "%23") if filedata.file_name else filedata.file_url,
|
||||
"file_name": filedata.file_name or filedata.file_url
|
||||
"icon": ' <i class="fa fa-lock text-warning"></i>' if _file.is_private else "",
|
||||
"file_url": _file.file_url.replace("#", "%23") if _file.file_name else _file.file_url,
|
||||
"file_name": _file.file_name or _file.file_url
|
||||
})))
|
||||
|
||||
return {
|
||||
"name": filedata.name,
|
||||
"file_name": filedata.file_name,
|
||||
"file_url": filedata.file_url,
|
||||
"is_private": filedata.is_private,
|
||||
"name": _file.name,
|
||||
"file_name": _file.file_name,
|
||||
"file_url": _file.file_url,
|
||||
"is_private": _file.is_private,
|
||||
"comment": comment.as_dict() if comment else {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,8 +293,9 @@ def create_recurring_profile(token, payerid):
|
|||
addons = data.get("addons")
|
||||
subscription_details = data.get("subscription_details")
|
||||
|
||||
if data.get('subscription_id') and addons:
|
||||
updating = True
|
||||
if data.get('subscription_id'):
|
||||
if addons:
|
||||
updating = True
|
||||
manage_recurring_payment_profile_status(data['subscription_id'], 'Cancel', params, url)
|
||||
|
||||
params.update({
|
||||
|
|
@ -351,7 +352,7 @@ def update_integration_request_status(token, data, status, error=False, doc=None
|
|||
|
||||
def get_redirect_uri(doc, token, payerid):
|
||||
data = json.loads(doc.data)
|
||||
|
||||
|
||||
if data.get("subscription_details") or data.get("subscription_id"):
|
||||
return get_url("{0}.create_recurring_profile?token={1}&payerid={2}".format(api_path, token, payerid))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ data_fieldtypes = (
|
|||
'Dynamic Link',
|
||||
'Password',
|
||||
'Select',
|
||||
'Rating',
|
||||
'Read Only',
|
||||
'Attach',
|
||||
'Attach Image',
|
||||
|
|
|
|||
|
|
@ -60,21 +60,31 @@ def set_user_and_static_default_values(doc):
|
|||
allowed_records = get_allowed_docs_for_doctype(doctype_user_permissions, df.parent)
|
||||
|
||||
user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records)
|
||||
|
||||
if user_default_value is not None:
|
||||
doc.set(df.fieldname, user_default_value)
|
||||
if user_default_value != None:
|
||||
# do not set default if the field on which current field is dependent is not set
|
||||
if is_dependent_field_set(df.depends_on, doc):
|
||||
doc.set(df.fieldname, user_default_value)
|
||||
else:
|
||||
if df.fieldname != doc.meta.title_field:
|
||||
static_default_value = get_static_default_value(df, doctype_user_permissions, allowed_records)
|
||||
if static_default_value is not None:
|
||||
if static_default_value != None and is_dependent_field_set(df.depends_on, doc):
|
||||
doc.set(df.fieldname, static_default_value)
|
||||
|
||||
|
||||
def is_dependent_field_set(fieldname, doc):
|
||||
value_dict = doc.as_dict()
|
||||
if not fieldname: return True
|
||||
# to check if fieldname passed is valid
|
||||
if fieldname not in value_dict: return True
|
||||
return value_dict[fieldname]
|
||||
|
||||
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records):
|
||||
# don't set defaults for "User" link field using User Permissions!
|
||||
if df.fieldtype == "Link" and df.options != "User":
|
||||
# 1 - look in user permissions only for document_type==Setup
|
||||
# We don't want to include permissions of transactions to be used for defaults.
|
||||
if frappe.get_meta(df.options).document_type=="Setup" and len(allowed_records)==1:
|
||||
if (frappe.get_meta(df.options).document_type=="Setup"
|
||||
and len(allowed_records)==1 and not df.ignore_user_permissions):
|
||||
return allowed_records[0]
|
||||
|
||||
# 2 - Look in user defaults
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class DatabaseQuery(object):
|
|||
ignore_permissions=False, user=None, with_comment_count=False,
|
||||
join='left join', distinct=False, start=None, page_length=None, limit=None,
|
||||
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None):
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False):
|
||||
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
|
||||
raise frappe.PermissionError(self.doctype)
|
||||
|
|
@ -79,6 +79,7 @@ class DatabaseQuery(object):
|
|||
self.user = user or frappe.session.user
|
||||
self.update = update
|
||||
self.user_settings_fields = copy.deepcopy(self.fields)
|
||||
self.return_query = return_query
|
||||
|
||||
# for contextual user permission check
|
||||
# to determine which user permission is applicable on link field of specific doctype
|
||||
|
|
@ -91,6 +92,8 @@ class DatabaseQuery(object):
|
|||
result = self.run_custom_query(query)
|
||||
else:
|
||||
result = self.build_and_run()
|
||||
if return_query:
|
||||
return result
|
||||
|
||||
if with_comment_count and not as_list and self.doctype:
|
||||
self.add_comment_count(result)
|
||||
|
|
@ -115,7 +118,10 @@ class DatabaseQuery(object):
|
|||
query = """select %(fields)s from %(tables)s %(conditions)s
|
||||
%(group_by)s %(order_by)s %(limit)s""" % args
|
||||
|
||||
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug, update=self.update)
|
||||
if self.return_query:
|
||||
return query
|
||||
else:
|
||||
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug, update=self.update)
|
||||
|
||||
def prepare_args(self):
|
||||
self.parse_args()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ type_map = {
|
|||
'Dynamic Link': ('varchar', varchar_len),
|
||||
'Password': ('varchar', varchar_len),
|
||||
'Select': ('varchar', varchar_len),
|
||||
'Rating': ('int', '1'),
|
||||
'Read Only': ('varchar', varchar_len),
|
||||
'Attach': ('text', ''),
|
||||
'Attach Image': ('text', ''),
|
||||
|
|
|
|||
|
|
@ -213,12 +213,12 @@ class Document(BaseDocument):
|
|||
self.set_docstatus()
|
||||
self.check_if_latest()
|
||||
self.run_method("before_insert")
|
||||
self._validate_links()
|
||||
self.set_new_name()
|
||||
self.set_parent_in_children()
|
||||
self.validate_higher_perm_levels()
|
||||
|
||||
self.flags.in_insert = True
|
||||
self._validate_links()
|
||||
self.run_before_save_methods()
|
||||
self._validate()
|
||||
self.set_docstatus()
|
||||
|
|
@ -1183,6 +1183,12 @@ class Document(BaseDocument):
|
|||
self.set("__onload", frappe._dict())
|
||||
self.get("__onload")[key] = value
|
||||
|
||||
def get_onload(self, key=None):
|
||||
if not key:
|
||||
return self.get("__onload", frappe._dict())
|
||||
|
||||
return self.get('__onload')[key]
|
||||
|
||||
def update_timeline_doc(self):
|
||||
if frappe.flags.in_install or not self.meta.get("timeline_field"):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -88,21 +88,21 @@ def set_name_by_naming_series(doc):
|
|||
|
||||
def make_autoname(key='', doctype='', doc=''):
|
||||
"""
|
||||
Creates an autoname from the given key:
|
||||
Creates an autoname from the given key:
|
||||
|
||||
**Autoname rules:**
|
||||
**Autoname rules:**
|
||||
|
||||
* The key is separated by '.'
|
||||
* '####' represents a series. The string before this part becomes the prefix:
|
||||
Example: ABC.#### creates a series ABC0001, ABC0002 etc
|
||||
* 'MM' represents the current month
|
||||
* 'YY' and 'YYYY' represent the current year
|
||||
* The key is separated by '.'
|
||||
* '####' represents a series. The string before this part becomes the prefix:
|
||||
Example: ABC.#### creates a series ABC0001, ABC0002 etc
|
||||
* 'MM' represents the current month
|
||||
* 'YY' and 'YYYY' represent the current year
|
||||
|
||||
|
||||
*Example:*
|
||||
|
||||
* DE/./.YY./.MM./.##### will create a series like
|
||||
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
|
||||
* DE/./.YY./.MM./.##### will create a series like
|
||||
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
|
||||
"""
|
||||
if key == "hash":
|
||||
return frappe.generate_hash(doctype, 10)
|
||||
|
|
@ -121,7 +121,6 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
n = ''
|
||||
if isinstance(parts, string_types):
|
||||
parts = parts.split('.')
|
||||
|
||||
series_set = False
|
||||
today = now_datetime()
|
||||
for e in parts:
|
||||
|
|
@ -129,7 +128,7 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
if e.startswith('#'):
|
||||
if not series_set:
|
||||
digits = len(e)
|
||||
part = getseries(n, digits, doctype)
|
||||
part = getseries(n, digits)
|
||||
series_set = True
|
||||
elif e == 'YY':
|
||||
part = today.strftime('%y')
|
||||
|
|
@ -141,6 +140,9 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
part = today.strftime('%Y')
|
||||
elif e == 'FY':
|
||||
part = frappe.defaults.get_user_default("fiscal_year")
|
||||
elif e.startswith('{') and doc:
|
||||
e = e.replace('{', '').replace('}', '')
|
||||
part = doc.get(e)
|
||||
elif doc and doc.get(e):
|
||||
part = doc.get(e)
|
||||
else:
|
||||
|
|
@ -152,7 +154,7 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
return n
|
||||
|
||||
|
||||
def getseries(key, digits, doctype=''):
|
||||
def getseries(key, digits):
|
||||
# series created ?
|
||||
current = frappe.db.sql("SELECT `current` FROM `tabSeries` WHERE `name`=%s FOR UPDATE", (key,))
|
||||
if current and current[0][0] is not None:
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F
|
|||
"""walk and sync all doctypes and pages"""
|
||||
|
||||
# load in sequence - warning for devs
|
||||
document_types = ['doctype', 'page', 'report', 'print_format',
|
||||
document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format',
|
||||
'website_theme', 'web_form', 'notification', 'print_style',
|
||||
'data_migration_mapping', 'data_migration_plan']
|
||||
for doctype in document_types:
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ Contacts
|
|||
Data Migration
|
||||
Chat
|
||||
Social
|
||||
Automation
|
||||
Automation
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from __future__ import unicode_literals, print_function
|
|||
import frappe, os, json
|
||||
import frappe.utils
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
|
||||
def export_module_json(doc, is_standard, module):
|
||||
"""Make a folder for the given doc and add its json file (make it a standard
|
||||
|
|
@ -39,11 +40,15 @@ def get_doc_module(module, doctype, name):
|
|||
def export_customizations(module, doctype, sync_on_migrate=0, with_permissions=0):
|
||||
"""Export Custom Field and Property Setter for the current document to the app folder.
|
||||
This will be synced with bench migrate"""
|
||||
|
||||
sync_on_migrate = cint(sync_on_migrate)
|
||||
with_permissions = cint(with_permissions)
|
||||
|
||||
if not frappe.get_conf().developer_mode:
|
||||
raise Exception('Not developer mode')
|
||||
|
||||
custom = {'custom_fields': [], 'property_setters': [], 'custom_perms': [],
|
||||
'doctype': doctype, 'sync_on_migrate': 1}
|
||||
'doctype': doctype, 'sync_on_migrate': sync_on_migrate}
|
||||
|
||||
def add(_doctype):
|
||||
custom['custom_fields'] += frappe.get_all('Custom Field',
|
||||
|
|
|
|||
|
|
@ -238,3 +238,4 @@ frappe.patches.v11_0.set_default_letter_head_source
|
|||
frappe.patches.v12_0.setup_comments_from_communications
|
||||
frappe.patches.v12_0.init_desk_settings #11-03-2019
|
||||
frappe.patches.v12_0.replace_null_values_in_tables
|
||||
frappe.patches.v12_0.reset_home_settings
|
||||
8
frappe/patches/v12_0/reset_home_settings.py
Normal file
8
frappe/patches/v12_0/reset_home_settings.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql('''
|
||||
UPDATE `tabUser`
|
||||
SET `home_settings` = ''
|
||||
WHERE `user_type` = 'System User'
|
||||
''')
|
||||
|
|
@ -28,7 +28,7 @@ def print_has_permission_check_logs(func):
|
|||
# print only if access denied
|
||||
# and if user is checking his own permission
|
||||
if not result and self_perm_check:
|
||||
msgprint(('<br>').join(frappe.flags['has_permission_check_logs']))
|
||||
msgprint(('<br>').join(frappe.flags.get('has_permission_check_logs')))
|
||||
frappe.flags.pop('has_permission_check_logs', None)
|
||||
return result
|
||||
return inner
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@
|
|||
"public/less/avatar.less",
|
||||
"node_modules/highlight.js/styles/zenburn.css",
|
||||
"public/less/form.less",
|
||||
"public/less/controls.less",
|
||||
"public/less/chat.less",
|
||||
"public/less/form_grid.less",
|
||||
"node_modules/frappe-datatable/dist/frappe-datatable.css"
|
||||
],
|
||||
"css/frappe-web-b4.css": [
|
||||
"public/css/font-awesome.css",
|
||||
"public/less/indicator.less",
|
||||
"public/scss/website.scss"
|
||||
],
|
||||
|
|
@ -93,7 +92,9 @@
|
|||
"public/js/frappe/form/controls/geolocation.js",
|
||||
"public/js/frappe/form/controls/multiselect.js",
|
||||
"public/js/frappe/form/controls/multicheck.js",
|
||||
"public/js/frappe/form/controls/table_multiselect.js"
|
||||
"public/js/frappe/form/controls/table_multiselect.js",
|
||||
"public/js/frappe/form/controls/multiselect_pills.js",
|
||||
"public/js/frappe/form/controls/rating.js"
|
||||
],
|
||||
"js/dialog.min.js": [
|
||||
"public/js/frappe/dom.js",
|
||||
|
|
@ -255,7 +256,10 @@
|
|||
|
||||
"public/js/frappe/chat.js",
|
||||
|
||||
"public/js/frappe/social/social_factory.js"
|
||||
"public/js/frappe/social/social_factory.js",
|
||||
|
||||
"public/js/frappe/misc/energy_point_utils.js"
|
||||
|
||||
],
|
||||
"css/module.min.css": [
|
||||
"public/less/module.less"
|
||||
|
|
@ -264,8 +268,8 @@
|
|||
"public/less/form_grid.less"
|
||||
],
|
||||
"js/form.min.js": [
|
||||
"public/js/frappe/form/templates/print_layout.html",
|
||||
"public/js/frappe/form/document_follow.js",
|
||||
"public/js/frappe/form/templates/print_layout.html",
|
||||
"public/js/frappe/form/templates/users_in_sidebar.html",
|
||||
"public/js/frappe/form/templates/set_sharing.html",
|
||||
"public/js/frappe/form/templates/form_sidebar.html",
|
||||
|
|
@ -286,6 +290,7 @@
|
|||
"public/js/frappe/form/form_sidebar.js",
|
||||
"public/js/frappe/form/user_image.js",
|
||||
"public/js/frappe/form/share.js",
|
||||
"public/js/frappe/form/review.js",
|
||||
"public/js/frappe/form/form_viewers.js",
|
||||
"public/js/frappe/form/footer/attachment.html",
|
||||
"public/js/frappe/form/footer/form_footer.html",
|
||||
|
|
@ -376,7 +381,8 @@
|
|||
"public/less/quill.less",
|
||||
"public/less/datepicker.less",
|
||||
"public/less/awesomplete.less",
|
||||
"public/less/form_grid.less"
|
||||
"public/less/form_grid.less",
|
||||
"public/less/controls.less"
|
||||
],
|
||||
"js/print_format_v3.min.js": [
|
||||
"public/js/legacy/layout.js",
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ frappe.Application = Class.extend({
|
|||
this.make_nav_bar();
|
||||
this.set_favicon();
|
||||
this.setup_analytics();
|
||||
|
||||
this.setup_energy_point_listeners();
|
||||
|
||||
frappe.ui.keys.setup();
|
||||
this.set_rtl();
|
||||
|
||||
|
|
@ -547,6 +550,12 @@ frappe.Application = Class.extend({
|
|||
frappe.show_alert(message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setup_energy_point_listeners() {
|
||||
frappe.realtime.on('energy_point_alert', (message) => {
|
||||
frappe.show_alert(message);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -178,4 +178,4 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
|
|||
$(this.disp_area).toggleClass("bold", !!(this.df.bold || this.df.reqd));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -32,7 +32,8 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
'HTML': 'ace/mode/html',
|
||||
'CSS': 'ace/mode/css',
|
||||
'Markdown': 'ace/mode/markdown',
|
||||
'SCSS': 'ace/mode/scss'
|
||||
'SCSS': 'ace/mode/scss',
|
||||
'JSON': 'ace/mode/json'
|
||||
};
|
||||
const language = this.df.options;
|
||||
|
||||
|
|
|
|||
150
frappe/public/js/frappe/form/controls/multiselect_pills.js
Normal file
150
frappe/public/js/frappe/form/controls/multiselect_pills.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import Awesomplete from 'awesomplete';
|
||||
|
||||
frappe.ui.form.ControlMultiSelectPills = frappe.ui.form.ControlAutocomplete.extend({
|
||||
make_input() {
|
||||
this._super();
|
||||
this.$input_area = $(this.input_area);
|
||||
this.$multiselect_wrapper = $('<div>')
|
||||
.addClass('form-control table-multiselect')
|
||||
.appendTo(this.$input_area);
|
||||
|
||||
this.$input.removeClass('form-control');
|
||||
this.$input_area.find('.awesomplete').appendTo(this.$multiselect_wrapper);
|
||||
|
||||
this.$input.on("awesomplete-selectcomplete", () => {
|
||||
this.$input.val('').focus();
|
||||
});
|
||||
|
||||
// used as an internal model to store values
|
||||
this.rows = [];
|
||||
|
||||
this.$input_area.on('click', '.btn-remove', (e) => {
|
||||
const $target = $(e.currentTarget);
|
||||
const $value = $target.closest('.tb-selected-value');
|
||||
|
||||
const value = decodeURIComponent($value.data().value);
|
||||
this.rows = this.rows.filter(val => val !== value);
|
||||
|
||||
this.parse_validate_and_set_in_model('');
|
||||
});
|
||||
|
||||
this.$input.on('keydown', e => {
|
||||
// if backspace key pressed on empty input, delete last value
|
||||
if (e.keyCode == frappe.ui.keyCode.BACKSPACE && e.target.value === '') {
|
||||
this.rows = this.rows.slice(0, this.rows.length - 1);
|
||||
this.parse_validate_and_set_in_model('');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
parse(value) {
|
||||
if (value) {
|
||||
this.rows.push(value);
|
||||
}
|
||||
|
||||
return this.rows;
|
||||
},
|
||||
|
||||
validate(value) {
|
||||
const rows = (value || []).slice();
|
||||
|
||||
if (rows.length === 0) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
const all_rows_except_last = rows.slice(0, rows.length - 1);
|
||||
const last_value = rows[rows.length - 1];
|
||||
|
||||
// falsy value
|
||||
if (!last_value) {
|
||||
return all_rows_except_last;
|
||||
}
|
||||
|
||||
// duplicate value
|
||||
if (all_rows_except_last.includes(last_value)) {
|
||||
return all_rows_except_last;
|
||||
}
|
||||
|
||||
return rows;
|
||||
},
|
||||
|
||||
set_formatted_input(value) {
|
||||
this.rows = value || [];
|
||||
this.set_pill_html(this.rows);
|
||||
},
|
||||
|
||||
set_pill_html(values) {
|
||||
const html = values
|
||||
.map(value => this.get_pill_html(value))
|
||||
.join('');
|
||||
|
||||
this.$multiselect_wrapper.find('.tb-selected-value').remove();
|
||||
this.$multiselect_wrapper.prepend(html);
|
||||
},
|
||||
|
||||
get_pill_html(value) {
|
||||
const encoded_value = encodeURIComponent(value);
|
||||
return `<div class="btn-group tb-selected-value" data-value="${encoded_value}">
|
||||
<button class="btn btn-default btn-xs btn-link-to-form">${__(value)}</button>
|
||||
<button class="btn btn-default btn-xs btn-remove">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
get_awesomplete_settings() {
|
||||
const settings = this._super();
|
||||
|
||||
return Object.assign(settings, {
|
||||
filter: function(text, input) {
|
||||
let d = this.get_item(text.value);
|
||||
if(!d) {
|
||||
return Awesomplete.FILTER_CONTAINS(text, input.match(/[^,]*$/)[0]);
|
||||
}
|
||||
|
||||
let getMatch = value => Awesomplete.FILTER_CONTAINS(value, input.match(/[^,]*$/)[0]);
|
||||
|
||||
// match typed input with label or value or description
|
||||
let v = getMatch(d.label);
|
||||
if(!v && d.value) {
|
||||
v = getMatch(d.value);
|
||||
}
|
||||
if(!v && d.description) {
|
||||
v = getMatch(d.description);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_value() {
|
||||
return this.rows;
|
||||
},
|
||||
|
||||
get_values() {
|
||||
return this.rows;
|
||||
},
|
||||
|
||||
get_data() {
|
||||
let data;
|
||||
if(this.df.get_data) {
|
||||
data = this.df.get_data();
|
||||
if (data && data.then) {
|
||||
data.then((r) => {
|
||||
this.set_data(r);
|
||||
});
|
||||
data = this.get_value();
|
||||
} else {
|
||||
this.set_data(data);
|
||||
}
|
||||
} else {
|
||||
data = this._super();
|
||||
}
|
||||
const values = this.get_values() || [];
|
||||
|
||||
// return values which are not already selected
|
||||
if (data) data.filter(d => !values.includes(d));
|
||||
return data;
|
||||
}
|
||||
});
|
||||
62
frappe/public/js/frappe/form/controls/rating.js
Normal file
62
frappe/public/js/frappe/form/controls/rating.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({
|
||||
make_input() {
|
||||
this._super();
|
||||
const star_template = `
|
||||
<div class="rating">
|
||||
<i class="fa fa-fw fa-star" data-idx=1></i>
|
||||
<i class="fa fa-fw fa-star" data-idx=2></i>
|
||||
<i class="fa fa-fw fa-star" data-idx=3></i>
|
||||
<i class="fa fa-fw fa-star" data-idx=4></i>
|
||||
<i class="fa fa-fw fa-star" data-idx=5></i>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$(this.input_area).html(star_template);
|
||||
|
||||
$(this.input_area).find('i').hover((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
let star_value = el.data('idx');
|
||||
el.parent().children('i.fa').each( function(e){
|
||||
if (e < star_value) {
|
||||
$(this).addClass('star-hover');
|
||||
} else {
|
||||
$(this).removeClass('star-hover');
|
||||
}
|
||||
});
|
||||
}, (ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
el.parent().children('i.fa').each( function() {
|
||||
$(this).removeClass('star-hover');
|
||||
});
|
||||
});
|
||||
|
||||
$(this.input_area).find('i').click((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
let star_value = el.data('idx');
|
||||
el.parent().children('i.fa').each( function(e) {
|
||||
if (e < star_value){
|
||||
$(this).addClass('star-click');
|
||||
} else {
|
||||
$(this).removeClass('star-click');
|
||||
}
|
||||
});
|
||||
this.validate_and_set_in_model(star_value, ev);
|
||||
if (this.doctype && this.docname) {
|
||||
this.set_input(star_value);
|
||||
}
|
||||
});
|
||||
},
|
||||
get_value() {
|
||||
return cint(this.value);
|
||||
},
|
||||
set_formatted_input(value) {
|
||||
let el = $(this.input_area).find('i');
|
||||
el.children('i.fa').prevObject.each( function(e) {
|
||||
if (e < value) {
|
||||
$(this).addClass('star-click');
|
||||
} else {
|
||||
$(this).removeClass('star-click');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -185,6 +185,7 @@ frappe.ui.add_assignment = function(opts, dialog) {
|
|||
var assign_to = dialog.fields_dict.assign_to.get_value();
|
||||
var args = dialog.get_values();
|
||||
if(args && assign_to) {
|
||||
dialog.set_message('Assigning...');
|
||||
return frappe.call({
|
||||
method: opts.method,
|
||||
args: $.extend(args, {
|
||||
|
|
@ -194,15 +195,17 @@ frappe.ui.add_assignment = function(opts, dialog) {
|
|||
bulk_assign: opts.bulk_assign || false,
|
||||
re_assign: opts.re_assign || false
|
||||
}),
|
||||
callback: function(r,rt) {
|
||||
btn: dialog.get_primary_btn(),
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(opts.callback){
|
||||
opts.callback(r);
|
||||
}
|
||||
dialog && dialog.hide();
|
||||
} else {
|
||||
dialog.clear_message();
|
||||
}
|
||||
},
|
||||
btn: this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
|
||||
// append comments
|
||||
timeline = timeline.concat(this.get_comments());
|
||||
|
||||
// append energy point logs
|
||||
timeline = timeline.concat(this.get_energy_point_logs());
|
||||
|
||||
// sort
|
||||
timeline
|
||||
.filter(a => a.content)
|
||||
|
|
@ -514,6 +518,16 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
return docinfo.comments;
|
||||
}
|
||||
|
||||
get_energy_point_logs() {
|
||||
let energy_point_logs = this.frm.get_docinfo().energy_point_logs;
|
||||
energy_point_logs.map(log => {
|
||||
log.comment_type = 'Energy Points';
|
||||
log.content = frappe.energy_points.format_form_log(log);
|
||||
return log;
|
||||
});
|
||||
return energy_point_logs;
|
||||
}
|
||||
|
||||
cast_comment_as_communication(c) {
|
||||
c.sender = c.comment_email;
|
||||
c.sender_full_name = c.comment_by;
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@
|
|||
{% } %}
|
||||
{%= __("Liked by {0}", [data.fullname]) %}
|
||||
</span>
|
||||
{% } else if (data.comment_type == "Energy Points") { %}
|
||||
{{ data.content_html }}
|
||||
{% } else { %}
|
||||
<b title="{{ data.comment_by }}">{%= data.fullname %}</b>
|
||||
{%= __(data.content) %}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
this.make_attachments();
|
||||
this.make_shared();
|
||||
this.make_viewers();
|
||||
|
||||
this.make_tags();
|
||||
this.make_like();
|
||||
this.make_follow();
|
||||
this.make_review();
|
||||
|
||||
this.bind_events();
|
||||
frappe.ui.form.setup_user_image_event(this.frm);
|
||||
|
|
@ -43,7 +45,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
frappe.ui.toggle_like(me.like_icon, me.frm.doctype, me.frm.doc.name, function() {
|
||||
me.refresh_like();
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
|
|
@ -155,6 +157,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
|
||||
refresh_image: function() {
|
||||
},
|
||||
|
||||
setup_ratings: function() {
|
||||
var _ratings = this.frm.get_docinfo().rating || 0;
|
||||
|
||||
|
|
@ -164,4 +167,13 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
this.ratings.find(".rating-icons").html(rating_icons);
|
||||
}
|
||||
},
|
||||
|
||||
make_review: function() {
|
||||
if (frappe.boot.energy_points_enabled) {
|
||||
this.frm.reviews = new frappe.ui.form.Review({
|
||||
parent: this.sidebar.find(".form-attachments"),
|
||||
frm: this.frm
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,6 +52,13 @@ frappe.form.formatters = {
|
|||
Percent: function(value, docfield, options) {
|
||||
return frappe.form.formatters._right(flt(value, 2) + "%", options)
|
||||
},
|
||||
Rating: function(value) {
|
||||
return `<span class="rating">
|
||||
${Array.from(new Array(5)).map((_, i) =>
|
||||
`<i class="fa fa-fw fa-star ${i < (value || 0) ? "star-click": "" } star-icon" data-idx="${(i+1)}"></i>`
|
||||
).join('')}
|
||||
</span>`;
|
||||
},
|
||||
Currency: function (value, docfield, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(docfield, doc);
|
||||
var precision = docfield.precision || cint(frappe.boot.sysdefaults.currency_precision) || 2;
|
||||
|
|
|
|||
204
frappe/public/js/frappe/form/review.js
Normal file
204
frappe/public/js/frappe/form/review.js
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide("frappe.ui.form");
|
||||
|
||||
frappe.ui.form.Review = class Review {
|
||||
constructor({parent, frm}) {
|
||||
this.parent = parent;
|
||||
this.frm = frm;
|
||||
this.fetch_energy_points()
|
||||
.then(() => {
|
||||
this.make_review_container();
|
||||
this.add_review_button();
|
||||
this.update_reviewers();
|
||||
});
|
||||
}
|
||||
fetch_energy_points() {
|
||||
return frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.get_energy_points', {
|
||||
user: frappe.session.user
|
||||
}).then(data => {
|
||||
this.points = data;
|
||||
});
|
||||
}
|
||||
make_review_container() {
|
||||
this.$wrapper = this.parent.append(`
|
||||
<ul class="list-unstyled sidebar-menu">
|
||||
<li class="divider"></li>
|
||||
<li class="h6 shared-with-label">${__('Reviews')}</li>
|
||||
<li class="review-list"></li>
|
||||
</ul>
|
||||
`);
|
||||
this.review_list_wrapper = this.$wrapper.find('.review-list');
|
||||
}
|
||||
add_review_button() {
|
||||
|
||||
this.review_list_wrapper.append(`
|
||||
<span class="avatar avatar-small avatar-empty btn-add-review" title="${__('Add Review')}">
|
||||
<i class="octicon octicon-plus text-muted"></i>
|
||||
</span>
|
||||
`);
|
||||
|
||||
const review_button = this.review_list_wrapper.find('.btn-add-review');
|
||||
|
||||
if (!this.points.review_points) {
|
||||
review_button.click(false);
|
||||
review_button.popover({
|
||||
trigger: 'hover',
|
||||
content: () => {
|
||||
return `<div class="text-medium">
|
||||
${__('You do not have enough review points')}
|
||||
</div>`;
|
||||
},
|
||||
html: true
|
||||
});
|
||||
} else {
|
||||
review_button.click(() => this.show_review_dialog());
|
||||
}
|
||||
}
|
||||
get_involved_users() {
|
||||
const user_fields = this.frm.meta.fields
|
||||
.filter(d => d.fieldtype === 'Link' && d.options === 'User')
|
||||
.map(d => d.fieldname);
|
||||
|
||||
user_fields.push('owner');
|
||||
let involved_users = user_fields.map(field => this.frm.doc[field]);
|
||||
|
||||
const docinfo = this.frm.get_docinfo();
|
||||
|
||||
involved_users = involved_users.concat(
|
||||
docinfo.communications.map(d => d.sender && d.delivery_status==='sent'),
|
||||
docinfo.comments.map(d => d.owner),
|
||||
docinfo.versions.map(d => d.owner),
|
||||
docinfo.assignments.map(d => d.owner)
|
||||
);
|
||||
|
||||
return involved_users
|
||||
.uniqBy(u => u)
|
||||
.filter(user => !['Administrator', frappe.session.user].includes(user))
|
||||
.filter(Boolean);
|
||||
}
|
||||
show_review_dialog() {
|
||||
const user_options = this.get_involved_users();
|
||||
const doc_owner = this.frm.doc.owner;
|
||||
const review_dialog = new frappe.ui.Dialog({
|
||||
'title': __('Add Review'),
|
||||
'fields': [{
|
||||
fieldname: 'to_user',
|
||||
fieldtype: 'Autocomplete',
|
||||
label: __('To User'),
|
||||
options: user_options,
|
||||
default: user_options.includes(doc_owner) ? doc_owner : user_options[0],
|
||||
description: __('Only users involved in the document are listed')
|
||||
}, {
|
||||
fieldname: 'review_type',
|
||||
fieldtype: 'Select',
|
||||
label: __('Action'),
|
||||
options: [{
|
||||
'label': __('Appreciate'),
|
||||
'value': 'Appreciation'
|
||||
}, {
|
||||
'label': __('Criticize'),
|
||||
'value': 'Criticism'
|
||||
}],
|
||||
default: 'Appreciation'
|
||||
}, {
|
||||
fieldname: 'points',
|
||||
fieldtype: 'Int',
|
||||
label: __('Points'),
|
||||
reqd: 1,
|
||||
description: __(`Currently you have ${this.points.review_points} review points`)
|
||||
}, {
|
||||
fieldtype: 'Small Text',
|
||||
fieldname: 'reason',
|
||||
reqd: 1,
|
||||
label: __('Reason')
|
||||
}],
|
||||
primary_action: (values) => {
|
||||
if (values.points > this.points.review_points) {
|
||||
return frappe.msgprint(__('You do not have enough points'));
|
||||
}
|
||||
frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.review', {
|
||||
doc: {
|
||||
doctype: this.frm.doc.doctype,
|
||||
name: this.frm.doc.name,
|
||||
},
|
||||
to_user: values.to_user,
|
||||
points: values.points,
|
||||
review_type: values.review_type,
|
||||
reason: values.reason
|
||||
}).then(review => {
|
||||
review_dialog.hide();
|
||||
review_dialog.clear();
|
||||
this.frm.get_docinfo().energy_point_logs.unshift(review);
|
||||
this.frm.timeline.refresh();
|
||||
this.update_reviewers();
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
review_dialog.show();
|
||||
}
|
||||
update_reviewers() {
|
||||
const review_logs = this.frm.get_docinfo().energy_point_logs
|
||||
.filter(log => ['Appreciation', 'Criticism'].includes(log.type));
|
||||
|
||||
this.review_list_wrapper.find('.review-pill').remove();
|
||||
review_logs.forEach(log => {
|
||||
let review_pill = $(`
|
||||
<span class="review-pill">
|
||||
${frappe.avatar(log.owner)}
|
||||
${frappe.energy_points.get_points(log.points)}
|
||||
</span>
|
||||
`);
|
||||
this.review_list_wrapper.prepend(review_pill);
|
||||
this.setup_detail_popover(review_pill, log);
|
||||
});
|
||||
}
|
||||
setup_detail_popover(el, data) {
|
||||
let subject = '';
|
||||
let fullname = frappe.user.full_name(data.user);
|
||||
let timestamp = `<span class="text-muted">${frappe.datetime.comment_when(data.creation)}</span>`;
|
||||
let message_parts = [Math.abs(data.points), fullname, timestamp];
|
||||
if (data.type === 'Appreciation') {
|
||||
if (data.points == 1) {
|
||||
subject = __('{0} appreciation point for {1} {2}', message_parts);
|
||||
} else {
|
||||
subject = __('{0} appreciation points for {1} {2}', message_parts);
|
||||
}
|
||||
} else {
|
||||
if (data.points == -1) {
|
||||
subject = __('{0} criticism point for {1} {2}', message_parts);
|
||||
} else {
|
||||
subject = __('{0} criticism points for {1} {2}', message_parts);
|
||||
}
|
||||
}
|
||||
el.popover({
|
||||
animation: true,
|
||||
trigger: 'hover',
|
||||
delay: 500,
|
||||
placement: 'top',
|
||||
template:`
|
||||
<div class="review-popover popover">
|
||||
<div class="arrow"></div>
|
||||
<div class="popover-content"></div>
|
||||
</div>
|
||||
`,
|
||||
content: () => {
|
||||
return `
|
||||
<div class="text-medium">
|
||||
<div class="subject">
|
||||
${subject}
|
||||
</div>
|
||||
<div class="body">
|
||||
<div>${data.reason}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
html: true,
|
||||
container: 'body'
|
||||
});
|
||||
return el;
|
||||
}
|
||||
};
|
||||
|
|
@ -68,8 +68,8 @@ frappe.ui.form.States = Class.extend({
|
|||
},
|
||||
|
||||
show_actions: function() {
|
||||
var added = false,
|
||||
me = this;
|
||||
var added = false;
|
||||
var me = this;
|
||||
|
||||
this.frm.page.clear_actions_menu();
|
||||
|
||||
|
|
@ -103,10 +103,15 @@ frappe.ui.form.States = Class.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
this.setup_btn(added);
|
||||
});
|
||||
|
||||
if(added) {
|
||||
},
|
||||
|
||||
setup_btn: function(action_added) {
|
||||
if(action_added) {
|
||||
this.frm.page.btn_primary.addClass("hide");
|
||||
this.frm.page.btn_secondary.addClass("hide");
|
||||
this.frm.toolbar.current_status = "";
|
||||
this.setup_help();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@ frappe.views.BaseList = class BaseList {
|
|||
this.toggle_result_area();
|
||||
this.before_render();
|
||||
this.render();
|
||||
this.after_render();
|
||||
this.freeze(false);
|
||||
if (this.settings.refresh) {
|
||||
this.settings.refresh(this);
|
||||
|
|
@ -393,6 +394,10 @@ frappe.views.BaseList = class BaseList {
|
|||
|
||||
}
|
||||
|
||||
after_render() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
// for child classes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,15 @@
|
|||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="assigned-to">
|
||||
<li class="assigned-to" style="display: none">
|
||||
<a class = "dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{%= __("Assigned To") %} <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu assigned-dropdown" style="max-height: 300px; overflow-y: auto;">
|
||||
<li><div class="list-loading text-center assigned-loading text-muted">
|
||||
{%= (__("Loading") + "..." ) %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% if(frappe.help.has_help(doctype)) { %}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
this.setup_reports();
|
||||
this.setup_list_filter();
|
||||
this.setup_assigned_to();
|
||||
this.setup_views();
|
||||
this.setup_kanban_boards();
|
||||
this.setup_calendar_view();
|
||||
|
|
@ -37,6 +36,15 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
if (limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) {
|
||||
this.setup_upgrade_box();
|
||||
}
|
||||
|
||||
if(this.doctype !== 'ToDo') {
|
||||
$('.assigned-to').show();
|
||||
}
|
||||
$('.assigned-to').on('click', () => {
|
||||
$('.assigned').remove();
|
||||
this.setup_assigned_to();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
setup_views() {
|
||||
|
|
@ -217,14 +225,18 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
}
|
||||
|
||||
setup_assigned_to() {
|
||||
$('.assigned-loading').show();
|
||||
let dropdown = this.page.sidebar.find('.assigned-dropdown');
|
||||
if(this.doctype === 'ToDo') {
|
||||
$('.assigned-to').remove();
|
||||
}
|
||||
frappe.call('frappe.desk.listview.get_user_assignments_and_count').then((data) => {
|
||||
let current_user_count = data.message.find(user => user.name === frappe.session.user).count;
|
||||
this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown);
|
||||
let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name));
|
||||
let current_filters = this.list_view.get_filters_for_args();
|
||||
|
||||
frappe.call('frappe.desk.listview.get_user_assignments_and_count', {doctype: this.doctype, current_filters: current_filters}).then((data) => {
|
||||
$('.assigned-loading').hide();
|
||||
let current_user = data.message.find(user => user.name === frappe.session.user);
|
||||
if(current_user) {
|
||||
let current_user_count = current_user.count;
|
||||
this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown);
|
||||
}
|
||||
let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name) && user.count!==0 );
|
||||
user_list.forEach((user) => {
|
||||
this.get_html_for_assigned(user.name, user.count).appendTo(dropdown);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -923,7 +923,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
get_checked_items(only_docnames) {
|
||||
const docnames = Array.from(this.$checks || [])
|
||||
.map(check => $(check).data().name);
|
||||
.map(check => cstr($(check).data().name));
|
||||
|
||||
if (only_docnames) return docnames;
|
||||
|
||||
|
|
|
|||
63
frappe/public/js/frappe/misc/energy_point_utils.js
Normal file
63
frappe/public/js/frappe/misc/energy_point_utils.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide('frappe.energy_points');
|
||||
|
||||
Object.assign(frappe.energy_points, {
|
||||
get_points(points) {
|
||||
return `<span class='bold' style="color: ${points >= 0 ? '#45A163': '#e42121'}">
|
||||
${points > 0 ? '+': ''}${points}
|
||||
</span>`;
|
||||
},
|
||||
format_form_log(log) {
|
||||
const separator = `<span> - </span>`;
|
||||
const formatted_log = `<span>
|
||||
${this.get_points(log.points)}
|
||||
<a href="#Form/Energy Point Log/${log.name}">${this.get_form_log_message(log)}</a>
|
||||
${log.reason ? separator + log.reason: ''}
|
||||
</span>`;
|
||||
return formatted_log;
|
||||
},
|
||||
format_history_log(log) {
|
||||
// redundant code to honor readability and to avoid confusion
|
||||
const separator = `<span> - </span>`;
|
||||
const formatted_log = `<span>
|
||||
${this.get_points(log.points)}
|
||||
<a href="#Form/Energy Point Log/${log.name}">${this.get_history_log_message(log)}</a>
|
||||
${log.reason ? separator + log.reason: ''}
|
||||
${separator + frappe.datetime.comment_when(log.creation)}
|
||||
</span>`;
|
||||
return formatted_log;
|
||||
},
|
||||
get_history_log_message(log) {
|
||||
const doc_link = frappe.utils.get_form_link(log.reference_doctype, log.reference_name, true);
|
||||
const owner_name = frappe.user.full_name(log.owner).bold();
|
||||
if (log.type === 'Appreciation') {
|
||||
return __('{0} appreciated on {1}', [owner_name, doc_link]);
|
||||
}
|
||||
if (log.type === 'Criticism') {
|
||||
return __('{0} criticized on {1}', [owner_name, doc_link]);
|
||||
}
|
||||
if (log.type === 'Revert') {
|
||||
return __('{0} reverted {1}', [owner_name,
|
||||
frappe.utils.get_form_link('Energy Point Log', log.revert_of, true)]);
|
||||
}
|
||||
return __('via automatic rule {0} on {1}', [log.rule.bold(), doc_link]);
|
||||
},
|
||||
get_form_log_message(log) {
|
||||
// redundant code to honor readability and to avoid confusion
|
||||
const owner_name = frappe.user.full_name(log.owner).bold();
|
||||
const user = frappe.user.full_name(log.user).bold();
|
||||
if (log.type === 'Appreciation') {
|
||||
return __('{0} appreciated {1}', [owner_name, user]);
|
||||
}
|
||||
if (log.type === 'Criticism') {
|
||||
return __('{0} criticized {1}', [owner_name, user]);
|
||||
}
|
||||
if (log.type === 'Revert') {
|
||||
return __('{0} reverted {1}', [user,
|
||||
frappe.utils.get_form_link('Energy Point Log', log.revert_of, true)]);
|
||||
}
|
||||
return __('gained by {0} via automatic rule {1}', [user, log.rule.bold()]);
|
||||
},
|
||||
});
|
||||
|
|
@ -9,7 +9,7 @@ frappe.user_info = function(uid) {
|
|||
fullname: __("Bot"),
|
||||
image: "/assets/frappe/images/ui/bot.png",
|
||||
abbr: "B"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if(!(frappe.boot.user_info && frappe.boot.user_info[uid])) {
|
||||
|
|
@ -24,7 +24,7 @@ frappe.user_info = function(uid) {
|
|||
user_info.color = frappe.get_palette(user_info.fullname);
|
||||
|
||||
return user_info;
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.set_user_background = function(src, selector, style) {
|
||||
if(!selector) selector = "#page-desktop";
|
||||
|
|
@ -47,7 +47,7 @@ frappe.ui.set_user_background = function(src, selector, style) {
|
|||
background:background,
|
||||
style: style==="Fill Screen" ? "background-size: cover;" : ""
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
frappe.provide('frappe.user');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
import deep_equal from "fast-deep-equal";
|
||||
frappe.provide('frappe.utils');
|
||||
|
||||
Object.assign(frappe.utils, {
|
||||
|
|
@ -30,7 +31,7 @@ Object.assign(frappe.utils, {
|
|||
if (!txt) return false;
|
||||
|
||||
if(txt.indexOf("<br>")==-1 && txt.indexOf("<p")==-1
|
||||
&& txt.indexOf("<img")==-1 && txt.indexOf("<div")==-1) {
|
||||
&& txt.indexOf("<img")==-1 && txt.indexOf("<div")==-1 && !txt.includes('<span')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -682,7 +683,11 @@ Object.assign(frappe.utils, {
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deep_equal(a, b) {
|
||||
return deep_equal(a, b);
|
||||
},
|
||||
});
|
||||
|
||||
// Array de duplicate
|
||||
|
|
|
|||
|
|
@ -83,11 +83,22 @@ export default {
|
|||
},
|
||||
update_primary_action(current_route) {
|
||||
if (current_route === 'home') {
|
||||
this.$root.page.set_title(__('Social'));
|
||||
frappe.breadcrumbs.update();
|
||||
this.$root.page.set_primary_action(__('Post'), () => {
|
||||
frappe.social.post_dialog.show();
|
||||
});
|
||||
} else {
|
||||
this.$root.page.clear_primary_action()
|
||||
frappe.breadcrumbs.add({
|
||||
type: 'Custom',
|
||||
label: __('Social Home'),
|
||||
route: '#social/home'
|
||||
});
|
||||
this.$root.page.clear_primary_action();
|
||||
}
|
||||
|
||||
if (current_route === 'users') {
|
||||
this.$root.page.set_title(__('Leaderboard'));
|
||||
}
|
||||
},
|
||||
get_current_page() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div>
|
||||
<ul class="log-list">
|
||||
<li class="history-log" v-for="log in history_logs" :key="log.name">
|
||||
<span v-html="frappe.energy_points.format_history_log(log, true)"></span>
|
||||
</li>
|
||||
<li v-if="fetching" class="history-log">
|
||||
{{ __('Fetching') + '...' }}
|
||||
</li>
|
||||
<li v-if="!fetching && has_more_logs" class="history-log">
|
||||
<button
|
||||
class="btn btn-default btn-xs"
|
||||
@click="get_logs()">
|
||||
{{ __('Load more') }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="!history_logs.length" class="history-log">
|
||||
{{ __('No logs found') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
history_logs: [],
|
||||
fetching: false,
|
||||
has_more_logs: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.get_logs();
|
||||
},
|
||||
methods: {
|
||||
get_logs() {
|
||||
this.fetching = true;
|
||||
const pull_limit = 10;
|
||||
frappe.db.get_list('Energy Point Log', {
|
||||
filters: {
|
||||
user: this.user,
|
||||
type: ['!=', 'Review']
|
||||
},
|
||||
fields: ['*'],
|
||||
limit: pull_limit,
|
||||
limit_start: this.history_logs.length
|
||||
}).then(data => {
|
||||
this.history_logs = this.history_logs.concat(data);
|
||||
this.has_more_logs = data.length === pull_limit;
|
||||
}).finally(() => {
|
||||
this.fetching = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import "frappe/public/less/common";
|
||||
.log-list {
|
||||
padding: 15px;
|
||||
padding-left: 0px;
|
||||
position: relative;
|
||||
}
|
||||
.log-list:before {
|
||||
content: " ";
|
||||
border-left: 1px solid @border-color;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 30px;
|
||||
z-index: 0;
|
||||
}
|
||||
.history-log {
|
||||
.text-muted;
|
||||
.text-medium;
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
padding-left: 50px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
.history-log:before {
|
||||
content: " ";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background-color: @border-color;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
left: 27px;
|
||||
top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="flex flex-column">
|
||||
<a class="route-link"
|
||||
@click.prevent="go_to_user_list()">
|
||||
{{ __('All Users') }}
|
||||
{{ __('Leaderboard') }}
|
||||
</a>
|
||||
<div class="links" v-if="frequently_visited_list.length">
|
||||
<div class="muted-title">
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@
|
|||
class="option"
|
||||
:class="{'bold': show_list === 'user_posts'}"
|
||||
@click="show_list = 'user_posts'">
|
||||
<span>Posts</span>
|
||||
<span>{{__('Posts')}}</span>
|
||||
<span>({{ user_posts_count }})</span>
|
||||
</div>
|
||||
<div
|
||||
class="option"
|
||||
:class="{'bold': show_list === 'liked_posts'}"
|
||||
@click="show_list = 'liked_posts'">
|
||||
<span>Likes</span>
|
||||
<span>{{__('Likes')}}</span>
|
||||
<span>({{ liked_posts_count }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,45 +1,98 @@
|
|||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="col-md-6">
|
||||
<div class="flex justify-between padding search-bar">
|
||||
<div class="flex col-md-6">
|
||||
<button class="btn" @click="frappe.set_route('social', 'home')">← {{ __('Back') }}</button>
|
||||
<input type="text" class="form-control" :placeholder="__('Search for a user...')" v-model="search_text">
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled user-list">
|
||||
<li
|
||||
class="padding cursor-pointer flex user-card"
|
||||
v-for="user in filtered_users" :key="user.name"
|
||||
@click="go_to_profile_page(user.name)">
|
||||
<span v-html="get_avatar(user.name)"></span>
|
||||
<div class="user-details">
|
||||
{{ user.fullname }}
|
||||
<div class="text-muted text-medium" :class="{'italic': !user.bio}">{{ frappe.ellipsis(user.bio, 100) || 'No Bio'}}</div>
|
||||
<div class="user-list-container">
|
||||
<ul class="list-unstyled user-list">
|
||||
<li class="user-card user-list-header text-medium">
|
||||
<span class="user-details text-muted">
|
||||
{{ __('User') }}
|
||||
</span>
|
||||
<span class="user-points text-muted">
|
||||
{{ __('Energy Points') }}
|
||||
</span>
|
||||
<span class="user-points text-muted">
|
||||
{{ __('Review Points') }}
|
||||
</span>
|
||||
<span class="user-points text-muted">
|
||||
{{ __('Points Given') }}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
v-for="user in filtered_users"
|
||||
:key="user.name">
|
||||
<div class="user-card" @click="toggle_log(user.name)">
|
||||
<div class="user-details flex">
|
||||
<span v-html="get_avatar(user.name)"></span>
|
||||
<span>
|
||||
<a @click="go_to_profile_page(user.name)">{{ user.fullname }}</a>
|
||||
<div class="text-muted text-medium" :class="{'italic': !user.bio}">
|
||||
{{ frappe.ellipsis(user.bio, 100) || 'No Bio'}}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="text-muted" v-if="!filtered_users.length">{{__('No user found')}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="text-muted text-nowrap user-points">
|
||||
{{ user.energy_points }}
|
||||
</span>
|
||||
<span class="text-muted text-nowrap user-points">
|
||||
{{ user.review_points }}
|
||||
</span>
|
||||
<span class="text-muted text-nowrap user-points">
|
||||
{{ user.given_points }}
|
||||
</span>
|
||||
</div>
|
||||
<energy-point-history
|
||||
v-show="show_log_for===user.name"
|
||||
class="energy-point-history"
|
||||
:user="user.name">
|
||||
</energy-point-history>
|
||||
</li>
|
||||
<li class="user-card text-muted" v-if="!filtered_users.length">{{__('No user found')}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import EnergyPointHistory from '../components/EnergyPointHistory.vue';
|
||||
export default {
|
||||
components: {
|
||||
EnergyPointHistory
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
search_text: ''
|
||||
filter_users_by: null,
|
||||
sort_users_by: 'energy_points',
|
||||
sort_order: 'desc',
|
||||
show_log_for: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filtered_users() {
|
||||
let filtered = this.users;
|
||||
if (this.search_text !== '') {
|
||||
filtered = filtered.filter(user => {
|
||||
if (user.fullname.toLowerCase().includes(this.search_text.toLowerCase())) {
|
||||
return true;
|
||||
let filtered = this.users.slice();
|
||||
|
||||
if (this.filter_users_by) {
|
||||
filtered = filtered.filter(user =>
|
||||
user.fullname.toLowerCase().includes(this.filter_users_by.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (this.sort_users_by) {
|
||||
filtered.sort((a, b) => {
|
||||
const value_a = a[this.sort_users_by];
|
||||
const value_b = b[this.sort_users_by];
|
||||
|
||||
let return_value = 0;
|
||||
if (value_a > value_b) {
|
||||
return_value = 1;
|
||||
}
|
||||
})
|
||||
|
||||
if (value_a < value_b) {
|
||||
return_value = -1;
|
||||
}
|
||||
|
||||
if (this.sort_order === 'desc') {
|
||||
return_value = -return_value
|
||||
}
|
||||
|
||||
return return_value
|
||||
});
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
|
@ -50,6 +103,10 @@ export default {
|
|||
// delete standard users from the list
|
||||
standard_users.forEach(user => delete this.users[user]);
|
||||
this.users = Object.values(this.users);
|
||||
this.fetch_users_energy_points_and_update_users();
|
||||
frappe.realtime.on('update_points', () => {
|
||||
this.fetch_users_energy_points_and_update_users();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
get_avatar(user) {
|
||||
|
|
@ -57,29 +114,60 @@ export default {
|
|||
},
|
||||
go_to_profile_page(user) {
|
||||
frappe.set_route('social', 'profile', user)
|
||||
},
|
||||
fetch_users_energy_points_and_update_users() {
|
||||
frappe.xcall(
|
||||
'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points'
|
||||
).then(data => {
|
||||
let users = this.users.slice();
|
||||
this.users = users.map(user => {
|
||||
const points = data[user.name] || {};
|
||||
user.energy_points = points.energy_points || 0;
|
||||
user.review_points = points.review_points || 0;
|
||||
user.given_points = points.given_points || 0;
|
||||
return user;
|
||||
});
|
||||
});
|
||||
},
|
||||
toggle_log(user) {
|
||||
if (this.show_log_for === user) {
|
||||
this.show_log_for = null
|
||||
} else {
|
||||
this.show_log_for = user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "frappe/public/less/variables";
|
||||
.user-list {
|
||||
// similar to search bar height
|
||||
margin-top: 75px;
|
||||
border-left: 1px solid @border-color;
|
||||
border-right: 1px solid @border-color;
|
||||
.user-card {
|
||||
&:hover {
|
||||
border: 1px solid #d1d8dd;
|
||||
}
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid @border-color;
|
||||
.user-details {
|
||||
margin-left: 10px;
|
||||
flex: 1;
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-points {
|
||||
flex: 0 0 20%;
|
||||
text-align: right;
|
||||
align-self: center;
|
||||
}
|
||||
.user-list-header {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
.search-bar {
|
||||
position: fixed;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
height: 75px;
|
||||
text-align: center;
|
||||
|
|
@ -89,6 +177,10 @@ export default {
|
|||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
.energy-point-history {
|
||||
border-bottom: 1px solid @border-color;
|
||||
background-color: @light-bg;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
this.$body.removeClass('hide');
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.clear_message();
|
||||
}
|
||||
|
||||
set_primary_action(label, click) {
|
||||
this.has_primary_action = true;
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -283,12 +283,12 @@ frappe.show_alert = function(message, seconds=7, actions={}) {
|
|||
<a class="close">×</a>
|
||||
</div>`);
|
||||
|
||||
div.find('.alert-message').append(message.message);
|
||||
|
||||
if(message.indicator) {
|
||||
div.find('.alert-message').addClass('indicator '+ message.indicator);
|
||||
div.find('.alert-message').append(`<span class="indicator ${message.indicator}"></span>`);
|
||||
}
|
||||
|
||||
div.find('.alert-message').append(message.message);
|
||||
|
||||
if (body_html) {
|
||||
div.find('.alert-body').show().html(body_html);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ frappe.ui.Slides = class Slides {
|
|||
.appendTo(this.container);
|
||||
this.$body = $(`<div>`).addClass(`slide-container`)
|
||||
.appendTo(this.container);
|
||||
this.$footer = $(`<div>`).addClass(`footer`)
|
||||
this.$footer = $(`<div>`).addClass(`slide-footer`)
|
||||
.appendTo(this.container);
|
||||
|
||||
this.render_progress_dots();
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@
|
|||
<a class="dropdown-toggle" data-toggle="dropdown" href="#"
|
||||
onclick="return false;">
|
||||
{{ avatar }}
|
||||
<span class="ellipsis toolbar-user-fullname hidden-xs hidden-sm">
|
||||
<span class="ellipsis toolbar-user-fullname hidden-xs hidden-sm">
|
||||
{%= frappe.user.full_name() %}</span>
|
||||
<b class="caret hidden-xs hidden-sm"></b></a>
|
||||
<b class="caret hidden-xs hidden-sm"></b></a>
|
||||
<ul class="dropdown-menu" id="toolbar-user" role="menu">
|
||||
<li><a href="#Form/User/{%= encodeURIComponent(frappe.session.user) %}">
|
||||
{%= __("My Settings") %}</a></li>
|
||||
{%= __("My Settings") %}</a></li>
|
||||
<li class="navbar-reload">
|
||||
<a href="#" onclick="return frappe.ui.toolbar.clear_cache();">
|
||||
{%= __("Reload") %}</a></li>
|
||||
{%= __("Reload") %}</a></li>
|
||||
<li><a href="/index" target="_blank" rel="noopener noreferrer">
|
||||
{%= __("View Website") %}</a></li>
|
||||
<li><a href="#background_jobs">
|
||||
|
|
@ -86,13 +86,13 @@
|
|||
</div>
|
||||
|
||||
<div class="hidden-xs">
|
||||
<form class="navbar-form navbar-right" role="search" onsubmit="return false;">
|
||||
<div class="form-group form-group-sm ui-front">
|
||||
<input id="navbar-search" type="text" class="form-control"
|
||||
<form class="navbar-form navbar-right" role="search" onsubmit="return false;">
|
||||
<div class="form-group form-group-sm ui-front">
|
||||
<input id="navbar-search" type="text" class="form-control"
|
||||
placeholder="{%= __("Search or type a command") %} {%= __("(Ctrl + G)") %}" aria-haspopup="true">
|
||||
<span class="octicon octicon-search navbar-search-icon"></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ frappe.upload = {
|
|||
upload_multiple_files: function(files /*FileData array*/, args, opts) {
|
||||
var i = -1;
|
||||
frappe.upload.total_files = files ? files.length : 0;
|
||||
|
||||
if (frappe.upload.total_files === 1) {
|
||||
return frappe.upload.upload_file(files[0], args, opts);
|
||||
}
|
||||
// upload the first file
|
||||
upload_next();
|
||||
// subsequent files will be uploaded after
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@
|
|||
v-if="!hidden"
|
||||
class="border module-box"
|
||||
:class="{ 'hovered-box': hovered }"
|
||||
:draggable="true"
|
||||
@dragstart="on_dragstart"
|
||||
@dragend="on_dragend"
|
||||
@dragenter="on_enter"
|
||||
@drop="on_drop"
|
||||
>
|
||||
<div class="flush-top">
|
||||
<div class="module-box-content">
|
||||
|
|
@ -20,23 +15,12 @@
|
|||
</div>
|
||||
</h4>
|
||||
</a>
|
||||
<dropdown v-if="links && links.length" :items="links">
|
||||
<dropdown v-if="dropdown_links && dropdown_links.length" :items="dropdown_links">
|
||||
<span class="pull-right">
|
||||
<i class="octicon octicon-chevron-down"></i>
|
||||
<i class="octicon octicon-chevron-down text-muted"></i>
|
||||
</span>
|
||||
</dropdown>
|
||||
<!-- <span class="drag-handle octicon octicon-three-bars text-extra-muted"></span> -->
|
||||
</div>
|
||||
<!-- <p v-if="links && links.length" class="small text-muted">
|
||||
<a
|
||||
v-for="shortcut in links"
|
||||
:key="(shortcut.name || shortcut.label) + shortcut.type"
|
||||
:href="shortcut.route"
|
||||
class="btn btn-default btn-xs shortcut-tag"
|
||||
title="toggle Tag"
|
||||
>{{ shortcut.label }}</a
|
||||
>
|
||||
</p>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -50,6 +34,7 @@ export default {
|
|||
"index",
|
||||
"name",
|
||||
"label",
|
||||
"category",
|
||||
"type",
|
||||
"module_name",
|
||||
"link",
|
||||
|
|
@ -75,32 +60,21 @@ export default {
|
|||
} else {
|
||||
return "octicon octicon-file-text";
|
||||
}
|
||||
}
|
||||
},
|
||||
dropdown_links() {
|
||||
return this.links.length > 0 ? this.links
|
||||
.filter(link => !link.hidden)
|
||||
.concat([
|
||||
{ label: __('Customize'), action: () => this.$emit('customize'), class: 'border-top' }
|
||||
]) : [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
on_dragstart() {
|
||||
this.$emit("box-dragstart", this.index);
|
||||
return 0;
|
||||
},
|
||||
on_dragend() {
|
||||
this.$emit("box-dragend", this.index);
|
||||
return 0;
|
||||
},
|
||||
on_enter() {
|
||||
this.$emit("box-enter", this.index);
|
||||
// this.hovered = 1;
|
||||
},
|
||||
on_drop() {
|
||||
this.$emit("box-drop", this.index);
|
||||
},
|
||||
on_exit() {
|
||||
// this.hovered = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "frappe/public/less/variables";
|
||||
|
||||
.module-box {
|
||||
border-radius: 4px;
|
||||
padding: 5px 15px;
|
||||
|
|
@ -109,16 +83,21 @@ export default {
|
|||
}
|
||||
|
||||
.module-box:hover {
|
||||
box-shadow: 0 3px 4px 0 rgba(18, 18, 19, 0.08);
|
||||
border-color: @text-muted;
|
||||
}
|
||||
|
||||
.hovered-box {
|
||||
background-color: #fafbfc;
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.octicon-chevron-down {
|
||||
font-size: 24px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
padding: 4px 6px 2px 6px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: @btn-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.octicon-chevron-down:hover {
|
||||
|
|
|
|||
|
|
@ -2,22 +2,15 @@
|
|||
<div>
|
||||
<div class="section-header level text-muted">
|
||||
<div class="module-category h6 uppercase">{{ category }}</div>
|
||||
|
||||
<div>
|
||||
<a class="small text-muted" @click="show_customize_dialog">{{ __("Customize") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modules-container">
|
||||
<desk-module-box
|
||||
v-for="(module, index) in modules.filter(m => !m.hidden)"
|
||||
v-for="(module, index) in modules"
|
||||
:key="module.name"
|
||||
:index="index"
|
||||
v-bind="module"
|
||||
@box-dragstart="box_dragstart($event)"
|
||||
@box-dragend="box_dragend($event)"
|
||||
@box-enter="box_enter($event)"
|
||||
@box-drop="box_drop($event)"
|
||||
@customize="show_module_card_customize_dialog(module)"
|
||||
></desk-module-box>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -25,228 +18,49 @@
|
|||
|
||||
<script>
|
||||
import DeskModuleBox from "./DeskModuleBox.vue";
|
||||
import { generate_route } from "./utils.js";
|
||||
|
||||
export default {
|
||||
props: ['category', 'all_modules', 'customization_settings'],
|
||||
props: ['category', 'modules'],
|
||||
components: {
|
||||
DeskModuleBox
|
||||
},
|
||||
data() {
|
||||
let default_modules = this.all_modules;
|
||||
let modules = this.get_customized_modules(default_modules, this.customization_settings);
|
||||
|
||||
return {
|
||||
default_modules: default_modules,
|
||||
modules: modules,
|
||||
new_settings: {},
|
||||
dragged_index: -1,
|
||||
hovered_index: -1,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show_customize_dialog() {
|
||||
if(!this.dialog) {
|
||||
const fields = this.make_fields();
|
||||
this.make_and_show_dialog(fields);
|
||||
} else {
|
||||
this.dialog.show();
|
||||
}
|
||||
},
|
||||
make_fields() {
|
||||
let fields = [];
|
||||
this.modules.forEach(module => {
|
||||
fields.push(this.get_module_select_field(module));
|
||||
|
||||
if(module.links) {
|
||||
fields.push(this.get_links_multiselect_field(module));
|
||||
}
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
make_and_show_dialog(fields) {
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("Customize " + this.category),
|
||||
fields: fields,
|
||||
primary_action_label: __("Update"),
|
||||
primary_action: (values) => {
|
||||
this.update_settings(values);
|
||||
}
|
||||
});
|
||||
|
||||
this.dialog.modal_body.find('.clearfix').css({'display': 'none'});
|
||||
this.dialog.modal_body.find('.frappe-control*[data-fieldtype="MultiSelect"]').css({'margin-bottom': '30px'});
|
||||
|
||||
this.dialog.show();
|
||||
},
|
||||
|
||||
update_settings(values) {
|
||||
// Figure out the diff from the default settings known from modules
|
||||
let new_settings = {};
|
||||
const checkbox_fields = Object.keys(values).filter(f => !f.includes('links'));
|
||||
|
||||
checkbox_fields.forEach(module_name => {
|
||||
const default_module = this.default_modules.filter(f => f.module_name === module_name)[0];
|
||||
|
||||
// Check if hidden changed
|
||||
const default_hidden = default_module.hidden ? 1 : 0;
|
||||
const new_hidden = !values[module_name] ? 1 : 0;
|
||||
const hidden_changed = new_hidden != default_hidden;
|
||||
|
||||
// Check if links changed
|
||||
let links_changed = 0;
|
||||
let new_links = [];
|
||||
|
||||
if(!new_hidden) {
|
||||
const default_links = default_module.links.map(l => (l.name || l.label));
|
||||
const new_links_str = values[module_name + '_links'] || '';
|
||||
new_links = new_links_str ? new_links_str.split(",") : [];
|
||||
links_changed = !this.are_arrays_equal(new_links, default_links);
|
||||
}
|
||||
|
||||
// Make new settings
|
||||
let new_module_settings;
|
||||
|
||||
if(hidden_changed || links_changed) {
|
||||
new_module_settings = {};
|
||||
if(hidden_changed) {
|
||||
new_module_settings.hidden = new_hidden;
|
||||
}
|
||||
if(links_changed) {
|
||||
new_module_settings.links = new_links;
|
||||
}
|
||||
}
|
||||
|
||||
if(new_module_settings) {
|
||||
new_module_settings.app = this.default_modules.filter(m => m.module_name === module_name)[0].app;
|
||||
new_settings[module_name] = new_module_settings;
|
||||
}
|
||||
});
|
||||
|
||||
if(Object.keys(new_settings)) {
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
method:'frappe.desk.moduleview.update_desk_section_settings',
|
||||
freeze: true,
|
||||
args: {
|
||||
desk_section: this.category,
|
||||
new_settings: new_settings
|
||||
},
|
||||
callback: (r) => {
|
||||
let new_settings_with_link_objects = r.message;
|
||||
let home_settings = JSON.parse(frappe.boot.home_settings);
|
||||
home_settings[this.category] = new_settings_with_link_objects;
|
||||
frappe.boot.home_settings = JSON.stringify(home_settings);
|
||||
|
||||
this.modules = this.get_customized_modules(this.default_modules, new_settings_with_link_objects);
|
||||
this.dialog.hide();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.dialog.hide();
|
||||
};
|
||||
},
|
||||
|
||||
get_customized_modules(default_modules, customization_settings={}) {
|
||||
return default_modules.map(module => {
|
||||
let customized_module = JSON.parse(JSON.stringify(module));
|
||||
|
||||
const module_settings = customization_settings[module.module_name];
|
||||
if(module_settings) {
|
||||
if(module_settings.links) {
|
||||
customized_module.links = module_settings.links;
|
||||
}
|
||||
customized_module.hidden = module_settings ? module_settings.hidden : 0;
|
||||
}
|
||||
|
||||
if(customized_module.links) {
|
||||
customized_module.links.forEach(link => {
|
||||
link.route = generate_route(link);
|
||||
});
|
||||
}
|
||||
|
||||
return customized_module;
|
||||
});
|
||||
},
|
||||
|
||||
get_module_select_field(module) {
|
||||
return {
|
||||
label: __(module.module_name),
|
||||
fieldname: module.module_name,
|
||||
fieldtype: "Check",
|
||||
default: module.hidden ? 0 : 1
|
||||
}
|
||||
},
|
||||
|
||||
get_links_multiselect_field(module) {
|
||||
return {
|
||||
label: __(""),
|
||||
fieldname: module.module_name + "_links",
|
||||
fieldtype: "MultiSelect",
|
||||
get_data: function() {
|
||||
let data = [];
|
||||
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
method:'frappe.desk.moduleview.get_links',
|
||||
async: false,
|
||||
no_spinner: true,
|
||||
args: {
|
||||
app: module.app,
|
||||
module: module.module_name,
|
||||
show_module_card_customize_dialog(module) {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Customize Shortcuts'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Shortcuts'),
|
||||
fieldname: 'links',
|
||||
fieldtype: 'MultiSelectPills',
|
||||
get_data() {
|
||||
return frappe.call('frappe.desk.moduleview.get_links', {
|
||||
app: module.app,
|
||||
module: module.module_name,
|
||||
}).then(r => r.message);
|
||||
},
|
||||
callback: function(r) {
|
||||
data = r.message;
|
||||
}
|
||||
default: module.links.filter(l => !l.hidden).map(l => l.name)
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: ({ links }) => {
|
||||
frappe.call('frappe.desk.moduleview.update_links_for_module', {
|
||||
module_name: module.module_name,
|
||||
links
|
||||
}).then(r => {
|
||||
this.$emit('update_home_settings', r.message);
|
||||
});
|
||||
return data;
|
||||
},
|
||||
default: module.links.map(l => (l.name || l.label)),
|
||||
depends_on: module.module_name
|
||||
};
|
||||
},
|
||||
|
||||
are_arrays_equal(arr1, arr2) {
|
||||
if(arr1.length !== arr2.length) return false;
|
||||
let areEqual = true;
|
||||
arr1.map((d, i) => {
|
||||
if(arr2[i] !== d) areEqual = false;
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
return areEqual;
|
||||
},
|
||||
|
||||
box_dragstart(index) {
|
||||
this.dragged_index = index;
|
||||
},
|
||||
|
||||
box_dragend(index) {
|
||||
this.dragged_index = -1;
|
||||
this.hovered_index = -1;
|
||||
},
|
||||
|
||||
box_enter(index) {
|
||||
this.hovered_index = index;
|
||||
},
|
||||
|
||||
box_drop(index) {
|
||||
let d = this.dragged_index;
|
||||
let h = this.hovered_index;
|
||||
if (d < h) {
|
||||
this.modules.splice(h, 0, this.modules[d]);
|
||||
this.modules.splice(d, 1);
|
||||
}
|
||||
d.show();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.section-header {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid #d0d8dd;
|
||||
}
|
||||
|
||||
.modules-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
|
|
@ -256,4 +70,3 @@ export default {
|
|||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
<template>
|
||||
<div class="modules-page-container">
|
||||
<div class="toolbar-underlay"></div>
|
||||
<div v-for="category in module_categories"
|
||||
:key="category">
|
||||
|
||||
<div class="modules-page-container" v-if="home_settings_fetched">
|
||||
<div
|
||||
class="modules-section"
|
||||
v-for="(category, i) in module_categories" :key="category"
|
||||
>
|
||||
<a
|
||||
v-if="i === 0"
|
||||
class="btn-show-hide text-muted text-medium"
|
||||
@click="show_hide_cards_dialog"
|
||||
>
|
||||
{{ __('Show / Hide Cards') }}
|
||||
</a>
|
||||
<desk-section
|
||||
v-if="modules.filter(m => m.category === category).length"
|
||||
v-if="get_modules_for_category(category).length"
|
||||
:category="category"
|
||||
:all_modules="modules.filter(m => m.category === category)"
|
||||
:customization_settings="home_settings ? home_settings[category] : {}"
|
||||
:modules="get_modules_for_category(category)"
|
||||
@update_home_settings="hs => update_modules_with_home_settings(hs)"
|
||||
>
|
||||
</desk-section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeskSection from './DeskSection.vue';
|
||||
import { generate_route } from './utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -26,77 +32,98 @@ export default {
|
|||
},
|
||||
data() {
|
||||
let modules_list = frappe.boot.allowed_modules
|
||||
.filter(d => (d.type==='module' || d.category==='Places') && !d.blocked);
|
||||
|
||||
modules_list.forEach(module => {
|
||||
module.count = this.get_module_count(module.module_name);
|
||||
});
|
||||
|
||||
const home_settings = frappe.boot.home_settings || '{}';
|
||||
.filter(d => (d.type==='module' || d.category==='Places') && !d.blocked)
|
||||
.map(d => {
|
||||
d.links = (d.links || []).map(link => {
|
||||
link.route = generate_route(link);
|
||||
return link;
|
||||
});
|
||||
return d;
|
||||
});
|
||||
|
||||
return {
|
||||
route_str: frappe.get_route()[1],
|
||||
module_label: '',
|
||||
module_categories: ["Modules", "Domains", "Places", "Administration"],
|
||||
module_categories: ['Modules', 'Domains', 'Places', 'Administration'],
|
||||
modules: modules_list,
|
||||
|
||||
// // Desk customizations. Format of user settings:
|
||||
|
||||
// home_settings = { // <--- Settings
|
||||
// "Domains": { // <--- Category (Desk Section)
|
||||
// "Manufacturing": { // <--- Module
|
||||
// "index": 3,
|
||||
// "links": [],
|
||||
// "hidden": 1,
|
||||
// },
|
||||
|
||||
// },
|
||||
// }
|
||||
|
||||
home_settings: JSON.parse(home_settings)
|
||||
home_settings_fetched: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch_home_settings();
|
||||
},
|
||||
methods: {
|
||||
get_settings() {
|
||||
fetch_home_settings() {
|
||||
return frappe.db.get_value('User', user, 'home_settings')
|
||||
.then(resp => {
|
||||
this.all_settings = JSON.parse(resp.message['home_settings']);
|
||||
this.settings = this.all_settings[this.category];
|
||||
.then(r => {
|
||||
let home_settings = JSON.parse(r.message.home_settings || '{}');
|
||||
this.update_modules_with_home_settings(home_settings);
|
||||
this.home_settings_fetched = true;
|
||||
});
|
||||
},
|
||||
get_module_count(module_name) {
|
||||
var module_doctypes = frappe.boot.notification_info.module_doctypes[module_name];
|
||||
var sum = 0;
|
||||
update_modules_with_home_settings(home_settings) {
|
||||
this.modules = this.modules.map(m => {
|
||||
let hidden_modules = home_settings.hidden_modules || [];
|
||||
m.hidden = hidden_modules.includes(m.module_name);
|
||||
|
||||
if(module_doctypes && frappe.boot.notification_info.open_count_doctype) {
|
||||
// sum all doctypes for a module
|
||||
for (var j=0, k=module_doctypes.length; j < k; j++) {
|
||||
var doctype = module_doctypes[j];
|
||||
let count = (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
let links = home_settings.links && home_settings.links[m.module_name];
|
||||
|
||||
if (links) {
|
||||
links = JSON.parse(links);
|
||||
|
||||
let default_links = m.links.map(link => link.name);
|
||||
m.links = m.links.map(link => {
|
||||
link.hidden = !links.includes(link.name);
|
||||
return link;
|
||||
});
|
||||
let new_links = links
|
||||
.filter(link => !default_links.includes(link))
|
||||
.filter(Boolean)
|
||||
.map(link => {
|
||||
let new_link = { name: link, label: link, type: 'doctype' };
|
||||
new_link.route = generate_route(new_link);
|
||||
return new_link;
|
||||
});
|
||||
m.links = m.links.concat(new_links);
|
||||
}
|
||||
}
|
||||
|
||||
if(frappe.boot.notification_info.open_count_doctype
|
||||
&& frappe.boot.notification_info.open_count_doctype[module_name]!=null) {
|
||||
// notification count explicitly for doctype
|
||||
let count = frappe.boot.notification_info.open_count_doctype[module_name] || 0;
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
}
|
||||
return m;
|
||||
});
|
||||
},
|
||||
get_modules_for_category(category) {
|
||||
return this.modules.filter(m => m.category === category && !m.hidden);
|
||||
},
|
||||
show_hide_cards_dialog() {
|
||||
let fields = this.module_categories.map(category => {
|
||||
let modules = this.modules.filter(m => m.category === category);
|
||||
let options = modules.map(
|
||||
m => ({ label: m.label, value: m.module_name, checked: !m.hidden })
|
||||
);
|
||||
return {
|
||||
label: category,
|
||||
fieldname: category,
|
||||
fieldtype: 'MultiCheck',
|
||||
options,
|
||||
columns: 2
|
||||
}
|
||||
});
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Show / Hide Cards'),
|
||||
fields: fields.filter(f => f.options.length > 0),
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: (values) => {
|
||||
let all_modules = this.modules.map(m => m.module_name);
|
||||
let modules_to_show = Object.keys(values).map(k => values[k]).flatMap(m => m);
|
||||
let modules_to_hide = all_modules.filter(m => !modules_to_show.includes(m));
|
||||
d.hide();
|
||||
|
||||
if(frappe.boot.notification_info.open_count_module
|
||||
&& frappe.boot.notification_info.open_count_module[module_name]!=null) {
|
||||
// notification count explicitly for module
|
||||
let count = frappe.boot.notification_info.open_count_module[module_name] || 0;
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
}
|
||||
frappe.call('frappe.desk.moduleview.hide_modules_from_desktop', {
|
||||
modules: modules_to_hide
|
||||
})
|
||||
.then(r => r.message)
|
||||
.then(hs => this.update_modules_with_home_settings(hs));
|
||||
}
|
||||
});
|
||||
|
||||
sum = sum > 99 ? "99+" : sum;
|
||||
|
||||
return sum;
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,12 +131,23 @@ export default {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.modules-page-container {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.toolbar-underlay{
|
||||
margin: 70px;
|
||||
.modules-section {
|
||||
position: relative;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.btn-show-hide {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 36px;
|
||||
}
|
||||
|
||||
.toolbar-underlay {
|
||||
margin: 70px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<Popover :align="align">
|
||||
<slot></slot>
|
||||
<ul slot="popover-content" class="list-reset">
|
||||
<li v-for="item of dropdownItems" :key="item.label">
|
||||
<a class="list-item" :href="item.route">{{ item.label }}</a>
|
||||
<ul slot="popover-content" class="list-reset border">
|
||||
<li v-for="item of dropdownItems" :key="item.label" :class="item.class || null">
|
||||
<a v-if="item.route" class="list-item" :href="item.route">{{ item.label }}</a>
|
||||
<div v-else class="list-item" @click="item.action">{{ item.label }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Popover>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue