ソースを参照

Merge branch 'develop' of frappe/frappe into FrappeOAuth2Client

version-14
Revant Nandgaonkar 7年前
コミット
f60a04c4ed
30個のファイルの変更980行の追加4073行の削除
  1. +0
    -1
      .eslintrc
  2. +46
    -0
      CODE_OF_CONDUCT.md
  3. +1
    -1
      frappe/__init__.py
  4. +5
    -2
      frappe/core/doctype/doctype/doctype.js
  5. +6
    -24
      frappe/desk/page/activity/activity.js
  6. +2
    -2
      frappe/desk/reportview.py
  7. +8
    -8
      frappe/model/delete_doc.py
  8. +1
    -0
      frappe/patches.txt
  9. +0
    -0
      frappe/patches/v8_10/__init__.py
  10. +5
    -0
      frappe/patches/v8_10/delete_static_web_page_from_global_search.py
  11. +0
    -2
      frappe/public/build.json
  12. +0
    -140
      frappe/public/css/cal-heatmap.css
  13. +4
    -0
      frappe/public/css/form.css
  14. +69
    -71
      frappe/public/css/graphs.css
  15. +2
    -1
      frappe/public/js/frappe/form/controls/text_editor.js
  16. +7
    -39
      frappe/public/js/frappe/form/dashboard.js
  17. +1
    -1
      frappe/public/js/frappe/form/templates/form_dashboard.html
  18. +595
    -91
      frappe/public/js/frappe/ui/graphs.js
  19. +2
    -0
      frappe/public/js/frappe/ui/toolbar/search_utils.js
  20. +1
    -1
      frappe/public/js/frappe/views/reports/print_grid.html
  21. +1
    -0
      frappe/public/js/frappe/views/reports/query_report.js
  22. +0
    -3471
      frappe/public/js/lib/cal-heatmap.js
  23. +5
    -0
      frappe/public/less/form.less
  24. +204
    -210
      frappe/public/less/graphs.less
  25. +1
    -1
      frappe/tests/test_twofactor.py
  26. +4
    -2
      frappe/tests/ui/data/test_lib.js
  27. +1
    -1
      frappe/tests/ui/test_test_runner.py
  28. +3
    -0
      frappe/twofactor.py
  29. +5
    -3
      frappe/utils/global_search.py
  30. +1
    -1
      requirements.txt

+ 0
- 1
.eslintrc ファイルの表示

@@ -49,7 +49,6 @@
"moment": true,
"hljs": true,
"Awesomplete": true,
"CalHeatMap": true,
"Sortable": true,
"Showdown": true,
"Taggle": true,


+ 46
- 0
CODE_OF_CONDUCT.md ファイルの表示

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

+ 1
- 1
frappe/__init__.py ファイルの表示

@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
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"

local = Local()


+ 5
- 2
frappe/core/doctype/doctype/doctype.js ファイルの表示

@@ -13,9 +13,12 @@

frappe.ui.form.on('DocType', {
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("beta", 0);
}

if(!frappe.boot.developer_mode && !frm.doc.custom) {


+ 6
- 24
frappe/desk/page/activity/activity.js ファイルの表示

@@ -180,30 +180,12 @@ frappe.activity.render_heatmap = function(page) {
method: "frappe.desk.page.activity.activity.get_heatmap_data",
callback: function(r) {
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);


+ 2
- 2
frappe/desk/reportview.py ファイルの表示

@@ -152,8 +152,8 @@ def export_query():
writer = csv.writer(f)
for r in data:
# 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)
frappe.response['result'] = text_type(f.read(), 'utf-8')


+ 8
- 8
frappe/model/delete_doc.py ファイルの表示

@@ -244,6 +244,14 @@ def delete_dynamic_links(doctype, name):
frappe.db.sql('''delete from `tabEmail Unsubscribe`
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
frappe.db.sql("""delete from `tabCommunication`
where
@@ -268,14 +276,6 @@ def delete_dynamic_links(doctype, name):
set timeline_doctype=null, timeline_name=null
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):
from frappe.utils import get_fullname



+ 1
- 0
frappe/patches.txt ファイルの表示

@@ -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_5.patch_event_colors
frappe.patches.v8_7.update_email_queue_status
frappe.patches.v8_10.delete_static_web_page_from_global_search

+ 0
- 0
frappe/patches/v8_10/__init__.py ファイルの表示


+ 5
- 0
frappe/patches/v8_10/delete_static_web_page_from_global_search.py ファイルの表示

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

+ 0
- 2
frappe/public/build.json ファイルの表示

@@ -97,7 +97,6 @@
"public/css/bootstrap.css",
"public/css/font-awesome.css",
"public/css/octicons/octicons.css",
"public/css/cal-heatmap.css",
"public/css/c3.min.css",
"public/css/desk.css",
"public/css/indicator.css",
@@ -231,7 +230,6 @@
],
"js/d3.min.js": [
"public/js/lib/d3.min.js",
"public/js/lib/cal-heatmap.js",
"public/js/lib/c3.min.js"
],
"css/module.min.css": [


+ 0
- 140
frappe/public/css/cal-heatmap.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;
}

+ 4
- 0
frappe/public/css/form.css ファイルの表示

@@ -98,6 +98,10 @@
.form-dashboard-section:last-child {
border-bottom: none;
}
.form-heatmap .heatmap {
display: flex;
justify-content: center;
}
.form-heatmap .heatmap-message {
margin-top: 10px;
}


+ 69
- 71
frappe/public/css/graphs.css ファイルの表示

@@ -2,9 +2,10 @@
.graph-container .graph-focus-margin {
margin: 0px 5%;
}
.graph-container .graph-graphics {
.graph-container .graphics {
margin-top: 10px;
padding: 10px 0px;
padding-top: 10px;
padding-bottom: 10px;
position: relative;
}
.graph-container .graph-stats-group {
@@ -34,31 +35,28 @@
.graph-container .graph-stats-container .graph-data .stats-value {
color: #98d85b;
}
.graph-container .bar-graph .axis,
.graph-container .line-graph .axis {
.graph-container .axis,
.graph-container .chart-label {
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);
}
.graph-container .percentage-graph {
margin-top: 35px;
}
.graph-container .percentage-graph .progress {
margin-bottom: 0px;
}
.graph-container .graph-data-points circle {
.graph-container .data-points circle {
stroke: #fff;
stroke-width: 2;
}
.graph-container .graph-data-points path {
.graph-container .data-points path {
fill: none;
stroke-opacity: 1;
stroke-width: 2px;
}
.graph-container line.graph-dashed {
.graph-container line.dashed {
stroke-dasharray: 5,3;
}
.graph-container .tick.x-axis-label {
@@ -73,7 +71,7 @@
.graph-container .tick .x-value-text {
text-anchor: middle;
}
.graph-container .graph-svg-tip {
.graph-svg-tip {
position: absolute;
z-index: 99999;
padding: 10px;
@@ -83,12 +81,12 @@
background: rgba(0, 0, 0, 0.8);
border-radius: 3px;
}
.graph-container .graph-svg-tip.comparison {
.graph-svg-tip.comparison {
padding: 0;
text-align: left;
pointer-events: none;
}
.graph-container .graph-svg-tip.comparison .title {
.graph-svg-tip.comparison .title {
display: block;
padding: 10px;
margin: 0;
@@ -96,28 +94,28 @@
line-height: 1;
pointer-events: none;
}
.graph-container .graph-svg-tip.comparison ul {
.graph-svg-tip.comparison ul {
margin: 0;
white-space: nowrap;
list-style: none;
}
.graph-container .graph-svg-tip.comparison li {
.graph-svg-tip.comparison li {
display: inline-block;
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;
display: flex;
}
.graph-container .graph-svg-tip ul.data-point-list li {
.graph-svg-tip ul.data-point-list li {
min-width: 90px;
flex: 1;
}
.graph-container .graph-svg-tip strong {
.graph-svg-tip strong {
color: #dfe2e5;
}
.graph-container .graph-svg-tip::after {
.graph-svg-tip .svg-pointer {
position: absolute;
bottom: -10px;
left: 50%;
@@ -128,147 +126,147 @@
border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8);
}
.graph-container .stroke.grey {
.stroke.grey {
stroke: #F0F4F7;
}
.graph-container .stroke.blue {
.stroke.blue {
stroke: #5e64ff;
}
.graph-container .stroke.red {
.stroke.red {
stroke: #ff5858;
}
.graph-container .stroke.light-green {
.stroke.light-green {
stroke: #98d85b;
}
.graph-container .stroke.green {
.stroke.green {
stroke: #28a745;
}
.graph-container .stroke.orange {
.stroke.orange {
stroke: #ffa00a;
}
.graph-container .stroke.purple {
.stroke.purple {
stroke: #743ee2;
}
.graph-container .stroke.darkgrey {
.stroke.darkgrey {
stroke: #b8c2cc;
}
.graph-container .stroke.black {
.stroke.black {
stroke: #36414C;
}
.graph-container .stroke.yellow {
.stroke.yellow {
stroke: #FEEF72;
}
.graph-container .stroke.light-blue {
.stroke.light-blue {
stroke: #7CD6FD;
}
.graph-container .stroke.lightblue {
.stroke.lightblue {
stroke: #7CD6FD;
}
.graph-container .fill.grey {
.fill.grey {
fill: #F0F4F7;
}
.graph-container .fill.blue {
.fill.blue {
fill: #5e64ff;
}
.graph-container .fill.red {
.fill.red {
fill: #ff5858;
}
.graph-container .fill.light-green {
.fill.light-green {
fill: #98d85b;
}
.graph-container .fill.green {
.fill.green {
fill: #28a745;
}
.graph-container .fill.orange {
.fill.orange {
fill: #ffa00a;
}
.graph-container .fill.purple {
.fill.purple {
fill: #743ee2;
}
.graph-container .fill.darkgrey {
.fill.darkgrey {
fill: #b8c2cc;
}
.graph-container .fill.black {
.fill.black {
fill: #36414C;
}
.graph-container .fill.yellow {
.fill.yellow {
fill: #FEEF72;
}
.graph-container .fill.light-blue {
.fill.light-blue {
fill: #7CD6FD;
}
.graph-container .fill.lightblue {
.fill.lightblue {
fill: #7CD6FD;
}
.graph-container .background.grey {
.background.grey {
background: #F0F4F7;
}
.graph-container .background.blue {
.background.blue {
background: #5e64ff;
}
.graph-container .background.red {
.background.red {
background: #ff5858;
}
.graph-container .background.light-green {
.background.light-green {
background: #98d85b;
}
.graph-container .background.green {
.background.green {
background: #28a745;
}
.graph-container .background.orange {
.background.orange {
background: #ffa00a;
}
.graph-container .background.purple {
.background.purple {
background: #743ee2;
}
.graph-container .background.darkgrey {
.background.darkgrey {
background: #b8c2cc;
}
.graph-container .background.black {
.background.black {
background: #36414C;
}
.graph-container .background.yellow {
.background.yellow {
background: #FEEF72;
}
.graph-container .background.light-blue {
.background.light-blue {
background: #7CD6FD;
}
.graph-container .background.lightblue {
.background.lightblue {
background: #7CD6FD;
}
.graph-container .border-top.grey {
.border-top.grey {
border-top: 3px solid #F0F4F7;
}
.graph-container .border-top.blue {
.border-top.blue {
border-top: 3px solid #5e64ff;
}
.graph-container .border-top.red {
.border-top.red {
border-top: 3px solid #ff5858;
}
.graph-container .border-top.light-green {
.border-top.light-green {
border-top: 3px solid #98d85b;
}
.graph-container .border-top.green {
.border-top.green {
border-top: 3px solid #28a745;
}
.graph-container .border-top.orange {
.border-top.orange {
border-top: 3px solid #ffa00a;
}
.graph-container .border-top.purple {
.border-top.purple {
border-top: 3px solid #743ee2;
}
.graph-container .border-top.darkgrey {
.border-top.darkgrey {
border-top: 3px solid #b8c2cc;
}
.graph-container .border-top.black {
.border-top.black {
border-top: 3px solid #36414C;
}
.graph-container .border-top.yellow {
.border-top.yellow {
border-top: 3px solid #FEEF72;
}
.graph-container .border-top.light-blue {
.border-top.light-blue {
border-top: 3px solid #7CD6FD;
}
.graph-container .border-top.lightblue {
.border-top.lightblue {
border-top: 3px solid #7CD6FD;
}

+ 2
- 1
frappe/public/js/frappe/form/controls/text_editor.js ファイルの表示

@@ -46,7 +46,8 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
// this function is executed only once
$(".note-editable[contenteditable='true']").one('focus', function() {
var $this = $(this);
$this.html($this.html() + '<br>');
if(!$this.html())
$this.html($this.html() + '<br>');
});
},
onChange: function(value) {


+ 7
- 39
frappe/public/js/frappe/form/dashboard.js ファイルの表示

@@ -334,22 +334,12 @@ frappe.ui.form.Dashboard = Class.extend({
// heatmap
render_heatmap: function() {
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
@@ -388,16 +378,14 @@ frappe.ui.form.Dashboard = Class.extend({
return indicator;
},

//graphs
// graphs
setup_graph: function() {
var me = this;

var method = this.data.graph_method;
var args = {
doctype: this.frm.doctype,
docname: this.frm.doc.name,
};

$.extend(args, this.data.graph_method_args);

frappe.call({
@@ -421,29 +409,9 @@ frappe.ui.form.Dashboard = Class.extend({
mode: 'line',
height: 140
});

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() {
this.section.removeClass('hidden');
}


+ 1
- 1
frappe/public/js/frappe/form/templates/form_dashboard.html ファイルの表示

@@ -2,7 +2,7 @@
<div class="progress-area hidden form-dashboard-section">
</div>
<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>
<div class="form-graph form-dashboard-section hidden"></div>


+ 595
- 91
frappe/public/js/frappe/ui/graphs.js ファイルの表示

@@ -28,7 +28,6 @@ frappe.ui.Graph = class Graph {
specific_values = [],
summary = [],

color = 'blue',
mode = '',
}) {

@@ -43,27 +42,28 @@ frappe.ui.Graph = class Graph {
}

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.subtitle = subtitle;

// Begin axis graph-related args

this.y = y;
this.x = x;

this.specific_values = specific_values;
this.summary = summary;

this.color = color;
this.mode = mode;

// this.current_hover_index = 0;
// this.current_selected_index = 0;

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));
}
@@ -81,18 +81,16 @@ frappe.ui.Graph = class Graph {

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_components();
this.setup_values();

this.setup_utils();

this.setup_components();
this.make_graph_components();

this.make_tooltip();

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() {
// Graph needs a dedicated parent element
this.parent.empty();
@@ -110,11 +122,11 @@ frappe.ui.Graph = class Graph {
.addClass('graph-container')
.append($(`<h6 class="title" style="margin-top: 15px;">${this.title}</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>`))
.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.$graph = $('<div>')
@@ -130,6 +142,13 @@ frappe.ui.Graph = class Graph {
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() {
// Multiplier
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() {
this.make_y_axis();
this.make_x_axis();
this.y_colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen',
'yellow', 'orange', 'red']

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) {
@@ -246,6 +260,11 @@ frappe.ui.Graph = class Graph {
transform: `translate(0,${start_at})`
});
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.snap.line(0, 0, 0, height),
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) {
let d = this.unit_args;
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.y[dataset_index].data_units.push(data_unit);
});
@@ -272,75 +296,58 @@ frappe.ui.Graph = class Graph {
make_path() { }

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();
}

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) => {
let offset = $(this.$graphics).offset();
let offset = this.$graphics.offset();
var relX = e.pageX - offset.left - this.translate_x;
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 {
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() {
this.specific_values.map(d => {
this.specific_y_lines.add(this.snap.g(
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({
dy: ".32em",
@@ -434,6 +441,9 @@ frappe.ui.Graph = class Graph {

let width = total_width / args.no_of_datasets;
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({
class: `bar mini fill ${color}`
});
@@ -442,6 +452,11 @@ frappe.ui.Graph = class Graph {
return this.snap.circle(x, y, args.radius).attr({
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 = {
type: 'bar',
args: {
// More intelligent width setting
space_width: this.y.length > 1 ?
me.avg_unit_width/2 : me.avg_unit_width/8,
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 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);
}
};
@@ -512,7 +528,9 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph {
}

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({
style: `padding-top: 0px; margin-bottom: 30px;`
});
@@ -533,37 +551,523 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph {
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_components() {
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() {
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.totals.map((total, 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.$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() {
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.$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();
// }
// }

+ 2
- 0
frappe/public/js/frappe/ui/toolbar/search_utils.js ファイルの表示

@@ -490,6 +490,8 @@ frappe.search.utils = {
// 0 - 6 for fuzzy contain

// **Specific use-case step**
keywords = keywords || '';

var item = __(_item || '').replace(/-/g, " ");

var ilen = item.length;


+ 1
- 1
frappe/public/js/frappe/views/reports/print_grid.html ファイルの表示

@@ -24,7 +24,7 @@
{% for col in columns %}
{% 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
? col.formatter(row._index, col._index, value, col, row, true)


+ 1
- 0
frappe/public/js/frappe/views/reports/query_report.js ファイルの表示

@@ -3,6 +3,7 @@

frappe.provide("frappe.views");
frappe.provide("frappe.query_reports");
frappe.provide("frappe.ui.graphs");

frappe.standard_pages["query-report"] = function() {
var wrapper = frappe.container.add_page('query-report');


+ 0
- 3471
frappe/public/js/lib/cal-heatmap.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 5
- 0
frappe/public/less/form.less ファイルの表示

@@ -136,6 +136,11 @@

.form-heatmap {

.heatmap {
display: flex;
justify-content: center;
}

.heatmap-message {
margin-top: 10px;
}


+ 204
- 210
frappe/public/less/graphs.less ファイルの表示

@@ -5,9 +5,10 @@
margin: 0px 5%;
}

.graph-graphics {
.graphics {
margin-top: 10px;
padding: 10px 0px;
padding-top: 10px;
padding-bottom: 10px;
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 {
margin-top: 35px;

.progress {
margin-bottom: 0px;
}
}

.graph-data-points {
.data-points {
circle {
// fill: #28a745;
stroke: #fff;
@@ -83,7 +78,7 @@
}
}

line.graph-dashed {
line.dashed {
stroke-dasharray: 5,3;
}

@@ -104,216 +99,215 @@
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;

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

+ 1
- 1
frappe/tests/test_twofactor.py ファイルの表示

@@ -51,6 +51,7 @@ class TestTwoFactor(unittest.TestCase):
'''Should return true if enabled for user.'''
toggle_2fa_all_role(state=True)
self.assertTrue(two_factor_is_enabled_for_(self.user))
self.assertFalse(two_factor_is_enabled_for_("Administrator"))
toggle_2fa_all_role(state=False)
self.assertFalse(two_factor_is_enabled_for_(self.user))

@@ -87,7 +88,6 @@ class TestTwoFactor(unittest.TestCase):
_str = render_string_template(_str,args)
self.assertEqual(_str,'Verification Code from Frappe Technologies')


def set_request(**kwargs):
builder = EnvironBuilder(**kwargs)
frappe.local.request = Request(builder.get_environ())


+ 4
- 2
frappe/tests/ui/data/test_lib.js ファイルの表示

@@ -36,7 +36,8 @@ frappe.tests = {
}
};
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,
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));
}
});



+ 1
- 1
frappe/tests/ui/test_test_runner.py ファイルの表示

@@ -22,7 +22,7 @@ class TestTestRunner(unittest.TestCase):
test, comment = test.split('#')
test = test.strip()
if comment.strip()=='long':
timeout = 240
timeout = 300

print('Running {0}...'.format(test))



+ 3
- 0
frappe/twofactor.py ファイルの表示

@@ -74,6 +74,9 @@ def cache_2fa_data(user, token, otp_secret, tmp_id):

def two_factor_is_enabled_for_(user):
'''Check if 2factor is enabled for user.'''
if user == "Administrator":
return False

if isinstance(user, string_types):
user = frappe.get_doc('User', user)



+ 5
- 3
frappe/utils/global_search.py ファイルの表示

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

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



+ 1
- 1
requirements.txt ファイルの表示

@@ -25,7 +25,7 @@ ipython
html2text
email_reply_parser
click
num2words==0.5.4
num2words==0.5.5
watchdog==0.8.0
bleach
bleach-whitelist


読み込み中…
キャンセル
保存