diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 2b692ced04..7ac6203ada 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -29,6 +29,7 @@ "message", "message_md", "message_html", + "campaign", "attachments", "send_unsubscribe_link", "send_webview_link", @@ -237,6 +238,13 @@ "label": "Total Views", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "options": "Marketing Campaign", + "reqd": 0 } ], "has_web_view": 1, @@ -245,7 +253,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2023-02-23 12:53:18.478018", + "modified": "2023-03-20 22:45:59.129630", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", @@ -270,4 +278,4 @@ "states": [], "title_field": "subject", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 43f767133c..4a2f69a44c 100644 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -167,7 +167,7 @@ class Newsletter(WebsiteGenerator): attachments = self.get_newsletter_attachments() sender = self.send_from or frappe.utils.get_formatted_email(self.owner) args = self.as_dict() - args["message"] = self.get_message() + args["message"] = self.get_message(medium="email") is_auto_commit_set = bool(frappe.db.auto_commit_on_many_writes) frappe.db.auto_commit_on_many_writes = not frappe.flags.in_test @@ -193,7 +193,7 @@ class Newsletter(WebsiteGenerator): frappe.db.auto_commit_on_many_writes = is_auto_commit_set - def get_message(self) -> str: + def get_message(self, medium=None) -> str: message = self.message if self.content_type == "Markdown": message = frappe.utils.md_to_html(self.message_md) @@ -202,9 +202,9 @@ class Newsletter(WebsiteGenerator): html = frappe.render_template(message, {"doc": self.as_dict()}) - return self.add_source(html) + return self.add_source(html, medium=medium) - def add_source(self, html: str) -> str: + def add_source(self, html: str, medium="None") -> str: """Add source to the site links in the newsletter content.""" from bs4 import BeautifulSoup @@ -216,8 +216,8 @@ class Newsletter(WebsiteGenerator): if href and not href.startswith("#"): if not frappe.utils.is_site_link(href): continue - new_href = frappe.utils.add_source_to_url( - href, reference_doctype=self.doctype, reference_docname=self.name + new_href = frappe.utils.add_trackers_to_url( + href, source="Newsletter", campaign=self.campaign, medium=medium ) link["href"] = new_href diff --git a/frappe/email/doctype/newsletter/templates/newsletter.html b/frappe/email/doctype/newsletter/templates/newsletter.html index 1244f4c49a..05f3560648 100644 --- a/frappe/email/doctype/newsletter/templates/newsletter.html +++ b/frappe/email/doctype/newsletter/templates/newsletter.html @@ -36,7 +36,7 @@

- {{ doc.get_message() }} + {{ doc.get_message(medium="web_page") }}
diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index 8bf8f36f7a..419a22d764 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -129,10 +129,10 @@ frappe.ui.toolbar.Toolbar = class { let awesome_bar = new frappe.search.AwesomeBar(); awesome_bar.setup("#navbar-search"); - // TODO: Remove this in v14 - frappe.search.utils.make_function_searchable(function () { - frappe.set_route("List", "Client Script"); - }, __("Custom Script List")); + frappe.search.utils.make_function_searchable( + frappe.utils.generate_tracking_url, + __("Generate Tracking URL") + ); } } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 594da353e6..ac9a18785b 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1610,4 +1610,65 @@ Object.assign(frappe.utils, { }); }, }, + generate_tracking_url() { + frappe.prompt( + [ + { + fieldname: "url", + label: __("Web Page URL"), + fieldtype: "Data", + options: "URL", + reqd: 1, + default: localStorage.getItem("tracker_url:url"), + }, + { + fieldname: "source", + label: __("Source"), + fieldtype: "Data", + default: localStorage.getItem("tracker_url:source"), + }, + { + fieldname: "campaign", + label: __("Campaign"), + fieldtype: "Link", + ignore_link_validation: 1, + options: "Marketing Campaign", + default: localStorage.getItem("tracker_url:campaign"), + }, + { + fieldname: "medium", + label: __("Medium"), + fieldtype: "Data", + default: localStorage.getItem("tracker_url:medium"), + }, + ], + function (data) { + let url = data.url; + localStorage.setItem("tracker_url:url", data.url); + + if (data.source) { + url += "?source=" + data.source; + localStorage.setItem("tracker_url:source", data.source); + } + if (data.campaign) { + url += "&campaign=" + data.campaign; + localStorage.setItem("tracker_url:campaign", data.campaign); + } + if (data.medium) { + url += "&medium=" + data.medium.toLowerCase(); + localStorage.setItem("tracker_url:medium", data.medium); + } + + frappe.utils.copy_to_clipboard(url); + + frappe.msgprint( + __("Tracking URL generated and copied to clipboard") + + ":
" + + `${url.bold()}`, + __("Here's your tracking URL") + ); + }, + __("Generate Tracking URL") + ); + }, }); diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d76d97f7e0..460ca26d85 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -2256,11 +2256,20 @@ def is_site_link(link: str) -> bool: return urlparse(link).netloc == urlparse(frappe.utils.get_url()).netloc -def add_source_to_url(url: str, reference_doctype: str, reference_docname: str) -> str: +def add_trackers_to_url(url: str, source: str, campaign: str, medium: str = "email") -> str: url_parts = list(urlparse(url)) - query = dict(parse_qsl(url_parts[4])) | { - "source": f"{reference_doctype} > {reference_docname}", + if url_parts[0] == "mailto": + return url + + trackers = { + "source": source, + "medium": medium, } + if campaign: + trackers["campaign"] = campaign + + query = dict(parse_qsl(url_parts[4])) | trackers + url_parts[4] = urlencode(query) return urlunparse(url_parts) diff --git a/frappe/website/doctype/marketing_campaign/__init__.py b/frappe/website/doctype/marketing_campaign/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/marketing_campaign/marketing_campaign.json b/frappe/website/doctype/marketing_campaign/marketing_campaign.json new file mode 100644 index 0000000000..0a5fc45b29 --- /dev/null +++ b/frappe/website/doctype/marketing_campaign/marketing_campaign.json @@ -0,0 +1,64 @@ +{ + "actions": [], + "autoname": "prompt", + "creation": "2023-03-20 22:36:45.058045", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "campaign_description" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "campaign_description", + "fieldtype": "Small Text", + "in_filter": 1, + "in_list_view": 1, + "label": "Campaign Description (Optional)" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-03-20 22:47:25.768582", + "modified_by": "Administrator", + "module": "Website", + "name": "Marketing Campaign", + "naming_rule": "Set by user", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Newsletter Manager", + "share": 1, + "write": 1 + }, + { + "role": "All", + "select": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/frappe/website/doctype/marketing_campaign/marketing_campaign.py b/frappe/website/doctype/marketing_campaign/marketing_campaign.py new file mode 100644 index 0000000000..ef23a182e7 --- /dev/null +++ b/frappe/website/doctype/marketing_campaign/marketing_campaign.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class MarketingCampaign(Document): + pass diff --git a/frappe/website/doctype/web_page_view/web_page_view.json b/frappe/website/doctype/web_page_view/web_page_view.json index 3eb0e7e722..2e514ffaec 100644 --- a/frappe/website/doctype/web_page_view/web_page_view.json +++ b/frappe/website/doctype/web_page_view/web_page_view.json @@ -13,8 +13,10 @@ "is_unique", "time_zone", "user_agent", - "visitor_id", - "source" + "source", + "campaign", + "medium", + "visitor_id" ], "fields": [ { @@ -68,11 +70,23 @@ "fieldtype": "Data", "label": "Source", "read_only": 1 + }, + { + "fieldname": "campaign", + "fieldtype": "Data", + "label": "Campaign", + "read_only": 1 + }, + { + "fieldname": "medium", + "fieldtype": "Data", + "label": "Medium", + "read_only": 1 } ], "in_create": 1, "links": [], - "modified": "2023-02-28 11:55:04.533663", + "modified": "2023-03-20 23:38:27.067285", "modified_by": "Administrator", "module": "Website", "name": "Web Page View", diff --git a/frappe/website/doctype/web_page_view/web_page_view.py b/frappe/website/doctype/web_page_view/web_page_view.py index 5cd6b16bc8..bbf2a394a6 100644 --- a/frappe/website/doctype/web_page_view/web_page_view.py +++ b/frappe/website/doctype/web_page_view/web_page_view.py @@ -19,6 +19,8 @@ def make_view_log( version=None, user_tz=None, source=None, + campaign=None, + medium=None, visitor_id=None, ): if not is_tracking_enabled(): @@ -55,6 +57,8 @@ def make_view_log( view.user_agent = user_agent view.is_unique = is_unique view.source = source + view.campaign = campaign + view.medium = (medium or "").lower() view.visitor_id = visitor_id try: diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js index d88a9de663..eabc4dee60 100644 --- a/frappe/website/report/website_analytics/website_analytics.js +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -37,6 +37,8 @@ frappe.query_reports["Website Analytics"] = { { value: "browser", label: __("Browser") }, { value: "referrer", label: __("Referrer") }, { value: "source", label: __("Source") }, + { value: "campaign", label: __("Campaign") }, + { value: "medium", label: __("Medium") }, ], default: "path", }, diff --git a/frappe/www/website_script.js b/frappe/www/website_script.js index 17a8a3e8e1..a95b21313e 100644 --- a/frappe/www/website_script.js +++ b/frappe/www/website_script.js @@ -33,6 +33,8 @@ ga('send', 'pageview'); version: browser.version, user_tz: Intl.DateTimeFormat().resolvedOptions().timeZone, source: query_params.source, + medium: query_params.medium, + campaign: query_params.campaign, visitor_id: result.visitorId }) })