Merge branch 'develop' of https://github.com/frappe/frappe into contacts-ref

This commit is contained in:
Himanshu Warekar 2019-08-26 12:51:26 +05:30
commit 5ca8bbdd36
29 changed files with 847 additions and 87 deletions

View file

@ -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
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)

View file

@ -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,
}

View file

@ -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

View file

@ -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':

View file

@ -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'))

View file

@ -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 = {

View file

@ -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',

View file

@ -2,10 +2,10 @@
<div class="col-xs-3 text-right activity-date"><span class="{%= date_class %}">
{%= date_sep || "" %}</span></div>
<div class="col-xs-9 activity-message"
data-doctype="{%= reference_doctype %}"
data-docname="{%= reference_name %}"
data-link-doctype="{{ link_doctype }}"
data-link-name="{{ link_name }}"
data-doctype="{%= encodeURIComponent(reference_doctype) %}"
data-docname="{%= encodeURIComponent(reference_name) %}"
data-link-doctype="{{ encodeURIComponent(link_doctype) }}"
data-link-name="{{ encodeURIComponent(link_name) }}"
title="{%= by %} / {%= frappe.datetime.str_to_user(creation) %}">
{{ avatar }}
<span class="small">

View file

@ -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;
}
}

View file

@ -0,0 +1,25 @@
<div class="row user-profile">
<div class="col-md-12">
<div class="performance-graphs">
<div class="chart-column-container heatmap-container hidden-xs hidden-sm">
<div class="performance-heatmap">
</div>
</div>
<div class="chart-column-container percentage-chart-container">
<div class="performance-percentage-chart">
</div>
</div>
<div class="chart-column-container line-chart-container">
<div class="performance-line-chart">
</div>
</div>
</div>
<div class="recent-activity">
<p class="recent-activity-title h6 uppercase">{%=__("Recent Activity") %}</p>
<div class="recent-activity-list">
</div>
<div class="show-more-activity"><a class="text-muted">{%=__("Show More Activity") %}</a></div>
</div>
</div>
</div>

View file

@ -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 = `<div class="chart-filter pull-right">
<a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-default btn-xs">
<span class="filter-label">${filter.label}</span>
<span class="caret"></span>
</button>
</a>`;
let options_html;
if (filter.fieldnames) {
options_html = filter.options.map((option, i) =>
`<li><a data-fieldname = "${filter.fieldnames[i]}">${option}</a></li>`).join('');
} else {
options_html = filter.options.map( option => `<li><a>${option}</a></li>`).join('');
}
let dropdown_html = chart_filter_html + `<ul class="dropdown-menu">${options_html}</ul></div>`;
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 = $(__(`<p class="user-energy-points text-muted">${__('Energy Points: ')}<span class="rank">{0}</span></p>
<p class="user-energy-points text-muted">${__('Review Points: ')}<span class="rank">{1}</span></p>
<p class="user-energy-points text-muted">${__('Rank: ')}<span class="rank">{2}</span></p>
<p class="user-energy-points text-muted">${__('Monthly Rank: ')}<span class="rank">{3}</span></p>
`, [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 `<p class="recent-activity-item text-muted"> ${message_html} </p>`;
};
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 = `<span class="text-muted">${__('No More Activity')}</span>`;
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();
}
}

View file

@ -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
}

View file

@ -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')

View file

@ -0,0 +1,23 @@
<div class="user-profile-sidebar">
<div class="user-image-container hidden-xs hidden-sm">
{% if user_image %}
{{user_image}}
{% endif %}
</div>
<div class="profile-details">
{% if user_bio %}
<p class="user-bio">{{user_bio}}</p>
{% endif %}
{% if user_location %}
<p class="user-location text-muted"><span class="interest-icon"><i class="fa fa-map-marker"></i></span>{{user_location}}</p>
{% endif %}
{% if user_interest %}
<p class="user-interests text-muted"><span class="interest-icon"><i class="fa fa-puzzle-piece"></i></span>{{user_interest}}</p>
{% endif %}
</div>
<div class="profile-links">
<p><a class="edit-profile-link">{%=__("Edit Profile") %}</a></p>
<p><a class="user-settings-link">{%=__("User Settings") %}</a></p>
<p><a class="leaderboard-link" href="#social/users">{%=__("Leaderboard") %}</a></p>
</div>
</div>

View file

@ -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}."), [`<a href='#Form/Google Settings'>${__('Google Settings')}</a>`]);
}
frappe.realtime.on('import_google_contacts', (data) => {
if (data.progress) {

View file

@ -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",

View file

@ -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}.", [`<a href='https://erpnext.com/docs/user/manual/en/erpnext_integration/google_settings'>${__('Click here')</a>`]));
}
});

View file

@ -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"
],

View file

@ -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);
},

View file

@ -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,

View file

@ -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 `<li class="group-by-field list-link">
<div class="btn-group">

View file

@ -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 = $(`<li class="points-date-range text-muted">${new_item_date_range}</li>`);
$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 += `<li class="points-date-range text-muted">${__(date_range)}</li>`;
let date_range = this.get_date_range_title(this.dropdown_items[0].creation);
body_html += `<li class="points-date-range text-muted">${date_range}</li>`;
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+=`<li class="points-date-range text-muted">${__(current_field_date_range)}</li>`;
date_range=current_field_date_range;
body_html += `<li class="points-date-range text-muted">${current_field_date_range}</li>`;
date_range = current_field_date_range;
}
let item_html = this.get_dropdown_item_html(field);
if (item_html) body_html += item_html;

View file

@ -24,7 +24,7 @@
{%= __("Settings") %}</span>
<b class="caret hidden-xs hidden-sm"></b></a>
<ul class="dropdown-menu" id="toolbar-user" role="menu">
<li><a href="#social/profile/{%= encodeURIComponent(frappe.session.user) %}">
<li><a href="#user-profile">
{%= __("My Profile") %}</a></li>
<li><a href="#Form/User/{%= encodeURIComponent(frappe.session.user) %}">
{%= __("My Settings") %}</a></li>

View file

@ -5,7 +5,7 @@ frappe.provide('frappe.energy_points');
Object.assign(frappe.energy_points, {
get_points(points) {
return `<span class='bold' style="color: ${points >= 0 ? '#45A163': '#e42121'}">
return `<span class="bold" style="color: ${points >= 0 ? '#45A163': '#e42121'}">
${points > 0 ? '+': ''}${points}
</span>`;
},
@ -24,7 +24,7 @@ Object.assign(frappe.energy_points, {
const route = frappe.utils.get_form_link(log.reference_doctype, log.reference_name);
const formatted_log = `<span>
${this.get_points(log.points)}&nbsp;
<a href="${route}">${this.get_history_log_message(log)}</a>
<a href="${route}" class="text-muted">${this.get_history_log_message(log)}</a>
${log.reason ? separator + log.reason: ''}
${separator + frappe.datetime.comment_when(log.creation)}
</span>`;

View file

@ -1081,6 +1081,18 @@ body.full-width {
}
}
body.no-sidebar {
@media (min-width: @screen-md) {
.layout-side-section {
display: none;
}
.layout-main-section-wrapper {
width: 100% !important;
}
}
}
// utilities
.whitespace-nowrap {

View file

@ -399,7 +399,7 @@ body[data-route^="Module"] .main-menu {
.group-by-dropdown, .list-stats-dropdown {
max-height: 300px;
overflow-y: auto;
max-width: 200px;
max-width: 250px;
}
.dropdown-search {
padding: 8px;

View file

@ -1,4 +1,5 @@
@import "variables";
@import "frappe/public/css/font-awesome";
@import "~bootstrap/scss/bootstrap";
@import "multilevel-dropdown";
@import "website-image";

View file

@ -221,7 +221,7 @@ def get_context(context):
"payer_name": frappe.utils.get_fullname(frappe.session.user),
"order_id": doc.name,
"currency": self.currency,
"redirect_to": frappe.utils.get_url(self.route)
"redirect_to": frappe.utils.get_url(self.success_url or self.route)
}
# Redirect the user to this url