Skip to content

Commit

Permalink
Add ability to use Plausible(.io) for tracking
Browse files Browse the repository at this point in the history
Does not track unless explicitly configured by search.xml.
Tracks initial page view on search and article page, as well as the submitted pattern and filters. (i.e. word and filters, but not pagination, sorting, grouping, etc.).
  • Loading branch information
KCMertens committed Sep 13, 2022
1 parent 7d9c948 commit a00ce8d
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 65 deletions.
56 changes: 46 additions & 10 deletions src/frontend/package-lock.json

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

1 change: 1 addition & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"urijs": "^1.19.7",
"v-tooltip": "2.0.2",
"vue": "^2.6.10",
"vue-plausible": "^1.3.2",
"vue-rx": "^6.0.1",
"vue-slider-component": "^2.8.1",
"vuex": "^3.6.2",
Expand Down
14 changes: 13 additions & 1 deletion src/frontend/src/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import HighchartsExportingData from 'highcharts/modules/export-data';
import HighchartsBoost from 'highcharts/modules/boost';

import URI from 'urijs';
//@ts-ignore
import VuePlausible from 'vue-plausible/lib/esm/vue-plugin.js';

import * as RootStore from '@/store/article';
import ArticlePageComponent from '@/pages/article/ArticlePage.vue';
Expand All @@ -36,7 +38,17 @@ HighchartsExporting(Highcharts);
HighchartsExportingData(Highcharts);
HighchartsBoost(Highcharts);

Vue.use(HighchartsVue);
declare const PLAUSIBLE_DOMAIN: string|undefined;
declare const PLAUSIBLE_APIHOST: string|undefined;
if (PLAUSIBLE_DOMAIN && PLAUSIBLE_APIHOST) {
Vue.use(VuePlausible, {
domain: PLAUSIBLE_DOMAIN,
trackLocalhost: true,
apiHost: PLAUSIBLE_APIHOST,
});
//@ts-ignore
Vue.$plausible.trackPageview();
}Vue.use(HighchartsVue);

$(document).ready(() => {
RootStore.init();
Expand Down
16 changes: 15 additions & 1 deletion src/frontend/src/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Vue from 'vue';

// @ts-ignore
import VTooltip from 'v-tooltip';
//@ts-ignore
import VuePlausible from 'vue-plausible/lib/esm/vue-plugin.js';

import Filters from '@/components/filters';

Expand Down Expand Up @@ -135,7 +137,7 @@ function initQueryBuilder() {
// --------------
Vue.config.productionTip = false;
Vue.config.errorHandler = (err, vm, info) => {
if (err.message !== '[vuex] Do not mutate vuex store state outside mutation handlers.') { // already logged and annoying
if (!err.message.includes('[vuex]' /* do not mutate vuex store state outside mutation handlers */)) { // already logged and annoying
ga('send', 'exception', { exDescription: err.message, exFatal: true });
console.error(err);
} else {
Expand All @@ -162,6 +164,18 @@ Vue.mixin({
// tslint:enable
});


declare const PLAUSIBLE_DOMAIN: string|undefined;
declare const PLAUSIBLE_APIHOST: string|undefined;
if (PLAUSIBLE_DOMAIN && PLAUSIBLE_APIHOST) {
Vue.use(VuePlausible, {
domain: PLAUSIBLE_DOMAIN,
trackLocalhost: true,
apiHost: PLAUSIBLE_APIHOST,
});
//@ts-ignore
Vue.$plausible.trackPageview();
}
Vue.use(Filters);
Vue.use(VTooltip, {
popover: {
Expand Down
5 changes: 2 additions & 3 deletions src/frontend/src/store/search/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as Api from '@/api';
import * as BLTypes from '@/types/blacklabtypes';
import jsonStableStringify from 'json-stable-stringify';
import { debugLog } from '@/utils/debug';
import Vue from 'vue';

type QueryState = {
params?: BLTypes.BLSearchParameters,
Expand Down Expand Up @@ -307,9 +308,7 @@ export default () => {
query: state.query
}
}),
v => {
url$.next(cloneDeep(v));
},
v => url$.next(cloneDeep(v)),
{
immediate: true,
deep: true
Expand Down
115 changes: 65 additions & 50 deletions src/main/java/nl/inl/corpuswebsite/utils/WebsiteConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public String toString() {
/** Google analytics key, analytics are disabled if not provided */
private final Optional<String> analyticsKey;

private final Optional<String> plausibleDomain;
private final Optional<String> plausibleApiHost;

/** Link to put in the top bar */
private final List<LinkInTopBar> linksInTopBar;

Expand All @@ -111,55 +114,59 @@ public String toString() {
*/

public WebsiteConfig(File configFile, Optional<CorpusConfig> corpusConfig, String contextPath) throws ConfigurationException {
Parameters parameters = new Parameters();
ConfigurationBuilder<XMLConfiguration> cb = new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(parameters.fileBased()
.setFile(configFile)
.setListDelimiterHandler(new DisabledListDelimiterHandler())
.setPrefixLookups(new HashMap<String, Lookup>(ConfigurationInterpolator.getDefaultPrefixLookups()) {{
put("request", key -> {
switch (key) {
case "contextPath": return contextPath;
case "corpusId": return corpusConfig.map(CorpusConfig::getCorpusId).orElse(""); // don't return null, or the interpolation string (${request:corpusId}) will be rendered
case "corpusPath": return contextPath + corpusConfig.map(c -> "/" + c.getCorpusId()).orElse("");
default: return key;
}
});
}}));
// Load the specified config file
XMLConfiguration xmlConfig = cb.getConfiguration();

corpusId = corpusConfig.map(CorpusConfig::getCorpusId);
// Can be specified in multiple places: search.xml, corpusConfig (in blacklab), or as a fallback, just the corpusname with some capitalization and any username removed.
corpusDisplayName = Arrays.asList(
xmlConfig.getString("InterfaceProperties.DisplayName"),
corpusConfig.flatMap(CorpusConfig::getDisplayName).orElse(""),
MainServlet.getCorpusName(corpusId).orElse("")
)
.stream().map(StringUtils::trimToNull).filter(s -> s != null).findFirst();
corpusOwner = MainServlet.getCorpusOwner(corpusId);
pathToCustomJs = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.CustomJs")));
pathToCustomCss = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.CustomCss")));
pathToFaviconDir = xmlConfig.getString("InterfaceProperties.FaviconDir", contextPath + "/img");
propColumns = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.PropColumns")));
pagination = xmlConfig.getBoolean("InterfaceProperties.Article.Pagination", false);
pageSize = Math.max(1, xmlConfig.getInt("InterfaceProperties.Article.PageSize", 1000));
analyticsKey = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.Analytics.Key")));
linksInTopBar = Stream.concat(
corpusOwner.isPresent() ? Stream.of(new LinkInTopBar("My corpora", contextPath + "/corpora", false)) : Stream.empty(),
xmlConfig.configurationsAt("InterfaceProperties.NavLinks.Link").stream().map(sub -> {
String label = sub.getString("");
String href = StringUtils.defaultIfEmpty(sub.getString("[@value]"), label);
boolean newWindow = sub.getBoolean("[@newWindow]", false);
boolean relative = sub.getBoolean("[@relative]", false); // No longer supported, keep around for compatibility
if (relative)
href = contextPath + "/" + href;

return new LinkInTopBar(label, href, newWindow);
})
).collect(Collectors.toList());
xsltParameters = xmlConfig.configurationsAt("XsltParameters.XsltParameter").stream()
.collect(Collectors.toMap(sub -> sub.getString("[@name]"), sub -> sub.getString("[@value]")));
Parameters parameters = new Parameters();
ConfigurationBuilder<XMLConfiguration> cb = new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(parameters.fileBased()
.setFile(configFile)
.setListDelimiterHandler(new DisabledListDelimiterHandler())
.setPrefixLookups(new HashMap<String, Lookup>(ConfigurationInterpolator.getDefaultPrefixLookups()) {{
put("request", key -> {
switch (key) {
case "contextPath": return contextPath;
case "corpusId": return corpusConfig.map(CorpusConfig::getCorpusId).orElse(""); // don't return null, or the interpolation string (${request:corpusId}) will be rendered
case "corpusPath": return contextPath + corpusConfig.map(c -> "/" + c.getCorpusId()).orElse("");
default: return key;
}
});
}}));
// Load the specified config file
XMLConfiguration xmlConfig = cb.getConfiguration();

corpusId = corpusConfig.map(CorpusConfig::getCorpusId);
// Can be specified in multiple places: search.xml, corpusConfig (in blacklab), or as a fallback, just the corpusname with some capitalization and any username removed.
corpusDisplayName = Arrays.asList(
xmlConfig.getString("InterfaceProperties.DisplayName"),
corpusConfig.flatMap(CorpusConfig::getDisplayName).orElse(""),
MainServlet.getCorpusName(corpusId).orElse("")
)
.stream().map(StringUtils::trimToNull).filter(s -> s != null).findFirst();
corpusOwner = MainServlet.getCorpusOwner(corpusId);
pathToCustomJs = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.CustomJs")));
pathToCustomCss = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.CustomCss")));
pathToFaviconDir = xmlConfig.getString("InterfaceProperties.FaviconDir", contextPath + "/img");
propColumns = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.PropColumns")));
pagination = xmlConfig.getBoolean("InterfaceProperties.Article.Pagination", false);
pageSize = Math.max(1, xmlConfig.getInt("InterfaceProperties.Article.PageSize", 1000));
analyticsKey = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.Analytics.Key")));
linksInTopBar = Stream.concat(
corpusOwner.isPresent() ? Stream.of(new LinkInTopBar("My corpora", contextPath + "/corpora", false)) : Stream.empty(),
xmlConfig.configurationsAt("InterfaceProperties.NavLinks.Link").stream().map(sub -> {
String label = sub.getString("");
String href = StringUtils.defaultIfEmpty(sub.getString("[@value]"), label);
boolean newWindow = sub.getBoolean("[@newWindow]", false);
boolean relative = sub.getBoolean("[@relative]", false); // No longer supported, keep around for compatibility
if (relative)
href = contextPath + "/" + href;

return new LinkInTopBar(label, href, newWindow);
})
).collect(Collectors.toList());
xsltParameters = xmlConfig.configurationsAt("XsltParameters.XsltParameter").stream()
.collect(Collectors.toMap(sub -> sub.getString("[@name]"), sub -> sub.getString("[@value]")));

// plausible
this.plausibleDomain = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.Plausible.domain")));
this.plausibleApiHost = Optional.ofNullable(StringUtils.trimToNull(xmlConfig.getString("InterfaceProperties.Plausible.apiHost")));
}

public Optional<String> getCorpusId() {
Expand Down Expand Up @@ -213,10 +220,18 @@ public boolean usePagination() {
}

public int getPageSize() {
return pageSize;
return pageSize;
}

public Optional<String> getAnalyticsKey() {
return analyticsKey;
}

public Optional<String> getPlausibleDomain() {
return plausibleDomain;
}

public Optional<String> getPlausibleApiHost() {
return plausibleApiHost;
}
}
6 changes: 6 additions & 0 deletions src/main/resources/interface-default/search.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
</Analytics>
-->

<!-- see https://github.com/moritzsternemann/vue-plausible -->
<!-- <Plausible>
<domain>The domain registered with plausible</domain>
<apiHost>The instance of plausible to use (for when self-hosting), normally https://plausible.io</apiHost>
</Plausible> -->

</InterfaceProperties>
<XsltParameters>
<!--
Expand Down
4 changes: 4 additions & 0 deletions src/main/webapp/WEB-INF/templates/header.vm
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
var CONTEXT_URL = "$esc.javascript($pathToTop)";
// Not suffixed with the corpus id
var BLS_URL = "$esc.javascript($blsUrl)";

var PLAUSIBLE_DOMAIN = "$esc.javascript($websiteConfig.getPlausibleDomain().orElse(null))"
var PLAUSIBLE_APIHOST = "$esc.javascript($websiteConfig.getPlausibleApiHost().orElse(null))"

// Place to register CustomJS hook functions
var hooks = {};
</script>
Expand Down

0 comments on commit a00ce8d

Please sign in to comment.