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 @@
+
+
+
+
+
{%=__("Recent Activity") %}
+
+
+
+
+
+
+
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 `
diff --git a/frappe/public/js/frappe/ui/toolbar/energy_points_notifications.js b/frappe/public/js/frappe/ui/toolbar/energy_points_notifications.js
index 2b9f97932f..5b12f52322 100644
--- a/frappe/public/js/frappe/ui/toolbar/energy_points_notifications.js
+++ b/frappe/public/js/frappe/ui/toolbar/energy_points_notifications.js
@@ -46,28 +46,41 @@ frappe.ui.EnergyPointsNotifications = class {
this.$dropdown_list.find('.recent-points-item').last().remove();
this.dropdown_items.pop();
}
- let new_item_html = this.get_dropdown_item_html(new_item);
- $(new_item_html).insertAfter(this.$dropdown_list.find('.points-date-range').eq(0));
+ this.insert_into_dropdown();
});
}
+ insert_into_dropdown() {
+ let new_item = this.dropdown_items[0];
+ let new_item_html = this.get_dropdown_item_html(new_item);
+ let new_item_date_range = this.get_date_range_title(new_item.creation);
+ let current_date_range = this.get_date_range_title(this.dropdown_items[1].creation);
+ if (current_date_range !== new_item_date_range) {
+ let $date_range = $(`
${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") %}