Skip to content
Snippets Groups Projects
Commit fb24d424 authored by Neuber, Eugen Ramon's avatar Neuber, Eugen Ramon :speech_balloon: Committed by Bekerle, Patrizio
Browse files

Add Matomo web component - WIP

Activate Matomo logging only after user logged in successfully. Deactivate if user logs out.
parent 0e0c67ea
No related branches found
No related tags found
No related merge requests found
Showing
with 919 additions and 48 deletions
......@@ -36,44 +36,6 @@
<link rel="preload" href="<%= getPrivateUrl('fonts/SourceSansPro-Semibold.otf.woff2') %>" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<%= getPrivateUrl('fonts/SourceSansPro-Bold.otf.woff2') %>" as="font" type="font/woff2" crossorigin>
<!-- Matomo -->
<script type="text/javascript">
var _paq = window._paq || [];
_paq.push(['setCustomVariable', 1, "GitCommit", "<%= buildInfo.info %>", "visit"]);
_paq.push(['enableHeartBeatTimer']);
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.tugraz.at/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '<%= matomoSiteId %>']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
// track changed locations
window.addEventListener('locationchanged', function(e) {
_paq.push(['setReferrerUrl', e.detail.referrerUrl]);
_paq.push(['setCustomUrl', location.href]);
// _paq.push(['setDocumentTitle', '']);
_paq.push(['trackPageView']);
// make Matomo aware of newly added content
var content = document.getElementById('content');
_paq.push(['MediaAnalytics::scanForMedia', content]);
_paq.push(['FormAnalytics::scanForForms', content]);
_paq.push(['trackContentImpressionsWithinNode', content]);
});
// track errors
window.addEventListener('error', function(e) {
_paq.push(['trackEvent', 'Error', e.error.message + '\n' + e.error.stack]);
});
</script>
<!-- End Matomo Code -->
<!-- Font related CSS -->
<style>
body {
......@@ -134,6 +96,7 @@
entry-point-url="<%= entryPointURL %>"
base-path="<%= getUrl('') %>"
keycloak-config='{"url": "<%= keyCloakBaseURL %>", "realm": "tugraz", "clientId": "<%= keyCloakClientId %>", "silentCheckSsoRedirectUri": "<%= getUrl('silent-check-sso.html') %>"}'
matomo-url="<%= matomoUrl %>" matomo-site-id="<%= matomoSiteId %>"
><dbp-loading-spinner></dbp-loading-spinner></<%= name %>>
<!-- Error handling for too old browsers -->
......
......@@ -43,7 +43,8 @@ let keyCloakServer = '';
let keyCloakBaseURL = '';
let keyCloakClientId = '';
let pdfAsQualifiedlySigningServer = '';
let matomoSiteId = 131;
const matomoUrl = "https://analytics.tugraz.at/";
const matomoSiteId = 131;
let useTerser = buildFull;
let useBabel = buildFull;
let checkLicenses = buildFull;
......@@ -185,6 +186,7 @@ export default {
keyCloakClientId: keyCloakClientId,
pdfAsQualifiedlySigningServer: pdfAsQualifiedlySigningServer,
environment: build,
matomoUrl: matomoUrl,
matomoSiteId: matomoSiteId,
buildInfo: getBuildInfo()
}
......
......@@ -16,9 +16,13 @@ activities. Handles login, language selection, activity switching, menus etc.
- `lang` (optional, default: `de`): set to `de` or `en` for German or English
- example `<dbp-app-shell lang="de" </dbp-app-shell>`
- `src`: The path to a topic metadata file (json)
- `base-path` (optional, default: `/`: An absolute base path for routing
- `base-path` (optional, default: `/`): An absolute base path for routing
- `entry-point-url`: Entry point URL to access the API
- `keycloak-config`: An object with the following keys: url, realm, clientId, silentCheckSsoRedirectUri, scope
- `matomo-url` (optional): set to your *Matomo* server (required only for tracking)
- example `<dbp-app-shell matomo-url="https://my-matomo.tld"></dbp-app-shell>`
- `matomo-site-id` (optional): set to your site id (required only for tracking)
- example `<dbp-app-shell matomo-site-id="456789"></dbp-app-shell>`
## Topic Metadata
......@@ -38,13 +42,13 @@ activities. Handles login, language selection, activity switching, menus etc.
},
"routing_name": "example",
"activities": [
{"path": "example.metadata.json", visible: true}
{"path": "example.metadata.json", "visible": true}
],
"attributes": []
}
```
## Activity Metada
## Activity Metadata
```json
{
......@@ -70,15 +74,18 @@ activities. Handles login, language selection, activity switching, menus etc.
```bash
# get the source
git clone git@gitlab.tugraz.at:dbp/apps/AppShell.git
cd AppShell
git clone git@gitlab.tugraz.at:dbp/web-components/toolkit.git
cd toolkit/packages/app-shell
git submodule update --init
# install dependencies (make sure you have npm version 4+ installed, so symlinks to the git submodules are created automatically)
npm install
yarn install
# constantly build dist/bundle.js and run a local web-server on port 8002
npm run watch
# constantly build dist/bundle.js and run a local web-server on port 8002
yarn run watch
# run tests
yarn test
```
Jump to <http://localhost:8002> and you should get a Single Sign On login page.
......@@ -23,6 +23,7 @@
"rollup-plugin-consts": "^1.0.1",
"rollup-plugin-copy": "^3.1.0",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-emit-ejs": "^3.1.0",
"rollup-plugin-serve": "^1.0.1"
},
"dependencies": {
......@@ -35,7 +36,8 @@
"dbp-common": "^1.0.0",
"dbp-language-select": "^1.0.0",
"dbp-notification": "^1.0.0",
"dbp-person-profile": "^1.0.0"
"dbp-person-profile": "^1.0.0",
"dbp-matomo": "^1.0.0"
},
"scripts": {
"build": "npm run build-local",
......
......@@ -15,6 +15,7 @@ import {BuildInfo} from './build-info.js';
import {TUGrazLogo} from './tugraz-logo.js';
import {send as notify} from 'dbp-common/notification';
import {appWelcomeMeta} from './dbp-app-shell-welcome.js';
import {MatomoElement} from "dbp-matomo/src/matomo";
const i18n = createI18nInstance();
......@@ -58,6 +59,10 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
this._updateAuth = this._updateAuth.bind(this);
this._loginStatus = 'unknown';
this.matomoUrl = '';
this.matomoSiteId = -1;
this.matomo = null;
this._attrObserver = new MutationObserver(this.onAttributeObserved);
}
......@@ -70,6 +75,7 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
'dbp-auth-menu-button': AuthMenuButton,
'dbp-notification': Notification,
'dbp-icon': Icon,
'dbp-matomo': MatomoElement,
};
}
......@@ -222,6 +228,8 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
subtitle: { type: String, attribute: false },
description: { type: String, attribute: false },
_loginStatus: { type: Boolean, attribute: false },
matomoUrl: { type: String, attribute: "matomo-url" },
matomoSiteId: { type: Number, attribute: "matomo-site-id" },
};
}
......@@ -248,6 +256,10 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
this.initRouter();
this._bus.subscribe('auth-update', this._updateAuth);
this.updateComplete.then(()=> {
this.matomo = this.shadowRoot.querySelector(this.constructor.getScopedTagName('dbp-matomo'));
});
}
disconnectedCallback() {
......@@ -638,6 +650,8 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
}
}
this.track('renderActivity', activity.element);
const elm = document.createElement(activity.element);
this._onActivityAdded(elm);
this._lastElm = elm;
......@@ -658,6 +672,12 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
this._attrObserver.disconnect();
}
track(action, message) {
if (this.matomo !== null) {
this.matomo.track(action, message);
}
}
_renderActivity() {
const act = this.metadata[this.activeView];
if (act === undefined)
......@@ -713,6 +733,7 @@ export class AppShell extends ScopedElementsMixin(LitElement) {
return html`
<slot class="${slotClassMap}"></slot>
<dbp-auth-keycloak lang="${this.lang}" url="${kc.url}" realm="${kc.realm}" client-id="${kc.clientId}" silent-check-sso-redirect-uri="${kc.silentCheckSsoRedirectUri || ''}" scope="${kc.scope || ''}" idp-hint="${kc.idpHint || ''}" load-person try-login></dbp-auth-keycloak>
<dbp-matomo endpoint="${this.matomoUrl}" site-id="${this.matomoSiteId}"></dbp-matomo>
<div class="${mainClassMap}">
<div id="main">
<dbp-notification lang="${this.lang}"></dbp-notification>
......
dist
node_modules
.idea
npm-debug.log
package-lock.json
[submodule "vendor/auth"]
path = vendor/auth
url = git@gitlab.tugraz.at:dbp/web-components/Auth.git
[submodule "vendor/common"]
path = vendor/common
url = git@gitlab.tugraz.at:dbp/web-components/Common.git
This diff is collapsed.
# Matomo Web Component
[GitLab Repository](git@gitlab.tugraz.at:dbp/web-components/toolkit.git)
## Usage
```html
<dbp-matomo></dbp-matomo>
```
## Attributes
- `endpoint` (required): set to your *Matomo* server
- example `<dbp-matomo endpoint="https://my-matomo.tld"></dbp-matomo>`
- `site-id` (required): set to your site id
- example `<dbp-matomo site-id="456789"></dbp-matomo>`
## Tracking actions
```javascript
class Demo {
track(action, message) {
const matomo = this.shadowRoot.querySelector(this.constructor.getScopedTagName('dbp-matomo'));
if (matomo !== null) {
matomo.track(action, message);
}
}
clickMe() {
this.track('button clicked', 'alert()');
}
}
```
## Local development
```bash
# get the source
git clone git@gitlab.tugraz.at:dbp/web-components/toolkit.git
cd toolkit/packages/matomo
git submodule update --init
# install dependencies (make sure you have npm version 4+ installed, so symlinks to the git submodules are created automatically)
yarn install
# constantly build dist/bundle.js and run a local web-server on port 8002
yarn run watch
# run tests
yarn test
```
Jump to <http://localhost:8002> and you should get a demo page.
packages/matomo/assets/favicon.ico

2.49 KiB

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script type="module" src="dbp-matomo-demo.js"></script>
</head>
<body>
<dbp-matomo-demo lang="de" matomo-url="<%= matomoUrl %>" matomo-site-id="<%= matomoSiteId %>"></dbp-matomo-demo>
<p>version: <span style="color: white; background-color: black;"><%= buildInfo.info %></span></p>
<p>Matomo: url: <%= matomoUrl %>, site-id: <%= matomoSiteId %></p>
</body>
</html>
module.exports = {
input: [
'src/*.js',
],
output: './',
options: {
debug: false,
removeUnusedKeys: true,
lngs: ['en','de'],
resource: {
loadPath: 'src/i18n/{{lng}}/{{ns}}.json',
savePath: 'src/i18n/{{lng}}/{{ns}}.json'
},
},
}
// Trick to use the auto-downloaded puppeteer chrome binary
process.env.CHROME_BIN = require('puppeteer').executablePath();
module.exports = function(config) {
config.set({
basePath: 'dist',
frameworks: ['mocha', 'chai'],
files: [
{pattern: './*.js', included: true, watched: true, served: true, type: 'module'},
{pattern: './**/*', included: false, watched: true, served: true},
],
autoWatch: true,
browsers: ['ChromeHeadlessNoSandbox'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
singleRun: false,
logLevel: config.LOG_ERROR
});
}
{
"name": "dbp-matomo",
"version": "1.0.0",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"private": true,
"devDependencies": {
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"@rollup/plugin-url": "^5.0.1",
"chai": "^4.2.0",
"i18next-scanner": "^2.10.2",
"karma": "^5.1.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.0.0",
"karma-mocha": "^2.0.1",
"mocha": "^8.0.1",
"puppeteer": "^2.1.1",
"rollup": "^2.19.0",
"rollup-plugin-consts": "^1.0.1",
"rollup-plugin-copy": "^3.1.0",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-serve": "^1.0.1",
"rollup-plugin-terser": "^6.1.0"
},
"dependencies": {
"@open-wc/scoped-elements": "^1.1.1",
"lit-element": "^2.3.1",
"dbp-auth": "^1.0.0",
"dbp-common": "^1.0.0"
},
"scripts": {
"clean": "rm dist/*",
"build-local": "rollup -c",
"build-dev": "rollup -c --environment BUILD:development",
"build-prod": "rollup -c --environment BUILD:production",
"build-demo": "rollup -c --environment BUILD:demo",
"build-test": "rollup -c --environment BUILD:test",
"i18next": "i18next-scanner",
"watch": "rollup -c --watch",
"watch-local": "yarn run watch",
"watch-dev": "rollup -c --watch --environment BUILD:development",
"test": "rollup -c --environment BUILD:test && karma start --singleRun"
}
}
import glob from 'glob';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import copy from 'rollup-plugin-copy';
import {terser} from "rollup-plugin-terser";
import json from '@rollup/plugin-json';
import serve from 'rollup-plugin-serve';
import url from "@rollup/plugin-url";
import consts from 'rollup-plugin-consts';
import del from 'rollup-plugin-delete';
import emitEJS from 'rollup-plugin-emit-ejs'
import {getPackagePath} from '../../rollup.utils.js';
const pkg = require('./package.json');
const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : 'local';
console.log("build: " + build);
const matomoUrl = "https://analytics.tugraz.at/";
const matomoSiteId = 131;
function getBuildInfo() {
const child_process = require('child_process');
const url = require('url');
let remote = child_process.execSync('git config --get remote.origin.url').toString().trim();
let commit = child_process.execSync('git rev-parse --short HEAD').toString().trim();
let parsed = url.parse(remote);
// convert git urls
if (parsed.protocol === null) {
parsed = url.parse('git://' + remote.replace(":", "/"));
parsed.protocol = 'https:';
}
let newPath = parsed.path.slice(0, parsed.path.lastIndexOf('.'));
let newUrl = parsed.protocol + '//' + parsed.host + newPath + '/commit/' + commit;
return {
info: commit,
url: newUrl,
time: new Date().toISOString(),
env: build
}
}
export default (async () => {
return {
input: (build != 'test') ? ['src/dbp-matomo.js', 'src/dbp-matomo-demo.js'] : glob.sync('test/**/*.js'),
output: {
dir: 'dist',
entryFileNames: '[name].js',
chunkFileNames: 'shared/[name].[hash].[format].js',
format: 'esm',
sourcemap: true
},
onwarn: function (warning, warn) {
// keycloak bundled code uses eval
if (warning.code === 'EVAL') {
return;
}
warn(warning);
},
plugins: [
del({
targets: 'dist/*'
}),
consts({
environment: build,
buildInfo: getBuildInfo(),
}),
emitEJS({
src: 'assets',
include: ['**/*.ejs', '**/.*.ejs'],
data: {
getUrl: (p) => {
return url.resolve(basePath, p);
},
getPrivateUrl: (p) => {
return url.resolve(`${basePath}local/${pkg.name}/`, p);
},
name: pkg.name,
environment: build,
matomoUrl: matomoUrl,
matomoSiteId: matomoSiteId,
buildInfo: getBuildInfo()
}
}),
resolve(),
commonjs(),
url({
limit: 0,
include: [
await getPackagePath('select2', '**/*.css'),
],
emitFiles: true,
fileName: 'shared/[name].[hash][extname]'
}),
json(),
(build !== 'local' && build !== 'test') ? terser() : false,
copy({
targets: [
{src: 'assets/index.html', dest: 'dist'},
{src: await getPackagePath('dbp-common', 'assets/icons/*.svg'), dest: 'dist/local/dbp-common/icons'},
{src: 'assets/favicon.ico', dest:'dist'},
],
}),
(process.env.ROLLUP_WATCH === 'true') ? serve({contentBase: 'dist', host: '127.0.0.1', port: 8002}) : false
]
};
})();
\ No newline at end of file
import {i18n} from './i18n.js';
import {css, html, LitElement} from 'lit-element';
import {ScopedElementsMixin} from '@open-wc/scoped-elements';
import {AuthKeycloak, LoginButton} from 'dbp-auth';
import * as commonUtils from 'dbp-common/utils';
import * as commonStyles from 'dbp-common/styles';
import {MatomoElement} from './matomo';
class MatomoDemo extends ScopedElementsMixin(LitElement) {
constructor() {
super();
this.lang = 'de';
this.matomoUrl = '';
this.matomoSiteId = -1;
}
static get scopedElements() {
return {
'dbp-auth-keycloak': AuthKeycloak,
'dbp-login-button': LoginButton,
'dbp-matomo': MatomoElement,
};
}
static get properties() {
return {
lang: { type: String },
matomoUrl: { type: String, attribute: "matomo-url" },
matomoSiteId: { type: Number, attribute: "matomo-site-id" },
};
}
connectedCallback() {
super.connectedCallback();
i18n.changeLanguage(this.lang);
}
track(action, message) {
const matomo = this.shadowRoot.querySelector(this.constructor.getScopedTagName('dbp-matomo'));
if (matomo !== null) {
matomo.track(action, message);
}
}
clickMe() {
this.track('button clicked', 'alert()');
alert('button clicked!');
}
static get styles() {
// language=css
return [
commonStyles.getThemeCSS(),
commonStyles.getGeneralCSS(),
css`
h1.title {margin-bottom: 1em;}
div.container {margin-bottom: 1.5em; padding-left:20px;}
`
];
}
render() {
return html`
<section class="section">
<div class="container">
<h1 class="title">Matomo-Demo</h1>
</div>
<div class="container">
<dbp-auth-keycloak lang="${this.lang}" url="https://auth-dev.tugraz.at/auth" realm="tugraz" client-id="auth-dev-mw-frontend-local" load-person try-login></dbp-auth-keycloak>
<dbp-login-button lang="${this.lang}" show-image></dbp-login-button>
<dbp-matomo endpoint="${this.matomoUrl}" site-id="${this.matomoSiteId}"></dbp-matomo>
</div>
<div class="container">
<p>Click this button (and watch the network traffic) ...</p>
<p><input type="button" value="click me" @click="${this.clickMe}"></p>
</div>
</section>
`;
}
}
commonUtils.defineCustomElement('dbp-matomo-demo', MatomoDemo);
import * as commonUtils from 'dbp-common/utils';
import {MatomoElement} from './matomo.js';
commonUtils.defineCustomElement('dbp-matomo', MatomoElement);
import './dbp-matomo-demo.js';
\ No newline at end of file
import {createInstance} from 'dbp-common/i18next.js';
import de from './i18n/de/translation.json';
import en from './i18n/en/translation.json';
export const i18n = createInstance({en: en, de: de}, 'de', 'en');
\ No newline at end of file
{
"matomo": {
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment