@@ -49,7 +49,6 @@ | |||||
"moment": true, | "moment": true, | ||||
"hljs": true, | "hljs": true, | ||||
"Awesomplete": true, | "Awesomplete": true, | ||||
"CalHeatMap": true, | |||||
"Sortable": true, | "Sortable": true, | ||||
"Showdown": true, | "Showdown": true, | ||||
"Taggle": true, | "Taggle": true, | ||||
@@ -0,0 +1,46 @@ | |||||
# Contributor Covenant Code of Conduct | |||||
## Our Pledge | |||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. | |||||
## Our Standards | |||||
Examples of behavior that contributes to creating a positive environment include: | |||||
* Using welcoming and inclusive language | |||||
* Being respectful of differing viewpoints and experiences | |||||
* Gracefully accepting constructive criticism | |||||
* Focusing on what is best for the community | |||||
* Showing empathy towards other community members | |||||
Examples of unacceptable behavior by participants include: | |||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances | |||||
* Trolling, insulting/derogatory comments, and personal or political attacks | |||||
* Public or private harassment | |||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission | |||||
* Other conduct which could reasonably be considered inappropriate in a professional setting | |||||
## Our Responsibilities | |||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. | |||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. | |||||
## Scope | |||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. | |||||
## Enforcement | |||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@frappe.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. | |||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. | |||||
## Attribution | |||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] | |||||
[homepage]: http://contributor-covenant.org | |||||
[version]: http://contributor-covenant.org/version/1/4/ |
@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template | from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template | ||||
__version__ = '8.10.4' | |||||
__version__ = '8.10.5' | |||||
__title__ = "Frappe Framework" | __title__ = "Frappe Framework" | ||||
local = Local() | local = Local() | ||||
@@ -13,9 +13,12 @@ | |||||
frappe.ui.form.on('DocType', { | frappe.ui.form.on('DocType', { | ||||
refresh: function(frm) { | refresh: function(frm) { | ||||
if(frm.is_new() && (frappe.session.user !== "Administrator" || !frappe.boot.developer_mode)) { | |||||
frm.set_value("custom", 1); | |||||
if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) { | |||||
if(frm.is_new()) { | |||||
frm.set_value("custom", 1); | |||||
} | |||||
frm.toggle_enable("custom", 0); | frm.toggle_enable("custom", 0); | ||||
frm.toggle_enable("beta", 0); | |||||
} | } | ||||
if(!frappe.boot.developer_mode && !frm.doc.custom) { | if(!frappe.boot.developer_mode && !frm.doc.custom) { | ||||
@@ -180,30 +180,12 @@ frappe.activity.render_heatmap = function(page) { | |||||
method: "frappe.desk.page.activity.activity.get_heatmap_data", | method: "frappe.desk.page.activity.activity.get_heatmap_data", | ||||
callback: function(r) { | callback: function(r) { | ||||
if(r.message) { | if(r.message) { | ||||
var legend = []; | |||||
var max = Math.max.apply(this, $.map(r.message, function(v) { return v })); | |||||
var legend = [cint(max/5), cint(max*2/5), cint(max*3/5), cint(max*4/5)]; | |||||
var heatmap = new CalHeatMap(); | |||||
heatmap.init({ | |||||
itemSelector: ".heatmap", | |||||
domain: "month", | |||||
subDomain: "day", | |||||
start: moment().subtract(1, 'year').add(1, 'month').toDate(), | |||||
cellSize: 9, | |||||
cellPadding: 2, | |||||
domainGutter: 2, | |||||
range: 12, | |||||
domainLabelFormat: function(date) { | |||||
return moment(date).format("MMM").toUpperCase(); | |||||
}, | |||||
displayLegend: false, | |||||
legend: legend, | |||||
tooltip: true, | |||||
subDomainTitleFormat: { | |||||
empty: "{date}", | |||||
filled: "{count} actions on {date}" | |||||
}, | |||||
subDomainDateFormat: "%d-%b" | |||||
var heatmap = new frappe.ui.HeatMap({ | |||||
parent: $(".heatmap"), | |||||
height: 100, | |||||
start: new Date(moment().subtract(1, 'year').toDate()), | |||||
count_label: "actions", | |||||
discrete_domains: 0 | |||||
}); | }); | ||||
heatmap.update(r.message); | heatmap.update(r.message); | ||||
@@ -152,8 +152,8 @@ def export_query(): | |||||
writer = csv.writer(f) | writer = csv.writer(f) | ||||
for r in data: | for r in data: | ||||
# encode only unicode type strings and not int, floats etc. | # encode only unicode type strings and not int, floats etc. | ||||
writer.writerow(map(lambda v: isinstance(v, string_types) and | |||||
handle_html(frappe.as_unicode(v)) or v, r)) | |||||
writer.writerow([handle_html(frappe.as_unicode(v)).encode('utf-8') \ | |||||
if isinstance(v, string_types) else v for v in r]) | |||||
f.seek(0) | f.seek(0) | ||||
frappe.response['result'] = text_type(f.read(), 'utf-8') | frappe.response['result'] = text_type(f.read(), 'utf-8') | ||||
@@ -244,6 +244,14 @@ def delete_dynamic_links(doctype, name): | |||||
frappe.db.sql('''delete from `tabEmail Unsubscribe` | frappe.db.sql('''delete from `tabEmail Unsubscribe` | ||||
where reference_doctype=%s and reference_name=%s''', (doctype, name)) | where reference_doctype=%s and reference_name=%s''', (doctype, name)) | ||||
# delete shares | |||||
delete_doc("DocShare", frappe.db.sql_list("""select name from `tabDocShare` | |||||
where share_doctype=%s and share_name=%s""", (doctype, name)), | |||||
ignore_on_trash=True, force=True) | |||||
# delete versions | |||||
frappe.db.sql('delete from tabVersion where ref_doctype=%s and docname=%s', (doctype, name)) | |||||
# delete comments | # delete comments | ||||
frappe.db.sql("""delete from `tabCommunication` | frappe.db.sql("""delete from `tabCommunication` | ||||
where | where | ||||
@@ -268,14 +276,6 @@ def delete_dynamic_links(doctype, name): | |||||
set timeline_doctype=null, timeline_name=null | set timeline_doctype=null, timeline_name=null | ||||
where timeline_doctype=%s and timeline_name=%s""", (doctype, name)) | where timeline_doctype=%s and timeline_name=%s""", (doctype, name)) | ||||
# delete shares | |||||
delete_doc("DocShare", frappe.db.sql_list("""select name from `tabDocShare` | |||||
where share_doctype=%s and share_name=%s""", (doctype, name)), | |||||
ignore_on_trash=True, force=True) | |||||
# delete versions | |||||
frappe.db.sql('delete from tabVersion where ref_doctype=%s and docname=%s', (doctype, name)) | |||||
def insert_feed(doc): | def insert_feed(doc): | ||||
from frappe.utils import get_fullname | from frappe.utils import get_fullname | ||||
@@ -192,3 +192,4 @@ frappe.patches.v8_5.delete_email_group_member_with_invalid_emails | |||||
frappe.patches.v8_x.update_user_permission | frappe.patches.v8_x.update_user_permission | ||||
frappe.patches.v8_5.patch_event_colors | frappe.patches.v8_5.patch_event_colors | ||||
frappe.patches.v8_7.update_email_queue_status | frappe.patches.v8_7.update_email_queue_status | ||||
frappe.patches.v8_10.delete_static_web_page_from_global_search |
@@ -0,0 +1,5 @@ | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
def execute(): | |||||
frappe.db.sql("""delete from `__global_search` where doctype='Static Web Page'"""); |
@@ -97,7 +97,6 @@ | |||||
"public/css/bootstrap.css", | "public/css/bootstrap.css", | ||||
"public/css/font-awesome.css", | "public/css/font-awesome.css", | ||||
"public/css/octicons/octicons.css", | "public/css/octicons/octicons.css", | ||||
"public/css/cal-heatmap.css", | |||||
"public/css/c3.min.css", | "public/css/c3.min.css", | ||||
"public/css/desk.css", | "public/css/desk.css", | ||||
"public/css/indicator.css", | "public/css/indicator.css", | ||||
@@ -231,7 +230,6 @@ | |||||
], | ], | ||||
"js/d3.min.js": [ | "js/d3.min.js": [ | ||||
"public/js/lib/d3.min.js", | "public/js/lib/d3.min.js", | ||||
"public/js/lib/cal-heatmap.js", | |||||
"public/js/lib/c3.min.js" | "public/js/lib/c3.min.js" | ||||
], | ], | ||||
"css/module.min.css": [ | "css/module.min.css": [ | ||||
@@ -1,140 +0,0 @@ | |||||
/* Cal-HeatMap CSS */ | |||||
.cal-heatmap-container { | |||||
display: block; | |||||
} | |||||
.cal-heatmap-container .graph-label | |||||
{ | |||||
fill: #999; | |||||
font-size: 10px | |||||
} | |||||
.cal-heatmap-container .graph, .cal-heatmap-container .graph-legend rect { | |||||
shape-rendering: crispedges | |||||
} | |||||
.cal-heatmap-container .graph-rect | |||||
{ | |||||
fill: #ededed | |||||
} | |||||
.cal-heatmap-container .graph-subdomain-group rect:hover | |||||
{ | |||||
stroke: #000; | |||||
stroke-width: 1px | |||||
} | |||||
.cal-heatmap-container .subdomain-text { | |||||
font-size: 8px; | |||||
fill: #999; | |||||
pointer-events: none | |||||
} | |||||
.cal-heatmap-container .hover_cursor:hover { | |||||
cursor: pointer | |||||
} | |||||
.cal-heatmap-container .qi { | |||||
background-color: #999; | |||||
fill: #999 | |||||
} | |||||
/* | |||||
Remove comment to apply this style to date with value equal to 0 | |||||
.q0 | |||||
{ | |||||
background-color: #fff; | |||||
fill: #fff; | |||||
stroke: #ededed | |||||
} | |||||
*/ | |||||
.cal-heatmap-container .q1 | |||||
{ | |||||
background-color: #dae289; | |||||
fill: #dae289 | |||||
} | |||||
.cal-heatmap-container .q2 | |||||
{ | |||||
background-color: #cedb9c; | |||||
fill: #9cc069 | |||||
} | |||||
.cal-heatmap-container .q3 | |||||
{ | |||||
background-color: #b5cf6b; | |||||
fill: #669d45 | |||||
} | |||||
.cal-heatmap-container .q4 | |||||
{ | |||||
background-color: #637939; | |||||
fill: #637939 | |||||
} | |||||
.cal-heatmap-container .q5 | |||||
{ | |||||
background-color: #3b6427; | |||||
fill: #3b6427 | |||||
} | |||||
.cal-heatmap-container rect.highlight | |||||
{ | |||||
stroke:#444; | |||||
stroke-width:1 | |||||
} | |||||
.cal-heatmap-container text.highlight | |||||
{ | |||||
fill: #444 | |||||
} | |||||
.cal-heatmap-container rect.now | |||||
{ | |||||
stroke: red | |||||
} | |||||
.cal-heatmap-container text.now | |||||
{ | |||||
fill: red; | |||||
font-weight: 800 | |||||
} | |||||
.cal-heatmap-container .domain-background { | |||||
fill: none; | |||||
shape-rendering: crispedges | |||||
} | |||||
.ch-tooltip { | |||||
padding: 10px; | |||||
background: #222; | |||||
color: #bbb; | |||||
font-size: 12px; | |||||
line-height: 1.4; | |||||
width: 140px; | |||||
position: absolute; | |||||
z-index: 99999; | |||||
text-align: center; | |||||
border-radius: 2px; | |||||
box-shadow: 2px 2px 2px rgba(0,0,0,0.2); | |||||
display: none; | |||||
box-sizing: border-box; | |||||
} | |||||
.ch-tooltip::after{ | |||||
position: absolute; | |||||
width: 0; | |||||
height: 0; | |||||
border-color: transparent; | |||||
border-style: solid; | |||||
content: ""; | |||||
padding: 0; | |||||
display: block; | |||||
bottom: -6px; | |||||
left: 50%; | |||||
margin-left: -6px; | |||||
border-width: 6px 6px 0; | |||||
border-top-color: #222; | |||||
} |
@@ -98,6 +98,10 @@ | |||||
.form-dashboard-section:last-child { | .form-dashboard-section:last-child { | ||||
border-bottom: none; | border-bottom: none; | ||||
} | } | ||||
.form-heatmap .heatmap { | |||||
display: flex; | |||||
justify-content: center; | |||||
} | |||||
.form-heatmap .heatmap-message { | .form-heatmap .heatmap-message { | ||||
margin-top: 10px; | margin-top: 10px; | ||||
} | } | ||||
@@ -2,9 +2,10 @@ | |||||
.graph-container .graph-focus-margin { | .graph-container .graph-focus-margin { | ||||
margin: 0px 5%; | margin: 0px 5%; | ||||
} | } | ||||
.graph-container .graph-graphics { | |||||
.graph-container .graphics { | |||||
margin-top: 10px; | margin-top: 10px; | ||||
padding: 10px 0px; | |||||
padding-top: 10px; | |||||
padding-bottom: 10px; | |||||
position: relative; | position: relative; | ||||
} | } | ||||
.graph-container .graph-stats-group { | .graph-container .graph-stats-group { | ||||
@@ -34,31 +35,28 @@ | |||||
.graph-container .graph-stats-container .graph-data .stats-value { | .graph-container .graph-stats-container .graph-data .stats-value { | ||||
color: #98d85b; | color: #98d85b; | ||||
} | } | ||||
.graph-container .bar-graph .axis, | |||||
.graph-container .line-graph .axis { | |||||
.graph-container .axis, | |||||
.graph-container .chart-label { | |||||
font-size: 10px; | font-size: 10px; | ||||
fill: #6a737d; | |||||
fill: #959ba1; | |||||
} | } | ||||
.graph-container .bar-graph .axis line, | |||||
.graph-container .line-graph .axis line { | |||||
.graph-container .axis line, | |||||
.graph-container .chart-label line { | |||||
stroke: rgba(27, 31, 35, 0.1); | stroke: rgba(27, 31, 35, 0.1); | ||||
} | } | ||||
.graph-container .percentage-graph { | |||||
margin-top: 35px; | |||||
} | |||||
.graph-container .percentage-graph .progress { | .graph-container .percentage-graph .progress { | ||||
margin-bottom: 0px; | margin-bottom: 0px; | ||||
} | } | ||||
.graph-container .graph-data-points circle { | |||||
.graph-container .data-points circle { | |||||
stroke: #fff; | stroke: #fff; | ||||
stroke-width: 2; | stroke-width: 2; | ||||
} | } | ||||
.graph-container .graph-data-points path { | |||||
.graph-container .data-points path { | |||||
fill: none; | fill: none; | ||||
stroke-opacity: 1; | stroke-opacity: 1; | ||||
stroke-width: 2px; | stroke-width: 2px; | ||||
} | } | ||||
.graph-container line.graph-dashed { | |||||
.graph-container line.dashed { | |||||
stroke-dasharray: 5,3; | stroke-dasharray: 5,3; | ||||
} | } | ||||
.graph-container .tick.x-axis-label { | .graph-container .tick.x-axis-label { | ||||
@@ -73,7 +71,7 @@ | |||||
.graph-container .tick .x-value-text { | .graph-container .tick .x-value-text { | ||||
text-anchor: middle; | text-anchor: middle; | ||||
} | } | ||||
.graph-container .graph-svg-tip { | |||||
.graph-svg-tip { | |||||
position: absolute; | position: absolute; | ||||
z-index: 99999; | z-index: 99999; | ||||
padding: 10px; | padding: 10px; | ||||
@@ -83,12 +81,12 @@ | |||||
background: rgba(0, 0, 0, 0.8); | background: rgba(0, 0, 0, 0.8); | ||||
border-radius: 3px; | border-radius: 3px; | ||||
} | } | ||||
.graph-container .graph-svg-tip.comparison { | |||||
.graph-svg-tip.comparison { | |||||
padding: 0; | padding: 0; | ||||
text-align: left; | text-align: left; | ||||
pointer-events: none; | pointer-events: none; | ||||
} | } | ||||
.graph-container .graph-svg-tip.comparison .title { | |||||
.graph-svg-tip.comparison .title { | |||||
display: block; | display: block; | ||||
padding: 10px; | padding: 10px; | ||||
margin: 0; | margin: 0; | ||||
@@ -96,28 +94,28 @@ | |||||
line-height: 1; | line-height: 1; | ||||
pointer-events: none; | pointer-events: none; | ||||
} | } | ||||
.graph-container .graph-svg-tip.comparison ul { | |||||
.graph-svg-tip.comparison ul { | |||||
margin: 0; | margin: 0; | ||||
white-space: nowrap; | white-space: nowrap; | ||||
list-style: none; | list-style: none; | ||||
} | } | ||||
.graph-container .graph-svg-tip.comparison li { | |||||
.graph-svg-tip.comparison li { | |||||
display: inline-block; | display: inline-block; | ||||
padding: 5px 10px; | padding: 5px 10px; | ||||
} | } | ||||
.graph-container .graph-svg-tip ul, | |||||
.graph-container .graph-svg-tip ol { | |||||
.graph-svg-tip ul, | |||||
.graph-svg-tip ol { | |||||
padding-left: 0; | padding-left: 0; | ||||
display: flex; | display: flex; | ||||
} | } | ||||
.graph-container .graph-svg-tip ul.data-point-list li { | |||||
.graph-svg-tip ul.data-point-list li { | |||||
min-width: 90px; | min-width: 90px; | ||||
flex: 1; | flex: 1; | ||||
} | } | ||||
.graph-container .graph-svg-tip strong { | |||||
.graph-svg-tip strong { | |||||
color: #dfe2e5; | color: #dfe2e5; | ||||
} | } | ||||
.graph-container .graph-svg-tip::after { | |||||
.graph-svg-tip .svg-pointer { | |||||
position: absolute; | position: absolute; | ||||
bottom: -10px; | bottom: -10px; | ||||
left: 50%; | left: 50%; | ||||
@@ -128,147 +126,147 @@ | |||||
border: 5px solid transparent; | border: 5px solid transparent; | ||||
border-top-color: rgba(0, 0, 0, 0.8); | border-top-color: rgba(0, 0, 0, 0.8); | ||||
} | } | ||||
.graph-container .stroke.grey { | |||||
.stroke.grey { | |||||
stroke: #F0F4F7; | stroke: #F0F4F7; | ||||
} | } | ||||
.graph-container .stroke.blue { | |||||
.stroke.blue { | |||||
stroke: #5e64ff; | stroke: #5e64ff; | ||||
} | } | ||||
.graph-container .stroke.red { | |||||
.stroke.red { | |||||
stroke: #ff5858; | stroke: #ff5858; | ||||
} | } | ||||
.graph-container .stroke.light-green { | |||||
.stroke.light-green { | |||||
stroke: #98d85b; | stroke: #98d85b; | ||||
} | } | ||||
.graph-container .stroke.green { | |||||
.stroke.green { | |||||
stroke: #28a745; | stroke: #28a745; | ||||
} | } | ||||
.graph-container .stroke.orange { | |||||
.stroke.orange { | |||||
stroke: #ffa00a; | stroke: #ffa00a; | ||||
} | } | ||||
.graph-container .stroke.purple { | |||||
.stroke.purple { | |||||
stroke: #743ee2; | stroke: #743ee2; | ||||
} | } | ||||
.graph-container .stroke.darkgrey { | |||||
.stroke.darkgrey { | |||||
stroke: #b8c2cc; | stroke: #b8c2cc; | ||||
} | } | ||||
.graph-container .stroke.black { | |||||
.stroke.black { | |||||
stroke: #36414C; | stroke: #36414C; | ||||
} | } | ||||
.graph-container .stroke.yellow { | |||||
.stroke.yellow { | |||||
stroke: #FEEF72; | stroke: #FEEF72; | ||||
} | } | ||||
.graph-container .stroke.light-blue { | |||||
.stroke.light-blue { | |||||
stroke: #7CD6FD; | stroke: #7CD6FD; | ||||
} | } | ||||
.graph-container .stroke.lightblue { | |||||
.stroke.lightblue { | |||||
stroke: #7CD6FD; | stroke: #7CD6FD; | ||||
} | } | ||||
.graph-container .fill.grey { | |||||
.fill.grey { | |||||
fill: #F0F4F7; | fill: #F0F4F7; | ||||
} | } | ||||
.graph-container .fill.blue { | |||||
.fill.blue { | |||||
fill: #5e64ff; | fill: #5e64ff; | ||||
} | } | ||||
.graph-container .fill.red { | |||||
.fill.red { | |||||
fill: #ff5858; | fill: #ff5858; | ||||
} | } | ||||
.graph-container .fill.light-green { | |||||
.fill.light-green { | |||||
fill: #98d85b; | fill: #98d85b; | ||||
} | } | ||||
.graph-container .fill.green { | |||||
.fill.green { | |||||
fill: #28a745; | fill: #28a745; | ||||
} | } | ||||
.graph-container .fill.orange { | |||||
.fill.orange { | |||||
fill: #ffa00a; | fill: #ffa00a; | ||||
} | } | ||||
.graph-container .fill.purple { | |||||
.fill.purple { | |||||
fill: #743ee2; | fill: #743ee2; | ||||
} | } | ||||
.graph-container .fill.darkgrey { | |||||
.fill.darkgrey { | |||||
fill: #b8c2cc; | fill: #b8c2cc; | ||||
} | } | ||||
.graph-container .fill.black { | |||||
.fill.black { | |||||
fill: #36414C; | fill: #36414C; | ||||
} | } | ||||
.graph-container .fill.yellow { | |||||
.fill.yellow { | |||||
fill: #FEEF72; | fill: #FEEF72; | ||||
} | } | ||||
.graph-container .fill.light-blue { | |||||
.fill.light-blue { | |||||
fill: #7CD6FD; | fill: #7CD6FD; | ||||
} | } | ||||
.graph-container .fill.lightblue { | |||||
.fill.lightblue { | |||||
fill: #7CD6FD; | fill: #7CD6FD; | ||||
} | } | ||||
.graph-container .background.grey { | |||||
.background.grey { | |||||
background: #F0F4F7; | background: #F0F4F7; | ||||
} | } | ||||
.graph-container .background.blue { | |||||
.background.blue { | |||||
background: #5e64ff; | background: #5e64ff; | ||||
} | } | ||||
.graph-container .background.red { | |||||
.background.red { | |||||
background: #ff5858; | background: #ff5858; | ||||
} | } | ||||
.graph-container .background.light-green { | |||||
.background.light-green { | |||||
background: #98d85b; | background: #98d85b; | ||||
} | } | ||||
.graph-container .background.green { | |||||
.background.green { | |||||
background: #28a745; | background: #28a745; | ||||
} | } | ||||
.graph-container .background.orange { | |||||
.background.orange { | |||||
background: #ffa00a; | background: #ffa00a; | ||||
} | } | ||||
.graph-container .background.purple { | |||||
.background.purple { | |||||
background: #743ee2; | background: #743ee2; | ||||
} | } | ||||
.graph-container .background.darkgrey { | |||||
.background.darkgrey { | |||||
background: #b8c2cc; | background: #b8c2cc; | ||||
} | } | ||||
.graph-container .background.black { | |||||
.background.black { | |||||
background: #36414C; | background: #36414C; | ||||
} | } | ||||
.graph-container .background.yellow { | |||||
.background.yellow { | |||||
background: #FEEF72; | background: #FEEF72; | ||||
} | } | ||||
.graph-container .background.light-blue { | |||||
.background.light-blue { | |||||
background: #7CD6FD; | background: #7CD6FD; | ||||
} | } | ||||
.graph-container .background.lightblue { | |||||
.background.lightblue { | |||||
background: #7CD6FD; | background: #7CD6FD; | ||||
} | } | ||||
.graph-container .border-top.grey { | |||||
.border-top.grey { | |||||
border-top: 3px solid #F0F4F7; | border-top: 3px solid #F0F4F7; | ||||
} | } | ||||
.graph-container .border-top.blue { | |||||
.border-top.blue { | |||||
border-top: 3px solid #5e64ff; | border-top: 3px solid #5e64ff; | ||||
} | } | ||||
.graph-container .border-top.red { | |||||
.border-top.red { | |||||
border-top: 3px solid #ff5858; | border-top: 3px solid #ff5858; | ||||
} | } | ||||
.graph-container .border-top.light-green { | |||||
.border-top.light-green { | |||||
border-top: 3px solid #98d85b; | border-top: 3px solid #98d85b; | ||||
} | } | ||||
.graph-container .border-top.green { | |||||
.border-top.green { | |||||
border-top: 3px solid #28a745; | border-top: 3px solid #28a745; | ||||
} | } | ||||
.graph-container .border-top.orange { | |||||
.border-top.orange { | |||||
border-top: 3px solid #ffa00a; | border-top: 3px solid #ffa00a; | ||||
} | } | ||||
.graph-container .border-top.purple { | |||||
.border-top.purple { | |||||
border-top: 3px solid #743ee2; | border-top: 3px solid #743ee2; | ||||
} | } | ||||
.graph-container .border-top.darkgrey { | |||||
.border-top.darkgrey { | |||||
border-top: 3px solid #b8c2cc; | border-top: 3px solid #b8c2cc; | ||||
} | } | ||||
.graph-container .border-top.black { | |||||
.border-top.black { | |||||
border-top: 3px solid #36414C; | border-top: 3px solid #36414C; | ||||
} | } | ||||
.graph-container .border-top.yellow { | |||||
.border-top.yellow { | |||||
border-top: 3px solid #FEEF72; | border-top: 3px solid #FEEF72; | ||||
} | } | ||||
.graph-container .border-top.light-blue { | |||||
.border-top.light-blue { | |||||
border-top: 3px solid #7CD6FD; | border-top: 3px solid #7CD6FD; | ||||
} | } | ||||
.graph-container .border-top.lightblue { | |||||
.border-top.lightblue { | |||||
border-top: 3px solid #7CD6FD; | border-top: 3px solid #7CD6FD; | ||||
} | } |
@@ -46,7 +46,8 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
// this function is executed only once | // this function is executed only once | ||||
$(".note-editable[contenteditable='true']").one('focus', function() { | $(".note-editable[contenteditable='true']").one('focus', function() { | ||||
var $this = $(this); | var $this = $(this); | ||||
$this.html($this.html() + '<br>'); | |||||
if(!$this.html()) | |||||
$this.html($this.html() + '<br>'); | |||||
}); | }); | ||||
}, | }, | ||||
onChange: function(value) { | onChange: function(value) { | ||||
@@ -334,22 +334,12 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
// heatmap | // heatmap | ||||
render_heatmap: function() { | render_heatmap: function() { | ||||
if(!this.heatmap) { | if(!this.heatmap) { | ||||
this.heatmap = new CalHeatMap(); | |||||
this.heatmap.init({ | |||||
itemSelector: "#heatmap-" + frappe.model.scrub(this.frm.doctype), | |||||
domain: "month", | |||||
subDomain: "day", | |||||
start: moment().subtract(1, 'year').add(1, 'month').toDate(), | |||||
cellSize: 9, | |||||
cellPadding: 2, | |||||
domainGutter: 2, | |||||
range: 12, | |||||
domainLabelFormat: function(date) { | |||||
return moment(date).format("MMM").toUpperCase(); | |||||
}, | |||||
displayLegend: false, | |||||
legend: [5, 10, 15, 20] | |||||
// subDomainTextFormat: "%d", | |||||
this.heatmap = new frappe.ui.HeatMap({ | |||||
parent: this.heatmap_area.find("#heatmap-" + frappe.model.scrub(this.frm.doctype)), | |||||
height: 100, | |||||
start: new Date(moment().subtract(1, 'year').toDate()), | |||||
count_label: "items", | |||||
discrete_domains: 0 | |||||
}); | }); | ||||
// center the heatmap | // center the heatmap | ||||
@@ -388,16 +378,14 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
return indicator; | return indicator; | ||||
}, | }, | ||||
//graphs | |||||
// graphs | |||||
setup_graph: function() { | setup_graph: function() { | ||||
var me = this; | var me = this; | ||||
var method = this.data.graph_method; | var method = this.data.graph_method; | ||||
var args = { | var args = { | ||||
doctype: this.frm.doctype, | doctype: this.frm.doctype, | ||||
docname: this.frm.doc.name, | docname: this.frm.doc.name, | ||||
}; | }; | ||||
$.extend(args, this.data.graph_method_args); | $.extend(args, this.data.graph_method_args); | ||||
frappe.call({ | frappe.call({ | ||||
@@ -421,29 +409,9 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
mode: 'line', | mode: 'line', | ||||
height: 140 | height: 140 | ||||
}); | }); | ||||
new frappe.ui.Graph(args); | new frappe.ui.Graph(args); | ||||
}, | }, | ||||
setup_chart: function(opts) { | |||||
var me = this; | |||||
this.graph_area.removeClass('hidden'); | |||||
$.extend(opts, { | |||||
wrapper: me.graph_area, | |||||
padding: { | |||||
right: 30, | |||||
bottom: 30 | |||||
} | |||||
}); | |||||
this.chart = new frappe.ui.Chart(opts); | |||||
if(this.chart) { | |||||
this.show(); | |||||
this.chart.set_chart_size(me.wrapper.width() - 60); | |||||
} | |||||
}, | |||||
show: function() { | show: function() { | ||||
this.section.removeClass('hidden'); | this.section.removeClass('hidden'); | ||||
} | } | ||||
@@ -2,7 +2,7 @@ | |||||
<div class="progress-area hidden form-dashboard-section"> | <div class="progress-area hidden form-dashboard-section"> | ||||
</div> | </div> | ||||
<div class="form-heatmap hidden form-dashboard-section"> | <div class="form-heatmap hidden form-dashboard-section"> | ||||
<div id="heatmap-{{ frappe.model.scrub(frm.doctype) }}"></div> | |||||
<div id="heatmap-{{ frappe.model.scrub(frm.doctype) }}" class="heatmap"></div> | |||||
<div class="text-muted small heatmap-message hidden"></div> | <div class="text-muted small heatmap-message hidden"></div> | ||||
</div> | </div> | ||||
<div class="form-graph form-dashboard-section hidden"></div> | <div class="form-graph form-dashboard-section hidden"></div> | ||||
@@ -28,7 +28,6 @@ frappe.ui.Graph = class Graph { | |||||
specific_values = [], | specific_values = [], | ||||
summary = [], | summary = [], | ||||
color = 'blue', | |||||
mode = '', | mode = '', | ||||
}) { | }) { | ||||
@@ -43,27 +42,28 @@ frappe.ui.Graph = class Graph { | |||||
} | } | ||||
this.parent = parent; | this.parent = parent; | ||||
this.base_height = height; | |||||
this.height = height - 40; | |||||
this.translate_x = 60; | |||||
this.translate_y = 10; | |||||
this.set_margins(height); | |||||
this.title = title; | this.title = title; | ||||
this.subtitle = subtitle; | this.subtitle = subtitle; | ||||
// Begin axis graph-related args | |||||
this.y = y; | this.y = y; | ||||
this.x = x; | this.x = x; | ||||
this.specific_values = specific_values; | this.specific_values = specific_values; | ||||
this.summary = summary; | this.summary = summary; | ||||
this.color = color; | |||||
this.mode = mode; | this.mode = mode; | ||||
// this.current_hover_index = 0; | |||||
// this.current_selected_index = 0; | |||||
this.$graph = null; | this.$graph = null; | ||||
// Validate all arguments | |||||
// Validate all arguments, check passed data format, set defaults | |||||
frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this)); | frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this)); | ||||
} | } | ||||
@@ -81,18 +81,16 @@ frappe.ui.Graph = class Graph { | |||||
refresh() { | refresh() { | ||||
this.base_width = this.parent.width() - 20; | |||||
this.width = this.base_width - 100; | |||||
this.setup_base_values(); | |||||
this.set_width(); | |||||
this.width = this.base_width - this.translate_x * 2; | |||||
this.setup_container(); | this.setup_container(); | ||||
this.setup_components(); | |||||
this.setup_values(); | this.setup_values(); | ||||
this.setup_utils(); | this.setup_utils(); | ||||
this.setup_components(); | |||||
this.make_graph_components(); | this.make_graph_components(); | ||||
this.make_tooltip(); | this.make_tooltip(); | ||||
if(this.summary.length > 0) { | if(this.summary.length > 0) { | ||||
@@ -102,6 +100,20 @@ frappe.ui.Graph = class Graph { | |||||
} | } | ||||
} | } | ||||
set_margins(height) { | |||||
this.base_height = height; | |||||
this.height = height - 40; | |||||
this.translate_x = 60; | |||||
this.translate_y = 10; | |||||
} | |||||
set_width() { | |||||
this.base_width = this.parent.width(); | |||||
} | |||||
setup_base_values() {} | |||||
setup_container() { | setup_container() { | ||||
// Graph needs a dedicated parent element | // Graph needs a dedicated parent element | ||||
this.parent.empty(); | this.parent.empty(); | ||||
@@ -110,11 +122,11 @@ frappe.ui.Graph = class Graph { | |||||
.addClass('graph-container') | .addClass('graph-container') | ||||
.append($(`<h6 class="title" style="margin-top: 15px;">${this.title}</h6>`)) | .append($(`<h6 class="title" style="margin-top: 15px;">${this.title}</h6>`)) | ||||
.append($(`<h6 class="sub-title uppercase">${this.subtitle}</h6>`)) | .append($(`<h6 class="sub-title uppercase">${this.subtitle}</h6>`)) | ||||
.append($(`<div class="graph-graphics"></div>`)) | |||||
.append($(`<div class="graphics"></div>`)) | |||||
.append($(`<div class="graph-stats-container"></div>`)) | .append($(`<div class="graph-stats-container"></div>`)) | ||||
.appendTo(this.parent); | .appendTo(this.parent); | ||||
this.$graphics = this.container.find('.graph-graphics'); | |||||
this.$graphics = this.container.find('.graphics'); | |||||
this.$stats_container = this.container.find('.graph-stats-container'); | this.$stats_container = this.container.find('.graph-stats-container'); | ||||
this.$graph = $('<div>') | this.$graph = $('<div>') | ||||
@@ -130,6 +142,13 @@ frappe.ui.Graph = class Graph { | |||||
return this.$svg; | return this.$svg; | ||||
} | } | ||||
setup_components() { | |||||
this.y_axis_group = this.snap.g().attr({ class: "y axis" }); | |||||
this.x_axis_group = this.snap.g().attr({ class: "x axis" }); | |||||
this.data_units = this.snap.g().attr({ class: "data-points" }); | |||||
this.specific_y_lines = this.snap.g().attr({ class: "specific axis" }); | |||||
} | |||||
setup_values() { | setup_values() { | ||||
// Multiplier | // Multiplier | ||||
let all_values = this.specific_values.map(d => d.value); | let all_values = this.specific_values.map(d => d.value); | ||||
@@ -170,20 +189,15 @@ frappe.ui.Graph = class Graph { | |||||
}); | }); | ||||
} | } | ||||
setup_components() { | |||||
this.y_axis_group = this.snap.g().attr({ class: "y axis" }); | |||||
this.x_axis_group = this.snap.g().attr({ class: "x axis" }); | |||||
this.data_units = this.snap.g().attr({ class: "graph-data-points" }); | |||||
this.specific_y_lines = this.snap.g().attr({ class: "specific axis" }); | |||||
} | |||||
make_graph_components() { | make_graph_components() { | ||||
this.make_y_axis(); | this.make_y_axis(); | ||||
this.make_x_axis(); | this.make_x_axis(); | ||||
this.y_colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen', | |||||
'yellow', 'orange', 'red'] | |||||
this.y.map((d, i) => { | this.y.map((d, i) => { | ||||
this.make_units(d.y_tops, d.color, i); | |||||
this.make_path(d); | |||||
this.make_units(d.y_tops, d.color || this.y_colors[i], i); | |||||
this.make_path(d, d.color || this.y_colors[i]); | |||||
}); | }); | ||||
if(this.specific_values.length > 0) { | if(this.specific_values.length > 0) { | ||||
@@ -246,6 +260,11 @@ frappe.ui.Graph = class Graph { | |||||
transform: `translate(0,${start_at})` | transform: `translate(0,${start_at})` | ||||
}); | }); | ||||
this.x.values.map((point, i) => { | this.x.values.map((point, i) => { | ||||
let allowed_space = this.avg_unit_width * 1.5; | |||||
if(this.get_strwidth(point) > allowed_space) { | |||||
let allowed_letters = allowed_space / 8; | |||||
point = point.slice(0, allowed_letters-3) + " ..."; | |||||
} | |||||
this.x_axis_group.add(this.snap.g( | this.x_axis_group.add(this.snap.g( | ||||
this.snap.line(0, 0, 0, height), | this.snap.line(0, 0, 0, height), | ||||
this.snap.text(0, text_start_at, point).attr({ | this.snap.text(0, text_start_at, point).attr({ | ||||
@@ -262,8 +281,13 @@ frappe.ui.Graph = class Graph { | |||||
make_units(y_values, color, dataset_index) { | make_units(y_values, color, dataset_index) { | ||||
let d = this.unit_args; | let d = this.unit_args; | ||||
y_values.map((y, i) => { | y_values.map((y, i) => { | ||||
let data_unit = this.draw[d.type](this.x_axis_values[i], | |||||
y, d.args, color, dataset_index); | |||||
let data_unit = this.draw[d.type]( | |||||
this.x_axis_values[i], | |||||
y, | |||||
d.args, | |||||
color, | |||||
dataset_index | |||||
); | |||||
this.data_units.add(data_unit); | this.data_units.add(data_unit); | ||||
this.y[dataset_index].data_units.push(data_unit); | this.y[dataset_index].data_units.push(data_unit); | ||||
}); | }); | ||||
@@ -272,75 +296,58 @@ frappe.ui.Graph = class Graph { | |||||
make_path() { } | make_path() { } | ||||
make_tooltip() { | make_tooltip() { | ||||
this.tip = $(`<div class="graph-svg-tip comparison"> | |||||
<span class="title"></span> | |||||
<ul class="data-point-list"> | |||||
</ul> | |||||
</div>`).attr({ | |||||
style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;` | |||||
}).appendTo(this.$graphics); | |||||
this.tip_title = this.tip.find('.title'); | |||||
this.tip_data_point_list = this.tip.find('.data-point-list'); | |||||
// should be w.r.t. this.parent | |||||
this.tip = new frappe.ui.SvgTip({ | |||||
parent: this.$graphics, | |||||
}); | |||||
this.bind_tooltip(); | this.bind_tooltip(); | ||||
} | } | ||||
bind_tooltip() { | bind_tooltip() { | ||||
// should be w.r.t. this.parent, but will have to take care of | |||||
// all the elements and padding, margins on top | |||||
this.$graphics.on('mousemove', (e) => { | this.$graphics.on('mousemove', (e) => { | ||||
let offset = $(this.$graphics).offset(); | |||||
let offset = this.$graphics.offset(); | |||||
var relX = e.pageX - offset.left - this.translate_x; | var relX = e.pageX - offset.left - this.translate_x; | ||||
var relY = e.pageY - offset.top - this.translate_y; | var relY = e.pageY - offset.top - this.translate_y; | ||||
if(relY < this.height) { | |||||
for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) { | |||||
let x_val = this.x_axis_values[i]; | |||||
if(relX > x_val - this.avg_unit_width/2) { | |||||
let x = x_val - this.tip.width()/2 + this.translate_x; | |||||
let y = this.y_min_tops[i] - this.tip.height() + this.translate_y; | |||||
this.fill_tooltip(i); | |||||
this.tip.attr({ | |||||
style: `top: ${y}px; left: ${x-0.5}px; opacity: 1; pointer-events: none;` | |||||
}); | |||||
break; | |||||
} | |||||
} | |||||
if(relY < this.height + this.translate_y * 2) { | |||||
this.map_tooltip_x_position_and_show(relX); | |||||
} else { | } else { | ||||
this.tip.attr({ | |||||
style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;` | |||||
}); | |||||
this.tip.hide_tip() | |||||
} | } | ||||
}); | }); | ||||
this.$graphics.on('mouseleave', () => { | |||||
this.tip.attr({ | |||||
style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;` | |||||
}); | |||||
}); | |||||
} | } | ||||
fill_tooltip(i) { | |||||
this.tip_title.html(this.x.formatted && this.x.formatted.length>0 | |||||
? this.x.formatted[i] : this.x.values[i]); | |||||
this.tip_data_point_list.empty(); | |||||
this.y.map(y_set => { | |||||
let $li = $(`<li> | |||||
<strong style="display: block;"> | |||||
${y_set.formatted ? y_set.formatted[i] : y_set.values[i]} | |||||
</strong> | |||||
${y_set.title ? y_set.title : '' } | |||||
</li>`).addClass(`border-top ${y_set.color}`); | |||||
this.tip_data_point_list.append($li); | |||||
}); | |||||
map_tooltip_x_position_and_show(relX) { | |||||
for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) { | |||||
let x_val = this.x_axis_values[i]; | |||||
// let delta = i === 0 ? this.avg_unit_width : x_val - this.x_axis_values[i-1]; | |||||
if(relX > x_val - this.avg_unit_width/2) { | |||||
let x = x_val + this.translate_x - 0.5; | |||||
let y = this.y_min_tops[i] + this.translate_y; | |||||
let title = this.x.formatted && this.x.formatted.length>0 | |||||
? this.x.formatted[i] : this.x.values[i]; | |||||
let values = this.y.map((set, j) => { | |||||
return { | |||||
title: set.title, | |||||
value: set.formatted ? set.formatted[i] : set.values[i], | |||||
color: set.color || this.y_colors[j], | |||||
} | |||||
}); | |||||
this.tip.set_values(x, y, title, '', values); | |||||
this.tip.show_tip(); | |||||
break; | |||||
} | |||||
} | |||||
} | } | ||||
show_specific_values() { | show_specific_values() { | ||||
this.specific_values.map(d => { | this.specific_values.map(d => { | ||||
this.specific_y_lines.add(this.snap.g( | this.specific_y_lines.add(this.snap.g( | ||||
this.snap.line(0, 0, this.width, 0).attr({ | this.snap.line(0, 0, this.width, 0).attr({ | ||||
class: d.line_type === "dashed" ? "graph-dashed": "" | |||||
class: d.line_type === "dashed" ? "dashed": "" | |||||
}), | }), | ||||
this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({ | this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({ | ||||
dy: ".32em", | dy: ".32em", | ||||
@@ -434,6 +441,9 @@ frappe.ui.Graph = class Graph { | |||||
let width = total_width / args.no_of_datasets; | let width = total_width / args.no_of_datasets; | ||||
let current_x = start_x + width * index; | let current_x = start_x + width * index; | ||||
if(y == this.height) { | |||||
y = this.height * 0.98; | |||||
} | |||||
return this.snap.rect(current_x, y, width, this.height - y).attr({ | return this.snap.rect(current_x, y, width, this.height - y).attr({ | ||||
class: `bar mini fill ${color}` | class: `bar mini fill ${color}` | ||||
}); | }); | ||||
@@ -442,6 +452,11 @@ frappe.ui.Graph = class Graph { | |||||
return this.snap.circle(x, y, args.radius).attr({ | return this.snap.circle(x, y, args.radius).attr({ | ||||
class: `fill ${color}` | class: `fill ${color}` | ||||
}); | }); | ||||
}, | |||||
'rect': (x, y, args, color) => { | |||||
return this.snap.rect(x, y, args.width, args.height).attr({ | |||||
class: `fill ${color}` | |||||
}); | |||||
} | } | ||||
}; | }; | ||||
@@ -470,6 +485,7 @@ frappe.ui.BarGraph = class BarGraph extends frappe.ui.Graph { | |||||
this.unit_args = { | this.unit_args = { | ||||
type: 'bar', | type: 'bar', | ||||
args: { | args: { | ||||
// More intelligent width setting | |||||
space_width: this.y.length > 1 ? | space_width: this.y.length > 1 ? | ||||
me.avg_unit_width/2 : me.avg_unit_width/8, | me.avg_unit_width/2 : me.avg_unit_width/8, | ||||
no_of_datasets: this.y.length | no_of_datasets: this.y.length | ||||
@@ -498,10 +514,10 @@ frappe.ui.LineGraph = class LineGraph extends frappe.ui.Graph { | |||||
}; | }; | ||||
} | } | ||||
make_path(d) { | |||||
make_path(d, color) { | |||||
let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); | let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); | ||||
let path_str = "M"+points_list.join("L"); | let path_str = "M"+points_list.join("L"); | ||||
d.path = this.snap.path(path_str).attr({class: `stroke ${d.color}`}); | |||||
d.path = this.snap.path(path_str).attr({class: `stroke ${color}`}); | |||||
this.data_units.prepend(d.path); | this.data_units.prepend(d.path); | ||||
} | } | ||||
}; | }; | ||||
@@ -512,7 +528,9 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph { | |||||
} | } | ||||
make_graph_area() { | make_graph_area() { | ||||
this.$graphics.addClass('graph-focus-margin'); | |||||
this.$graphics.addClass('graph-focus-margin').attr({ | |||||
style: `margin-top: 45px;` | |||||
}); | |||||
this.$stats_container.addClass('graph-focus-margin').attr({ | this.$stats_container.addClass('graph-focus-margin').attr({ | ||||
style: `padding-top: 0px; margin-bottom: 30px;` | style: `padding-top: 0px; margin-bottom: 30px;` | ||||
}); | }); | ||||
@@ -533,37 +551,523 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph { | |||||
return total; | return total; | ||||
}); | }); | ||||
// Calculate x unit distances for tooltips | |||||
if(!this.x.colors) { | |||||
this.x.colors = ['green', 'blue', 'purple', 'red', 'orange', | |||||
'yellow', 'lightblue', 'lightgreen']; | |||||
} | |||||
} | } | ||||
setup_utils() { } | setup_utils() { } | ||||
setup_components() { | setup_components() { | ||||
this.$percentage_bar = $(`<div class="progress"> | this.$percentage_bar = $(`<div class="progress"> | ||||
</div>`).appendTo(this.$chart); | |||||
</div>`).appendTo(this.$chart); // get this.height, width and avg from this if needed | |||||
} | } | ||||
make_graph_components() { | make_graph_components() { | ||||
let grand_total = this.x.totals.reduce((a, b) => a + b, 0); | |||||
this.grand_total = this.x.totals.reduce((a, b) => a + b, 0); | |||||
this.x.units = []; | this.x.units = []; | ||||
this.x.totals.map((total, i) => { | this.x.totals.map((total, i) => { | ||||
let $part = $(`<div class="progress-bar background ${this.x.colors[i]}" | let $part = $(`<div class="progress-bar background ${this.x.colors[i]}" | ||||
style="width: ${total*100/grand_total}%"></div>`); | |||||
style="width: ${total*100/this.grand_total}%"></div>`); | |||||
this.x.units.push($part); | this.x.units.push($part); | ||||
this.$percentage_bar.append($part); | this.$percentage_bar.append($part); | ||||
}); | }); | ||||
} | } | ||||
make_tooltip() { } | |||||
bind_tooltip() { | |||||
this.x.units.map(($part, i) => { | |||||
$part.on('mouseenter', () => { | |||||
let g_off = this.$graphics.offset(), p_off = $part.offset(); | |||||
let x = p_off.left - g_off.left + $part.width()/2; | |||||
let y = p_off.top - g_off.top - 6; | |||||
let title = (this.x.formatted && this.x.formatted.length>0 | |||||
? this.x.formatted[i] : this.x.values[i]) + ': '; | |||||
let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1); | |||||
this.tip.set_values(x, y, title, percent); | |||||
this.tip.show_tip(); | |||||
}); | |||||
}); | |||||
} | |||||
show_summary() { | show_summary() { | ||||
let values = this.x.formatted.length > 0 ? this.x.formatted : this.x.values; | |||||
let x_values = this.x.formatted && this.x.formatted.length > 0 | |||||
? this.x.formatted : this.x.values; | |||||
this.x.totals.map((d, i) => { | this.x.totals.map((d, i) => { | ||||
this.$stats_container.append($(`<div class="stats"> | |||||
<span class="indicator ${this.x.colors[i]}"> | |||||
<span class="text-muted">${values[i]}:</span> | |||||
${d} | |||||
</span> | |||||
</div>`)); | |||||
if(d) { | |||||
this.$stats_container.append($(`<div class="stats"> | |||||
<span class="indicator ${this.x.colors[i]}"> | |||||
<span class="text-muted">${x_values[i]}:</span> | |||||
${d} | |||||
</span> | |||||
</div>`)); | |||||
} | |||||
}); | |||||
} | |||||
}; | |||||
frappe.ui.HeatMap = class HeatMap extends frappe.ui.Graph { | |||||
constructor({ | |||||
parent = null, | |||||
height = 240, | |||||
title = '', subtitle = '', | |||||
start = new Date(moment().subtract(1, 'year').toDate()), | |||||
domain = '', | |||||
subdomain = '', | |||||
data = {}, | |||||
discrete_domains = 0, | |||||
count_label = '', | |||||
// remove these graph related args | |||||
y = [], | |||||
x = [], | |||||
specific_values = [], | |||||
summary = [], | |||||
mode = 'heatmap', | |||||
} = {}) { | |||||
super(arguments[0]); | |||||
this.start = start || new Date(moment().subtract(1, 'year').toDate()); | |||||
this.data = data; | |||||
this.discrete_domains = discrete_domains; | |||||
this.count_label = count_label; | |||||
this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||||
} | |||||
setup_base_values() { | |||||
this.today = new Date(); | |||||
this.first_week_start = new Date(this.start.toDateString()); | |||||
this.last_week_start = new Date(this.today.toDateString()); | |||||
if(this.first_week_start.getDay() !== 7) { | |||||
this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay()); | |||||
} | |||||
if(this.last_week_start.getDay() !== 7) { | |||||
this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay()); | |||||
} | |||||
this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1; | |||||
} | |||||
set_width() { | |||||
this.base_width = (this.no_of_cols) * 12; | |||||
} | |||||
setup_components() { | |||||
this.domain_label_group = this.snap.g().attr({ class: "domain-label-group chart-label" }); | |||||
this.data_groups = this.snap.g().attr({ class: "data-groups", transform: `translate(0, 20)` }); | |||||
} | |||||
setup_values() { | |||||
this.distribution = this.get_distribution(this.data, this.legend_colors); | |||||
this.month_names = ["January", "February", "March", "April", "May", "June", | |||||
"July", "August", "September", "October", "November", "December" | |||||
]; | |||||
this.render_all_weeks_and_store_x_values(this.no_of_cols); | |||||
} | |||||
render_all_weeks_and_store_x_values(no_of_weeks) { | |||||
let current_week_sunday = new Date(this.first_week_start); | |||||
this.week_col = 0; | |||||
this.current_month = current_week_sunday.getMonth(); | |||||
this.months = [this.current_month + '']; | |||||
this.month_weeks = {}, this.month_start_points = []; | |||||
this.month_weeks[this.current_month] = 0; | |||||
this.month_start_points.push(13); | |||||
for(var i = 0; i < no_of_weeks; i++) { | |||||
let data_group, month_change = 0; | |||||
let day = new Date(current_week_sunday); | |||||
[data_group, month_change] = this.get_week_squares_group(day, this.week_col); | |||||
this.data_groups.add(data_group); | |||||
this.week_col += 1 + parseInt(this.discrete_domains && month_change); | |||||
this.month_weeks[this.current_month]++; | |||||
if(month_change) { | |||||
this.current_month = (this.current_month + 1) % 12; | |||||
this.months.push(this.current_month + ''); | |||||
this.month_weeks[this.current_month] = 1; | |||||
} | |||||
this.add_days(current_week_sunday, 7); | |||||
} | |||||
this.render_month_labels(); | |||||
} | |||||
get_week_squares_group(current_date, index) { | |||||
const no_of_weekdays = 7; | |||||
const square_side = 10; | |||||
const cell_padding = 2; | |||||
const step = 1; | |||||
let month_change = 0; | |||||
let week_col_change = 0; | |||||
let data_group = this.snap.g().attr({ class: "data-group" }); | |||||
for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { | |||||
let data_value = 0; | |||||
let color_index = 0; | |||||
// TODO: More foolproof for any data | |||||
let timestamp = Math.floor(current_date.getTime()/1000).toFixed(1); | |||||
if(this.data[timestamp]) { | |||||
data_value = this.data[timestamp]; | |||||
color_index = this.get_max_checkpoint(data_value, this.distribution); | |||||
} | |||||
if(this.data[Math.round(timestamp)]) { | |||||
data_value = this.data[Math.round(timestamp)]; | |||||
color_index = this.get_max_checkpoint(data_value, this.distribution); | |||||
} | |||||
let x = 13 + (index + week_col_change) * 12; | |||||
data_group.add(this.snap.rect(x, y, square_side, square_side).attr({ | |||||
'class': `day`, | |||||
'fill': this.legend_colors[color_index], | |||||
'data-date': this.get_dd_mm_yyyy(current_date), | |||||
'data-value': data_value, | |||||
'data-day': current_date.getDay() | |||||
})); | |||||
let next_date = new Date(current_date); | |||||
this.add_days(next_date, 1); | |||||
if(next_date.getMonth() - current_date.getMonth()) { | |||||
month_change = 1; | |||||
if(this.discrete_domains) { | |||||
week_col_change = 1; | |||||
} | |||||
this.month_start_points.push(13 + (index + week_col_change) * 12); | |||||
} | |||||
current_date = next_date; | |||||
} | |||||
return [data_group, month_change]; | |||||
} | |||||
render_month_labels() { | |||||
this.first_month_label = 1; | |||||
// if (this.first_week_start.getDate() > 8) { | |||||
// this.first_month_label = 0; | |||||
// } | |||||
this.last_month_label = 1; | |||||
let first_month = this.months.shift(); | |||||
let first_month_start = this.month_start_points.shift(); | |||||
// render first month if | |||||
let last_month = this.months.pop(); | |||||
let last_month_start = this.month_start_points.pop(); | |||||
// render last month if | |||||
this.month_start_points.map((start, i) => { | |||||
let month_name = this.month_names[this.months[i]].substring(0, 3); | |||||
this.domain_label_group.add(this.snap.text(start + 12, 10, month_name).attr({ | |||||
dy: ".32em", | |||||
class: "y-value-text" | |||||
})); | |||||
}); | |||||
} | |||||
make_graph_components() { | |||||
this.container.find('.graph-stats-container, .sub-title, .title').hide(); | |||||
this.container.find('.graphics').css({'margin-top': '0px', 'padding-top': '0px'}); | |||||
} | |||||
bind_tooltip() { | |||||
this.container.on('mouseenter', '.day', (e) => { | |||||
let subdomain = $(e.target); | |||||
let count = subdomain.attr('data-value'); | |||||
let date_parts = subdomain.attr('data-date').split('-'); | |||||
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); | |||||
let g_off = this.$graphics.offset(), p_off = subdomain.offset(); | |||||
let width = parseInt(subdomain.attr('width')); | |||||
let x = p_off.left - g_off.left + (width+2)/2; | |||||
let y = p_off.top - g_off.top - (width+2)/2; | |||||
let value = count + ' ' + this.count_label; | |||||
let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; | |||||
this.tip.set_values(x, y, name, value, [], 1); | |||||
this.tip.show_tip(); | |||||
}); | |||||
} | |||||
update(data) { | |||||
this.data = data; | |||||
this.setup_values(); | |||||
} | |||||
get_distribution(data, mapper_array) { | |||||
let data_values = Object.keys(data).map(key => data[key]); | |||||
let data_max_value = Math.max(...data_values); | |||||
let distribution_step = 1 / (mapper_array.length - 1); | |||||
let distribution = []; | |||||
mapper_array.map((color, i) => { | |||||
let checkpoint = data_max_value * (distribution_step * i); | |||||
distribution.push(checkpoint); | |||||
}); | |||||
return distribution; | |||||
} | |||||
get_max_checkpoint(value, distribution) { | |||||
return distribution.filter(d => { | |||||
return d <= value; | |||||
}).length - 1; | |||||
} | |||||
// TODO: date utils, move these out | |||||
// https://stackoverflow.com/a/11252167/6495043 | |||||
treat_as_utc(date_str) { | |||||
let result = new Date(date_str); | |||||
result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); | |||||
return result; | |||||
} | |||||
get_dd_mm_yyyy(date) { | |||||
let dd = date.getDate(); | |||||
let mm = date.getMonth() + 1; // getMonth() is zero-based | |||||
return [ | |||||
(dd>9 ? '' : '0') + dd, | |||||
(mm>9 ? '' : '0') + mm, | |||||
date.getFullYear() | |||||
].join('-'); | |||||
} | |||||
get_weeks_between(start_date_str, end_date_str) { | |||||
return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7); | |||||
} | |||||
get_days_between(start_date_str, end_date_str) { | |||||
let milliseconds_per_day = 24 * 60 * 60 * 1000; | |||||
return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day; | |||||
} | |||||
// mutates | |||||
add_days(date, number_of_days) { | |||||
date.setDate(date.getDate() + number_of_days); | |||||
} | |||||
get_month_name() {} | |||||
} | |||||
frappe.ui.SvgTip = class { | |||||
constructor({ | |||||
parent = null | |||||
}) { | |||||
this.parent = parent; | |||||
this.title_name = ''; | |||||
this.title_value = ''; | |||||
this.list_values = []; | |||||
this.title_value_first = 0; | |||||
this.x = 0; | |||||
this.y = 0; | |||||
this.top = 0; | |||||
this.left = 0; | |||||
this.setup(); | |||||
} | |||||
setup() { | |||||
this.make_tooltip(); | |||||
} | |||||
refresh() { | |||||
this.fill(); | |||||
this.calc_position(); | |||||
// this.show_tip(); | |||||
} | |||||
make_tooltip() { | |||||
this.container = $(`<div class="graph-svg-tip comparison"> | |||||
<span class="title"></span> | |||||
<ul class="data-point-list"></ul> | |||||
<div class="svg-pointer"></div> | |||||
</div>`).appendTo(this.parent); | |||||
this.hide_tip(); | |||||
this.title = this.container.find('.title'); | |||||
this.data_point_list = this.container.find('.data-point-list'); | |||||
this.parent.on('mouseleave', () => { | |||||
this.hide_tip(); | |||||
}); | |||||
} | |||||
fill() { | |||||
let title; | |||||
if(this.title_value_first) { | |||||
title = `<strong>${this.title_value}</strong>${this.title_name}`; | |||||
} else { | |||||
title = `${this.title_name}<strong>${this.title_value}</strong>`; | |||||
} | |||||
this.title.html(title); | |||||
this.data_point_list.empty(); | |||||
this.list_values.map((set, i) => { | |||||
let $li = $(`<li> | |||||
<strong style="display: block;">${set.value ? set.value : '' }</strong> | |||||
${set.title ? set.title : '' } | |||||
</li>`).addClass(`border-top ${set.color || 'black'}`); | |||||
this.data_point_list.append($li); | |||||
}); | |||||
} | |||||
calc_position() { | |||||
this.top = this.y - this.container.height(); | |||||
this.left = this.x - this.container.width()/2; | |||||
let max_left = this.parent.width() - this.container.width(); | |||||
let $pointer = this.container.find('.svg-pointer'); | |||||
if(this.left < 0) { | |||||
$pointer.css({ 'left': `calc(50% - ${-1 * this.left}px)` }); | |||||
this.left = 0; | |||||
} else if(this.left > max_left) { | |||||
let delta = this.left - max_left; | |||||
$pointer.css({ 'left': `calc(50% + ${delta}px)` }); | |||||
this.left = max_left; | |||||
} else { | |||||
$pointer.css({ 'left': `50%` }); | |||||
} | |||||
} | |||||
set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) { | |||||
this.title_name = title_name; | |||||
this.title_value = title_value; | |||||
this.list_values = list_values; | |||||
this.x = x; | |||||
this.y = y; | |||||
this.title_value_first = title_value_first; | |||||
this.refresh(); | |||||
} | |||||
hide_tip() { | |||||
this.container.css({ | |||||
'top': '0px', | |||||
'left': '0px', | |||||
'opacity': '0' | |||||
}); | |||||
} | |||||
show_tip() { | |||||
this.container.css({ | |||||
'top': this.top + 'px', | |||||
'left': this.left + 'px', | |||||
'opacity': '1' | |||||
}); | }); | ||||
} | } | ||||
}; | }; | ||||
frappe.provide("frappe.ui.graphs"); | |||||
frappe.ui.graphs.get_timeseries = function(start, frequency, length) { | |||||
} | |||||
frappe.ui.graphs.map_c3 = function(chart) { | |||||
if (chart.data) { | |||||
let data = chart.data; | |||||
let mode = chart.chart_type || 'line'; | |||||
if(mode === 'pie') { | |||||
mode = 'percentage'; | |||||
} | |||||
let x = {}, y = []; | |||||
if(data.columns) { | |||||
let columns = data.columns; | |||||
x.values = columns.filter(col => { | |||||
return col[0] === data.x; | |||||
})[0]; | |||||
if(x.values && x.values.length) { | |||||
let dataset_length = x.values.length; | |||||
let dirty = false; | |||||
columns.map(col => { | |||||
if(col[0] !== data.x) { | |||||
if(col.length === dataset_length) { | |||||
let title = col[0]; | |||||
col.splice(0, 1); | |||||
y.push({ | |||||
title: title, | |||||
values: col, | |||||
}); | |||||
} else { | |||||
dirty = true; | |||||
} | |||||
} | |||||
}) | |||||
if(dirty) { | |||||
return; | |||||
} | |||||
x.values.splice(0, 1); | |||||
return { | |||||
mode: mode, | |||||
y: y, | |||||
x: x | |||||
} | |||||
} | |||||
} else if(data.rows) { | |||||
let rows = data.rows; | |||||
x.values = rows[0]; | |||||
rows.map((row, i) => { | |||||
if(i === 0) { | |||||
x.values = row; | |||||
} else { | |||||
y.push({ | |||||
title: 'data' + i, | |||||
values: row, | |||||
}) | |||||
} | |||||
}); | |||||
return { | |||||
mode: mode, | |||||
y: y, | |||||
x: x | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// frappe.ui.CompositeGraph = class { | |||||
// constructor({ | |||||
// parent = null | |||||
// }) { | |||||
// this.parent = parent; | |||||
// this.title_name = ''; | |||||
// this.title_value = ''; | |||||
// this.list_values = []; | |||||
// this.x = 0; | |||||
// this.y = 0; | |||||
// this.top = 0; | |||||
// this.left = 0; | |||||
// this.setup(); | |||||
// } | |||||
// } |
@@ -490,6 +490,8 @@ frappe.search.utils = { | |||||
// 0 - 6 for fuzzy contain | // 0 - 6 for fuzzy contain | ||||
// **Specific use-case step** | // **Specific use-case step** | ||||
keywords = keywords || ''; | |||||
var item = __(_item || '').replace(/-/g, " "); | var item = __(_item || '').replace(/-/g, " "); | ||||
var ilen = item.length; | var ilen = item.length; | ||||
@@ -24,7 +24,7 @@ | |||||
{% for col in columns %} | {% for col in columns %} | ||||
{% if col.name && col._id !== "_check" %} | {% if col.name && col._id !== "_check" %} | ||||
{% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %} | |||||
{% var value = col.fieldname ? row[col.fieldname] : row[col.field]; %} | |||||
<td>{{ col.formatter | <td>{{ col.formatter | ||||
? col.formatter(row._index, col._index, value, col, row, true) | ? col.formatter(row._index, col._index, value, col, row, true) | ||||
@@ -3,6 +3,7 @@ | |||||
frappe.provide("frappe.views"); | frappe.provide("frappe.views"); | ||||
frappe.provide("frappe.query_reports"); | frappe.provide("frappe.query_reports"); | ||||
frappe.provide("frappe.ui.graphs"); | |||||
frappe.standard_pages["query-report"] = function() { | frappe.standard_pages["query-report"] = function() { | ||||
var wrapper = frappe.container.add_page('query-report'); | var wrapper = frappe.container.add_page('query-report'); | ||||
@@ -136,6 +136,11 @@ | |||||
.form-heatmap { | .form-heatmap { | ||||
.heatmap { | |||||
display: flex; | |||||
justify-content: center; | |||||
} | |||||
.heatmap-message { | .heatmap-message { | ||||
margin-top: 10px; | margin-top: 10px; | ||||
} | } | ||||
@@ -5,9 +5,10 @@ | |||||
margin: 0px 5%; | margin: 0px 5%; | ||||
} | } | ||||
.graph-graphics { | |||||
.graphics { | |||||
margin-top: 10px; | margin-top: 10px; | ||||
padding: 10px 0px; | |||||
padding-top: 10px; | |||||
padding-bottom: 10px; | |||||
position: relative; | position: relative; | ||||
} | } | ||||
@@ -43,28 +44,22 @@ | |||||
} | } | ||||
} | } | ||||
.bar-graph, .line-graph { | |||||
// baselines | |||||
.axis { | |||||
font-size: 10px; | |||||
fill: #6a737d; | |||||
.axis, .chart-label { | |||||
font-size: 10px; | |||||
fill: #959ba1; | |||||
line { | |||||
stroke: rgba(27,31,35,0.1); | |||||
} | |||||
line { | |||||
stroke: rgba(27,31,35,0.1); | |||||
} | } | ||||
} | } | ||||
.percentage-graph { | .percentage-graph { | ||||
margin-top: 35px; | |||||
.progress { | .progress { | ||||
margin-bottom: 0px; | margin-bottom: 0px; | ||||
} | } | ||||
} | } | ||||
.graph-data-points { | |||||
.data-points { | |||||
circle { | circle { | ||||
// fill: #28a745; | // fill: #28a745; | ||||
stroke: #fff; | stroke: #fff; | ||||
@@ -83,7 +78,7 @@ | |||||
} | } | ||||
} | } | ||||
line.graph-dashed { | |||||
line.dashed { | |||||
stroke-dasharray: 5,3; | stroke-dasharray: 5,3; | ||||
} | } | ||||
@@ -104,216 +99,215 @@ | |||||
text-anchor: middle; | text-anchor: middle; | ||||
} | } | ||||
} | } | ||||
} | |||||
.graph-svg-tip { | |||||
position: absolute; | |||||
z-index: 99999; | |||||
padding: 10px; | |||||
font-size: 12px; | |||||
color: #959da5; | |||||
text-align: center; | |||||
background: rgba(0,0,0,0.8); | |||||
border-radius: 3px; | |||||
&.comparison { | |||||
padding: 0; | |||||
text-align: left; | |||||
.graph-svg-tip { | |||||
position: absolute; | |||||
z-index: 99999; | |||||
padding: 10px; | |||||
font-size: 12px; | |||||
color: #959da5; | |||||
text-align: center; | |||||
background: rgba(0,0,0,0.8); | |||||
border-radius: 3px; | |||||
&.comparison { | |||||
padding: 0; | |||||
text-align: left; | |||||
pointer-events: none; | |||||
.title { | |||||
display: block; | |||||
padding: 10px; | |||||
margin: 0; | |||||
font-weight: 600; | |||||
line-height: 1; | |||||
pointer-events: none; | pointer-events: none; | ||||
.title { | |||||
display: block; | |||||
padding: 10px; | |||||
margin: 0; | |||||
font-weight: 600; | |||||
line-height: 1; | |||||
pointer-events: none; | |||||
} | |||||
ul { | |||||
margin: 0; | |||||
white-space: nowrap; | |||||
list-style: none; | |||||
} | |||||
li { | |||||
display: inline-block; | |||||
padding: 5px 10px; | |||||
} | |||||
} | |||||
ul, ol { | |||||
padding-left: 0; | |||||
display: flex; | |||||
} | |||||
ul.data-point-list li { | |||||
min-width: 90px; | |||||
flex: 1; | |||||
} | } | ||||
strong { | |||||
color: #dfe2e5; | |||||
ul { | |||||
margin: 0; | |||||
white-space: nowrap; | |||||
list-style: none; | |||||
} | } | ||||
&::after { | |||||
position: absolute; | |||||
bottom: -10px; | |||||
left: 50%; | |||||
width: 5px; | |||||
height: 5px; | |||||
margin: 0 0 0 -5px; | |||||
content: " "; | |||||
border: 5px solid transparent; | |||||
border-top-color: rgba(0,0,0,0.8); | |||||
li { | |||||
display: inline-block; | |||||
padding: 5px 10px; | |||||
} | } | ||||
} | } | ||||
.stroke.grey { | |||||
stroke: #F0F4F7; | |||||
} | |||||
.stroke.blue { | |||||
stroke: #5e64ff; | |||||
} | |||||
.stroke.red { | |||||
stroke: #ff5858; | |||||
} | |||||
.stroke.light-green { | |||||
stroke: #98d85b; | |||||
} | |||||
.stroke.green { | |||||
stroke: #28a745; | |||||
} | |||||
.stroke.orange { | |||||
stroke: #ffa00a; | |||||
} | |||||
.stroke.purple { | |||||
stroke: #743ee2; | |||||
} | |||||
.stroke.darkgrey { | |||||
stroke: #b8c2cc; | |||||
} | |||||
.stroke.black { | |||||
stroke: #36414C; | |||||
} | |||||
.stroke.yellow { | |||||
stroke: #FEEF72; | |||||
} | |||||
.stroke.light-blue { | |||||
stroke: #7CD6FD; | |||||
} | |||||
.stroke.lightblue { | |||||
stroke: #7CD6FD; | |||||
ul, ol { | |||||
padding-left: 0; | |||||
display: flex; | |||||
} | } | ||||
.fill.grey { | |||||
fill: #F0F4F7; | |||||
} | |||||
.fill.blue { | |||||
fill: #5e64ff; | |||||
} | |||||
.fill.red { | |||||
fill: #ff5858; | |||||
} | |||||
.fill.light-green { | |||||
fill: #98d85b; | |||||
} | |||||
.fill.green { | |||||
fill: #28a745; | |||||
} | |||||
.fill.orange { | |||||
fill: #ffa00a; | |||||
} | |||||
.fill.purple { | |||||
fill: #743ee2; | |||||
} | |||||
.fill.darkgrey { | |||||
fill: #b8c2cc; | |||||
} | |||||
.fill.black { | |||||
fill: #36414C; | |||||
} | |||||
.fill.yellow { | |||||
fill: #FEEF72; | |||||
} | |||||
.fill.light-blue { | |||||
fill: #7CD6FD; | |||||
} | |||||
.fill.lightblue { | |||||
fill: #7CD6FD; | |||||
ul.data-point-list li { | |||||
min-width: 90px; | |||||
flex: 1; | |||||
} | } | ||||
.background.grey { | |||||
background: #F0F4F7; | |||||
} | |||||
.background.blue { | |||||
background: #5e64ff; | |||||
} | |||||
.background.red { | |||||
background: #ff5858; | |||||
} | |||||
.background.light-green { | |||||
background: #98d85b; | |||||
} | |||||
.background.green { | |||||
background: #28a745; | |||||
} | |||||
.background.orange { | |||||
background: #ffa00a; | |||||
} | |||||
.background.purple { | |||||
background: #743ee2; | |||||
} | |||||
.background.darkgrey { | |||||
background: #b8c2cc; | |||||
} | |||||
.background.black { | |||||
background: #36414C; | |||||
} | |||||
.background.yellow { | |||||
background: #FEEF72; | |||||
} | |||||
.background.light-blue { | |||||
background: #7CD6FD; | |||||
} | |||||
.background.lightblue { | |||||
background: #7CD6FD; | |||||
strong { | |||||
color: #dfe2e5; | |||||
} | } | ||||
.border-top.grey { | |||||
border-top: 3px solid #F0F4F7; | |||||
} | |||||
.border-top.blue { | |||||
border-top: 3px solid #5e64ff; | |||||
} | |||||
.border-top.red { | |||||
border-top: 3px solid #ff5858; | |||||
} | |||||
.border-top.light-green { | |||||
border-top: 3px solid #98d85b; | |||||
} | |||||
.border-top.green { | |||||
border-top: 3px solid #28a745; | |||||
} | |||||
.border-top.orange { | |||||
border-top: 3px solid #ffa00a; | |||||
} | |||||
.border-top.purple { | |||||
border-top: 3px solid #743ee2; | |||||
} | |||||
.border-top.darkgrey { | |||||
border-top: 3px solid #b8c2cc; | |||||
} | |||||
.border-top.black { | |||||
border-top: 3px solid #36414C; | |||||
} | |||||
.border-top.yellow { | |||||
border-top: 3px solid #FEEF72; | |||||
} | |||||
.border-top.light-blue { | |||||
border-top: 3px solid #7CD6FD; | |||||
} | |||||
.border-top.lightblue { | |||||
border-top: 3px solid #7CD6FD; | |||||
.svg-pointer { | |||||
position: absolute; | |||||
bottom: -10px; | |||||
left: 50%; | |||||
width: 5px; | |||||
height: 5px; | |||||
margin: 0 0 0 -5px; | |||||
content: " "; | |||||
border: 5px solid transparent; | |||||
border-top-color: rgba(0,0,0,0.8); | |||||
} | } | ||||
} | |||||
.stroke.grey { | |||||
stroke: #F0F4F7; | |||||
} | |||||
.stroke.blue { | |||||
stroke: #5e64ff; | |||||
} | |||||
.stroke.red { | |||||
stroke: #ff5858; | |||||
} | |||||
.stroke.light-green { | |||||
stroke: #98d85b; | |||||
} | |||||
.stroke.green { | |||||
stroke: #28a745; | |||||
} | |||||
.stroke.orange { | |||||
stroke: #ffa00a; | |||||
} | |||||
.stroke.purple { | |||||
stroke: #743ee2; | |||||
} | |||||
.stroke.darkgrey { | |||||
stroke: #b8c2cc; | |||||
} | |||||
.stroke.black { | |||||
stroke: #36414C; | |||||
} | |||||
.stroke.yellow { | |||||
stroke: #FEEF72; | |||||
} | |||||
.stroke.light-blue { | |||||
stroke: #7CD6FD; | |||||
} | |||||
.stroke.lightblue { | |||||
stroke: #7CD6FD; | |||||
} | |||||
.fill.grey { | |||||
fill: #F0F4F7; | |||||
} | |||||
.fill.blue { | |||||
fill: #5e64ff; | |||||
} | |||||
.fill.red { | |||||
fill: #ff5858; | |||||
} | |||||
.fill.light-green { | |||||
fill: #98d85b; | |||||
} | |||||
.fill.green { | |||||
fill: #28a745; | |||||
} | |||||
.fill.orange { | |||||
fill: #ffa00a; | |||||
} | |||||
.fill.purple { | |||||
fill: #743ee2; | |||||
} | |||||
.fill.darkgrey { | |||||
fill: #b8c2cc; | |||||
} | |||||
.fill.black { | |||||
fill: #36414C; | |||||
} | |||||
.fill.yellow { | |||||
fill: #FEEF72; | |||||
} | |||||
.fill.light-blue { | |||||
fill: #7CD6FD; | |||||
} | |||||
.fill.lightblue { | |||||
fill: #7CD6FD; | |||||
} | |||||
.background.grey { | |||||
background: #F0F4F7; | |||||
} | |||||
.background.blue { | |||||
background: #5e64ff; | |||||
} | |||||
.background.red { | |||||
background: #ff5858; | |||||
} | |||||
.background.light-green { | |||||
background: #98d85b; | |||||
} | |||||
.background.green { | |||||
background: #28a745; | |||||
} | |||||
.background.orange { | |||||
background: #ffa00a; | |||||
} | |||||
.background.purple { | |||||
background: #743ee2; | |||||
} | |||||
.background.darkgrey { | |||||
background: #b8c2cc; | |||||
} | |||||
.background.black { | |||||
background: #36414C; | |||||
} | |||||
.background.yellow { | |||||
background: #FEEF72; | |||||
} | |||||
.background.light-blue { | |||||
background: #7CD6FD; | |||||
} | |||||
.background.lightblue { | |||||
background: #7CD6FD; | |||||
} | |||||
.border-top.grey { | |||||
border-top: 3px solid #F0F4F7; | |||||
} | |||||
.border-top.blue { | |||||
border-top: 3px solid #5e64ff; | |||||
} | |||||
.border-top.red { | |||||
border-top: 3px solid #ff5858; | |||||
} | |||||
.border-top.light-green { | |||||
border-top: 3px solid #98d85b; | |||||
} | |||||
.border-top.green { | |||||
border-top: 3px solid #28a745; | |||||
} | |||||
.border-top.orange { | |||||
border-top: 3px solid #ffa00a; | |||||
} | |||||
.border-top.purple { | |||||
border-top: 3px solid #743ee2; | |||||
} | |||||
.border-top.darkgrey { | |||||
border-top: 3px solid #b8c2cc; | |||||
} | |||||
.border-top.black { | |||||
border-top: 3px solid #36414C; | |||||
} | |||||
.border-top.yellow { | |||||
border-top: 3px solid #FEEF72; | |||||
} | |||||
.border-top.light-blue { | |||||
border-top: 3px solid #7CD6FD; | |||||
} | |||||
.border-top.lightblue { | |||||
border-top: 3px solid #7CD6FD; | |||||
} | } |
@@ -51,6 +51,7 @@ class TestTwoFactor(unittest.TestCase): | |||||
'''Should return true if enabled for user.''' | '''Should return true if enabled for user.''' | ||||
toggle_2fa_all_role(state=True) | toggle_2fa_all_role(state=True) | ||||
self.assertTrue(two_factor_is_enabled_for_(self.user)) | self.assertTrue(two_factor_is_enabled_for_(self.user)) | ||||
self.assertFalse(two_factor_is_enabled_for_("Administrator")) | |||||
toggle_2fa_all_role(state=False) | toggle_2fa_all_role(state=False) | ||||
self.assertFalse(two_factor_is_enabled_for_(self.user)) | self.assertFalse(two_factor_is_enabled_for_(self.user)) | ||||
@@ -87,7 +88,6 @@ class TestTwoFactor(unittest.TestCase): | |||||
_str = render_string_template(_str,args) | _str = render_string_template(_str,args) | ||||
self.assertEqual(_str,'Verification Code from Frappe Technologies') | self.assertEqual(_str,'Verification Code from Frappe Technologies') | ||||
def set_request(**kwargs): | def set_request(**kwargs): | ||||
builder = EnvironBuilder(**kwargs) | builder = EnvironBuilder(**kwargs) | ||||
frappe.local.request = Request(builder.get_environ()) | frappe.local.request = Request(builder.get_environ()) | ||||
@@ -36,7 +36,8 @@ frappe.tests = { | |||||
} | } | ||||
}; | }; | ||||
tasks.push(task); | tasks.push(task); | ||||
tasks.push(() => frappe.timeout(0.2)); | |||||
tasks.push(frappe.after_ajax); | |||||
tasks.push(() => frappe.timeout(0.4)); | |||||
} | } | ||||
}); | }); | ||||
@@ -68,7 +69,8 @@ frappe.tests = { | |||||
return frappe.model.set_value(grid_row.doc.doctype, | return frappe.model.set_value(grid_row.doc.doctype, | ||||
grid_row.doc.name, child_key, child_value[child_key]); | grid_row.doc.name, child_key, child_value[child_key]); | ||||
}); | }); | ||||
grid_value_tasks.push(() => frappe.timeout(0.2)); | |||||
grid_value_tasks.push(frappe.after_ajax); | |||||
grid_value_tasks.push(() => frappe.timeout(0.4)); | |||||
} | } | ||||
}); | }); | ||||
@@ -22,7 +22,7 @@ class TestTestRunner(unittest.TestCase): | |||||
test, comment = test.split('#') | test, comment = test.split('#') | ||||
test = test.strip() | test = test.strip() | ||||
if comment.strip()=='long': | if comment.strip()=='long': | ||||
timeout = 240 | |||||
timeout = 300 | |||||
print('Running {0}...'.format(test)) | print('Running {0}...'.format(test)) | ||||
@@ -74,6 +74,9 @@ def cache_2fa_data(user, token, otp_secret, tmp_id): | |||||
def two_factor_is_enabled_for_(user): | def two_factor_is_enabled_for_(user): | ||||
'''Check if 2factor is enabled for user.''' | '''Check if 2factor is enabled for user.''' | ||||
if user == "Administrator": | |||||
return False | |||||
if isinstance(user, string_types): | if isinstance(user, string_types): | ||||
user = frappe.get_doc('User', user) | user = frappe.get_doc('User', user) | ||||
@@ -307,9 +307,11 @@ def search(text, start=0, limit=20, doctype=""): | |||||
limit {start}, {limit}'''.format(start=start, limit=limit), (doctype, text), as_dict=True) | limit {start}, {limit}'''.format(start=start, limit=limit), (doctype, text), as_dict=True) | ||||
for r in results: | for r in results: | ||||
if frappe.get_meta(r.doctype).image_field: | |||||
doc = frappe.get_doc(r.doctype, r.name) | |||||
r.image = doc.get(doc.meta.image_field) | |||||
try: | |||||
if frappe.get_meta(r.doctype).image_field: | |||||
r.image = frappe.db.get_value(r.doctype, r.name, frappe.get_meta(r.doctype).image_field) | |||||
except Exception: | |||||
frappe.clear_messages() | |||||
return results | return results | ||||
@@ -25,7 +25,7 @@ ipython | |||||
html2text | html2text | ||||
email_reply_parser | email_reply_parser | ||||
click | click | ||||
num2words==0.5.4 | |||||
num2words==0.5.5 | |||||
watchdog==0.8.0 | watchdog==0.8.0 | ||||
bleach | bleach | ||||
bleach-whitelist | bleach-whitelist | ||||