diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 2173ca1864..cd754aef3a 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1075,12 +1075,4 @@ def generate_keys(user): user_details.save() return {"api_secret": api_secret} - frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) - -@frappe.whitelist() -def update_profile_info(profile_info): - profile_info = json.loads(profile_info) - user = frappe.get_doc('User', frappe.session.user) - user.update(profile_info) - user.save() - return user \ No newline at end of file + frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) \ No newline at end of file diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index ebaf8615d4..e937d0e4fc 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -214,7 +214,7 @@ class DashboardChart { return frappe.xcall( method, { - chart_name: this.chart_doc.name, + chart: this.chart_doc, filters: filters, refresh: refresh ? 1 : 0, } diff --git a/frappe/core/page/dashboard/dashboard.py b/frappe/core/page/dashboard/dashboard.py index 552299f2ac..487f0d0ec5 100644 --- a/frappe/core/page/dashboard/dashboard.py +++ b/frappe/core/page/dashboard/dashboard.py @@ -8,21 +8,25 @@ from frappe.utils import add_to_date def cache_source(function): def wrapper(*args, **kwargs): - chart_name = kwargs.get("chart_name") - cache_key = 'chart-data:{}'.format(chart_name) + chart = kwargs.get("chart") + no_cache = kwargs.get("no_cache") + if no_cache: + return function(chart, no_cache) + chart_name = frappe.parse_json(chart).name + cache_key = "chart-data:{}".format(chart_name) if int(kwargs.get("refresh") or 0): - results = generate_and_cache_results(chart_name, function, cache_key) + results = generate_and_cache_results(chart, chart_name, function, cache_key) else: cached_results = frappe.cache().get_value(cache_key) if cached_results: - results = json.loads(frappe.safe_decode(cached_results)) + results = frappe.parse_json(frappe.safe_decode(cached_results)) else: - results = generate_and_cache_results(chart_name, function, cache_key) + results = generate_and_cache_results(chart, chart_name, function, cache_key) return results return wrapper -def generate_and_cache_results(chart_name, function, cache_key): - results = function(chart_name) +def generate_and_cache_results(chart, chart_name, function, cache_key): + results = function(chart) 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(), update_modified = False) return results diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 488e13a774..6f08253182 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -3,20 +3,21 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe from frappe import _ +import datetime from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate from frappe.model.document import Document @frappe.whitelist() @cache_source -def get(chart_name, from_date=None, to_date=None, refresh = None): - chart = frappe.get_doc('Dashboard Chart', chart_name) +def get(chart, no_cache=None, from_date=None, to_date=None, refresh = None): + chart = frappe.parse_json(chart) timespan = chart.timespan timegrain = chart.time_interval - filters = json.loads(chart.filters_json) + filters = frappe.parse_json(chart.filters_json) # don't include cancelled documents filters['docstatus'] = ('<', 2) @@ -24,7 +25,7 @@ def get(chart_name, from_date=None, to_date=None, refresh = None): if not from_date: from_date = get_from_date_from_timespan(to_date, timespan) if not to_date: - to_date = nowdate() + to_date = datetime.datetime.now() # get conditions from filters conditions, values = frappe.db.build_conditions(filters) @@ -78,7 +79,7 @@ def convert_to_dates(data, timegrain): result = [] for d in data: if timegrain == 'Daily': - result.append([add_to_date('{:d}-01-01'.format(int(d[0])), days = d[1]), d[2]]) + result.append([add_to_date('{:d}-01-01'.format(int(d[0])), days = d[1] - 1), d[2]]) elif timegrain == 'Weekly': result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), weeks = d[1] + 1), days = -1), d[2]]) elif timegrain == 'Monthly': diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 7cd5f28ae7..b70a44d062 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -40,9 +40,10 @@ class TestDashboardChart(unittest.TestCase): if frappe.db.exists('Dashboard Chart', 'Test Dashboard Chart'): frappe.delete_doc('Dashboard Chart', 'Test Dashboard Chart') - frappe.get_doc(dict( + chart = frappe.get_doc(dict( doctype = 'Dashboard Chart', chart_name = 'Test Dashboard Chart', + name = 'Test Dashboard Chart', chart_type = 'Count', document_type = 'DocType', based_on = 'creation', @@ -54,7 +55,7 @@ class TestDashboardChart(unittest.TestCase): cur_date = datetime.now() - relativedelta(years=1) - result = get(chart_name ='Test Dashboard Chart', refresh = 1) + result = get(chart = chart) for idx in range(13): month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1])) month = formatdate(month.strftime('%Y-%m-%d')) @@ -72,9 +73,9 @@ class TestDashboardChart(unittest.TestCase): frappe.db.sql('delete from `tabError Log`') - frappe.get_doc(dict( + chart = dict( doctype = 'Dashboard Chart', - chart_name = 'Test Empty Dashboard Chart', + name = 'Test Empty Dashboard Chart', chart_type = 'Count', document_type = 'Error Log', based_on = 'creation', @@ -82,11 +83,11 @@ class TestDashboardChart(unittest.TestCase): time_interval = 'Monthly', filters_json = '{}', timeseries = 1 - )).insert() + ) cur_date = datetime.now() - relativedelta(years=1) - result = get(chart_name ='Test Empty Dashboard Chart', refresh = 1) + result = get(chart = chart, refresh = 1) for idx in range(13): month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1])) month = formatdate(month.strftime('%Y-%m-%d')) @@ -104,9 +105,9 @@ class TestDashboardChart(unittest.TestCase): # create one data point frappe.get_doc(dict(doctype = 'Error Log', creation = '2018-06-01 00:00:00')).insert() - frappe.get_doc(dict( + chart = dict( doctype = 'Dashboard Chart', - chart_name = 'Test Empty Dashboard Chart 2', + name = 'Test Empty Dashboard Chart 2', chart_type = 'Count', document_type = 'Error Log', based_on = 'creation', @@ -114,11 +115,10 @@ class TestDashboardChart(unittest.TestCase): time_interval = 'Monthly', filters_json = '{}', timeseries = 1 - )).insert() - + ) cur_date = datetime.now() - relativedelta(years=1) - result = get(chart_name ='Test Empty Dashboard Chart 2', refresh = 1) + result = get(chart = chart, refresh = 1) for idx in range(13): month = datetime(int(cur_date.year), int(cur_date.strftime('%m')), int(calendar.monthrange(cur_date.year, cur_date.month)[1])) month = formatdate(month.strftime('%Y-%m-%d')) diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 790aae5f90..dd2c5fd07a 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -29,6 +29,12 @@ frappe.pages['activity'].on_page_load = function(wrapper) { doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname"); + [link_doctype, link_name, doctype, docname] = + [link_doctype, link_name, doctype, docname].map(decodeURIComponent); + + link_doctype = link_doctype && link_doctype !== 'null' ? link_doctype : null; + link_name = link_name && link_name !== 'null' ? link_name : null; + if (doctype && docname) { if (link_doctype && link_name) { frappe.route_options = { diff --git a/frappe/desk/page/activity/activity.py b/frappe/desk/page/activity/activity.py index f60dc9ffd6..7de294d2f0 100644 --- a/frappe/desk/page/activity/activity.py +++ b/frappe/desk/page/activity/activity.py @@ -13,18 +13,14 @@ def get_feed(start, page_length): match_conditions_comment = get_feed_match_conditions(frappe.session.user, 'Comment') result = frappe.db.sql("""select X.* - from (select `tabCommunication`.name, `tabCommunication`.owner, `tabCommunication`.modified, - `tabCommunication`.creation, `tabCommunication`.seen, `tabCommunication`.comment_type, - `tabCommunication`.reference_doctype, `tabCommunication`.reference_name, `tabCommunication`.subject, - `tabCommunication`.communication_type, `tabCommunication`.communication_medium, `tabCommunication`.content, - `tabCommunication Link`.link_doctype, `tabCommunication Link`.link_name + from (select name, owner, modified, creation, seen, comment_type, + reference_doctype, reference_name, '' as link_doctype, '' as link_name, subject, + communication_type, communication_medium, content from `tabCommunication` - inner join `tabCommunication Link` - on `tabCommunication`.name=`tabCommunication Link`.parent where - `tabCommunication`.communication_type = "Communication" - and `tabCommunication`.communication_medium != "Email" + communication_type = 'Communication' + and communication_medium != 'Email' and {match_conditions_communication} UNION select name, owner, modified, creation, '0', 'Updated', diff --git a/frappe/desk/page/activity/activity_row.html b/frappe/desk/page/activity/activity_row.html index 2eb1a91a91..4a15d3d9cd 100644 --- a/frappe/desk/page/activity/activity_row.html +++ b/frappe/desk/page/activity/activity_row.html @@ -2,10 +2,10 @@
{%= date_sep || "" %}
{{ avatar }} diff --git a/frappe/desk/page/user_profile/__init__.py b/frappe/desk/page/user_profile/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/page/user_profile/user_profile.css b/frappe/desk/page/user_profile/user_profile.css new file mode 100644 index 0000000000..c05a52ada2 --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile.css @@ -0,0 +1,121 @@ +.user-image-container { + margin-top: 7px; + padding-bottom: 100%; +} + +.user-image-container .standard-image { + font-size: 72px; +} + +.profile-details { + margin: -5px 5px; +} + +.profile-links { + margin: 30px 5px; +} + +.user-initial { + font-size: 72px; +} + +.chart-column-container{ + border-bottom: 1px solid #d1d8dd; + margin: 5px 0; +} + +.chart-container text.title { + text-transform: uppercase; + font-size: 11px; +} + +.heatmap-container { + height: 170px +} + +.performance-heatmap { + width: 80%; + display: inline-block; +} + +.performance-heatmap .chart-container { + margin-left: 30px; +} + +.performance-heatmap .frappe-chart { + margin-top: 5px; +} + +.performance-heatmap .frappe-chart .chart-legend { + display: none; +} + +.performance-percentage-chart .frappe-chart { + position: absolute; + top: 5px; +} + +.performance-line-chart .frappe-chart { + margin-top: -20px; +} + +.percentage-chart-container { + height: 130px; +} + +.line-chart-container .chart-filter { + z-index: 1; +} + +.recent-activity { + margin: 20px; + font-size: 12px; +} + +.show-more-activity { + text-align: center; + margin-top: 20px; +} + +.recent-activity-item { + margin: 15px 5px; +} + +.interest-icon { + margin-right: 5px; +} + +.chart-filter { + position: relative; + top: 5px; + margin-right: 10px; +} + +.filter-label { + margin-right: 4px; +} + +.performance-title { + position: relative; + left: 30px; + top: 20px; +} + +@media (max-width: 991px) { + .user-profile-sidebar { + display: flex; + } + + .percentage-chart-container { + border-top: 1px solid #d1d8dd; + } + + .user-profile-sidebar .profile-links { + margin: 0; + } + + .user-profile-sidebar .profile-details { + width: 50%; + margin: 0; + } +} diff --git a/frappe/desk/page/user_profile/user_profile.html b/frappe/desk/page/user_profile/user_profile.html new file mode 100644 index 0000000000..ca9c8f0001 --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile.html @@ -0,0 +1,25 @@ + + diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js new file mode 100644 index 0000000000..486c64192e --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile.js @@ -0,0 +1,456 @@ +frappe.provide('frappe.energy_points'); + +frappe.pages['user-profile'].on_page_load = function(wrapper) { + + frappe.ui.make_app_page({ + parent: wrapper, + title: __('User Profile'), + }); + + let user_profile = new UserProfile(wrapper); + $(wrapper).bind('show', ()=> { + user_profile.show(); + }); +}; + +class UserProfile { + + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = wrapper.page; + this.sidebar = this.wrapper.find('.layout-side-section'); + this.main_section = this.wrapper.find('.layout-main-section'); + } + + show() { + let route = frappe.get_route(); + this.user_id = route[1] || frappe.session.user; + + //validate if user + if (route.length > 1) { + frappe.db.exists('User', this.user_id).then( exists => { + if (exists) { + this.make_user_profile(); + } else { + frappe.msgprint(__('User does not exist')); + } + }); + } else { + this.user_id = frappe.session.user; + this.make_user_profile(); + } + } + + make_user_profile() { + frappe.set_route('user-profile', this.user_id); + this.user = frappe.user_info(this.user_id); + this.page.set_title(this.user.fullname); + this.setup_user_search(); + this.main_section.empty().append(frappe.render_template('user_profile')); + this.energy_points = 0; + this.review_points = 0; + this.rank = 0; + this.month_rank = 0; + this.render_user_details(); + this.render_points_and_rank(); + this.render_heatmap(); + this.render_line_chart(); + this.render_percentage_chart('type', 'Type Distribution'); + this.create_percentage_chart_filters(); + this.setup_show_more_activity(); + this.render_user_activity(); + } + + setup_user_search() { + this.$user_search_button = this.page.set_secondary_action('Change User', () => { + this.show_user_search_dialog(); + }); + } + + show_user_search_dialog() { + let dialog = new frappe.ui.Dialog({ + title: __('Change User'), + fields: [ + { + fieldtype: 'Link', + fieldname: 'user', + options: 'User', + label: __('User'), + } + ], + primary_action_label: __('Go'), + primary_action: ({ user }) => { + dialog.hide(); + this.user_id = user; + this.make_user_profile(); + } + }); + dialog.show(); + } + + render_heatmap() { + this.heatmap = new frappe.Chart('.performance-heatmap', { + type: 'heatmap', + countLabel: 'Energy Points', + data: {}, + discreteDomains: 0, + }); + this.update_heatmap_data(); + this.create_heatmap_chart_filters(); + } + + update_heatmap_data(date_from) { + frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_heatmap_data', { + user: this.user_id, + date: date_from || frappe.datetime.year_start(), + }).then((r) => { + this.heatmap.update( {dataPoints: r} ); + }); + } + + get_years_since_creation() { + //Get years since user account created + this.user_creation = frappe.boot.user.creation; + let creation_year = this.get_year(this.user_creation); + let current_year = this.get_year(frappe.datetime.now_date()); + let years_list = []; + for (var year = current_year; year >= creation_year; year--) { + years_list.push(year); + } + return years_list; + } + + get_year(date_str) { + return date_str.substring(0, date_str.indexOf('-')); + } + + render_line_chart() { + this.line_chart_filters = {'user': this.user_id}; + this.line_chart_config = { + timespan: 'Last Month', + time_interval: 'Daily', + type: 'Line', + value_based_on: 'points', + chart_type: 'Sum', + document_type: 'Energy Point Log', + name: 'Energy Points', + width: 'half', + based_on: 'creation' + }; + + this.line_chart = new frappe.Chart( '.performance-line-chart', { + title: 'Energy Points', + type: 'line', + height: 200, + data: { + labels: [], + datasets: [{}] + }, + colors: ['purple'], + axisOptions: { + xIsSeries: 1 + } + }); + this.update_line_chart_data(); + this.create_line_chart_filters(); + } + + update_line_chart_data() { + this.line_chart_config.filters_json = JSON.stringify(this.line_chart_filters); + + frappe.xcall('frappe.desk.doctype.dashboard_chart.dashboard_chart.get', { + chart: this.line_chart_config, + no_cache: 1, + }).then(chart => { + this.line_chart.update(chart); + }); + } + + render_percentage_chart(field, title) { + frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', { + user: this.user_id, + field: field + }).then(chart => { + if (chart.labels.length) { + this.percentage_chart = new frappe.Chart( '.performance-percentage-chart', { + title: title, + type: 'percentage', + data: { + labels: chart.labels, + datasets: chart.datasets + }, + barOptions: { + height: 11, + depth: 1 + }, + height: 160, + maxSlices: 8, + colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'], + }); + } else { + this.wrapper.find('.percentage-chart-container').hide(); + } + }); + } + + create_line_chart_filters() { + let filters = [ + { + label: 'All', + options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'], + action: (selected_item) => { + if (selected_item === 'All') delete this.line_chart_filters.type; + else this.line_chart_filters.type = selected_item; + this.update_line_chart_data(); + } + }, + { + label: 'Last Month', + options: ['Last Week', 'Last Month', 'Last Quarter'], + action: (selected_item) => { + this.line_chart_config.timespan = selected_item; + this.update_line_chart_data(); + } + }, + { + label: 'Daily', + options: ['Daily', 'Weekly', 'Monthly'], + action: (selected_item) => { + this.line_chart_config.time_interval = selected_item; + this.update_line_chart_data(); + } + }, + ]; + this.render_chart_filters(filters, '.line-chart-container', 1); + } + + create_percentage_chart_filters() { + let filters = [ + { + label: 'Type', + options: ['Type', 'Reference Doctype', 'Rule'], + fieldnames: ['type', 'reference_doctype', 'rule'], + action: (selected_item, fieldname) => { + let title = selected_item + ' Distribution'; + this.render_percentage_chart(fieldname, title); + } + }, + ]; + this.render_chart_filters(filters, '.percentage-chart-container'); + } + + create_heatmap_chart_filters() { + let filters = [ + { + label: this.get_year(frappe.datetime.now_date()), + options: this.get_years_since_creation(), + action: (selected_item) => { + this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); + } + }, + ]; + this.render_chart_filters(filters, '.heatmap-container'); + } + + render_chart_filters(filters, container, append) { + filters.forEach(filter => { + let chart_filter_html = `
+ `; + let options_html; + + if (filter.fieldnames) { + options_html = filter.options.map((option, i) => + `
  • ${option}
  • `).join(''); + } else { + options_html = filter.options.map( option => `
  • ${option}
  • `).join(''); + } + + let dropdown_html = chart_filter_html + `
    `; + let $chart_filter = $(dropdown_html); + + if (append) { + $chart_filter.prependTo(this.wrapper.find(container)); + } else $chart_filter.appendTo(this.wrapper.find(container)); + + $chart_filter.find('.dropdown-menu').on('click', 'li a', (e) => { + let $el = $(e.currentTarget); + let fieldname; + if ($el.attr('data-fieldname')) { + fieldname = $el.attr('data-fieldname'); + } + let selected_item = $el.text(); + $el.parents('.chart-filter').find('.filter-label').text(selected_item); + filter.action(selected_item, fieldname); + }); + }); + + } + + edit_profile() { + let edit_profile_dialog = new frappe.ui.Dialog({ + title: __('Edit Profile'), + fields: [ + { + fieldtype: 'Attach Image', + fieldname: 'user_image', + label: 'Profile Image', + }, + { + fieldtype: 'Data', + fieldname: 'interest', + label: 'Interests', + }, + { + fieldtype: 'Column Break' + }, + { + fieldtype: 'Data', + fieldname: 'location', + label: 'Location', + }, + { + fieldtype: 'Section Break', + fieldname: 'Interest', + }, + { + fieldtype: 'Small Text', + fieldname: 'bio', + label: 'Bio', + } + ], + primary_action: values => { + edit_profile_dialog.disable_primary_action(); + frappe.xcall('frappe.desk.page.user_profile.user_profile.update_profile_info', { + profile_info: values + }).then(user => { + user.image = user.user_image; + this.user = Object.assign(values, user); + edit_profile_dialog.hide(); + this.render_user_details(); + }).finally(() => { + edit_profile_dialog.enable_primary_action(); + }); + }, + primary_action_label: __('Save') + }); + + edit_profile_dialog.set_values({ + user_image: this.user.image, + location: this.user.location, + interest: this.user.interest, + bio: this.user.bio + }); + edit_profile_dialog.show(); + } + + render_user_details() { + this.sidebar.empty().append(frappe.render_template('user_profile_sidebar', { + user_image: frappe.avatar(this.user_id, 'avatar-frame', 'user_image', this.user.image), + user_abbr: this.user.abbr, + user_location: this.user.location, + user_interest: this.user.interest, + user_bio: this.user.bio, + })); + + this.setup_user_profile_links(); + } + + setup_user_profile_links() { + if (this.user_id !== frappe.session.user) { + this.wrapper.find('.profile-links').hide(); + } else { + this.wrapper.find('.edit-profile-link').on('click', () => { + this.edit_profile(); + }); + + this.wrapper.find('.user-settings-link').on('click', () => { + this.go_to_user_settings(); + }); + } + } + + get_user_rank() { + return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_user_rank', { + user: this.user_id, + }).then(r => { + + if (r.monthly_rank[0]) this.month_rank = r.monthly_rank[0][1]; + if (r.all_time_rank[0]) this.rank = r.all_time_rank[0][1]; + }); + } + + get_user_points() { + return frappe.xcall( + 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points', + { + user: this.user_id, + } + ).then(r => { + if (r[this.user_id]) { + this.energy_points = r[this.user_id].energy_points; + this.review_points = r[this.user_id].review_points; + } + }); + } + + render_points_and_rank() { + let $profile_details = this.wrapper.find('.profile-details'); + + this.get_user_rank().then(() => { + this.get_user_points().then(() => { + let html = $(__(`

    ${__('Energy Points: ')}{0}

    +

    ${__('Review Points: ')}{1}

    +

    ${__('Rank: ')}{2}

    +

    ${__('Monthly Rank: ')}{3}

    + `, [this.energy_points, this.review_points, this.rank, this.month_rank])); + + $profile_details.append(html); + }); + }); + } + + go_to_user_settings() { + frappe.set_route('Form', 'User', this.user_id); + } + + render_user_activity() { + this.$recent_activity_list = this.wrapper.find('.recent-activity-list'); + + let get_recent_energy_points_html = (field) => { + let message_html = frappe.energy_points.format_history_log(field); + return `

    ${message_html}

    `; + }; + + frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_list', { + start: this.activity_start, + limit: this.activity_end, + user: this.user_id + }).then(list => { + if (list.length < 11) { + let activity_html = `${__('No More Activity')}`; + this.wrapper.find('.show-more-activity').html(activity_html); + } + let html = list.slice(0, 10).map(get_recent_energy_points_html).join(''); + this.$recent_activity_list.append(html); + }); + } + + setup_show_more_activity() { + //Show 10 items at a time + this.activity_start = 0; + this.activity_end = 11; + this.wrapper.find('.show-more-activity').on('click', () => this.show_more_activity()); + } + + show_more_activity() { + this.activity_start = this.activity_end; + this.activity_end += 11; + this.render_user_activity(); + } + +} diff --git a/frappe/desk/page/user_profile/user_profile.json b/frappe/desk/page/user_profile/user_profile.json new file mode 100644 index 0000000000..1fb0085eb2 --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile.json @@ -0,0 +1,22 @@ +{ + "content": null, + "creation": "2019-07-22 12:23:38.425877", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2019-07-22 12:23:38.425877", + "modified_by": "Administrator", + "module": "Desk", + "name": "user-profile", + "owner": "Administrator", + "page_name": "User Profile", + "roles": [ + { + "role": "All" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0 +} \ No newline at end of file diff --git a/frappe/desk/page/user_profile/user_profile.py b/frappe/desk/page/user_profile/user_profile.py new file mode 100644 index 0000000000..6060fc8163 --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile.py @@ -0,0 +1,76 @@ +import frappe +from datetime import datetime + +@frappe.whitelist() +def get_energy_points_heatmap_data(user, date): + return dict(frappe.db.sql("""select unix_timestamp(date(creation)), sum(points) + from `tabEnergy Point Log` + where + date(creation) > subdate('{date}', interval 1 year) and + date(creation) < subdate('{date}', interval -1 year) and + user = '{user}' and + type != 'Review' + group by date(creation) + order by creation asc""".format(user = user, date = date))) + + +@frappe.whitelist() +def get_energy_points_percentage_chart_data(user, field): + result = frappe.db.get_all('Energy Point Log', + filters = {'user': user, 'type': ['!=', 'Review']}, + group_by = field, + order_by = field, + fields = [field, 'ABS(sum(points)) as points'], + as_list = True) + + return { + "labels": [r[0] for r in result if r[0] != None], + "datasets": [{ + "values": [r[1] for r in result] + }] + } + +@frappe.whitelist() +def get_user_rank(user): + month_start = datetime.today().replace(day=1) + monthly_rank = frappe.db.get_all('Energy Point Log', + group_by = 'user', + filters = {'creation': ['>', month_start], 'type' : ['!=', 'Review']}, + fields = ['user', 'rank() over (order by sum(points) desc) as rank'], + as_list = True) + + all_time_rank = frappe.db.get_all('Energy Point Log', + group_by = 'user', + filters = {'type' : ['!=', 'Review']}, + fields = ['user', 'rank() over (order by sum(points) desc) as rank'], + as_list = True) + + return { + 'monthly_rank': [r for r in monthly_rank if r[0] == user], + 'all_time_rank': [r for r in all_time_rank if r[0] == user] + } + + +@frappe.whitelist() +def update_profile_info(profile_info): + profile_info = frappe.parse_json(profile_info) + keys = ['location', 'interest', 'user_image', 'bio'] + + for key in keys: + if key not in profile_info: + profile_info[key] = None + + user = frappe.get_doc('User', frappe.session.user) + user.update(profile_info) + user.save() + return user + +@frappe.whitelist() +def get_energy_points_list(start, limit, user): + return frappe.db.get_list('Energy Point Log', + filters = {'user': user, 'type': ['!=', 'Review']}, + fields = ['name','user', 'points', 'reference_doctype', 'reference_name', 'reason', + 'type', 'seen', 'rule', 'owner', 'creation', 'revert_of'], + start = start, + limit = limit, + order_by = 'creation desc') diff --git a/frappe/desk/page/user_profile/user_profile_sidebar.html b/frappe/desk/page/user_profile/user_profile_sidebar.html new file mode 100644 index 0000000000..02fb214d6e --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile_sidebar.html @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.js b/frappe/integrations/doctype/google_contacts/google_contacts.js index 911c16ea91..2d5c5c34e5 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.js +++ b/frappe/integrations/doctype/google_contacts/google_contacts.js @@ -3,7 +3,9 @@ frappe.ui.form.on('Google Contacts', { refresh: function(frm) { - frm.set_value("user", frappe.session.user); + if (!frm.doc.enable) { + frm.dashboard.set_headline(__("To use Google Contacts, enable {0}."), [`${__('Google Settings')}`]); + } frappe.realtime.on('import_google_contacts', (data) => { if (data.progress) { diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.json b/frappe/integrations/doctype/google_contacts/google_contacts.json index 9bd14d722f..42fb9e68c8 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.json +++ b/frappe/integrations/doctype/google_contacts/google_contacts.json @@ -1,5 +1,5 @@ { - "autoname": "format:GC-{user}", + "autoname": "format:GC-{email_id}", "creation": "2019-06-14 00:09:39.441961", "doctype": "DocType", "engine": "InnoDB", @@ -9,7 +9,6 @@ "email_id", "authorize_google_contacts_access", "cb_00", - "user", "last_sync_on", "authorization_code", "refresh_token" @@ -21,13 +20,6 @@ "fieldtype": "Check", "label": "Enable" }, - { - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User", - "read_only": 1 - }, { "fieldname": "authorization_code", "fieldtype": "Password", @@ -72,7 +64,7 @@ "label": "Authorize Google Contacts Access" } ], - "modified": "2019-06-19 15:26:24.494101", + "modified": "2019-08-23 13:50:52.789503", "modified_by": "Administrator", "module": "Integrations", "name": "Google Contacts", diff --git a/frappe/integrations/doctype/google_settings/google_settings.js b/frappe/integrations/doctype/google_settings/google_settings.js index 0d2ede9b9b..1658631dc9 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.js +++ b/frappe/integrations/doctype/google_settings/google_settings.js @@ -2,6 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Google Settings', { - // refresh: function(frm) { - // } + refresh: function(frm) { + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')`])); + } }); diff --git a/frappe/public/build.json b/frappe/public/build.json index 720b2fb2e3..62fbced608 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -10,7 +10,6 @@ "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" ], diff --git a/frappe/public/js/frappe/form/controls/barcode.js b/frappe/public/js/frappe/form/controls/barcode.js index 1e2f3e367a..1cd1411b49 100644 --- a/frappe/public/js/frappe/form/controls/barcode.js +++ b/frappe/public/js/frappe/form/controls/barcode.js @@ -17,10 +17,15 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({ set_formatted_input(value) { // Set values to display - const svg = value; + let svg = value; const barcode_value = $(svg).attr('data-barcode-value'); - this.$input.val(barcode_value); + if(!barcode_value) { + svg = this.get_barcode_html(value); + this.doc[this.df.fieldname] = svg; + } + + this.$input.val(barcode_value || value); this.barcode_area.html(svg); }, diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 42c356c358..fef7f954bc 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -199,13 +199,21 @@ frappe.views.BaseList = class BaseList { } toggle_side_bar() { - this.list_sidebar.parent.toggleClass('hide'); - this.page.current_view.find('.layout-main-section-wrapper').toggleClass('col-md-10 col-md-12'); + let show_sidebar = JSON.parse(localStorage.show_sidebar || 'true'); + show_sidebar = !show_sidebar; + localStorage.show_sidebar = show_sidebar; + this.show_or_hide_sidebar(); + } + + show_or_hide_sidebar() { + let show_sidebar = JSON.parse(localStorage.show_sidebar || 'true'); + $(document.body).toggleClass('no-sidebar', !show_sidebar); } setup_main_section() { return frappe.run_serially([ this.setup_list_wrapper, + this.show_or_hide_sidebar, this.setup_filter_area, this.setup_sort_selector, this.setup_result_area, diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js index 445c4721fc..e2db471b51 100644 --- a/frappe/public/js/frappe/list/list_sidebar_group_by.js +++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js @@ -7,7 +7,7 @@ frappe.views.ListGroupBy = class ListGroupBy { this.make_wrapper(); this.user_settings = frappe.get_user_settings(this.doctype); - this.group_by_fields = ['assigned_to']; + this.group_by_fields = ['assigned_to', 'owner']; if(this.user_settings.group_by_fields) { this.group_by_fields = this.group_by_fields.concat(this.user_settings.group_by_fields); } @@ -24,7 +24,7 @@ frappe.views.ListGroupBy = class ListGroupBy { }); d.set_primary_action("Save", ({ group_by_fields }) => { frappe.model.user_settings.save(this.doctype, 'group_by_fields', group_by_fields || null); - this.group_by_fields = group_by_fields ? ['assigned_to', ...group_by_fields] : ['assigned_to']; + this.group_by_fields = group_by_fields ? ['assigned_to', 'owner', ...group_by_fields] : ['assigned_to', 'owner']; this.render_group_by_items(); d.hide(); }); @@ -53,9 +53,14 @@ frappe.views.ListGroupBy = class ListGroupBy { render_group_by_items() { let get_item_html = (fieldname) => { - let label = fieldname === 'assigned_to' - ? __('Assigned To') - : frappe.meta.get_label(this.doctype, fieldname); + let label; + if (fieldname === 'assigned_to') { + label = __('Assigned To'); + } else if (fieldname === 'owner') { + label = __('Created By'); + } else { + label = frappe.meta.get_label(this.doctype, fieldname); + } return `
  • ${new_item_date_range}
  • `); + $date_range.insertAfter(this.$dropdown_list.find('.points-updates-header')); + $(new_item_html).insertAfter($date_range); + } else { + $(new_item_html).insertAfter(this.$dropdown_list.find('.points-date-range').eq(0)); + } + } + check_seen() { let unseen_logs = this.dropdown_items.filter(item => item.seen === 0); frappe.call('frappe.social.doctype.energy_point_log.energy_point_log.set_notification_as_seen', {point_logs: unseen_logs}); } - get_energy_points_date_range(date) { + get_date_range_title(date) { let current_date = frappe.datetime.now_date(); let prev_week = frappe.datetime.add_days(current_date, -7); let prev_month = frappe.datetime.add_months(frappe.datetime.now_date(), -1); if (date >= current_date) { - return 'Today'; + return __('Today'); } else if (date > prev_week) { - return 'Last 7 days'; + return __('Last 7 days'); } else if (date > prev_month) { - return 'Last 30 days'; + return __('Last 30 days'); } else { - return 'Older'; + return __('Older'); } } @@ -96,13 +109,13 @@ frappe.ui.EnergyPointsNotifications = class { let view_full_log_html = ''; if (this.dropdown_items.length) { - let date_range= this.get_energy_points_date_range(this.dropdown_items[0].creation); - body_html += `
  • ${__(date_range)}
  • `; + let date_range = this.get_date_range_title(this.dropdown_items[0].creation); + body_html += `
  • ${date_range}
  • `; this.dropdown_items.forEach(field => { - let current_field_date_range = this.get_energy_points_date_range(field.creation); + let current_field_date_range = this.get_date_range_title(field.creation); if (date_range !== current_field_date_range) { - body_html+=`
  • ${__(current_field_date_range)}
  • `; - date_range=current_field_date_range; + body_html += `
  • ${current_field_date_range}
  • `; + date_range = current_field_date_range; } let item_html = this.get_dropdown_item_html(field); if (item_html) body_html += item_html; diff --git a/frappe/public/js/frappe/ui/toolbar/navbar.html b/frappe/public/js/frappe/ui/toolbar/navbar.html index 42d854280f..035d2ff679 100644 --- a/frappe/public/js/frappe/ui/toolbar/navbar.html +++ b/frappe/public/js/frappe/ui/toolbar/navbar.html @@ -24,7 +24,7 @@ {%= __("Settings") %}