* [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 mistakesversion-14
@@ -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(); | |||
} | |||
} | |||
}); | |||
} | |||
}); |
@@ -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 +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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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)""") |
@@ -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 +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(); | |||
}); | |||
} | |||
}); |
@@ -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 > 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><h3>Issue Resolved</h3>\n\n<p>Issue {{ doc.name }} Is resolved. Please check and confirm the same.</p>\n\n<p> Your Feedback is important for us. Please give us your Feedback for {{ doc.name }}</p>\n\n<p> Please visit the following url for feedback.</p>\n\n{{ feedback_url }}\n\n<h4>Details</h4></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 | |||
} |
@@ -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 } |
@@ -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) |
@@ -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] |
@@ -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} |
@@ -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" | |||
@@ -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", | |||
@@ -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(), | |||
@@ -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;"> | |||
– {%= 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)) { %} | |||
@@ -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); | |||
} | |||
} | |||
@@ -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); | |||
} | |||
} | |||
}); |
@@ -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> | |||
@@ -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> | |||
{% } %} |
@@ -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> |
@@ -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): | |||
@@ -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 %} |
@@ -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 |