Selaa lähdekoodia

[WIP] Feedback Triggers (#2643)

* [wip] feedback

* [WIP] Feedback Trigger, Feedback Request, Feedback & Rating Timeline and Form Sidebar

* [minor] minor fixes in feedback trigger

* [Test Cases] Test Cases for Feedback Trigger

* [minor] replaced sql_list by get_all changed the button label

* [minor] removed whitespaces, fixed spelling mistakes
version-14
Makarand Bauskar 8 vuotta sitten
committed by Rushabh Mehta
vanhempi
commit
e422e16953
26 muutettua tiedostoa jossa 1547 lisäystä ja 102 poistoa
  1. +61
    -1
      frappe/core/doctype/communication/communication.js
  2. +114
    -2
      frappe/core/doctype/communication/communication.json
  3. +0
    -0
      frappe/core/doctype/feedback_request/__init__.py
  4. +8
    -0
      frappe/core/doctype/feedback_request/feedback_request.js
  5. +312
    -0
      frappe/core/doctype/feedback_request/feedback_request.json
  6. +28
    -0
      frappe/core/doctype/feedback_request/feedback_request.py
  7. +12
    -0
      frappe/core/doctype/feedback_request/test_feedback_request.py
  8. +0
    -0
      frappe/core/doctype/feedback_trigger/__init__.py
  9. +50
    -0
      frappe/core/doctype/feedback_trigger/feedback_trigger.js
  10. +455
    -0
      frappe/core/doctype/feedback_trigger/feedback_trigger.json
  11. +109
    -0
      frappe/core/doctype/feedback_trigger/feedback_trigger.py
  12. +102
    -0
      frappe/core/doctype/feedback_trigger/test_feedback_trigger.py
  13. +18
    -4
      frappe/desk/form/load.py
  14. +1
    -2
      frappe/email/doctype/email_alert/email_alert.py
  15. +4
    -2
      frappe/hooks.py
  16. +4
    -2
      frappe/public/build.json
  17. +58
    -46
      frappe/public/js/frappe/form/footer/timeline.js
  18. +29
    -23
      frappe/public/js/frappe/form/footer/timeline_item.html
  19. +25
    -18
      frappe/public/js/frappe/form/grid.js
  20. +13
    -0
      frappe/public/js/frappe/form/sidebar.js
  21. +7
    -1
      frappe/public/js/frappe/form/templates/form_sidebar.html
  22. +6
    -0
      frappe/public/js/frappe/misc/rating_icons.html
  23. +8
    -0
      frappe/public/js/frappe/misc/ratings.html
  24. +1
    -1
      frappe/utils/jinja.py
  25. +94
    -0
      frappe/www/feedback.html
  26. +28
    -0
      frappe/www/feedback.py

+ 61
- 1
frappe/core/doctype/communication/communication.js Näytä tiedosto

@@ -31,6 +31,12 @@ frappe.ui.form.on("Communication", {
}
}

if(frm.doc.communication_type == "Feedback") {
frm.add_custom_button(__("Resend"), function() {
frm.events.resend_feedback(frm);
});
}

if(frm.doc.status==="Open") {
frm.add_custom_button(__("Close"), function() {
frm.set_value("status", "Closed");
@@ -93,6 +99,60 @@ frappe.ui.form.on("Communication", {
}
});
d.show();
}
},

resend_feedback: function(frm) {
/* resend the feedback request email */
return frappe.call({
method: "frappe.core.doctype.feedback_trigger.feedback_trigger.get_feedback_alert_details",
args: {
request: frm.doc.feedback_request,
reference_name: frm.doc.reference_name,
reference_doctype: frm.doc.reference_doctype
},
callback: function(r) {
if(r.message) {
details = r.message;

dialog = new frappe.ui.Dialog({
title: __("Resend Feedback Request"),
fields: [
{
"reqd": 1,
"label": __("Message"),
"fieldname": "message",
"fieldtype": "Text Editor",
"default": details.message
}
],
});

dialog.set_primary_action(__("Send"), function() {
args = dialog.get_values();
if(!args)
return;
else
details.message = args.message
dialog.hide();

return frappe.call({
method: "frappe.core.doctype.feedback_trigger.feedback_trigger.send_feedback_alert",
args: {
reference_name: frm.doc.reference_name,
reference_doctype: frm.doc.reference_doctype,
alert_details: details,
},
freeze: true,
callback: function(r) {
frappe.msgprint(__("Feedback Alert for {0} is sent to {1}",
[frm.doc.reference_name, frm.doc.sender]));
}
})
});

dialog.show();
}
}
});
}
});

+ 114
- 2
frappe/core/doctype/communication/communication.json Näytä tiedosto

@@ -400,7 +400,7 @@
"label": "Communication Type",
"length": 0,
"no_copy": 0,
"options": "Communication\nComment\nChat\nBot\nNotification",
"options": "Communication\nComment\nChat\nBot\nNotification\nFeedback",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1208,6 +1208,118 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "feedback_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Feedback",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "feedback",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Feedback",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rating",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Rating",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "feedback_request",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Feedback Request",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@@ -1221,7 +1333,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-01-20 05:20:58.187840",
"modified": "2017-01-27 16:11:37.344686",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",


+ 0
- 0
frappe/core/doctype/feedback_request/__init__.py Näytä tiedosto


+ 8
- 0
frappe/core/doctype/feedback_request/feedback_request.js Näytä tiedosto

@@ -0,0 +1,8 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Feedback Request', {
refresh: function(frm) {

}
});

+ 312
- 0
frappe/core/doctype/feedback_request/feedback_request.json Näytä tiedosto

@@ -0,0 +1,312 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:key",
"beta": 0,
"creation": "2017-01-27 15:43:33.780808",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_sent",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Sent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_feedback_submitted",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Feedback Submitted",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "feedback_trigger",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Feedback Trigger",
"length": 0,
"no_copy": 0,
"options": "Feedback Trigger",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-01-30 16:37:34.346362",
"modified_by": "Administrator",
"module": "Core",
"name": "Feedback Request",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

+ 28
- 0
frappe/core/doctype/feedback_request/feedback_request.py Näytä tiedosto

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

class FeedbackRequest(Document):
def before_insert(self):
from frappe.utils import random_string

self.key = random_string(32)

@frappe.whitelist(allow_guest=True)
def is_valid_feedback_request(key=None):
if not key:
return False

is_feedback_submitted = frappe.db.get_value("Feedback Request", key, "is_feedback_submitted")
if is_feedback_submitted:
return False
else:
return True

def delete_feedback_request():
""" clear 100 days old feedback request """
frappe.db.sql("""delete from `tabFeedback Request` where creation<DATE_SUB(NOW(), INTERVAL 100 DAY)""")

+ 12
- 0
frappe/core/doctype/feedback_request/test_feedback_request.py Näytä tiedosto

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest

# test_records = frappe.get_test_records('Feedback Request')

class TestFeedbackRequest(unittest.TestCase):
pass

+ 0
- 0
frappe/core/doctype/feedback_trigger/__init__.py Näytä tiedosto


+ 50
- 0
frappe/core/doctype/feedback_trigger/feedback_trigger.js Näytä tiedosto

@@ -0,0 +1,50 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Feedback Trigger', {
onload: function(frm) {
frm.set_query("document_type", function() {
return {
"filters": {
"istable": 0
}
}
})
},

refresh: function(frm) {
frm.events.setup_field_options(frm);
},

document_type: function(frm) {
frm.set_value('email_field', '');
frm.set_value('email_fieldname');

frm.events.setup_field_options(frm);
},

email_field: function(frm) {
frm.set_value('email_fieldname', frm.fieldname_mapper[frm.doc.email_field]);
},

setup_field_options: function(frm) {
frm.fieldname_mapper = {};
frm.options = [];

if(!frm.doc.document_type)
return

frappe.model.with_doctype(frm.doc.document_type, function() {
var fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
$.each(fields, function(idx, field) {
if(!inList(frappe.model.no_value_type, field.fieldtype) && field.options == "Email") {
frm.options.push(field.label);
frm.fieldname_mapper[field.label] = field.fieldname;
}
})

frm.set_df_property("email_field", "options", [""].concat(frm.options));
frm.refresh_fields();
});
}
});

+ 455
- 0
frappe/core/doctype/feedback_trigger/feedback_trigger.json Näytä tiedosto

@@ -0,0 +1,455 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:document_type",
"beta": 0,
"creation": "2017-01-24 15:46:38.366213",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "document_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.document_type",
"fieldname": "email_field",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Field",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_fieldname",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Fieldname",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>{{ doc.name }} Delivered</code></pre></div>",
"fieldname": "subject",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subject",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Optional: The alert will be sent if this expression is true",
"fieldname": "condition",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Condition",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "html_8",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Closed\"\ndoc.due_date==nowdate()\ndoc.total &gt; 40000\n</pre>",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "message",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "example",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Example",
"length": 0,
"no_copy": 0,
"options": "<h5>Message Example</h5>\n\n<pre>&lt;h3&gt;Issue Resolved&lt;/h3&gt;\n\n&lt;p&gt;Issue {{ doc.name }} Is resolved. Please check and confirm the same.&lt;/p&gt;\n\n&lt;p&gt; Your Feedback is important for us. Please give us your Feedback for {{ doc.name }}&lt;/p&gt;\n\n&lt;p&gt; Please visit the following url for feedback.&lt;/p&gt;\n\n{{ feedback_url }}\n\n&lt;h4&gt;Details&lt;/h4&gt;</pre>",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-01-29 10:45:11.175365",
"modified_by": "Administrator",
"module": "Core",
"name": "Feedback Trigger",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "document_type",
"track_changes": 1,
"track_seen": 0
}

+ 109
- 0
frappe/core/doctype/feedback_trigger/feedback_trigger.py Näytä tiedosto

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import json
import frappe
from frappe import _
from frappe.utils import get_url
from frappe.model.document import Document
from frappe.utils.jinja import validate_template

class FeedbackTrigger(Document):
def validate(self):
validate_template(self.subject)
validate_template(self.message)
self.validate_condition()

def validate_condition(self):
temp_doc = frappe.new_doc(self.document_type)
if self.condition:
try:
eval(self.condition, get_context(temp_doc))
except:
frappe.throw(_("The Condition '{0}' is invalid").format(self.condition))

@frappe.whitelist()
def send_feedback_alert(reference_doctype, reference_name, trigger=None, alert_details=None):
""" send feedback alert """
details = json.loads(alert_details) if alert_details else \
get_feedback_alert_details(reference_doctype, reference_name, trigger=trigger)

if details:
feedback_request = details.pop("feedback_request")
frappe.sendmail(**details)
frappe.db.set_value("Feedback Request", feedback_request, "is_sent", 1)

def trigger_feedback_alert(doc, method):
""" trigger the feedback alert"""

feedback_trigger = frappe.db.get_value("Feedback Trigger", { "enabled": 1, "document_type": doc.doctype })
if feedback_trigger:
frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.send_feedback_alert',
trigger=feedback_trigger, reference_doctype=doc.doctype, reference_name=doc.name, now=frappe.flags.in_test)

@frappe.whitelist()
def get_feedback_alert_details(reference_doctype, reference_name, trigger=None, request=None):
feedback_url = ""

if not trigger and not request:
frappe.throw("Can not find Feedback Alert for {0}".format(reference_name))
elif not trigger and request:
trigger = frappe.db.get_value("Feedback Request", request, "feedback_trigger")

# check if feedback mail alert is already sent but feedback is not submitted
# to avoid sending multiple feedback mail alerts

feedback_requests = frappe.get_all("Feedback Request", {
"is_sent": 1,
"is_feedback_submitted": 0,
"reference_name": reference_name,
"reference_doctype": reference_doctype
}, ["name"])
if feedback_requests:
frappe.throw(_("Feedback Alert Mail has been already sent to the recipient"))

feedback_trigger = frappe.get_doc("Feedback Trigger", trigger)
doc = frappe.get_doc(reference_doctype, reference_name)

context = get_context(doc)

recipients = doc.get(feedback_trigger.email_fieldname, None)
if recipients and eval(feedback_trigger.condition, context):
subject = feedback_trigger.subject
feedback_request = frappe.get_doc({
"doctype": "Feedback Request",
"reference_name": doc.name,
"reference_doctype": doc.doctype,
"feedback_trigger": feedback_trigger.name
}).insert(ignore_permissions=True)

feedback_url = "{base_url}/feedback?reference_doctype={doctype}&reference_name={docname}&email={email_id}&key={nonce}".format(
base_url=get_url(),
doctype=doc.doctype,
docname=doc.name,
email_id=recipients,
nonce=feedback_request.name
)
context.update({ "alert": feedback_trigger, "feedback_url": feedback_url })

if "{" in subject:
subject = frappe.render_template(feedback_trigger.subject, context)

feedback_alert_message = frappe.render_template(feedback_trigger.message, context)

return {
"subject": subject,
"recipients": recipients,
"reference_name":doc.name,
"reference_doctype":doc.doctype,
"message": feedback_alert_message,
"feedback_request": feedback_request.name
}
else:
return None

def get_context(doc):
return { "doc": doc }

+ 102
- 0
frappe/core/doctype/feedback_trigger/test_feedback_trigger.py Näytä tiedosto

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest

# test_records = frappe.get_test_records('Feedback Trigger')
def get_feedback_request(todo, feedback_trigger):
return frappe.db.get_value("Feedback Request", {
"is_sent": 1,
"is_feedback_submitted": 0,
"reference_doctype": "ToDo",
"reference_name": todo,
"feedback_trigger": feedback_trigger
})

class TestFeedbackTrigger(unittest.TestCase):
def setUp(self):
new_user = frappe.get_doc(dict(doctype='User', email='test-feedback@example.com',
first_name='Tester')).insert(ignore_permissions=True)
new_user.add_roles("System Manager")

def tearDown(self):
frappe.delete_doc("User", "test-feedback@example.com")
frappe.delete_doc("Feedback Trigger", "ToDo")
frappe.db.sql('delete from `tabEmail Queue`')
frappe.db.sql('delete from `tabFeedback Request`')

def test_feedback_trigger(self):
""" Test feedback trigger """
from frappe.www.feedback import accept

frappe.delete_doc("Feedback Trigger", "ToDo")
frappe.db.sql('delete from `tabEmail Queue`')
frappe.db.sql('delete from `tabFeedback Request`')

feedback_trigger = frappe.get_doc({
"enabled": 1,
"doctype": "Feedback Trigger",
"document_type": "ToDo",
"email_field": "assigned_by",
"subject": "{{ doc.name }} Task Completed",
"condition": "doc.status == 'Closed'",
"message": """Task {{ doc.name }} is Completed by {{ doc.owner }}.
<br>Please visit the {{ feedback_url }} and give your feedback
regarding the Task {{ doc.name }}"""
}).insert(ignore_permissions=True)

# create a todo
todo = frappe.get_doc({
"doctype": "ToDo",
"owner": "test-feedback@example.com",
"allocated_by": "test-feedback@example.com",
"description": "Unable To Submit Sales Order #SO-00001"
}).insert(ignore_permissions=True)

email_queue = frappe.db.sql("""select name from `tabEmail Queue` where
reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name))

# feedback alert mail should be sent only on 'Closed' status
self.assertFalse(email_queue)

# check if feedback mail alert is triggered
todo.status = "Closed"
todo.save(ignore_permissions=True)

email_queue = frappe.db.sql("""select name from `tabEmail Queue` where
reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name))

self.assertTrue(email_queue)
frappe.db.sql('delete from `tabEmail Queue`')

# test if feedback is submitted for the todo
feedback_request = get_feedback_request(todo.name, feedback_trigger.name)
self.assertTrue(feedback_request)

# test if mail alerts are triggered multiple times for same document
self.assertRaises(Exception, todo.save, ignore_permissions=True)

# Test if feedback is submitted sucessfully
result = accept(feedback_request, "test-feedback@example.com", "ToDo", todo.name, "Great Work !!", 4)
self.assertTrue(result)

# test if feedback is saved in Communication
docname = frappe.db.get_value("Communication", {
"reference_doctype": "ToDo",
"reference_name": todo.name,
"communication_type": "Feedback",
"feedback_request": feedback_request
})

communication = frappe.get_doc("Communication", docname)
self.assertEqual(communication.rating, 4)
self.assertEqual(communication.feedback, "Great Work !!")

# test if link expired after feedback submission
self.assertRaises(Exception, accept, key=feedback_request, sender="test-feedback@example.com",
reference_doctype="ToDo", reference_name=todo.name, feedback="Thank You !!", rating=4)

frappe.delete_doc("ToDo", todo.name)

+ 18
- 4
frappe/desk/form/load.py Näytä tiedosto

@@ -97,7 +97,8 @@ def get_docinfo(doc=None, doctype=None, name=None):
'versions': get_versions(doc),
"assignments": get_assignments(doc.doctype, doc.name),
"permissions": get_doc_permissions(doc),
"shared": frappe.share.get_users(doc.doctype, doc.name)
"shared": frappe.share.get_users(doc.doctype, doc.name),
"rating": get_feedback_rating(doc.doctype, doc.name)
}

def get_user_permissions(meta):
@@ -139,7 +140,6 @@ def _get_communications(doctype, name, start=0, limit=20):

elif c.communication_type=="Comment" and c.comment_type=="Comment":
c.content = frappe.utils.markdown(c.content)

return communications

def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=None,
@@ -152,9 +152,9 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
timeline_doctype, timeline_name,
reference_doctype, reference_name,
link_doctype, link_name,
"Communication" as doctype'''
rating, feedback, "Communication" as doctype'''

conditions = '''communication_type in ("Communication", "Comment")
conditions = '''communication_type in ("Communication", "Comment", "Feedback")
and (
(reference_doctype=%(doctype)s and reference_name=%(name)s)
or (
@@ -206,3 +206,17 @@ def get_badge_info(doctypes, filters):
def run_onload(doc):
doc.set("__onload", frappe._dict())
doc.run_method("onload")

def get_feedback_rating(doctype, docname):
""" get and return the latest feedback rating if available """

rating= frappe.get_all("Communication", filters={
"reference_doctype": doctype,
"reference_name": docname,
"communication_type": "Feedback"
}, fields=["rating"], order_by="creation desc", as_list=True)

if not rating:
return 0
else:
return rating[0][0]

+ 1
- 2
frappe/email/doctype/email_alert/email_alert.py Näytä tiedosto

@@ -210,5 +210,4 @@ def evaluate_alert(doc, alert, event):
frappe.throw(_("Error while evaluating Email Alert {0}. Please fix your template.").format(alert))

def get_context(doc):
return {"doc": doc, "nowdate": nowdate}

return {"doc": doc, "nowdate": nowdate}

+ 4
- 2
frappe/hooks.py Näytä tiedosto

@@ -113,7 +113,8 @@ doc_events = {
"on_cancel": [
"frappe.desk.notifications.clear_doctype_notifications",
],
"on_trash": "frappe.desk.notifications.clear_doctype_notifications"
"on_trash": "frappe.desk.notifications.clear_doctype_notifications",
"on_change": "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_alert"
},
"Email Group Member": {
"validate": "frappe.email.doctype.email_group.email_group.restrict_email_group"
@@ -145,7 +146,8 @@ scheduler_events = {
"frappe.utils.scheduler.restrict_scheduler_events_if_dormant",
"frappe.limits.update_space_usage",
"frappe.email.doctype.auto_email_report.auto_email_report.send_daily",
"frappe.desk.page.backups.backups.delete_downloadable_backups"
"frappe.desk.page.backups.backups.delete_downloadable_backups",
"frappe.core.doctype.feedback_request.feedback_request.delete_feedback_request"
],
"monthly": [
"frappe.email.doctype.auto_email_report.auto_email_report.send_monthly"


+ 4
- 2
frappe/public/build.json Näytä tiedosto

@@ -19,7 +19,8 @@
"public/js/frappe/class.js",
"public/js/lib/microtemplate.js",
"public/js/frappe/query_string.js",
"website/js/website.js"
"website/js/website.js",
"public/js/frappe/misc/rating_icons.html"
],
"js/editor.min.js": [
"public/js/lib/jquery/jquery.hotkeys.js",
@@ -150,7 +151,8 @@
"public/js/frappe/desk.js",
"public/js/frappe/query_string.js",

"public/js/frappe/ui/charts.js"
"public/js/frappe/ui/charts.js",
"public/js/frappe/misc/rating_icons.html"
],
"js/d3.min.js": [
"public/js/lib/d3.min.js",


+ 58
- 46
frappe/public/js/frappe/form/footer/timeline.js Näytä tiedosto

@@ -98,7 +98,7 @@ frappe.ui.form.Timeline = Class.extend({

$.each(communications.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }),
function(i, c) {
if(c.content) {
if(c.content || c.feedback) {
c.frm = me.frm;
me.render_timeline_item(c);
}
@@ -267,6 +267,11 @@ frappe.ui.form.Timeline = Class.extend({

c.original_content = c.content;
c.content = frappe.utils.toggle_blockquote(c.content);
} else if (c.communication_type==="Feedback") {
c.content = frappe.utils.strip_original_content(c.feedback);

c.original_content = c.feedback;
c.content = frappe.utils.toggle_blockquote(c.feedback);
}

if(!frappe.utils.is_html(c.content)) {
@@ -307,57 +312,64 @@ frappe.ui.form.Timeline = Class.extend({
},

is_communication_or_comment: function(c) {
return c.communication_type==="Communication" || (c.communication_type==="Comment" && (c.comment_type==="Comment"||c.comment_type==="Relinked"));
return c.communication_type==="Communication"
|| c.communication_type==="Feedback"
|| (c.communication_type==="Comment" && (c.comment_type==="Comment"||c.comment_type==="Relinked"));
},

set_icon_and_color: function(c) {
c.icon = {
"Email": "octicon octicon-mail",
"Chat": "octicon octicon-comment-discussion",
"Phone": "octicon octicon-device-mobile",
"SMS": "octicon octicon-comment",
"Created": "octicon octicon-plus",
"Submitted": "octicon octicon-lock",
"Cancelled": "octicon octicon-x",
"Assigned": "octicon octicon-person",
"Assignment Completed": "octicon octicon-check",
"Comment": "octicon octicon-comment-discussion",
"Workflow": "octicon octicon-git-branch",
"Label": "octicon octicon-tag",
"Attachment": "octicon octicon-cloud-upload",
"Attachment Removed": "octicon octicon-trashcan",
"Shared": "octicon octicon-eye",
"Unshared": "octicon octicon-circle-slash",
"Like": "octicon octicon-heart",
"Edit": "octicon octicon-pencil",
"Relinked": "octicon octicon-check"
}[c.comment_type || c.communication_medium]

c.color = {
"Email": "#3498db",
"Chat": "#3498db",
"Phone": "#3498db",
"SMS": "#3498db",
"Created": "#1abc9c",
"Submitted": "#1abc9c",
"Cancelled": "#c0392b",
"Assigned": "#f39c12",
"Assignment Completed": "#16a085",
"Comment": "#f39c12",
"Workflow": "#2c3e50",
"Label": "#2c3e50",
"Attachment": "#7f8c8d",
"Attachment Removed": "#eee",
"Relinked": "#16a085"
}[c.comment_type || c.communication_medium];

c.icon_fg = {
"Attachment Removed": "#333",
}[c.comment_type || c.communication_medium]
if(c.communication_type == "Feedback"){
c.icon = "octicon octicon-comment-discussion"
c.rating_icons = frappe.render_template("rating_icons", {rating: c.rating, show_label: true})
c.color = "#f39c12"
} else {
c.icon = {
"Email": "octicon octicon-mail",
"Chat": "octicon octicon-comment-discussion",
"Phone": "octicon octicon-device-mobile",
"SMS": "octicon octicon-comment",
"Created": "octicon octicon-plus",
"Submitted": "octicon octicon-lock",
"Cancelled": "octicon octicon-x",
"Assigned": "octicon octicon-person",
"Assignment Completed": "octicon octicon-check",
"Comment": "octicon octicon-comment-discussion",
"Workflow": "octicon octicon-git-branch",
"Label": "octicon octicon-tag",
"Attachment": "octicon octicon-cloud-upload",
"Attachment Removed": "octicon octicon-trashcan",
"Shared": "octicon octicon-eye",
"Unshared": "octicon octicon-circle-slash",
"Like": "octicon octicon-heart",
"Edit": "octicon octicon-pencil",
"Relinked": "octicon octicon-check"
}[c.comment_type || c.communication_medium]

c.color = {
"Email": "#3498db",
"Chat": "#3498db",
"Phone": "#3498db",
"SMS": "#3498db",
"Created": "#1abc9c",
"Submitted": "#1abc9c",
"Cancelled": "#c0392b",
"Assigned": "#f39c12",
"Assignment Completed": "#16a085",
"Comment": "#f39c12",
"Workflow": "#2c3e50",
"Label": "#2c3e50",
"Attachment": "#7f8c8d",
"Attachment Removed": "#eee",
"Relinked": "#16a085"
}[c.comment_type || c.communication_medium];

c.icon_fg = {
"Attachment Removed": "#333",
}[c.comment_type || c.communication_medium]

}
if(!c.icon_fg)
c.icon_fg = "#fff";

},
get_communications: function(with_versions) {
var docinfo = this.frm.get_docinfo(),


+ 29
- 23
frappe/public/js/frappe/form/footer/timeline_item.html Näytä tiedosto

@@ -23,6 +23,7 @@
</span>
</div>
{% if(data.communication_type==="Communication"
|| data.communication_type==="Feedback"
|| (data.communication_type==="Comment"
&& data.comment_type==="Comment")) { %}
<div class="comment-header small" style="cursor: pointer;">
@@ -40,11 +41,11 @@
</span>
<span class="text-muted" style="font-weight: normal;">
&ndash; {%= data.comment_on %}</span>
{% if(data.communication_type==="Communication") { %}
{% if(inList(["Communication", "Feedback"], data.communication_type)) { %}
{% if (frappe.model.can_read(\'Communication\')) { %}
<a href="#Form/{%= data.doctype %}/{%= data.name %}"
class="text-muted">
{% } %}
{% } %}

{% if (data.delivery_status) {
if (in_list(["Sent", "Opened", "Clicked"], data.delivery_status)) {
@@ -68,15 +69,15 @@
{% } %}
{% } %}

{% if (frappe.model.can_read(\'Communication\')) { %}
</a>
{% } %}
{% if (frappe.model.can_read(\'Communication\')) { %}
</a>
{% } %}

{% if (data.communication_medium === "Email") { %}
<a class="text-muted reply-link pull-right timeline-content-show"
data-name="{%= data.name %}">{%= __("Reply") %}</a>
{% if (data.communication_medium === "Email") { %}
<a class="text-muted reply-link pull-right timeline-content-show"
data-name="{%= data.name %}">{%= __("Reply") %}</a>
{% } %}
{% } %}
{% } %}
<span class="comment-likes"
data-liked-by=\'{{ JSON.stringify(data._liked_by) }}\'>
<i class="octicon octicon-heart like-action
@@ -97,25 +98,30 @@
<hr>
{% endif %}

{% if data.communication_type == "Feedback" && data.rating_icons %}
<p class="text-muted small">{{ data.rating_icons }}</p>
<hr>
{% endif %}

{%= data.content_html %}
</div>
{% if(data.attachments && data.attachments.length) { %}
<div style="margin: 10px 0px">
{% $.each(data.attachments, function(i, a) { %}
<div class="ellipsis">
<a href="{%= encodeURI(a.file_url).replace(/#/g, \'%23\') %}"
class="text-muted small" target="_blank">
<i class="fa fa-paperclip"></i>
{%= a.file_url.split("/").slice(-1)[0] %}
{% if (a.is_private) { %}
<i class="fa fa-lock text-warning"></i>
{% } %}
</a>
<div style="margin: 10px 0px">
{% $.each(data.attachments, function(i, a) { %}
<div class="ellipsis">
<a href="{%= encodeURI(a.file_url).replace(/#/g, \'%23\') %}"
class="text-muted small" target="_blank">
<i class="fa fa-paperclip"></i>
{%= a.file_url.split("/").slice(-1)[0] %}
{% if (a.is_private) { %}
<i class="fa fa-lock text-warning"></i>
{% } %}
</a>
</div>
{% }); %}
</div>
{% }); %}
{% } %}
</div>
{% } %}
</div>

{% } else if(in_list(["Assignment Completed", "Assigned", "Shared",
"Unshared"], data.comment_type)) { %}


+ 25
- 18
frappe/public/js/frappe/form/grid.js Näytä tiedosto

@@ -855,6 +855,7 @@ frappe.ui.form.GridRow = Class.extend({
column.static_area.toggle(false);
column.field_area.toggle(true);
});

frappe.ui.form.editable_row = this;
return false;
} else {
@@ -899,7 +900,13 @@ frappe.ui.form.GridRow = Class.extend({
field.$input
.attr('data-col-idx', column.column_index)
.attr('placeholder', __(df.label));

// flag list input
if (this.columns_list && this.columns_list.slice(-1)[0]===column) {
field.$input.attr('data-last-input', 1);
}
}

this.set_arrow_keys(field);
column.field = field;
this.on_grid_fields_dict[df.fieldname] = field;
@@ -915,14 +922,26 @@ frappe.ui.form.GridRow = Class.extend({
var fieldname = $(this).attr('data-fieldname');
var fieldtype = $(this).attr('data-fieldtype');

// TAB
if(in_list(['Text', 'Small Text'], fieldtype)) {
return;
var move_up_down = function(base) {
if(in_list(['Text', 'Small Text'], fieldtype)) {
return;
}

base.toggle_editable_row();
setTimeout(function() {
var input = base.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)

}

// TAB
if(e.which==TAB) {
// last column
if(me.grid.wrapper.find('input:enabled:last').get(0)===this) {
if($(this).attr('data-last-input') ||
me.grid.wrapper.find(':input:enabled:last').get(0)===this) {
setTimeout(function() {
if(me.doc.idx === values.length) {
// last row
@@ -938,24 +957,12 @@ frappe.ui.form.GridRow = Class.extend({
} else if(e.which==UP_ARROW) {
if(me.doc.idx > 1) {
var prev = me.grid.grid_rows[me.doc.idx-2];
prev.toggle_editable_row();
setTimeout(function() {
var input = prev.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
move_up_down(prev);
}
} else if(e.which==DOWN_ARROW) {
if(me.doc.idx < values.length) {
var next = me.grid.grid_rows[me.doc.idx];
next.toggle_editable_row();
setTimeout(function() {
var input = next.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
move_up_down(next);
}
}



+ 13
- 0
frappe/public/js/frappe/form/sidebar.js Näytä tiedosto

@@ -11,6 +11,7 @@ frappe.ui.form.Sidebar = Class.extend({
.html(sidebar_content)
.appendTo(this.page.sidebar.empty());

this.ratings = this.sidebar.find(".sidebar-rating");
this.comments = this.sidebar.find(".sidebar-comments");
this.user_actions = this.sidebar.find(".user-actions");
this.image_section = this.sidebar.find(".sidebar-image-section");
@@ -63,6 +64,7 @@ frappe.ui.form.Sidebar = Class.extend({
"<br>" + comment_when(this.frm.doc.creation)]));

this.refresh_like();
this.setup_ratings();
frappe.ui.form.set_user_image(this.frm);
}
},
@@ -146,5 +148,16 @@ frappe.ui.form.Sidebar = Class.extend({
},

refresh_image: function() {
},

setup_ratings: function() {
_ratings = this.frm.get_docinfo().rating || 0;


if(_ratings) {
this.ratings.removeClass("hide");
rating_icons = frappe.render_template("rating_icons", {rating: _ratings, show_label: false});
this.ratings.find(".rating-icons").html(rating_icons);
}
}
});

+ 7
- 1
frappe/public/js/frappe/form/templates/form_sidebar.html Näytä tiedosto

@@ -12,8 +12,14 @@
<label class="label label-warning" title="{{ __("This feature is brand new and still experimental") }}">{{ __("Experimental Feature") }}</label>
</div>
{% endif %}
<ul class="list-unstyled sidebar-menu user-actions hide">
<ul class="list-unstyled sidebar-menu sidebar-rating hide">
<li class="divider"></li>
<li style="position: relative;">
<a class="strong badge-hover">
<span>{%= __("Feedback Rating") %}</span>
</a>
</li>
<li class="rating-icons"></li>
</ul>
<ul class="list-unstyled sidebar-menu">
<li class="divider"></li>


+ 6
- 0
frappe/public/js/frappe/misc/rating_icons.html Näytä tiedosto

@@ -0,0 +1,6 @@
{% if show_label %}
{{ __("Rating: ") }}
{% endif %}
{% for(var i=1, l=6; i<l; i++) { %}
<i class="fa fa-fw {{ i<=rating? "fa-star": "fa-star-o" }} star-icon" data-idx=1></i>
{% } %}

+ 8
- 0
frappe/public/js/frappe/misc/ratings.html Näytä tiedosto

@@ -0,0 +1,8 @@
<div>
{% if label && data.show_label %}
{{ __("{0}: ", label) }}
{% endif %}
{% for(var i=1, l=6; i<l; i++) { %}
<i class="fa fa-fw {{ i<=rating? "fa-star": "fa-star-o" }} star-icon" data-idx=1></i>
{% } %}
</div>

+ 1
- 1
frappe/utils/jinja.py Näytä tiedosto

@@ -31,7 +31,7 @@ def validate_template(html):
try:
jenv.from_string(html)
except TemplateSyntaxError, e:
frappe.msgprint('Line {}: {}'.format(e.lineno, e.message))
frappe.msgprint('Line {}: {}'.format(e.lineno, e.message))
frappe.throw(frappe._("Syntax error in template"))

def render_template(template, context, is_path=None):


+ 94
- 0
frappe/www/feedback.html Näytä tiedosto

@@ -0,0 +1,94 @@
{% extends "templates/web.html" %}

{% block title %}{{ _("Feedback") }}{% endblock %}

{% block page_content %}
<div class="feedback">
<p class='lead' id="feedback-msg"></p>

<div>
{{ _("Your rating: ") }}
<i class='fa fa-fw fa-star-o star-icon' data-idx=1></i>
<i class='fa fa-fw fa-star-o star-icon' data-idx=2></i>
<i class='fa fa-fw fa-star-o star-icon' data-idx=3></i>
<i class='fa fa-fw fa-star-o star-icon' data-idx=4></i>
<i class='fa fa-fw fa-star-o star-icon' data-idx=5></i>
</div>

<div style='max-width: 500px;'>
<p>{{ _("Detailed feedback") }}</p>
<textarea class='form-control feedback-text' style='min-height: 300px;'></textarea>
</div>

<p><button class='btn btn-primary btn-sm btn-submit'>{{ _("Submit") }}</button></p>
</div>
<div class="feedback-result" style="display: none">
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator darkgrey'>{{_("Thank You !!")}}</span>
</div>
<p id="feedback-result"></p>
<div><a href='/' class='btn btn-primary btn-sm'>{{ _("Home") }}</a></div>
</div>
</div>

<script>
window.feedback = {
init: function() {
var me = this;

this.key = get_url_arg("key");
this.reference_name = get_url_arg("reference_name");
this.reference_doctype = get_url_arg("reference_doctype");
this.sender = get_url_arg("email")

me.bind_events();
$("#feedback-msg").empty().html(__("Please share your feedback for {0}", [me.reference_name]));
},

bind_events: function() {
// bind ratings on hover
var me = this;

$('.star-icon').hover(function() {
var idx = $(this).attr('data-idx');
$('.star-icon.fa-star').removeClass('fa-star').addClass('fa-star-o');
for(var i=0; i<parseInt(idx); i++) {
$($('.star-icon').get(i)).removeClass('fa-star-o').addClass('fa-star');
}
});

// bind submit button
$('.btn-submit').on('click', function() {
if(!$('.star-icon.fa-star').length) {
frappe.msgprint(__("Please give a rating."));
return;
}

frappe.call({
method: 'frappe.www.feedback.accept',
args: {
key: me.key,
sender: me.sender,
reference_name: me.reference_name,
reference_doctype: me.reference_doctype,
feedback: $('.feedback-text').val(),
rating: $('.star-icon.fa-star').length
},
callback: function(r) {
if(r.message) {
$(".feedback, .feedback-result").toggle();
$("#feedback-result").empty().html(__("Your Feedback for document {0} is saved successfully",
[me.reference_name]));
}
}
})
});
}
};

frappe.ready(function() {
feedback.init();
})
</script>
{% endblock %}

+ 28
- 0
frappe/www/feedback.py Näytä tiedosto

@@ -0,0 +1,28 @@
import frappe
from frappe.core.doctype.feedback_request.feedback_request import is_valid_feedback_request

@frappe.whitelist(allow_guest=True)
def accept(key, sender, reference_doctype, reference_name, feedback, rating):
""" save the feedback in communication """
if not reference_doctype and not reference_name:
frappe.throw("Invalid Reference Doctype, Reference Name")

if not is_valid_feedback_request(key):
frappe.throw("Link is Expired")

frappe.get_doc({
"rating": rating,
"sender": sender,
"status": "Closed",
"feedback": feedback,
"feedback_request": key,
"doctype": "Communication",
"sent_or_received": "Received",
"communication_type": "Feedback",
"reference_name": reference_name,
"reference_doctype": reference_doctype,
"subject": "Feedback for {0} {1}".format(reference_doctype, reference_name),
}).insert(ignore_permissions=True)

frappe.db.set_value("Feedback Request", key, "is_feedback_submitted", 1)
return True

Ladataan…
Peruuta
Tallenna