Skip to content

Commit

Permalink
feat(analytics, ui): event based analytics service plus (pre alpha)ta…
Browse files Browse the repository at this point in the history
…ilwind UI for the analytics config (#4031)

Analytics

Allow admin user to setup analytics events. When the enduser of the web interface executes an action ( for example clicks on the 'contact' button) a signal is send to a analytics service ( third party; site-improve, google, .. or emx2 analytics endpoint). The app maintainer can configure which events are send in response to what ui element the user interacts with ( initial 'click' event only). The maintainer can configure these events on a per schema basis.



Tailwind (analytics) ui (pre alpha)
- ui path: /apps/ui/#/
- login as admin to update analytics settings
- analytics path: /ui/[schema-name]/analytics
  • Loading branch information
connoratrug authored Aug 16, 2024
1 parent 3a8e3ec commit 370db33
Show file tree
Hide file tree
Showing 133 changed files with 3,666 additions and 249 deletions.
5 changes: 3 additions & 2 deletions apps/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ subprojects { subproject ->
dependsOn ":apps:${match[1]}:buildJavascript";
}
//run the build
args = ['run', 'build']
def command = packageJson.contains("\"name\": \"ui\"") ? "generate" : "build"
args = ['run', command]
doLast {
copy {
from "dist"
from packageJson.contains("\"name\": \"ui\"") ? ".output/public" : "dist"
into "${parent.buildDir}/generated/main/resources/public_html/apps/" + project.name
}
}
Expand Down
6 changes: 6 additions & 0 deletions apps/catalogue.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
},
{
"path": "tailwind-components"
},
{
"path": "ui"
},
{
"path": "metadata-utils"
}
],
"settings": {}
Expand Down
4 changes: 4 additions & 0 deletions apps/dev-proxy.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ module.exports = {
target: HOST,
...opts,
},
"^/[a-zA-Z0-9_.%-]+/api/trigger": {
target: HOST,
...opts,
},
"/api": {
target: `${HOST}/api`,
...opts,
Expand Down
24 changes: 24 additions & 0 deletions apps/emx2-analytics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
49 changes: 49 additions & 0 deletions apps/emx2-analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Analytics module

Goal of the module is to allow service maintainers to track certain user actions.
For example maintainer of a Data Catalogue application might what to know when and by who a 'Contact' button is clicked.

This module when installed sets up 'Triggers' attched to page elements ( for example a Button in a given location).
Besides the need to install and configure this module in a app ( or compoment ),
the triggers ( what events to run when ) should be created using the analytics api or web ui.

### Install the module

use package manager to install the module '@molgenis/emx2-analytics'

```yarn add @molgenis/emx2-analytics``` (or use the * option for yarn workspaces)

### Setup the triggers in the app
```import { setupAnalytics } from "@molgenis/emx2-analytics"; ```

...

``` setupAnalytics(schema, providers);```

schema: The name of the emx schema/database

providers: A list of analytics profiders and there config options, for example; ```providers = [{ id: "site-improve", options: { analyticsKey } }];```

This setup should be run before the user interacts with the page

During the setupAnalytics call 3 steps are taken
- 1. For each provider the nessasary code is (fetched and) loaded
- 2. The triggers configured for this schema are fetched from the backend
- 3. For each triggers the DOM elements are located in the page and a eventhandler gets attached for the configured provider

When the end user visits the page and triggers the event the attached eventhandler uses the provider script to send the event.

The whole analyics module works an a fire-and-forget basis, if something goes wrong the end user is not notified ( except for the browser console error log)


## Development

includes playground 'app', run via `yarn dev`

## Build

`yarn build`

## Release

todo
13 changes: 13 additions & 0 deletions apps/emx2-analytics/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
48 changes: 48 additions & 0 deletions apps/emx2-analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@molgenis/emx2-analytics",
"description": "Molgenis EMX2 Analytics, client module",
"license": "LGPL-3.0-or-later",
"private": false,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"format": "prettier src --write --config ../.prettierrc.js",
"checkFormat": "prettier src --check --config ../.prettierrc.js"
},
"dependencies": {
"vue": "^3.4.29"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"prettier": "2.8.8",
"typescript": "^5.2.2",
"vite": "^5.3.1",
"vite-plugin-dts": "^3.9.1",
"vue-tsc": "^2.0.21"
},
"publishConfig": {
"access": "public"
},
"files": [
"dist",
"src",
"package.json",
"README.md"
],
"main": "./dist/analytics.umd.js",
"module": "./dist/analytics.es.js",
"typings": "./dist/lib/**/*.d.ts",
"exports": {
".": {
"import": "./dist/analytics.es.js",
"require": "./dist/analytics.umd.js"
},
"./dist/style.css": {
"import": "./dist/style.css",
"require": "./dist/style.css"
}
}
}
176 changes: 176 additions & 0 deletions apps/emx2-analytics/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<script setup lang="ts">
import { Ref, ref } from "vue";
import { setupAnalytics } from "./lib/analytics";
import { Trigger } from "./types/Trigger";
const analyticsKey = ref("1234");
// setupAnalytics("catalogue", [{ id: "site-improve", options: { analyticsKey: analyticsKey.value } }]);
const schemaName = ref("catalogue");
const triggerName = ref("");
const cssSelector = ref("");
const triggers: Ref<Trigger[]> = ref([]);
function fetchTriggers() {
fetch(`/catalogue/api/trigger`).then(async (response) => {
triggers.value = await response.json();
});
}
fetchTriggers();
const addTriggerError = ref("");
function addTrigger() {
if (triggerName.value && cssSelector.value) {
fetch(`/${schemaName.value}/api/trigger`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: triggerName.value,
cssSelector: cssSelector.value,
}),
})
.then((response) => response.json())
.then((data) => {
if (data.errors) {
addTriggerError.value = data.errors;
return;
} else fetchTriggers();
})
.catch((error) => {
console.error("Error:", error);
addTriggerError.value = error;
});
}
}
function testBtnClicked() {
console.log("Test button clicked");
}
function reRunSetup() {
console.log("reRunSetup");
setupAnalytics("catalogue", [
{ id: "site-improve", options: { analyticsKey: analyticsKey.value } },
]);
}
</script>

<template>
<h1>Analytics</h1>

<div id="page">
<div class="container">
<h2>Config</h2>
<label for="id">Analytics key</label>
<input id="id" v-model="analyticsKey" placeholder="event identifier" />

<button @click="reRunSetup">(re) run setup</button>
{{ analyticsKey }}
</div>
<div class="container">
<h2>Add Trigger</h2>
<label for="id">Event id</label>
<input id="id" v-model="triggerName" placeholder="event identifier" />
<label for="selector">Dom element css selector</label>
<input
id="selector"
type="textarea"
v-model="cssSelector"
placeholder="css selector for event"
/>
<button @click="addTrigger">Add Trigger</button>
{{ addTriggerError }}
</div>

<div class="container">
<h2>Triggers for {{ schemaName }}</h2>
<ul>
<li v-if="!triggers.length">No trigger setup</li>
<li v-else v-for="trigger in triggers">
<div>
<dl>
<dt>{{ trigger.name }}</dt>
<dd>{{ trigger.cssSelector }}</dd>

<dt v-if="trigger.appName">App</dt>
<dd v-if="trigger.appName">{{ trigger.appName }}</dd>
</dl>
</div>
</li>
</ul>
</div>

<div>
<button id="test-btn" @click="testBtnClicked">Test Button</button>
</div>
</div>
</template>

<style scoped>
input {
margin: 0.5rem 0 1rem 0;
padding: 0.3rem 2rem 0.3rem 0.3rem;
font-size: x-large;
}
#page {
display: flex;
justify-content: space-between;
padding: 1rem;
width: 100%;
#list-container {
padding-right: 5rem;
}
p,
dt {
font-weight: bold;
}
dl,
dd {
text-align: left;
font-size: 0.9rem;
}
dd {
font-family: monospace;
margin-bottom: 1em;
padding-left: 0;
}
.container {
display: flex;
flex-direction: column;
border: 1px solid black;
padding: 0rem 1rem 1rem 1rem;
label {
font-size: x-large;
text-align: left;
}
button {
margin-top: 1rem;
padding: 0.5rem 1rem;
font-size: x-large;
background-color: rgb(242, 185, 14);
}
}
#test-btn {
padding: 0.5rem 1rem;
font-size: x-large;
background-color: rgb(76, 72, 211);
color: white;
}
}
</style>
Loading

0 comments on commit 370db33

Please sign in to comment.