Skip to content

Commit

Permalink
Merge pull request #15 from axelwalter/piwik-pro
Browse files Browse the repository at this point in the history
Add piwik pro analytics
  • Loading branch information
axelwalter authored Oct 30, 2024
2 parents a7ca92d + f64ce2b commit 0fd3cce
Show file tree
Hide file tree
Showing 9 changed files with 1,831 additions and 1,690 deletions.
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ COPY assets/ /app/assets
COPY content/ /app/content
COPY example-data/ /app/example-data
COPY gdpr_consent/ /app/gdpr_consent
COPY hooks/ /app/hooks
COPY src/ /app/src
COPY app.py /app/app.py
COPY settings.json /app/settings.json
Expand All @@ -132,6 +133,12 @@ RUN echo "mamba run --no-capture-output -n streamlit-env streamlit run app.py" >
# make the script executable
RUN chmod +x /app/entrypoint.sh

# Patch Analytics
RUN mamba run -n streamlit-env python hooks/hook-analytics.py

# Set Online Deployment
RUN jq '.online_deployment = true' settings.json > tmp.json && mv tmp.json settings.json

# Download latest OpenMS App executable for Windows from Github actions workflow.
RUN WORKFLOW_ID=$(curl -s "https://api.github.com/repos/$GITHUB_USER/$GITHUB_REPO/actions/workflows" | jq -r '.workflows[] | select(.name == "Build executable for Windows") | .id') \
&& SUCCESSFUL_RUNS=$(curl -s "https://api.github.com/repos/$GITHUB_USER/$GITHUB_REPO/actions/runs?workflow_id=$WORKFLOW_ID&status=success" | jq -r '.workflow_runs[0].id') \
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile_simple
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ COPY assets/ /app/assets
COPY content/ /app/content
COPY example-data/ /app/example-data
COPY gdpr_consent/ /app/gdpr_consent
COPY hooks/ /app/hooks
COPY src/ /app/src
COPY app.py /app/app.py
COPY settings.json /app/settings.json
Expand All @@ -78,6 +79,11 @@ RUN echo "mamba run --no-capture-output -n streamlit-env streamlit run app.py" >
# make the script executable
RUN chmod +x /app/entrypoint.sh

# Patch Analytics
RUN mamba run -n streamlit-env python hooks/hook-analytics.py
# Set Online Deployment
RUN jq '.online_deployment = true' settings.json > tmp.json && mv tmp.json settings.json

# Download latest OpenMS App executable for Windows from Github actions workflow.
RUN WORKFLOW_ID=$(curl -s "https://api.github.com/repos/$GITHUB_USER/$GITHUB_REPO/actions/workflows" | jq -r '.workflows[] | select(.name == "Build executable for Windows") | .id') \
&& SUCCESSFUL_RUNS=$(curl -s "https://api.github.com/repos/$GITHUB_USER/$GITHUB_REPO/actions/runs?workflow_id=$WORKFLOW_ID&status=success" | jq -r '.workflow_runs[0].id') \
Expand Down
3,240 changes: 1,620 additions & 1,620 deletions gdpr_consent/dist/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gdpr_consent/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 48 additions & 27 deletions gdpr_consent/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Streamlit, RenderData } from "streamlit-component-lib"

// Define service
type Service = {
name: string;
purposes: string[];
onAccept: () => Promise<void>;
onDecline: () => Promise<void>;
cookies?: (string | RegExp)[];
};

// Defines the configuration for Klaro
const klaroConfig = {
let klaroConfig: {
mustConsent: boolean;
acceptAll: boolean;
services: Service[];
} = {
mustConsent: true,
acceptAll: true,
services: [
{
// In GTM, you should define a custom event trigger named `klaro-google-analytics-accepted` which should trigger the Google Analytics integration.
name: 'google-analytics',
cookies: [
/^_ga(_.*)?/ // we delete the Google Analytics cookies if the user declines its use
],
purposes: ['analytics'],
onAccept: onAcceptCallback,
onDecline: onDeclineCallback,
},
]
services: []
};

// This will make klaroConfig globally accessible
Expand Down Expand Up @@ -62,23 +64,15 @@ function handleError(error: unknown): void {
}

// Tracking was accepted
async function onAcceptCallback(): Promise<void> {
async function callback(): Promise<void> {
try {
const manager = await waitForKlaroManager()
if (manager.confirmed) {
Streamlit.setComponentValue(true)
}
} catch (error) {
handleError(error)
}
}

// Tracking was declined
async function onDeclineCallback(): Promise<void> {
try {
const manager = await waitForKlaroManager()
if (manager.confirmed) {
Streamlit.setComponentValue(false)
let return_vals : Record<string, boolean> = {}
for (const service of klaroConfig.services) {
return_vals[service.name] = manager.getConsent(service.name)
}
Streamlit.setComponentValue(return_vals)
}
} catch (error) {
handleError(error)
Expand All @@ -95,6 +89,32 @@ function onRender(event: Event): void {
}
rendered = true

const data = (event as CustomEvent<RenderData>).detail

if (data.args['google_analytics']) {
klaroConfig.services.push(
{
name: 'google-analytics',
cookies: [
/^_ga(_.*)?/ // we delete the Google Analytics cookies if the user declines its use
],
purposes: ['analytics'],
onAccept: callback,
onDecline: callback,
}
)
}
if (data.args['piwik_pro']) {
klaroConfig.services.push(
{
name: 'piwik-pro',
purposes: ['analytics'],
onAccept: callback,
onDecline: callback,
}
)
}

// Create a new script element
var script = document.createElement('script')

Expand All @@ -108,6 +128,7 @@ function onRender(event: Event): void {

// Append the script to the head or body
document.head.appendChild(script)

}

// Attach our `onRender` handler to Streamlit's render event.
Expand Down
84 changes: 84 additions & 0 deletions hooks/hook-analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import json
import streamlit as st

def patch_head(document, content):
return document.replace('<head>', '<head>' + content)

def patch_body(document, content):
return document.replace('<body>', '<body>' + content)

def google_analytics_head(gtm_tag):
return f"""
<script>
// Define dataLayer and the gtag function.
window.dataLayer = window.dataLayer || [];
function gtag(){{dataLayer.push(arguments);}}
// Set default consent to 'denied' as a placeholder
// Determine actual values based on your own requirements
gtag('consent', 'default', {{
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied'
}});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){{w[l]=w[l]||[];w[l].push({{'gtm.start':
new Date().getTime(),event:'gtm.js'}});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
}})(window,document,'script','dataLayer','{gtm_tag}');</script>
<!-- End Google Tag Manager -->
"""

def google_analytics_body(gtm_tag):
return f"""
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={gtm_tag}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
"""

def piwik_pro_body(piwik_tag):
return f"""
<script type="text/javascript">
(function(window, document, dataLayerName, id) {{
window[dataLayerName]=window[dataLayerName]||[],window[dataLayerName].push({{start:(new Date).getTime(),event:"stg.start"}});var scripts=document.getElementsByTagName('script')[0],tags=document.createElement('script');
function stgCreateCookie(a,b,c){{var d="";if(c){{var e=new Date;e.setTime(e.getTime()+24*c*60*60*1e3),d="; expires="+e.toUTCString();f="; SameSite=Strict"}}document.cookie=a+"="+b+d+f+"; path=/"}}
var isStgDebug=(window.location.href.match("stg_debug")||document.cookie.match("stg_debug"))&&!window.location.href.match("stg_disable_debug");stgCreateCookie("stg_debug",isStgDebug?1:"",isStgDebug?14:-1);
var qP=[];dataLayerName!=="dataLayer"&&qP.push("data_layer_name="+dataLayerName),isStgDebug&&qP.push("stg_debug");var qPString=qP.length>0?("?"+qP.join("&")):"";
tags.async=!0,tags.src="https://openms-web.containers.piwik.pro/"+id+".js"+qPString,scripts.parentNode.insertBefore(tags,scripts);
!function(a,n,i){{a[n]=a[n]||{{}};for(var c=0;c<i.length;c++)!function(i){{a[n][i]=a[n][i]||{{}},a[n][i].api=a[n][i].api||function(){{var a=[].slice.call(arguments,0);"string"==typeof a[0]&&window[dataLayerName].push({{event:n+"."+i+":"+a[0],parameters:[].slice.call(arguments,1)}})}}}}(i[c])}}(window,"ppms",["tm","cm"]);
}})(window, document, 'dataLayer', '{piwik_tag}');
</script>
"""


if __name__ == '__main__':

# Load configuration
settings_path = os.path.join(os.path.dirname(__file__), '..', 'settings.json')
with open(settings_path, 'r') as f:
settings = json.load(f)

# Load index.html
index_path = os.path.join(os.path.dirname(st.__file__), 'static', 'index.html')
with open(index_path, 'r') as f:
index = f.read()

# Configure google analytics
if settings['analytics']['google-analytics']['enabled']:
gtm_tag = settings['analytics']['google-analytics']['tag']
index = patch_head(index, google_analytics_head(gtm_tag))
index = patch_body(index, google_analytics_body(gtm_tag))

# Configure piwik pro
if settings['analytics']['piwik-pro']['enabled']:
piwik_tag = settings['analytics']['piwik-pro']['tag']
index = patch_body(index, piwik_pro_body(piwik_tag))

# Save index.html
with open(index_path, 'w') as f:
f.write(index)
16 changes: 11 additions & 5 deletions settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{
"google_analytics" : {
"enabled" : true,
"tag" : "G-E14B1EB0SB"
"analytics": {
"google-analytics": {
"enabled": false,
"tag": ""
},
"piwik-pro": {
"enabled": true,
"tag": "57690c44-d635-43b0-ab43-f8bd3064ca06"
}
},
"online_deployment": true
}
"online_deployment": false
}
8 changes: 6 additions & 2 deletions src/common/captcha_.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,14 @@ def captcha_control():
if "controllo" not in st.session_state or st.session_state["controllo"] == False:

# Check if consent for tracking was given
if (st.session_state.settings['google_analytics']['enabled']) and (st.session_state.tracking_consent is None):
ga = st.session_state.settings['analytics']['google-analytics']['enabled']
pp = st.session_state.settings['analytics']['piwik-pro']['enabled']
if (ga or pp) and (st.session_state.tracking_consent is None):
with st.spinner():
# Ask for consent
st.session_state.tracking_consent = consent_component()
st.session_state.tracking_consent = consent_component(
google_analytics=ga, piwik_pro=pp
)
if st.session_state.tracking_consent is None:
# No response by user yet
st.stop()
Expand Down
83 changes: 48 additions & 35 deletions src/common/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,42 +137,55 @@ def page_setup(page: str = "") -> dict[str, Any]:
st.logo("assets/pyopenms_transparent_background.png")

# Create google analytics if consent was given
if "tracking_consent" not in st.session_state:
st.session_state.tracking_consent = None
if (st.session_state.settings["google_analytics"]["enabled"]) and (
st.session_state.tracking_consent == True
if (
("tracking_consent" not in st.session_state)
or (st.session_state.tracking_consent is None)
or (not st.session_state.settings['online_deployment'])
):
html(
f"""
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id={st.session_state.settings['google_analytics']['tag']}" crossorigin='anonymous'></script>
<script crossorigin='anonymous'>
window.dataLayer = window.dataLayer || [];
function gtag(){{dataLayer.push(arguments);}}
gtag('js', new Date());
gtag('consent', 'default', {{
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'granted'
}});
gtag('config', '{st.session_state.settings['google_analytics']['tag']}' , {{
'debug_mode': true,
'cookie_flags': 'samesite=none;secure'
}});
console.log('Done!')
</script>
</head>
<body></body>
</html>
""",
width=1,
height=1,
)
st.session_state.tracking_consent = None
else:
if (st.session_state.settings["analytics"]["google-analytics"]["enabled"]) and (
st.session_state.tracking_consent["google-analytics"] == True
):
html(
"""
<!DOCTYPE html>
<html lang="en">
<head></head>
<body><script>
window.parent.gtag('consent', 'update', {
'analytics_storage': 'granted'
});
</script></body>
</html>
""",
width=1,
height=1,
)
if (st.session_state.settings["analytics"]["piwik-pro"]["enabled"]) and (
st.session_state.tracking_consent["piwik-pro"] == True
):
html(
"""
<!DOCTYPE html>
<html lang="en">
<head></head>
<body><script>
var consentSettings = {
analytics: { status: 1 } // Set Analytics consent to 'on' (1 for on, 0 for off)
};
window.parent.ppms.cm.api('setComplianceSettings', { consents: consentSettings }, function() {
console.log("PiwikPro Analytics consent set to on.");
}, function(error) {
console.error("Failed to set PiwikPro analytics consent:", error);
});
</script></body>
</html>
""",
width=1,
height=1,
)


# Determine the workspace for the current session
if ("workspace" not in st.session_state) or (
Expand Down

0 comments on commit 0fd3cce

Please sign in to comment.