feat: add countly analytics (#68)

This commit is contained in:
Teri Chadbourne
2021-03-26 11:00:57 -04:00
committed by GitHub
parent 0fabab14a2
commit 40bd9761ab
26 changed files with 24371 additions and 149 deletions
+24031 -29
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -151,7 +151,6 @@ module.exports = {
],
[require('./plugins/pageData')],
[require('./plugins/vuepress-plugin-trigger-scroll')],
// [require('./plugins/vuepress-plugin-ga-dnt'), { ga: 'UA-xxxxxx' }],
['vuepress-plugin-img-lazy'],
[
'@vuepress/blog',
@@ -1,18 +0,0 @@
export default {
mounted() {
// track outbound clicks
document.addEventListener('click', this.trackOutbound)
},
beforeDestroy() {
// remove on unmount
document.removeEventListener('click', this.trackOutbound)
},
methods: {
trackOutbound(e) {
if (!window.ga) return
const link = e.target.closest('a')
if (link === null || window.location.host === link.host) return
window.ga('send', 'event', 'outbound', 'click', link.href)
},
},
}
@@ -1,65 +0,0 @@
/* global GA_ID, ga */
export default ({ router, isServer }) => {
// only apply on client
if (isServer) return
const initAnalytics = () => {
// ga integration
if (process.env.NODE_ENV === 'production' && GA_ID) {
;(function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r
i[r] =
i[r] ||
function () {
;(i[r].q = i[r].q || []).push(arguments)
}
i[r].l = 1 * new Date()
a = s.createElement(o)
m = s.getElementsByTagName(o)[0]
a.async = 1
a.src = g
m.parentNode.insertBefore(a, m)
})(
window,
document,
'script',
'https://www.google-analytics.com/analytics.js',
'ga'
)
ga('create', GA_ID, 'auto')
ga('set', 'anonymizeIp', true)
router.afterEach(function (to) {
ga('set', 'page', to.fullPath)
ga('send', 'pageview')
})
}
}
if (
window.doNotTrack ||
navigator.doNotTrack ||
navigator.msDoNotTrack ||
(window.external && 'msTrackingProtectionEnabled' in window.external)
) {
// DNT available
if (
window.doNotTrack === '1' ||
navigator.doNotTrack === 'yes' ||
navigator.doNotTrack === '1' ||
navigator.msDoNotTrack === '1' ||
(typeof window.external.msTrackingProtectionEnabled === 'function' &&
window.external.msTrackingProtectionEnabled())
) {
// DNT enabled
} else {
// DNT disabled
initAnalytics()
}
} else {
// DNT not supported
initAnalytics()
}
}
@@ -1,15 +0,0 @@
const { path } = require('@vuepress/shared-utils')
// eslint-disable-next-line default-param-last
module.exports = (options = {}, context) => ({
name: 'vuepress-plugin-ga-dnt',
define() {
const { siteConfig = {} } = context
const ga = options.ga || siteConfig.ga
const GA_ID = ga || false
return { GA_ID }
},
clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js'),
enhanceAppFiles: path.resolve(__dirname, 'enhanceAppFile.js'),
})
+13
View File
@@ -13,6 +13,7 @@
:key="'link-' + index"
class="sm:mr-10 last:mr-0"
:class="[{ 'mb-4': item.children && item.children.length }]"
@click="onlinkClick(item)"
>
<NavLink
:item="item"
@@ -26,6 +27,7 @@
v-for="(childItem, childIndex) in item.children"
:key="'link-child' + childIndex"
class="mb-2 last:mb-0"
@click="onlinkClick(item)"
>
<NavLink
:item="childItem"
@@ -50,6 +52,8 @@ import FooterLegal from '@theme/components/FooterLegal'
import NavLink from '@theme/components/NavLink.vue'
import NewsletterForm from '@theme/components/blog/NewsletterForm'
import countly from '../util/countly'
export default {
name: 'Footer',
components: { SocialLinks, NewsletterForm, NavLink, FooterLegal },
@@ -58,6 +62,15 @@ export default {
return this.$themeLocaleConfig.footerLinks
},
},
methods: {
onlinkClick(item) {
countly.trackEvent(countly.events.LINK_CLICK_FOOTER, {
path: this.$route.path,
text: item.text,
href: item.link,
})
},
},
}
</script>
@@ -5,6 +5,7 @@
href="https://protocol.ai"
target="_blank"
class="mr-1 inline-block align-middle"
@click="(event) => onLinkClick(event, true)"
>
<SVGIcon
name="logo-icon"
@@ -17,6 +18,7 @@
class="text-blueGreenLight hover:underline"
href="https://protocol.ai"
target="_blank"
@click="onLinkClick"
>Protocol Labs</a
>
| Except as
@@ -24,12 +26,14 @@
class="text-blueGreenLight hover:underline"
href="https://protocol.ai/legal/"
target="_blank"
@click="onLinkClick"
>noted</a
>, content licensed
<a
class="text-blueGreenLight hover:underline"
href="https://creativecommons.org/licenses/by/3.0/"
target="_blank"
@click="onLinkClick"
>CC-BY 3.0</a
>
|
@@ -37,6 +41,7 @@
class="text-blueGreenLight hover:underline"
href="https://protocol.ai/legal/#terms-of-service"
target="_blank"
@click="onLinkClick"
>Terms</a
>
|
@@ -44,6 +49,7 @@
class="text-blueGreenLight hover:underline"
href="https://protocol.ai/legal/#privacy-policy"
target="_blank"
@click="onLinkClick"
>Privacy</a
></span
>
@@ -53,8 +59,27 @@
<script>
import SVGIcon from '@theme/components/base/SVGIcon.vue'
import countly from '../util/countly'
export default {
name: 'FooterLegal',
components: { SVGIcon },
methods: {
onLinkClick(event, isSvg) {
const href = isSvg
? event.srcElement.parentElement.href
: event.srcElement.href
const text = isSvg
? event.srcElement.firstChild.textContent
: event.srcElement.text
countly.trackEvent(countly.events.LINK_CLICK_FOOTER, {
view: this.$route.path,
text: text.trim(),
href: href,
})
},
},
}
</script>
+14 -1
View File
@@ -15,7 +15,11 @@
:key="page.title"
class="mb-3"
>
<Link class="mobile-nav__link type-h1" :item="page" />
<Link
class="mobile-nav__link type-h1"
:item="page"
:on-click="onClickNavLink"
/>
</li>
</ul>
</nav>
@@ -33,6 +37,8 @@ import SocialLinks from '@theme/components/SocialLinks'
import Link from '@theme/components/base/Link'
import trapFocus from '@theme/util/trapFocus'
import countly from '../util/countly'
export default {
name: 'MobileNav',
components: {
@@ -83,6 +89,13 @@ export default {
afterEnter() {
this.setTabItems()
},
onClickNavLink(item) {
countly.trackEvent(countly.events.LINK_CLICK_NAV, {
view: this.$route.path,
href: item.link,
text: item.text,
})
},
},
}
</script>
+14 -1
View File
@@ -32,7 +32,11 @@
:key="page.text"
class="nav__link-item first:m-0 font-semibold"
>
<Link class="nav__link font-display font-medium" :item="page" />
<Link
class="nav__link font-display font-medium"
:item="page"
:on-click="onClickNavLink"
/>
</li>
</ul>
<button
@@ -53,6 +57,8 @@ import { mapState } from 'vuex'
import SVGIcon from '@theme/components/base/SVGIcon'
import Link from '@theme/components/base/Link'
import countly from '../util/countly'
export default {
name: 'Nav',
components: {
@@ -131,6 +137,13 @@ export default {
toggleMobileMenu() {
this.$store.commit('appState/toggleMobileNav', !this.mobileNavActive)
},
onClickNavLink(item) {
countly.trackEvent(countly.events.LINK_CLICK_NAV, {
view: this.$route.path,
href: item.link,
text: item.text,
})
},
},
}
</script>
@@ -7,6 +7,7 @@
:href="link.link"
target="_blank"
rel="noopener noreferrer"
@click="socialLinkClick(link)"
>
<SVGIcon
class="w-8 h-8 fill-current hover:opacity-75 transition transition-opacity duration-300 ease-in-out"
@@ -18,6 +19,9 @@
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon'
import countly from '../util/countly'
export default {
name: 'SocialLinks',
components: { SVGIcon },
@@ -26,5 +30,14 @@ export default {
return this.$themeLocaleConfig.socialLinks
},
},
methods: {
socialLinkClick(link) {
countly.trackEvent(countly.events.SOCIAL_MEDIA_OUTBOUNDS, {
view: this.$route.path,
text: link.text,
link: link.link,
})
},
},
}
</script>
+11 -2
View File
@@ -1,8 +1,13 @@
<template>
<RouterLink v-if="isInternal" :to="link" :exact="exact">
<RouterLink
v-if="isInternal"
:to="link"
:exact="exact"
@click="onClick(item)"
>
{{ item.text }}
</RouterLink>
<a v-else :href="link" :target="target" :rel="rel">
<a v-else :href="link" :target="target" :rel="rel" @click="onClick(item)">
{{ item.text }}
</a>
</template>
@@ -18,6 +23,10 @@ export default {
type: Object,
required: true,
},
onClick: {
type: Function,
default: () => {},
},
},
computed: {
@@ -42,12 +42,15 @@
</button>
</div>
</div>
<div v-if="numberOfPosts === 0" class="flex mt-2">
Try removing some your search parameters, or&nbsp;
<button class="text-blueGreen" @click="handleClear()">
return to the blog & news index
</button>
.
<div v-if="numberOfPosts === 0" class="mt-2">
{{ 'Try removing some your search parameters, or ' }}
<router-link
:to="{ path: '/' }"
class="text-blueGreen"
@click.native="handleClear()"
>
return to the blog & news index.
</router-link>
</div>
</div>
<button
@@ -7,6 +7,7 @@
href="https://airtable.com/shrNH8YWole1xc70I"
target="_blank"
rel="noopener noreferrer"
@click="trackSubmitItem"
>
Submit</a
>
@@ -15,6 +16,7 @@
class="text-blueGreen hover:underline"
href="https://ipfs.io/media/"
rel="noopener noreferrer"
@click="trackPressKit"
>IPFS press kit.</a
>
</h2>
@@ -43,6 +45,8 @@
<script>
import RSSSubscription from '@theme/components/RSSSubscription.vue'
import countly from '../../util/countly'
export default {
name: 'LinksAndSocial',
components: {
@@ -55,6 +59,13 @@ export default {
},
},
computed: {},
methods: {},
methods: {
trackSubmitItem() {
countly.trackEvent(countly.events.LINK_CLICK_SUBMIT_ITEM)
},
trackPressKit() {
countly.trackEvent(countly.events.LINK_CLICK_PRESS_KIT)
},
},
}
</script>
@@ -21,6 +21,7 @@
action="https://ipfs.us4.list-manage.com/subscribe/post?u=25473244c7d18b897f5a1ff6b&amp;id=cad54b2230"
method="post"
target="_blank"
@submit="subscribeClick"
>
<div id="mc_embed_signup_scroll" class="grid gric-col-2 w-full">
<div
@@ -78,6 +79,8 @@
<script>
import { mapState } from 'vuex'
import countly from '../../util/countly'
export default {
name: 'NewsletterForm',
props: {},
@@ -87,6 +90,10 @@ export default {
computed: {
...mapState('appState', ['latestWeeklyPost']),
},
methods: {},
methods: {
subscribeClick() {
countly.trackEvent(countly.events.NEWSLETTER_SUBSCRIBE)
},
},
}
</script>
@@ -33,6 +33,7 @@
<script>
import { mapState } from 'vuex'
import Author from '@theme/components/mixins/Author'
import countly from '../../util/countly'
export default {
name: 'PostAuthor',
@@ -47,6 +48,10 @@ export default {
type: String,
default: '',
},
parent: {
type: String,
default: 'card',
},
},
computed: {
...mapState('appState', ['activeAuthor']),
@@ -68,8 +73,15 @@ export default {
},
},
methods: {
handleAuthorClick(piece) {
this.$store.commit('appState/setActiveAuthor', piece)
handleAuthorClick(authorName) {
const authorTracking = {
author: authorName,
method: `${this.parent}-select`,
}
countly.trackEvent(countly.events.FILTER, authorTracking)
this.$store.commit('appState/setActiveAuthor', authorName)
},
},
}
@@ -6,7 +6,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 pt-4">
<div class="flex flex-col md:pr-8">
<h1 class="type-h1">{{ title }}</h1>
<PostAuthor v-bind="author" light />
<PostAuthor v-bind="author" light parent="blog-post" />
<time
class="text-gray"
pubdate
@@ -34,7 +34,14 @@
class="tags flex flex-wrap text-sm text-gray-dark"
itemprop="keywords"
>
<PostTag v-for="tag in resolvedTags" :key="tag" :tag="tag" link dark />
<PostTag
v-for="tag in resolvedTags"
:key="tag"
:tag="tag"
link
dark
parent="blog-post"
/>
</div>
<div class="flex my-1 text-sm text-gray-dark">
Share this item:
@@ -68,6 +68,7 @@ import utc from 'dayjs/plugin/utc'
import PostTag from '@theme/components/blog/PostTag'
import PostAuthor from '@theme/components/blog/PostAuthor'
import UnstyledLink from '@theme/components/UnstyledLink'
import countly from '../../util/countly'
export default {
name: 'PostMeta',
@@ -132,6 +133,13 @@ export default {
},
methods: {
handleCatClick() {
const categoryTracking = {
category: this.category,
method: 'card-select',
}
countly.trackEvent(countly.events.FILTER, categoryTracking)
this.$store.commit('appState/setActiveCategory', this.category)
},
},
@@ -11,6 +11,7 @@
hashtags=""
:twitter-user="social.twitterHandle"
class="mr-1 last:mr-0"
@open="shareClick(social)"
>
<SVGIcon
class="w-6 h-6 opacity-50 fill-current text-blueGreen hover:opacity-100 transition transition-opacity duration-300 ease-in-out"
@@ -23,6 +24,8 @@
<script>
import SVGIcon from '@theme/components/base/SVGIcon'
import countly from '../../util/countly'
export default {
name: 'PostSocials',
components: { SVGIcon },
@@ -57,5 +60,13 @@ export default {
this.currentUrl = window.location.href
this.host = window.location.host
},
methods: {
shareClick(social) {
countly.trackEvent(countly.events.SOCIAL_MEDIA_SHARE, {
view: this.$route.path,
text: social.text,
})
},
},
}
</script>
@@ -11,6 +11,8 @@
</template>
<script>
import countly from '../../util/countly'
export default {
name: 'PostTag',
props: {
@@ -34,6 +36,10 @@ export default {
type: Function,
default: () => {},
},
parent: {
type: String,
default: 'card',
},
},
computed: {
computedClass() {
@@ -48,12 +54,22 @@ export default {
},
methods: {
handleTagClick() {
this.trackTag()
this.$store.commit('appState/setActiveTags', [this.tag])
},
addNewTag() {
this.trackTag()
this.$store.commit('appState/addNewTag', [this.tag])
this.callback()
},
trackTag() {
const tagTracking = {
tag: this.tag,
method: `${this.parent}-select`,
}
countly.trackEvent(countly.events.FILTER, tagTracking)
},
},
}
</script>
@@ -54,6 +54,8 @@
import Multiselect from 'vue-multiselect'
import { mapState } from 'vuex'
import countly from '../../util/countly'
export default {
name: 'SearchCategoriesAndTags',
components: { Multiselect },
@@ -94,9 +96,6 @@ export default {
this.updateTagsWithQuery()
},
},
mounted() {
this.updateTagsWithQuery()
},
created() {
this.calculateTagsLimit()
if (typeof window !== 'undefined') {
@@ -190,6 +189,13 @@ export default {
this.selectedTags = newTags
},
setActiveCategory(category) {
const categoryTracking = {
category: category,
method: 'filter-select',
}
countly.trackEvent(countly.events.FILTER, categoryTracking)
this.$store.commit(
'appState/setActiveCategory',
this.categoriesList.includes(category) ? category : ''
@@ -216,11 +222,26 @@ export default {
name: text,
value: text,
}
const textTracking = {
text: text,
method: 'filter-select',
}
countly.trackEvent(countly.events.FILTER, textTracking)
this.selectedTags.push(option)
this.calculateTagsLimit([...this.selectedTags, option])
this.$refs.select2.focus()
},
focusOnSubmit(option) {
const tagTracking = {
tag: option.value,
method: 'filter-select',
}
countly.trackEvent(countly.events.FILTER, tagTracking)
this.calculateTagsLimit()
this.$refs.select2.focus()
},
@@ -256,6 +277,15 @@ export default {
@apply bg-blueGreen;
}
.multiselect__tag-icon::after {
color: white;
opacity: 0.5;
}
.multiselect__tag-icon:hover::after {
opacity: 1;
}
.multiselect__option--highlight.multiselect__option--selected,
.multiselect__option--highlight.multiselect__option--selected::after {
@apply bg-aquaMuted;
@@ -47,6 +47,7 @@
:tag="tag"
:callback="closeModal"
class-name="text-sm"
parent="video-modal"
/>
</div>
</div>
@@ -86,6 +87,8 @@ import UnstyledLink from '@theme/components/UnstyledLink'
import PostSocials from '@theme/components/blog/PostSocials.vue'
import PostTag from '@theme/components/blog/PostTag'
import countly from '../../util/countly'
export default {
name: 'VideoModalContent',
components: { UnstyledLink, PostSocials, PostTag },
@@ -141,6 +144,13 @@ export default {
},
methods: {
handleCatClick() {
const categoryTracking = {
category: this.videoModalCard.frontmatter.type,
method: 'video-modal-select',
}
countly.trackEvent(countly.events.FILTER, categoryTracking)
this.$store.commit(
'appState/setActiveCategory',
this.videoModalCard.frontmatter.type
+9 -1
View File
@@ -10,7 +10,7 @@ import Transition from '@theme/components/directives/Transition.js'
import 'vue-multiselect/dist/vue-multiselect.min.css'
export default ({ Vue, router, siteData }) => {
export default ({ Vue, router, siteData, isServer }) => {
const { breakpoints } = siteData.themeConfig
/**
@@ -27,6 +27,14 @@ export default ({ Vue, router, siteData }) => {
return originalPush.call(this, location)
}
if (!isServer) {
// track page view via Countly when route changes
router.afterEach((to) => {
if (!window.Countly) return
window.Countly.q.push(['track_pageview', to.path])
})
}
Vue.use(Vuex)
Vue.use(VScrollLock)
Vue.use(VueMq, { breakpoints })
+43
View File
@@ -62,6 +62,7 @@ import { parseProtectedPost, checkItem } from '@theme/util/blogUtils'
import uniq from 'lodash/uniq'
import pick from 'lodash/pick'
import isEqual from 'lodash/isEqual'
import countly from '../util/countly'
const defaultCategory = 'Blog post'
@@ -257,6 +258,46 @@ export default {
const queryText = query.search
if (queryCategory !== '') {
const categoryTracking = {
category: queryCategory,
method: 'urlQuery',
}
countly.trackEvent(countly.events.FILTER, categoryTracking)
}
if (queryTags.length > 0) {
queryTags.forEach((tag) => {
const tagTracking = {
tag: tag,
method: 'urlQuery',
}
countly.trackEvent(countly.events.FILTER, tagTracking)
})
}
if (queryText) {
queryText.split(',').forEach((text) => {
const textTracking = {
text: text,
method: 'urlQuery',
}
countly.trackEvent(countly.events.FILTER, textTracking)
})
}
if (queryAuthor) {
const authorTracking = {
author: queryAuthor,
method: 'urlQuery',
}
countly.trackEvent(countly.events.FILTER, authorTracking)
}
this.$store.commit('appState/setActiveTags', queryTags)
this.$store.commit('appState/setActiveCategory', queryCategory)
this.$store.commit(
@@ -304,6 +345,8 @@ export default {
this.numberOfPagesToShow = this.numberOfPagesToShow + 24
},
handleLoadMoreClick() {
countly.trackEvent(countly.events.LOAD_MORE_BUTTON)
this.infiniteScroll = true
this.numberOfPagesToShow = this.numberOfPagesToShow + 24
},
@@ -17,6 +17,8 @@ import Footer from '@theme/components/Footer.vue'
import Nav from '@theme/components/Nav.vue'
import MobileNav from '@theme/components/MobileNav.vue'
import countly from '../util/countly'
export default {
name: 'GlobalLayout',
@@ -35,6 +37,10 @@ export default {
},
},
beforeMount() {
countly.loadScript()
},
methods: {
leaveScroll() {
// eslint-disable-next-line vue/custom-event-name-casing
+61
View File
@@ -0,0 +1,61 @@
export const events = {
LINK_CLICK_NAV: 'linkClickNav',
LINK_CLICK_FOOTER: 'linkClickFooter',
LINK_CLICK_SUBMIT_ITEM: 'linkClickSubmitItem',
LINK_CLICK_PRESS_KIT: 'linkClickPressKit',
SOCIAL_MEDIA_SHARE: 'socialMediaShare',
SOCIAL_MEDIA_OUTBOUNDS: 'socialMediaOutbounds',
LOAD_MORE_BUTTON: 'loadMoreButton',
NEWSLETTER_SUBSCRIBE: 'newsletterSubscribe',
FILTER: 'filter',
}
/*
Load Countly script.
*/
export function loadScript() {
const countlyScript = document.createElement('script')
countlyScript.innerHTML = `
//some default pre init
var Countly = Countly || {};
Countly.q = Countly.q || [];
//provide countly initialization parameters
Countly.app_key = location.hostname === 'blog.ipfs.io' ? '9e8a52b6b06d84f50321c4c3b96ba03d4bab7717' : 'c68a0191d53e5d079372653d7d6158f0374c2172';
Countly.url = 'https://countly.ipfs.io';
Countly.q.push(['track_sessions']);
Countly.q.push(['track_pageview']);
Countly.q.push(['track_clicks']);
Countly.q.push(['track_scrolls']);
Countly.q.push(['track_links']);
//load countly script asynchronously
(function() {
var cly = document.createElement('script'); cly.type = 'text/javascript';
cly.async = true;
//enter url of script here
cly.src = 'https://countly.ipfs.io/sdk/web/countly.min.js';
cly.onload = function(){Countly.init()};
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
})();`
document.body.appendChild(countlyScript)
}
/*
Track an event to countly with the provided data
*/
export function trackEvent(event, data = {}) {
// console.info('[countly]', 'trackEvent()', event, data)
window.Countly.q.push([
'add_event',
{
key: event,
segmentation: data,
},
])
}
export default {
events,
trackEvent,
loadScript,
}
+1 -1
View File
@@ -73,7 +73,7 @@ data:
- ProtoSchool
- static publishing
- js-ipfs
- ' IPLD'
- IPLD
- DAG
card_image: '/2020-09-11-tutorial-protoschool-blogging.png'
- name: 'ProtoSchool: P2P Data Links with Content Addressing'