From c9e12edb4259ad5d9c34d7579af0d2079bbf62ef Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Mon, 20 Mar 2023 23:41:18 +0530
Subject: [PATCH 1/4] feat: Add campaign and medium to web page view
---
.../doctype/web_page_view/web_page_view.json | 20 ++++++++++++++++---
.../doctype/web_page_view/web_page_view.py | 4 ++++
.../website_analytics/website_analytics.js | 2 ++
frappe/www/website_script.js | 2 ++
4 files changed, 25 insertions(+), 3 deletions(-)
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
})
})
From ed7d6931ca208f89b8888e41d73fb57549ec845a Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Mon, 20 Mar 2023 23:42:50 +0530
Subject: [PATCH 2/4] fix: Update code to update links with campaign and medium
---
.../email/doctype/newsletter/newsletter.json | 12 +++-
frappe/email/doctype/newsletter/newsletter.py | 12 ++--
.../newsletter/templates/newsletter.html | 2 +-
frappe/utils/data.py | 15 ++++-
.../doctype/marketing_campaign/__init__.py | 0
.../marketing_campaign.json | 64 +++++++++++++++++++
.../marketing_campaign/marketing_campaign.py | 9 +++
7 files changed, 102 insertions(+), 12 deletions(-)
create mode 100644 frappe/website/doctype/marketing_campaign/__init__.py
create mode 100644 frappe/website/doctype/marketing_campaign/marketing_campaign.json
create mode 100644 frappe/website/doctype/marketing_campaign/marketing_campaign.py
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index 2b692ced04..cd292bebbd 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": 1
}
],
"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/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
From 1abcd5a11a6336ae5408b61ea8d608e2f3ad6b82 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Mon, 20 Mar 2023 23:53:42 +0530
Subject: [PATCH 3/4] feat: Add a simple tool to generate tracking URL
---
frappe/public/js/frappe/ui/toolbar/toolbar.js | 8 +--
frappe/public/js/frappe/utils/utils.js | 52 +++++++++++++++++++
2 files changed, 56 insertions(+), 4 deletions(-)
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..f849373229 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1610,4 +1610,56 @@ Object.assign(frappe.utils, {
});
},
},
+ generate_tracking_url() {
+ frappe.prompt(
+ [
+ {
+ fieldname: "url",
+ label: __("Web Page URL"),
+ fieldtype: "Data",
+ options: "URL",
+ reqd: 1,
+ },
+ {
+ fieldname: "source",
+ label: __("Source"),
+ fieldtype: "Data",
+ },
+ {
+ fieldname: "campaign",
+ label: __("Campaign"),
+ fieldtype: "Link",
+ ignore_link_validation: 1,
+ options: "Marketing Campaign",
+ },
+ {
+ fieldname: "medium",
+ label: __("Medium"),
+ fieldtype: "Data",
+ },
+ ],
+ function (data) {
+ let url = data.url;
+ if (data.source) {
+ url += "?source=" + data.source;
+ }
+ if (data.campaign) {
+ url += "&campaign=" + data.campaign;
+ }
+ if (data.medium) {
+ url += "&medium=" + data.medium.toLowerCase();
+ }
+
+ 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")
+ );
+ },
});
From 02b661bbb9b85a7e73e93844d6b70a4ea6dd57fd Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Tue, 21 Mar 2023 11:31:05 +0530
Subject: [PATCH 4/4] fix: Remove mandatory and save URL params to localstorage
- Saving URL params to localstorage to avoid re-entering the data.
Usually only 1 or 2 param(s) change is required to generate new link
---
frappe/email/doctype/newsletter/newsletter.json | 2 +-
frappe/public/js/frappe/utils/utils.js | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index cd292bebbd..7ac6203ada 100644
--- a/frappe/email/doctype/newsletter/newsletter.json
+++ b/frappe/email/doctype/newsletter/newsletter.json
@@ -244,7 +244,7 @@
"fieldtype": "Link",
"label": "Campaign",
"options": "Marketing Campaign",
- "reqd": 1
+ "reqd": 0
}
],
"has_web_view": 1,
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index f849373229..ac9a18785b 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1619,11 +1619,13 @@ Object.assign(frappe.utils, {
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",
@@ -1631,23 +1633,30 @@ Object.assign(frappe.utils, {
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);