Refactor analytics events (#209)

* Refactor analytics.js

* Update analytics calls in app.vue

* Update analytics calls in presets.vue

* Update analytics calls in tools.vue (and app.vue)

* Update analytics calls in global.vue

* Update analytics calls in domain.vue

* Update analytics calls in setup.vue

* Add list of all events to analytics.js

* Add custom copy to clipboard that emits event

* Emit the events from the components

* Update copyright year in all files touched

* Update analytics calls in download.vue

* Update analytics calls in ssl.vue

* Update analytics calls in certbot.vue

* Update analytics calls in domain.vue

* Update analytics calls in app.vue

* Note down 'Code snippet copied' events
pull/214/head
Matt (IPv4) Cowley 2021-01-18 19:45:19 +00:00 committed by GitHub
parent c86fb3cf76
commit 3fdccfa68a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 650 additions and 113 deletions

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -101,6 +101,7 @@ THE SOFTWARE.
:name="confContents[0]"
:conf="confContents[1]"
:half="Object.keys(confFilesOutput).length > 1 && !splitColumn"
@copied="codeCopiedEvent(confContents[3])"
></component>
</template>
</div>
@ -125,7 +126,6 @@ THE SOFTWARE.
import isObject from '../util/is_object';
import analytics from '../util/analytics';
import browserLanguage from '../util/browser_language';
import { toSep } from '../util/language_pack_name';
import { defaultPack } from '../util/language_pack_default';
import { availablePacks } from '../util/language_pack_context';
@ -149,8 +149,8 @@ THE SOFTWARE.
Global,
Setup,
NginxPrism,
'YamlPrism': () => import('./prism/yaml'),
'DockerPrism': () => import('./prism/docker'),
YamlPrism: () => import('./prism/yaml'),
DockerPrism: () => import('./prism/docker'),
},
data() {
return {
@ -171,9 +171,10 @@ THE SOFTWARE.
splitColumn: false,
confWatcherWaiting: false,
confFilesPrevious: {},
confFilesOutput: {},
confFilesOutput: [],
languageLoading: false,
languagePrevious: defaultPack,
interactiveEvents: false,
};
},
computed: {
@ -214,8 +215,12 @@ THE SOFTWARE.
},
'$data.global.app.lang': {
handler(data) {
// Lock out the dropdown
this.$data.languageLoading = true;
// Store if we should fire the event when this is done loading
const interactive = this.$data.interactiveEvents;
// Ensure valid pack
if (!availablePacks.includes(data.value)) data.computed = data.default;
@ -227,7 +232,7 @@ THE SOFTWARE.
this.$data.languageLoading = false;
// Analytics
analytics(`set_language_${toSep(data.computed, '_')}`, 'Language');
this.languageSetEvent(!interactive);
}).catch((err) => {
// Error
console.log('Failed to set language to', data.computed);
@ -255,8 +260,10 @@ THE SOFTWARE.
if (language) this.lang = language;
}
// Send an initial GA event for column mode
// Initial analytics events
this.splitColumnEvent(true);
for (let i = 0; i < this.activeDomains.length; i++) this.addSiteEvent(i + 1, true);
this.$data.interactiveEvents = true;
},
methods: {
changes(index) {
@ -285,15 +292,16 @@ THE SOFTWARE.
this.$data.domains.push(data);
this.$data.active = this.$data.domains.length - 1;
// GA
analytics('add_site', 'Sites', undefined, this.activeDomains.length);
// Analytics
this.addSiteEvent(this.activeDomains.length);
},
remove(index) {
const name = this.$data.domains[index].server.domain.computed;
this.$set(this.$data.domains, index, null);
if (this.$data.active === index) this.$data.active = this.$data.domains.findIndex(d => d !== null);
// GA
analytics('remove_site', 'Sites', undefined, this.activeDomains.length);
// Analytics
this.removeSiteEvent(this.activeDomains.length, name);
},
checkChange(oldConf) {
// If nothing has changed for a tick, we can use the config files
@ -319,7 +327,7 @@ THE SOFTWARE.
const diffConf = diff(newConf, oldConf, {
highlightFunction: value => `<mark>${value}</mark>`,
});
this.$data.confFilesOutput = Object.values(diffConf).map(({ name, content }) => {
this.$data.confFilesOutput = Object.entries(diffConf).map(([ file, { name, content } ]) => {
const diffName = name.filter(x => !x.removed).map(x => x.value).join('');
const confName = `${escape(this.$data.global.nginx.nginxConfigDirectory.computed)}/${diffName}`;
const diffContent = content.filter(x => !x.removed).map(x => x.value).join('');
@ -328,6 +336,7 @@ THE SOFTWARE.
confName,
diffContent,
`${sha2_256(confName)}-${sha2_256(diffContent)}`,
file,
];
});
} catch (e) {
@ -339,6 +348,7 @@ THE SOFTWARE.
confName,
content,
`${sha2_256(confName)}-${sha2_256(content)}`,
name,
];
});
}
@ -351,7 +361,42 @@ THE SOFTWARE.
this.splitColumnEvent();
},
splitColumnEvent(nonInteraction = false) {
analytics('toggle_split_column', 'Button', undefined, Number(this.$data.splitColumn), nonInteraction);
analytics({
category: 'Split column',
action: this.$data.splitColumn ? 'Enabled' : 'Disabled',
nonInteraction,
});
},
languageSetEvent(nonInteraction = false) {
analytics({
category: 'Language',
action: 'Set',
label: this.$data.global.app.lang.computed,
nonInteraction,
});
},
addSiteEvent(count, nonInteraction = false) {
analytics({
category: 'Site',
action: 'Added',
value: count,
nonInteraction,
});
},
removeSiteEvent(count, name) {
analytics({
category: 'Site',
action: 'Removed',
label: name,
value: count,
});
},
codeCopiedEvent(file) {
analytics({
category: 'Config files',
action: 'Code snippet copied',
label: file,
});
},
getPrismComponent(confName) {
switch (confName) {

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -34,7 +34,7 @@ THE SOFTWARE.
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="tabClass(tab.key)">
<a @click="active = tab.key">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
<a @click="showTab(tab.key)">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
</li>
</ul>
</div>
@ -48,10 +48,10 @@ THE SOFTWARE.
></component>
<div class="navigation-buttons">
<a v-if="previousTab !== false" class="button is-mini" @click="active = previousTab">
<a v-if="previousTab !== false" class="button is-mini" @click="showPreviousTab">
<i class="fas fa-long-arrow-alt-left"></i> <span>{{ $t('common.back') }}</span>
</a>
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="active = nextTab">
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="showNextTab">
<span>{{ $t('common.next') }}</span> <i class="fas fa-long-arrow-alt-right"></i>
</a>
</div>
@ -60,6 +60,7 @@ THE SOFTWARE.
</template>
<script>
import analytics from '../util/analytics';
import isChanged from '../util/is_changed';
import Presets from './domain_sections/presets';
import * as Sections from './domain_sections';
@ -127,6 +128,39 @@ THE SOFTWARE.
if (tabs.indexOf(tab) < tabs.indexOf(this.$data.active)) classes.push('is-before');
return classes.join(' ');
},
showTab(target) {
// Analytics
analytics({
category: 'Site',
action: 'Tab clicked',
label: `${this.$data.active}, ${target}`,
});
// Go!
this.$data.active = target;
},
showPreviousTab() {
// Analytics
analytics({
category: 'Site',
action: 'Back clicked',
label: `${this.$data.active}, ${this.previousTab}`,
});
// Go!
this.$data.active = this.previousTab;
},
showNextTab() {
// Analytics
analytics({
category: 'Site',
action: 'Next clicked',
label: `${this.$data.active}, ${this.nextTab}`,
});
// Go!
this.$data.active = this.nextTab;
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -56,7 +56,7 @@ THE SOFTWARE.
</div>
</template>
<div class="control" v-if="incorrectEnding">
<div v-if="incorrectEnding" class="control">
<label class="text message is-warning">
<span class="message-body">
{{ $t('templates.domainSections.onion.onionLocationExpectedToEndWithOnion') }}

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -56,7 +56,6 @@ THE SOFTWARE.
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
import analytics from '../../util/analytics';
import camelToSnake from '../../util/camel_to_snake';
const defaults = {
frontend: {
@ -208,7 +207,7 @@ THE SOFTWARE.
setPreset(key) {
// Set that we're using this preset
Object.keys(this.$props.data).forEach(preset => this[preset] = preset === key);
analytics(`apply_${camelToSnake(key)}`, 'Presets');
this.presetEvent(key, this.interacted);
// Restore some specific defaults first
this.$parent.resetValue('server', 'domain');
@ -271,6 +270,13 @@ THE SOFTWARE.
break;
}
},
presetEvent(name, overwrite = false) {
analytics({
category: 'Preset',
action: overwrite ? 'Overwritten' : 'Applied', // TODO: Is overwritten the best word here?
label: name,
});
},
toggleCollapse() {
if (this.interacted) {
this.expanded = !this.expanded;

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,7 +29,7 @@ THE SOFTWARE.
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="tabClass(tab.key)">
<a @click="active = tab.key">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
<a @click="showTab(tab.key)">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
</li>
</ul>
</div>
@ -43,10 +43,10 @@ THE SOFTWARE.
></component>
<div class="navigation-buttons">
<a v-if="previousTab !== false" class="button is-mini" @click="active = previousTab">
<a v-if="previousTab !== false" class="button is-mini" @click="showPreviousTab">
<i class="fas fa-long-arrow-alt-left"></i> <span>{{ $t('common.back') }}</span>
</a>
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="active = nextTab">
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="showNextTab">
<span>{{ $t('common.next') }}</span> <i class="fas fa-long-arrow-alt-right"></i>
</a>
</div>
@ -54,6 +54,7 @@ THE SOFTWARE.
</template>
<script>
import analytics from '../util/analytics';
import isChanged from '../util/is_changed';
import * as Sections from './global_sections';
@ -113,6 +114,39 @@ THE SOFTWARE.
if (tabs.indexOf(tab) < tabs.indexOf(this.$data.active)) classes.push('is-before');
return classes.join(' ');
},
showTab(target) {
// Analytics
analytics({
category: 'Global',
action: 'Tab clicked',
label: `${this.$data.active}, ${target}`,
});
// Go!
this.$data.active = target;
},
showPreviousTab() {
// Analytics
analytics({
category: 'Global',
action: 'Back clicked',
label: `${this.$data.active}, ${this.previousTab}`,
});
// Go!
this.$data.active = this.previousTab;
},
showNextTab() {
// Analytics
analytics({
category: 'Global',
action: 'Next clicked',
label: `${this.$data.active}, ${this.nextTab}`,
});
// Go!
this.$data.active = this.nextTab;
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -243,7 +243,10 @@ THE SOFTWARE.
this.$t('templates.globalSections.tools.resetGlobalConfig'),
this.$t('templates.globalSections.tools.resetGlobalConfigBody'),
() => {
analytics('reset_global', 'Reset');
// Analytics
this.resetGlobalEvent();
// Do the reset
Object.values(this.$parent.$props.data).forEach(category => {
Object.values(category).forEach(property => {
property.value = property.default;
@ -264,7 +267,10 @@ THE SOFTWARE.
${domain.server.domain.computed}
${this.$t('templates.globalSections.tools.domain')}`,
() => {
analytics('reset_domain', 'Reset', domain.server.domain.computed);
// Analytics
this.resetDomainEvent(domain.server.domain.computed);
// Do the reset
this.doResetDomain(domain);
},
);
@ -280,13 +286,10 @@ THE SOFTWARE.
${domain.server.domain.computed}
${this.$t('templates.globalSections.tools.domainConfiguration')}`,
() => {
analytics(
'remove_domain',
'Remove',
domain.server.domain.computed,
this.$parent.$parent.activeDomains.length - 1,
);
// Analytics
this.removeDomainEvent(domain.server.domain.computed);
// Do the removal
this.doRemoveDomain(index);
},
);
@ -296,13 +299,13 @@ THE SOFTWARE.
this.$t('templates.globalSections.tools.resetAllDomainsConfig'),
this.$t('templates.globalSections.tools.resetAllDomainsConfigBody'),
() => {
analytics(
'reset_all',
'Reset',
this.$parent.$parent.activeDomains.map(x => x[0].server.domain.computed).join(','),
// Analytics
this.resetDomainsEvent(
this.$parent.$parent.activeDomains.map(x => x[0].server.domain.computed),
this.$parent.$parent.activeDomains.length,
);
// Do the reset
for (let i = 0; i < this.$parent.$parent.$data.domains.length; i++) {
this.doResetDomain(this.$parent.$parent.$data.domains[i]);
}
@ -314,19 +317,62 @@ THE SOFTWARE.
this.$t('templates.globalSections.tools.removeAllDomains'),
this.$t('templates.globalSections.tools.removeAllDomainsBody'),
() => {
analytics(
'remove_all',
'Remove',
this.$parent.$parent.activeDomains.map(x => x[0].server.domain.computed).join(','),
// Analytics
this.removeDomainsEvent(
this.$parent.$parent.activeDomains.map(x => x[0].server.domain.computed),
this.$parent.$parent.activeDomains.length,
);
// Do the removal
for (let i = 0; i < this.$parent.$parent.$data.domains.length; i++) {
this.doRemoveDomain(i);
}
},
);
},
resetGlobalEvent() {
analytics({
category: 'Tools',
action: 'Global settings reset',
});
},
resetDomainEvent(name) {
analytics({
category: 'Tools',
action: 'Site reset',
label: name,
});
},
removeDomainEvent(name) {
analytics({
category: 'Tools',
action: 'Removed site',
label: name,
});
// Also fire the general site removal event
this.$parent.$parent.removeSiteEvent(this.$parent.$parent.activeDomains.length - 1, name);
},
resetDomainsEvent(names, count) {
analytics({
category: 'Tools',
action: 'All sites reset',
label: names.join(', '),
value: count,
});
},
removeDomainsEvent(names, count) {
analytics({
category: 'Tools',
action: 'All sites removed',
label: names.join(', '),
value: count,
});
// Also fire the general site removal event
for (let i = 0; i < this.$parent.$parent.$data.domains.length; i++)
this.$parent.$parent.removeSiteEvent(this.$parent.$parent.activeDomains.length - i - 1, names[i]);
},
select(event) {
event.target.setSelectionRange(0, event.target.value.length);
},

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -25,7 +25,7 @@ THE SOFTWARE.
-->
<template>
<div>
<div @copied="copied">
<pre><code class="language-bash">{{ cmd }}</code></pre>
</div>
</template>
@ -42,5 +42,10 @@ THE SOFTWARE.
console.info(`Highlighting ${this.$props.cmd}...`);
Prism.highlightAllUnder(this.$el);
},
methods: {
copied(event) {
this.$emit('copied', event.detail.text);
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -25,7 +25,7 @@ THE SOFTWARE.
-->
<template>
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`">
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`" @copied="copied">
<h3 v-html="name"></h3>
<pre><code class="language-docker" v-html="conf"></code></pre>
</div>
@ -46,5 +46,10 @@ THE SOFTWARE.
console.info(`Highlighting ${this.$props.name}...`);
Prism.highlightAllUnder(this.$el);
},
methods: {
copied(event) {
this.$emit('copied', event.detail.text);
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -25,7 +25,7 @@ THE SOFTWARE.
-->
<template>
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`">
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`" @copied="copied">
<h3 v-html="name"></h3>
<pre><code class="language-nginx" v-html="conf"></code></pre>
</div>
@ -45,5 +45,10 @@ THE SOFTWARE.
console.info(`Highlighting ${this.$props.name}...`);
Prism.highlightAllUnder(this.$el);
},
methods: {
copied(event) {
this.$emit('copied', event.detail.text);
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -25,7 +25,7 @@ THE SOFTWARE.
-->
<template>
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`">
<div :class="`column ${half ? 'is-half' : 'is-full'} is-full-mobile is-full-tablet`" @copied="copied">
<h3 v-html="name"></h3>
<pre><code class="language-yaml" v-html="conf"></code></pre>
</div>
@ -46,5 +46,10 @@ THE SOFTWARE.
console.info(`Highlighting ${this.$props.name}...`);
Prism.highlightAllUnder(this.$el);
},
methods: {
copied(event) {
this.$emit('copied', event.detail.text);
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -30,7 +30,7 @@ THE SOFTWARE.
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="tabClass(tab.key)">
<a @click="active = tab.key">{{ $t(tab.display) }}</a>
<a @click="showTab(tab.key)">{{ $t(tab.display) }}</a>
</li>
</ul>
</div>
@ -44,10 +44,10 @@ THE SOFTWARE.
></component>
<div class="navigation-buttons">
<a v-if="previousTab !== false" class="button is-mini" @click="active = previousTab">
<a v-if="previousTab !== false" class="button is-mini" @click="showPreviousTab">
<i class="fas fa-long-arrow-alt-left"></i> <span>{{ $t('common.back') }}</span>
</a>
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="active = nextTab">
<a v-if="nextTab !== false" class="button is-primary is-mini" @click="showNextTab">
<span>{{ $t('common.next') }}</span> <i class="fas fa-long-arrow-alt-right"></i>
</a>
</div>
@ -92,6 +92,9 @@ THE SOFTWARE.
if (index >= 0) return tabs[index];
return false;
},
domainCount() {
return this.$props.data.domains.filter(d => d !== null).length;
},
tarName() {
const domains = this.$props.data.domains.filter(d => d !== null).map(d => d.server.domain.computed);
return `nginxconfig.io-${domains.join(',')}.tar.gz`;
@ -123,11 +126,27 @@ THE SOFTWARE.
return new Tar(data).gz();
},
downloadTar() {
analytics('download_tar', 'Download', this.tarName);
// Analytics
analytics({
category: 'Setup',
action: 'Downloaded tar file',
label: this.tarName,
value: this.domainCount,
});
// Do tar generation
this.tarContents().download(this.tarName);
},
copyTar() {
analytics('download_base64', 'Download', this.tarName);
// Analytics
analytics({
category: 'Setup',
action: 'Copied base64 tar',
label: this.tarName,
value: this.domainCount,
});
// Do tar generation
const path = `${this.$props.data.global.nginx.nginxConfigDirectory.computed}/${this.tarName}`;
return this.tarContents().base64(path);
},
@ -155,6 +174,39 @@ THE SOFTWARE.
resetText();
});
},
showTab(target) {
// Analytics
analytics({
category: 'Setup',
action: 'Tab clicked',
label: `${this.$data.active}, ${target}`,
});
// Go!
this.$data.active = target;
},
showPreviousTab() {
// Analytics
analytics({
category: 'Setup',
action: 'Back clicked',
label: `${this.$data.active}, ${this.previousTab}`,
});
// Go!
this.$data.active = this.previousTab;
},
showNextTab() {
// Analytics
analytics({
category: 'Setup',
action: 'Next clicked',
label: `${this.$data.active}, ${this.nextTab}`,
});
// Go!
this.$data.active = this.nextTab;
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -34,6 +34,7 @@ THE SOFTWARE.
</p>
<BashPrism :key="sitesAvailable"
:cmd="`sed -i -r 's/(listen .*443)/\\1;#/g; s/(ssl_(certificate|certificate_key|trusted_certificate) )/#;#\\1/g' ${sitesAvailable}`"
@copied="codeCopiedEvent('Disable ssl directives')"
></BashPrism>
</li>
@ -42,7 +43,9 @@ THE SOFTWARE.
{{ $t('templates.setupSections.certbot.reloadYourNginxServer') }}
<br />
</p>
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"></BashPrism>
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"
@copied="codeCopiedEvent('Reload nginx')"
></BashPrism>
</li>
<li>
@ -50,7 +53,10 @@ THE SOFTWARE.
{{ $t('templates.setupSections.certbot.obtainSslCertificatesFromLetsEncrypt') }}
<br />
</p>
<BashPrism :key="certbotCmds" :cmd="certbotCmds"></BashPrism>
<BashPrism :key="certbotCmds"
:cmd="certbotCmds"
@copied="codeCopiedEvent('Obtain certificates using certbot')"
></BashPrism>
</li>
<li>
@ -58,7 +64,10 @@ THE SOFTWARE.
{{ $t('templates.setupSections.certbot.uncommentSslDirectivesInConfiguration') }}
<br />
</p>
<BashPrism :key="sitesAvailable" :cmd="`sed -i -r 's/#?;#//g' ${sitesAvailable}`"></BashPrism>
<BashPrism :key="sitesAvailable"
:cmd="`sed -i -r 's/#?;#//g' ${sitesAvailable}`"
@copied="codeCopiedEvent('Enable ssl directives')"
></BashPrism>
</li>
<li>
@ -66,7 +75,9 @@ THE SOFTWARE.
{{ $t('templates.setupSections.certbot.reloadYourNginxServer') }}
<br />
</p>
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"></BashPrism>
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"
@copied="codeCopiedEvent('Reload nginx (2)')"
></BashPrism>
</li>
<li>
@ -74,8 +85,12 @@ THE SOFTWARE.
{{ $t('templates.setupSections.certbot.configureCertbotToReloadNginxOnCertificateRenewal') }}
<br />
</p>
<BashPrism cmd="echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"></BashPrism>
<BashPrism cmd="sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"></BashPrism>
<BashPrism cmd="echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"
@copied="codeCopiedEvent('Create nginx auto-restart on renewal')"
></BashPrism>
<BashPrism cmd="sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh"
@copied="codeCopiedEvent('Enable execution of auto-restart')"
></BashPrism>
</li>
</ol>
@ -95,6 +110,7 @@ THE SOFTWARE.
<script>
import BashPrism from '../prism/bash';
import analytics from '../../util/analytics';
export default {
name: 'SetupCertbot',
@ -144,5 +160,14 @@ THE SOFTWARE.
)).join('\n');
},
},
methods: {
codeCopiedEvent(step) {
analytics({
category: 'Setup',
action: 'Code snippet copied',
label: `certbot: ${step}`,
});
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -52,6 +52,7 @@ THE SOFTWARE.
<br />
<BashPrism :key="$props.data.global.nginx.nginxConfigDirectory.computed"
:cmd="`cd ${$props.data.global.nginx.nginxConfigDirectory.computed}`"
@copied="codeCopiedEvent('Navigate to nginx config directory')"
></BashPrism>
</p>
</li>
@ -60,7 +61,9 @@ THE SOFTWARE.
<p>
<span v-html="$t('templates.setupSections.download.createABackupOfYourCurrentNginxConfiguration')"></span>
<br />
<BashPrism cmd="tar -czvf nginx_$(date +'%F_%H-%M-%S').tar.gz nginx.conf sites-available/ sites-enabled/ nginxconfig.io/"></BashPrism>
<BashPrism cmd="tar -czvf nginx_$(date +'%F_%H-%M-%S').tar.gz nginx.conf sites-available/ sites-enabled/ nginxconfig.io/"
@copied="codeCopiedEvent('Create nginx config backup tar')"
></BashPrism>
</p>
</li>
@ -68,7 +71,10 @@ THE SOFTWARE.
<p>
<span v-html="$t('templates.setupSections.download.extractTheNewCompressedConfigurationArchiveUsingTar')"></span>
<br />
<BashPrism :key="$parent.tarName" :cmd="`tar -xzvf ${$parent.tarName}`"></BashPrism>
<BashPrism :key="$parent.tarName"
:cmd="`tar -xzvf ${$parent.tarName}`"
@copied="codeCopiedEvent('Extract new nginx config tar')"
></BashPrism>
</p>
</li>
</ol>
@ -77,6 +83,7 @@ THE SOFTWARE.
<script>
import BashPrism from '../prism/bash';
import analytics from '../../util/analytics';
export default {
name: 'SetupDownload',
@ -91,5 +98,14 @@ THE SOFTWARE.
mounted() {
this.$parent.setupCopy(this.$refs.copyTar);
},
methods: {
codeCopiedEvent(step) {
analytics({
category: 'Setup',
action: 'Code snippet copied',
label: `download: ${step}`,
});
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -32,13 +32,16 @@ THE SOFTWARE.
<p>
{{ $t('templates.setupSections.goLive.reloadNginxToLoadInYourNewConfiguration') }}
<br />
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"></BashPrism>
<BashPrism cmd="sudo nginx -t && sudo systemctl reload nginx"
@copied="codeCopiedEvent('Reload nginx')"
></BashPrism>
</p>
</div>
</template>
<script>
import BashPrism from '../prism/bash';
import analytics from '../../util/analytics';
export default {
name: 'SetupGoLive',
@ -50,5 +53,14 @@ THE SOFTWARE.
props: {
data: Object,
},
methods: {
codeCopiedEvent(step) {
analytics({
category: 'Setup',
action: 'Code snippet copied',
label: `goLive: ${step}`,
});
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<!--
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -33,6 +33,7 @@ THE SOFTWARE.
<br />
<BashPrism :key="`${$props.data.global.nginx.nginxConfigDirectory.computed}-${diffieHellmanValue}`"
:cmd="`openssl dhparam -out ${$props.data.global.nginx.nginxConfigDirectory.computed}/dhparam.pem ${diffieHellmanValue}`"
@copied="codeCopiedEvent('Generate diffie-hellman keys')"
></BashPrism>
</p>
</li>
@ -41,9 +42,13 @@ THE SOFTWARE.
<p>
<span v-html="$t('templates.setupSections.ssl.createACommonAcmeChallengeDirectoryForLetsEncrypt')"></span>
<br />
<BashPrism :key="letsEncryptDir" :cmd="`mkdir -p ${letsEncryptDir}`"></BashPrism>
<BashPrism :key="letsEncryptDir"
:cmd="`mkdir -p ${letsEncryptDir}`"
@copied="codeCopiedEvent('Create let\'s encrypt directory')"
></BashPrism>
<BashPrism :key="`${nginxUser}-${letsEncryptDir}`"
:cmd="`chown ${nginxUser} ${letsEncryptDir}`"
@copied="codeCopiedEvent('Set let\'s encrypt directory ownership')"
></BashPrism>
</p>
</li>
@ -65,6 +70,7 @@ THE SOFTWARE.
<script>
import BashPrism from '../prism/bash';
import analytics from '../../util/analytics';
export default {
name: 'SetupSSL',
@ -103,5 +109,14 @@ THE SOFTWARE.
return false;
},
},
methods: {
codeCopiedEvent(step) {
analytics({
category: 'Setup',
action: 'Code snippet copied',
label: `ssl: ${step}`,
});
},
},
};
</script>

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,49 +24,234 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
export default (action, category, label, value, nonInteraction = false) => {
export default ({ category, action, label, value, nonInteraction }) => {
console.info('Analytics event:', { category, action, label, value, nonInteraction });
try {
const tracker = window.ga.getAll()[0];
if (tracker) {
tracker.send({
hitType: 'event',
eventCategory: category,
eventAction: action,
eventLabel: label,
eventValue: value,
nonInteraction,
});
}
// Google
window.ga('send', 'event', {
eventCategory: category,
eventAction: action,
eventLabel: label,
eventValue: value,
nonInteraction,
});
} catch (_) {
// If analytics fail, don't block anything else
}
/*try {
// gtag.js
if (window.gtag) {
window.gtag('event', action, {
event_category: category,
event_label: label,
value,
});
}
try {
// Segment
window.analytics.track(`${category} ${action}`, {
label,
value,
nonInteraction,
});
} catch (_) {
// If analytics fail, don't block anything else
}*/
/*try {
// analytics.js
if (window.ga) {
window.ga('send', {
hitType: 'event',
eventCategory: category,
eventAction: action,
eventLabel: label,
eventValue: value,
nonInteraction,
});
}
} catch (_) {
// If analytics fail, don't block anything else
}*/
}
};
/*
All analytics events in app:
# Initial language set (from browser or query param)
File: app.vue
Category: 'Language'
Action: 'Set'
Label: language pack name
Non-interaction: true
# User manually changing tool language
File: app.vue
Category: 'Language'
Action: 'Set'
Label: language pack name
Non-interaction: false
# Initial domains set (from query params)
File: app.vue
Category: 'Site'
Action: 'Added'
Value: total number of sites active in tool
Non-interaction: true
# User adding a domain
File: app.vue
Category: 'Site'
Action: 'Added'
Value: total number of sites active in tool
Non-interaction: false
# User removing a domain
File: app.vue
Category: 'Site'
Action: 'Removed'
Label: domain name being removed
Value: total number of sites active in tool after removal
# Initial split column mode (will always be disabled)
File: app.vue
Category: 'Split column'
Action: 'Disabled'
Non-interaction: true
# User changing the column mode
File: app.vue
Category: 'Split column'
Action: 'Disabled' / 'Enabled'
Non-interaction: false
# User applying a preset
File: domain_sections/presets.vue
Category: 'Preset'
Action: 'Applied'
Label: preset internal name
# User applying a preset with previous customisations
File: domain_sections/presets.vue
Category: 'Preset'
Action: 'Overwritten'
Label: preset internal name
# User resetting global settings
File: global_sections/tools.vue
Category: 'Tools'
Action: 'Global settings reset'
# User resetting a domain
File: global_sections/tools.vue
Category: 'Tools'
Action: 'Site reset'
Label: domain name being reset
# User removing a domain in the tools tab
Note: This will also trigger the regular site removal event in app.vue
File: global_sections/tools.vue
Category: 'Tools'
Action: 'Removed site'
Label: domain name being removed
# User resetting all domains
File: global_sections/tools.vue
Category: 'Tools'
Action: 'All sites reset'
Label: comma-separated list of domain names being reset
Value: total number of domains being reset
# User removing all domains
Note: This will also trigger the regular site removal event in app.vue for each domain removed
File: global_sections/tools.vue
Category: 'Tools'
Action: 'All sites removed'
Label: comma-separated list of domain names being removed
Value: total number of domains being removed
# User clicking a tab in global settings
File: global.vue
Category: 'Global'
Action: 'Tab clicked'
Label: from tab, to tab
# User clicking back in global settings
File: global.vue
Category: 'Global'
Action: 'Back clicked'
Label: from tab, to tab
# User clicking next in global settings
File: global.vue
Category: 'Global'
Action: 'Next clicked'
Label: from tab, to tab
# User clicking a tab in domain settings
File: domain.vue
Category: 'Site'
Action: 'Tab clicked'
Label: from tab, to tab
# User clicking back in domain settings
File: domain.vue
Category: 'Site'
Action: 'Back clicked'
Label: from tab, to tab
# User clicking next in domain settings
File: domain.vue
Category: 'Site'
Action: 'Next clicked'
Label: from tab, to tab
# User clicking a tab in setup
File: setup.vue
Category: 'Setup'
Action: 'Tab clicked'
Label: from tab, to tab
# User clicking back in setup
File: setup.vue
Category: 'Setup'
Action: 'Back clicked'
Label: from tab, to tab
# User clicking next in setup
File: setup.vue
Category: 'Setup'
Action: 'Next clicked'
Label: from tab, to tab
# User downloading the config
File: setup.vue
Category: 'Setup'
Action: 'Downloaded tar file'
Label: name of the tar file (incl. domain names)
Value: total number of active domains
# User copying the base64 config
File: setup.vue
Category: 'Setup'
Action: 'Copied base64 tar'
Label: name of the tar file (incl. domain names)
Value: total number of active domains
# User copying a code snippet in setup
File: setup.vue
Category: 'Setup'
Action: 'Code snippet copied'
Label: tab name: a summary of the code snippet
# User copying a config file
File: app.vue
Category: 'Config files'
Action: 'Code snippet copied'
Label: name of file without nginx directory
*/

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,10 +24,57 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import 'prismjs';
import Clipboard from 'clipboard';
import Prism from 'prismjs';
import 'prismjs/components/prism-nginx';
import 'prismjs/components/prism-bash';
import 'prismjs/plugins/keep-markup/prism-keep-markup';
import 'prismjs/plugins/toolbar/prism-toolbar';
import 'prismjs/plugins/toolbar/prism-toolbar.css';
import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard';
// Custom copy to clipboard (based on the Prism one)
const copyToClipboard = () => {
if (!Prism.plugins.toolbar) {
console.warn('Copy to Clipboard loaded before Toolbar.');
return;
}
Prism.plugins.toolbar.registerButton('copy-to-clipboard', env => {
const linkCopy = document.createElement('button');
linkCopy.textContent = 'Copy';
const element = env.element;
const clip = new Clipboard(linkCopy, {
'text': () => element.textContent,
});
const resetText = () => {
setTimeout(() => {
linkCopy.textContent = 'Copy';
}, 5000);
};
const emitEvent = () => {
linkCopy.dispatchEvent(new CustomEvent('copied', {
bubbles: true,
detail: { text: element.textContent },
}));
};
clip.on('success', () => {
linkCopy.textContent = 'Copied!';
emitEvent();
resetText();
});
clip.on('error', () => {
const isMac = navigator.platform.includes('Mac');
linkCopy.textContent = `Press ${isMac ? 'Cmd' : 'Ctrl'}+C to copy`;
resetText();
});
return linkCopy;
});
};
copyToClipboard();