diff --git a/app/application/route.ts b/app/application/route.ts index dec15ba40aa..dbb4b4bd404 100644 --- a/app/application/route.ts +++ b/app/application/route.ts @@ -1,8 +1,10 @@ import Route from '@ember/routing/route'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import Intl from 'ember-intl/services/intl'; import checkAuth from 'ember-osf-web/decorators/check-auth'; +import Analytics from 'ember-osf-web/services/analytics'; import CurrentUser from 'ember-osf-web/services/current-user'; @checkAuth @@ -18,6 +20,7 @@ export default class ApplicationRoute extends Route.extend( ) { @service intl!: Intl; @service currentUser!: CurrentUser; + @service analytics!: Analytics; queryParams = { viewOnlyToken: { @@ -28,4 +31,9 @@ export default class ApplicationRoute extends Route.extend( beforeModel() { return this.intl.setLocale('en-us'); } + + @action + didTransition() { + this.analytics.trackPage(); + } } diff --git a/app/dashboard/route.ts b/app/dashboard/route.ts index 51ad4f01898..1b19f961a79 100644 --- a/app/dashboard/route.ts +++ b/app/dashboard/route.ts @@ -1,4 +1,3 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { taskFor } from 'ember-concurrency-ts'; @@ -6,13 +5,11 @@ import Session from 'ember-simple-auth/services/session'; import DashboardController from 'ember-osf-web/dashboard/controller'; import requireAuth from 'ember-osf-web/decorators/require-auth'; -import Analytics from 'ember-osf-web/services/analytics'; import CurrentUser from 'ember-osf-web/services/current-user'; import Ready from 'ember-osf-web/services/ready'; @requireAuth('home') export default class Dashboard extends Route { - @service analytics!: Analytics; @service currentUser!: CurrentUser; @service ready!: Ready; @service session!: Session; @@ -27,9 +24,4 @@ export default class Dashboard extends Route { blocker.errored(e); } } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/goodbye/route.ts b/app/goodbye/route.ts index 15b5dedcf4b..34eca0d5366 100644 --- a/app/goodbye/route.ts +++ b/app/goodbye/route.ts @@ -1,13 +1,9 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import Session from 'ember-simple-auth/services/session'; -import Analytics from 'ember-osf-web/services/analytics'; - export default class Goodbye extends Route { - @service analytics!: Analytics; @service session!: Session; async beforeModel(transition: Transition) { @@ -15,9 +11,4 @@ export default class Goodbye extends Route { const queryParams = this.session.isAuthenticated ? {} : { goodbye: true }; this.transitionTo('home', { queryParams }); } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/guid-file/route.ts b/app/guid-file/route.ts index 593d77e262c..a1d920da771 100644 --- a/app/guid-file/route.ts +++ b/app/guid-file/route.ts @@ -1,4 +1,3 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; @@ -8,7 +7,6 @@ import { taskFor } from 'ember-concurrency-ts'; import moment from 'moment'; import Institution from 'ember-osf-web/models/institution'; -import Analytics from 'ember-osf-web/services/analytics'; import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; import Ready from 'ember-osf-web/services/ready'; import OsfStorageFile from 'ember-osf-web/packages/files/osf-storage-file'; @@ -27,7 +25,6 @@ import CurrentUserService from 'ember-osf-web/services/current-user'; import RegistrationModel from 'ember-osf-web/models/registration'; export default class GuidFile extends Route { - @service analytics!: Analytics; @service('head-tags') headTagsService!: HeadTagsService; @service metaTags!: MetaTags; @service ready!: Ready; @@ -116,8 +113,11 @@ export default class GuidFile extends Route { taskFor(this.setHeadTags).perform(model.fileModel); } - @action - didTransition() { - this.analytics.trackPage(true, 'files'); + buildRouteInfoMetadata() { + return { + osfMetrics: { + itemGuid: this.controller.model.id, + }, + }; } } diff --git a/app/guid-node/forks/route.ts b/app/guid-node/forks/route.ts index 46f077d0090..75d88485181 100644 --- a/app/guid-node/forks/route.ts +++ b/app/guid-node/forks/route.ts @@ -1,25 +1,7 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Node from 'ember-osf-web/models/node'; -import { GuidRouteModel } from 'ember-osf-web/resolve-guid/guid-route'; -import Analytics from 'ember-osf-web/services/analytics'; -import Ready from 'ember-osf-web/services/ready'; export default class GuidNodeForks extends Route { - @service analytics!: Analytics; - @service ready!: Ready; - model() { return this.modelFor('guid-node'); } - - @action - async didTransition() { - const { taskInstance } = this.controller.model as GuidRouteModel; - await taskInstance; - const node = taskInstance.value; - this.analytics.trackPage(node ? node.public : undefined, 'nodes'); - } } diff --git a/app/guid-node/registrations/route.ts b/app/guid-node/registrations/route.ts index a1d2790061e..9a9a9a48f51 100644 --- a/app/guid-node/registrations/route.ts +++ b/app/guid-node/registrations/route.ts @@ -1,18 +1,13 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; import { taskFor } from 'ember-concurrency-ts'; import Node from 'ember-osf-web/models/node'; import { GuidRouteModel } from 'ember-osf-web/resolve-guid/guid-route'; -import Analytics from 'ember-osf-web/services/analytics'; import Controller from './controller'; export default class GuidNodeRegistrations extends Route { - @service analytics!: Analytics; - model() { return this.modelFor('guid-node') as GuidRouteModel; } @@ -21,12 +16,4 @@ export default class GuidNodeRegistrations extends Route { super.setupController(controller, model, transition); taskFor(controller.getRegistrationSchemas).perform(); } - - @action - async didTransition() { - const { taskInstance } = this.controller.model as GuidRouteModel; - await taskInstance; - const node = taskInstance.value; - this.analytics.trackPage(node ? node.public : undefined, 'nodes'); - } } diff --git a/app/guid-registration/forks/route.ts b/app/guid-registration/forks/route.ts index cf75c990b36..7e026cfc6f6 100644 --- a/app/guid-registration/forks/route.ts +++ b/app/guid-registration/forks/route.ts @@ -1,25 +1,12 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import Registration from 'ember-osf-web/models/registration'; -import { GuidRouteModel } from 'ember-osf-web/resolve-guid/guid-route'; -import Analytics from 'ember-osf-web/services/analytics'; import Ready from 'ember-osf-web/services/ready'; export default class GuidRegistrationForks extends Route { - @service analytics!: Analytics; @service ready!: Ready; model() { return this.modelFor('guid-registration'); } - - @action - async didTransition() { - const { taskInstance } = this.controller.model as GuidRouteModel; - await taskInstance; - const registration = taskInstance.value; - this.analytics.trackPage(registration ? registration.public : undefined, 'registrations'); - } } diff --git a/app/helpers/open-badges-icon-map.ts b/app/helpers/open-badges-icon-map.ts index 3090ef6c4c7..ec8807018be 100644 --- a/app/helpers/open-badges-icon-map.ts +++ b/app/helpers/open-badges-icon-map.ts @@ -1,6 +1,6 @@ import { ResourceTypes } from 'ember-osf-web/models/resource'; -export function getBadgeIcon(resourceType: ResourceTypes) { +export function getBadgeIcon(resourceType: ResourceTypes | 'undefined') { switch (resourceType) { case ResourceTypes.Data: return '/assets/images/badges/data_small_color.png'; @@ -17,7 +17,7 @@ export function getBadgeIcon(resourceType: ResourceTypes) { } } -export function getBadgeIconDisabled(resourceType: ResourceTypes) { +export function getBadgeIconDisabled(resourceType: ResourceTypes | 'undefined') { switch (resourceType) { case ResourceTypes.Data: return '/assets/images/badges/data_small_gray.png'; diff --git a/app/home/route.ts b/app/home/route.ts index c505b08f018..88bf9199b5a 100644 --- a/app/home/route.ts +++ b/app/home/route.ts @@ -1,19 +1,10 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import Features from 'ember-feature-flags/services/features'; -import config from 'ember-get-config'; import Session from 'ember-simple-auth/services/session'; -import Analytics from 'ember-osf-web/services/analytics'; - -const { featureFlagNames: { ABTesting } } = config; - export default class Home extends Route { - @service analytics!: Analytics; @service session!: Session; - @service features!: Features; async beforeModel(transition: Transition) { await super.beforeModel(transition); @@ -22,11 +13,4 @@ export default class Home extends Route { this.transitionTo('dashboard'); } } - - @action - didTransition() { - const shouldShowVersionB = this.features.isEnabled(ABTesting.homePageHeroTextVersionB); - const version = shouldShowVersionB ? 'versionB' : 'versionA'; - this.analytics.trackPage(undefined, undefined, undefined, version); - } } diff --git a/app/institutions/dashboard/route.ts b/app/institutions/dashboard/route.ts index ecc21b5e1f7..b50773fe0f1 100644 --- a/app/institutions/dashboard/route.ts +++ b/app/institutions/dashboard/route.ts @@ -1,4 +1,3 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; @@ -10,7 +9,6 @@ import InstitutionModel from 'ember-osf-web/models/institution'; import InstitutionDepartmentModel from 'ember-osf-web/models/institution-department'; import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; import { QueryHasManyResult } from 'ember-osf-web/models/osf-model'; -import Analytics from 'ember-osf-web/services/analytics'; import captureException from 'ember-osf-web/utils/capture-exception'; export interface InstitutionsDashboardModel { @@ -19,7 +17,6 @@ export interface InstitutionsDashboardModel { summaryMetrics: InstitutionSummaryMetricModel; } export default class InstitutionsDashboardRoute extends Route { - @service analytics!: Analytics; @service router!: RouterService; @task @@ -57,9 +54,4 @@ export default class InstitutionsDashboardRoute extends Route { taskInstance: taskFor(this.modelTask).perform(params.institution_id), }; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/institutions/index/route.ts b/app/institutions/index/route.ts index 80bd7e8b723..a6ee57bcf37 100644 --- a/app/institutions/index/route.ts +++ b/app/institutions/index/route.ts @@ -1,20 +1,11 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; - export default class Institutions extends Route { - @service analytics!: Analytics; @service store!: Store; model() { return this.store.findAll('institution'); } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/meetings/detail/route.ts b/app/meetings/detail/route.ts index 6dd4193b8b2..d1f2ec6f722 100644 --- a/app/meetings/detail/route.ts +++ b/app/meetings/detail/route.ts @@ -1,15 +1,12 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; +import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; import { task } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; -import Analytics from 'ember-osf-web/services/analytics'; - export default class MeetingsDetail extends Route { - @service analytics!: Analytics; - @service router!: any; + @service router!: RouterService; @task @waitFor @@ -28,9 +25,4 @@ export default class MeetingsDetail extends Route { taskInstance: taskFor(this.loadMeetingDetail).perform(params.meeting_id), }; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/meetings/index/route.ts b/app/meetings/index/route.ts index 7442f5587bb..d15b64a4021 100644 --- a/app/meetings/index/route.ts +++ b/app/meetings/index/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class MeetingsIndex extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/metrics-adapters/keen.ts b/app/metrics-adapters/keen.ts index d24b95a50d3..d1a25505234 100644 --- a/app/metrics-adapters/keen.ts +++ b/app/metrics-adapters/keen.ts @@ -1,6 +1,7 @@ import { getOwner } from '@ember/application'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; +import Store from '@ember-data/store'; import { TaskInstance } from 'ember-concurrency'; import Cookies from 'ember-cookies/services/cookies'; import config from 'ember-get-config'; @@ -33,6 +34,7 @@ export default class KeenAdapter extends BaseAdapter { @service currentUser!: CurrentUser; @service headData!: any; @service router!: RouterService; + @service store!: Store; config?: KeenConfig; publicClient?: KeenTracking; @@ -102,23 +104,19 @@ export default class KeenAdapter extends BaseAdapter { } async trackPage(params: PageParams) { + const node = await this.getCurrentNode(); + const isPublic = Boolean(params.pagePublic ?? (node && node.public)); const eventProperties = { page: { meta: { title: params.title, - public: params.pagePublic, + public: isPublic, }, }, }; - let sendPublicEvent = params.pagePublic; - - const node = await this.getCurrentNode(); - if (node) { - sendPublicEvent = node.public; - if (sendPublicEvent) { - this.trackPublicEvent(`pageviews-${node.id.charAt(0)}`, eventProperties); - } + if (node && isPublic) { + this.trackPublicEvent(`pageviews-${node.id.charAt(0)}`, eventProperties); } this.trackPrivateEvent('pageviews', eventProperties); @@ -145,6 +143,7 @@ export default class KeenAdapter extends BaseAdapter { } } + getOrCreateKeenId() { if (!this.cookies.exists(keenUserIdCookie)) { this.cookies.write( @@ -169,6 +168,7 @@ export default class KeenAdapter extends BaseAdapter { path: '/', }, ); + return sessionId; } defaultKeenPayload() { diff --git a/app/register/route.ts b/app/register/route.ts index 09a22b6f2d8..6ec9f31a2fe 100644 --- a/app/register/route.ts +++ b/app/register/route.ts @@ -1,13 +1,10 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import Session from 'ember-simple-auth/services/session'; -import Analytics from 'ember-osf-web/services/analytics'; export default class Register extends Route { - @service analytics!: Analytics; @service session!: Session; async beforeModel(transition: Transition) { @@ -21,9 +18,4 @@ export default class Register extends Route { model() { return this.store.createRecord('user-registration'); } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/resolve-guid/guid-route.ts b/app/resolve-guid/guid-route.ts index 41b394fc1b2..9ddbd37f091 100644 --- a/app/resolve-guid/guid-route.ts +++ b/app/resolve-guid/guid-route.ts @@ -58,4 +58,12 @@ export default abstract class GuidRoute extends Route { task: this.getModel, }; } + + buildRouteInfoMetadata() { + return { + osfMetrics: { + itemGuid: this.controller.model.guid, + }, + }; + } } diff --git a/app/services/analytics.ts b/app/services/analytics.ts index 19e179566dd..21b1fd04c80 100644 --- a/app/services/analytics.ts +++ b/app/services/analytics.ts @@ -6,17 +6,25 @@ import Service, { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; import { restartableTask, waitForQueue } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; +import Cookies from 'ember-cookies/services/cookies'; import config from 'ember-get-config'; import Metrics from 'ember-metrics/services/metrics'; import Session from 'ember-simple-auth/services/session'; import Toast from 'ember-toastr/services/toast'; +import moment from 'moment'; +import CurrentUser from 'ember-osf-web/services/current-user'; import Ready from 'ember-osf-web/services/ready'; const { metricsAdapters, OSF: { analyticsAttrs, + apiUrl, + cookies: { + cookieConsent: cookieConsentCookie, + keenSessionId: sessionIdCookie, + }, }, } = config; @@ -36,6 +44,14 @@ export interface InitialEventInfo { nonInteraction?: boolean; } +export interface RouteMetricsMetadata { + itemGuid?: string; + isSearch?: boolean; + providerId?: string; +} + +type PageviewActionLabel = 'web' | 'view' | 'search'; + function logEvent(analytics: Analytics, title: string, data: object) { runInDebug(() => { const logMessage = Object.entries(data) @@ -153,6 +169,8 @@ export default class Analytics extends Service { @service ready!: Ready; @service router!: RouterService; @service toast!: Toast; + @service cookies!: Cookies; + @service currentUser!: CurrentUser; shouldToastOnEvent = false; @@ -169,6 +187,9 @@ export default class Analytics extends Service { // Wait until everything has settled await waitForQueue('destroy'); + // osf metrics + await this._sendCountedUsage(this._getPageviewPayload()); + const eventParams = { page: this.router.currentURL, title: this.router.currentRouteName, @@ -297,6 +318,88 @@ export default class Analytics extends Service { logEvent(this, 'Tracked event', trackedData); } + + async _sendCountedUsage(payload: object) { + await this.currentUser.authenticatedAJAX({ + method: 'POST', + url: `${apiUrl}/_/metrics/events/counted_usage/`, + data: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/vnd.api+json', + }, + }); + } + + _getPageviewPayload() { + const routeMetricsMetadata = this._getRouteMetricsMetadata(); + const all_attrs = { + item_guid: routeMetricsMetadata.itemGuid, + provider_id: routeMetricsMetadata.providerId, + action_labels: this._getPageviewActionLabels(routeMetricsMetadata), + client_session_id: this._sessionId, + } as const; + const attributes = Object.fromEntries( + Object.entries(all_attrs).filter( + ([_,value]: [unknown, unknown]) => (typeof value !== 'undefined'), + ), + ); + return { + data: { + type: 'counted-usage', + attributes: { + ...attributes, + pageview_info: { + page_url: document.URL, + page_title: document.title, + referer_url: document.referrer, + route_name: `ember-osf-web.${this.router.currentRouteName}`, + }, + }, + }, + }; + } + + get _sessionId() { + if (!this.cookies.exists(cookieConsentCookie)) { + return undefined; + } + const sessionId = ( + this.cookies.read(sessionIdCookie) + || ('randomUUID' in crypto && (crypto as any).randomUUID()) + || Math.random().toString() + ); + this.cookies.write(sessionIdCookie, sessionId, { + expires: moment().add(25, 'minutes').toDate(), + path: '/', + }); + return sessionId; + } + + _getRouteMetricsMetadata(): RouteMetricsMetadata { + // build list of `osfMetrics` values from all current active routes + // for merging, ordered from root to leaf (so values from leafier + // routes can override those from rootier routes) + const metricsMetadatums = []; + let currentRouteInfo = this.router.currentRoute; + while (currentRouteInfo) { + if (currentRouteInfo.metadata?.osfMetrics) { + metricsMetadatums.unshift(currentRouteInfo.metadata.osfMetrics); + } + currentRouteInfo = currentRouteInfo.parent; + } + const mergedMetricsMetadata = Object.assign({}, ...metricsMetadatums); + return mergedMetricsMetadata; + } + + _getPageviewActionLabels(routeMetricsMetadata: RouteMetricsMetadata): PageviewActionLabel[] { + const actionLabelMap: Record = { + web: true, + view: Boolean(routeMetricsMetadata.itemGuid), + search: Boolean(routeMetricsMetadata.isSearch), + }; + const labels = Object.keys(actionLabelMap) as PageviewActionLabel[]; + return labels.filter(label => actionLabelMap[label]); + } } declare module '@ember/service' { diff --git a/app/settings/tokens/create/route.ts b/app/settings/tokens/create/route.ts index bd5b7a131c8..49a4d78ebe4 100644 --- a/app/settings/tokens/create/route.ts +++ b/app/settings/tokens/create/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class SettingsTokensCreateRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/settings/tokens/edit/route.ts b/app/settings/tokens/edit/route.ts index b8171e812b5..8cb4af370f8 100644 --- a/app/settings/tokens/edit/route.ts +++ b/app/settings/tokens/edit/route.ts @@ -6,13 +6,11 @@ import { waitFor } from '@ember/test-waiters'; import { task } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; -import Analytics from 'ember-osf-web/services/analytics'; import { notFoundURL } from 'ember-osf-web/utils/clean-url'; import SettingsTokensEditController from './controller'; export default class SettingsTokensEditRoute extends Route { - @service analytics!: Analytics; @service router!: RouterService; @task @@ -44,9 +42,4 @@ export default class SettingsTokensEditRoute extends Route { refreshRoute() { this.refresh(); } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/settings/tokens/index/route.ts b/app/settings/tokens/index/route.ts index 427d4a51b89..e6f9c4f1b85 100644 --- a/app/settings/tokens/index/route.ts +++ b/app/settings/tokens/index/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class SettingsTokensRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/app/support/route.ts b/app/support/route.ts index 54de9931716..e4a4018ef7d 100644 --- a/app/support/route.ts +++ b/app/support/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class Support extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/config/environment.js b/config/environment.js index fd0701ed87c..90533c15e49 100644 --- a/config/environment.js +++ b/config/environment.js @@ -102,7 +102,7 @@ module.exports = function(environment) { metricsAdapters: [ { name: 'GoogleAnalytics', - environments: ['all'], + environments: GOOGLE_ANALYTICS_ID ? ['all'] : [], config: { id: GOOGLE_ANALYTICS_ID, setFields: { diff --git a/handbook-docs/analytics/template.md b/handbook-docs/analytics/template.md index 5ad4c75d2ee..f397654626f 100644 --- a/handbook-docs/analytics/template.md +++ b/handbook-docs/analytics/template.md @@ -84,23 +84,50 @@ Params for `track()`: - `extraInfo` (optional): string ## Tracking page views - - -Call `trackPage()` on the `analytics` service in the `didTransition` hook for your route or a parent route: +In the `didTransition` hook of any application routes (in both app and engines), +call the `analytics` service's `trackPage()` method. ```ts -// route.ts +// application/route.ts @action didTransition() { this.analytics.trackPage(); } ``` -If the page is under a GUID route, pass the following two arguments to `trackPage`: -- `pagePublic`: boolean indicating whether the GUID referent is public or private -- `resourceType`: string for the API type of the GUID referent (e.g. `nodes`, `registrations`, etc.) +In route handlers that have pageview-related metadata, implement +[`Route.buildRouteInfoMetadata()`](https://api.emberjs.com/ember/3.26/classes/Route/methods/buildRouteInfoMetadata?anchor=buildRouteInfoMetadata) +to return an object. Currently only a few values on the returned +`metadata` object will have any consequence: +- `metadata.osfMetrics.itemGuid`, for route handlers that directly + handle the guid from the URL +- `metadata.osfMetrics.providerId`, for route handlers that have a + provider of some kind (if missing, `providerId` will be auto-filled + based on `itemGuid`, so this is mostly important for branded, non-guid + pages) +- `metadata.osfMetrics.isSearch`, should be `true` for discover pages + +Examples: +```ts +// route.ts +buildRouteInfoMetadata() { + return { + osfMetrics: { + itemGuid: this.controller.model.id, + }, + }; +} +``` +```ts +// route.ts +buildRouteInfoMetadata() { + return { + osfMetrics: { + isSearch: true, + providerId: 'osf', + }, + }; +} +``` ## Testing analytics Any action worth testing is worth tracking, and vice versa. diff --git a/lib/analytics-page/addon/application/route.ts b/lib/analytics-page/addon/application/route.ts index 5084ec32719..4194534e538 100644 --- a/lib/analytics-page/addon/application/route.ts +++ b/lib/analytics-page/addon/application/route.ts @@ -7,10 +7,8 @@ import { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; import { task, TaskInstance } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; -import { pluralize } from 'ember-inflector'; import Node from 'ember-osf-web/models/node'; -import { GuidRouteModel } from 'ember-osf-web/resolve-guid/guid-route'; import AnalyticsService from 'ember-osf-web/services/analytics'; import Ready, { Blocker } from 'ember-osf-web/services/ready'; @@ -69,14 +67,16 @@ export default class AnalyticsPageRoute extends Route { return taskFor(this.getNodeWithCounts).perform(model.taskInstance); } + buildRouteInfoMetadata() { + return { + osfMetrics: { + itemGuid: (this.controller as any).model.id as string, + }, + }; + } + @action async didTransition() { - const { taskInstance } = this.controller.model as GuidRouteModel; - await taskInstance; - const model = taskInstance.value; - this.analytics.trackPage( - model ? model.public : undefined, - model ? pluralize(model.modelName) : undefined, - ); + this.analytics.trackPage(); } } diff --git a/lib/collections/addon/application/route.ts b/lib/collections/addon/application/route.ts index 4e124135e0c..c6424e2532f 100644 --- a/lib/collections/addon/application/route.ts +++ b/lib/collections/addon/application/route.ts @@ -1,6 +1,8 @@ +import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; +import Analytics from 'ember-osf-web/services/analytics'; import Theme from 'ember-osf-web/services/theme'; export default class Application extends Route.extend({ @@ -17,4 +19,10 @@ export default class Application extends Route.extend({ }, }) { @service theme!: Theme; + @service analytics!: Analytics; + + @action + didTransition() { + this.analytics.trackPage(); + } } diff --git a/lib/collections/addon/discover/route.ts b/lib/collections/addon/discover/route.ts index 4f48a3306f2..fac7a89014d 100644 --- a/lib/collections/addon/discover/route.ts +++ b/lib/collections/addon/discover/route.ts @@ -1,13 +1,9 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; - export default class Discover extends Route { @service store!: Store; - @service analytics!: Analytics; queryParams = { queryString: { @@ -19,8 +15,12 @@ export default class Discover extends Route { return this.store.findAll('collection-provider', { reload: true }); } - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + isSearch: true, + providerId: 'osf', + }, + }; } } diff --git a/lib/collections/addon/guid/route.ts b/lib/collections/addon/guid/route.ts index b5e9161b92c..6b1d8f5f2f0 100644 --- a/lib/collections/addon/guid/route.ts +++ b/lib/collections/addon/guid/route.ts @@ -84,6 +84,15 @@ export default class Guid extends Route { return { taskInstance: taskFor(this.loadModel).perform(guid), + guid, + }; + } + + buildRouteInfoMetadata() { + return { + osfMetrics: { + itemGuid: this.controller.model.guid, + }, }; } } diff --git a/lib/collections/addon/provider/discover/route.ts b/lib/collections/addon/provider/discover/route.ts index bfc61891145..655ddcd8f00 100644 --- a/lib/collections/addon/provider/discover/route.ts +++ b/lib/collections/addon/provider/discover/route.ts @@ -1,8 +1,6 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; import Theme from 'ember-osf-web/services/theme'; export default class ProviderDiscover extends Route { @@ -11,14 +9,16 @@ export default class ProviderDiscover extends Route { @service store!: Store; @service theme!: Theme; - @service analytics!: Analytics; model() { return []; } - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + isSearch: true, + }, + }; } } diff --git a/lib/collections/addon/provider/route.ts b/lib/collections/addon/provider/route.ts index 2222ef0791d..7d0803c2525 100644 --- a/lib/collections/addon/provider/route.ts +++ b/lib/collections/addon/provider/route.ts @@ -38,4 +38,12 @@ export default class Provider extends Route { deactivate() { this.theme.reset(); } + + buildRouteInfoMetadata() { + return { + osfMetrics: { + providerId: this.theme.id, + }, + }; + } } diff --git a/lib/registries/addon/application/route.ts b/lib/registries/addon/application/route.ts new file mode 100644 index 00000000000..3dc88e95ee2 --- /dev/null +++ b/lib/registries/addon/application/route.ts @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Analytics from 'ember-osf-web/services/analytics'; + +export default class RegistriesApplicationRoute extends Route { + @service analytics!: Analytics; + + @action + didTransition() { + this.analytics.trackPage(); + } +} + diff --git a/lib/registries/addon/branded/discover/route.ts b/lib/registries/addon/branded/discover/route.ts index 36ecde1aea6..9a881cc4070 100644 --- a/lib/registries/addon/branded/discover/route.ts +++ b/lib/registries/addon/branded/discover/route.ts @@ -1,17 +1,12 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; import { notFoundURL } from 'ember-osf-web/utils/clean-url'; export default class BrandedRegistriesDiscoverRoute extends Route { // this route uses the registries.discover page template where the custom branding is handled templateName = 'discover'; - @service analytics!: Analytics; - model() { return this.modelFor('branded'); } @@ -26,8 +21,11 @@ export default class BrandedRegistriesDiscoverRoute extends Route { } } - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + isSearch: true, + }, + }; } } diff --git a/lib/registries/addon/branded/moderation/moderators/route.ts b/lib/registries/addon/branded/moderation/moderators/route.ts index de4ee46e23b..49b9e710bdb 100644 --- a/lib/registries/addon/branded/moderation/moderators/route.ts +++ b/lib/registries/addon/branded/moderation/moderators/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class BrandedModerationModeratorsRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/moderation/pending/route.ts b/lib/registries/addon/branded/moderation/pending/route.ts index db72890e7e0..ef6a6e43559 100644 --- a/lib/registries/addon/branded/moderation/pending/route.ts +++ b/lib/registries/addon/branded/moderation/pending/route.ts @@ -1,18 +1,13 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; import { RegistrationReviewStates } from 'ember-osf-web/models/registration'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; import { RevisionReviewStates } from 'ember-osf-web/models/schema-response'; -import Analytics from 'ember-osf-web/services/analytics'; import RegistriesModerationPendingController from './controller'; export default class BrandedModerationPendingRoute extends Route { - @service analytics!: Analytics; - setupController( controller: RegistriesModerationPendingController, model: RegistrationProviderModel, @@ -30,9 +25,4 @@ export default class BrandedModerationPendingRoute extends Route { this.replaceWith('branded.moderation.pending'); } } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/moderation/route.ts b/lib/registries/addon/branded/moderation/route.ts index d3d3bf6631f..2617dd1a315 100644 --- a/lib/registries/addon/branded/moderation/route.ts +++ b/lib/registries/addon/branded/moderation/route.ts @@ -1,15 +1,12 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; import CurrentUserService from 'ember-osf-web/services/current-user'; export default class BrandedModerationRoute extends Route { - @service analytics!: Analytics; @service currentUser!: CurrentUserService; @service router!: RouterService; @service store!: Store; @@ -21,9 +18,4 @@ export default class BrandedModerationRoute extends Route { this.transitionTo('page-not-found', window.location.pathname.slice(1)); } } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/moderation/settings/route.ts b/lib/registries/addon/branded/moderation/settings/route.ts index d659ae64a56..87f80ee10bc 100644 --- a/lib/registries/addon/branded/moderation/settings/route.ts +++ b/lib/registries/addon/branded/moderation/settings/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class BrandedModerationSettingsRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/moderation/submitted/route.ts b/lib/registries/addon/branded/moderation/submitted/route.ts index 9a7a87293a6..90d7e245f33 100644 --- a/lib/registries/addon/branded/moderation/submitted/route.ts +++ b/lib/registries/addon/branded/moderation/submitted/route.ts @@ -1,17 +1,12 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; import { RegistrationReviewStates } from 'ember-osf-web/models/registration'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; import RegistriesModerationSubmittedController from './controller'; export default class BrandedModerationSubmittedRoute extends Route { - @service analytics!: Analytics; - setupController( controller: RegistriesModerationSubmittedController, model: RegistrationProviderModel, @@ -30,9 +25,4 @@ export default class BrandedModerationSubmittedRoute extends Route { this.replaceWith('branded.moderation.submitted'); } } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/new/route.ts b/lib/registries/addon/branded/new/route.ts index 5b0df72928b..92cbc69deb9 100644 --- a/lib/registries/addon/branded/new/route.ts +++ b/lib/registries/addon/branded/new/route.ts @@ -1,4 +1,3 @@ -import { action } from '@ember/object'; import Transition from '@ember/routing/-private/transition'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; @@ -8,7 +7,6 @@ import config from 'ember-get-config'; import requireAuth from 'ember-osf-web/decorators/require-auth'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; import BrandedRegistriesNewSubmissionController from './controller'; const { @@ -19,7 +17,6 @@ const { @requireAuth() export default class BrandedRegistriesNewSubmissionRoute extends Route { - @service analytics!: Analytics; @service features!: Features; model() { @@ -50,9 +47,4 @@ export default class BrandedRegistriesNewSubmissionRoute extends Route { taskFor(controller.projectSearch).perform(); taskFor(controller.findAllSchemas).perform(); } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/branded/route.ts b/lib/registries/addon/branded/route.ts index bcb1e71b1f0..075ec91b58c 100644 --- a/lib/registries/addon/branded/route.ts +++ b/lib/registries/addon/branded/route.ts @@ -1,14 +1,11 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; export default class BrandedRegistriesRoute extends Route { - @service analytics!: Analytics; @service store!: Store; @service metaTags!: MetaTags; headTags?: HeadTagDef[]; @@ -30,8 +27,11 @@ export default class BrandedRegistriesRoute extends Route { } } - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + providerId: this.controller.model.id, + }, + }; } } diff --git a/lib/registries/addon/discover/route.ts b/lib/registries/addon/discover/route.ts index f0e17532671..f0755af90c5 100644 --- a/lib/registries/addon/discover/route.ts +++ b/lib/registries/addon/discover/route.ts @@ -1,14 +1,12 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class RegistriesDiscoverRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + isSearch: true, + providerId: 'osf', + }, + }; } } diff --git a/lib/registries/addon/drafts/draft/metadata/route.ts b/lib/registries/addon/drafts/draft/metadata/route.ts index be2399a2bd3..530ffca321c 100644 --- a/lib/registries/addon/drafts/draft/metadata/route.ts +++ b/lib/registries/addon/drafts/draft/metadata/route.ts @@ -1,16 +1,12 @@ import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; - import { DraftRoute } from 'registries/drafts/draft/navigation-manager'; import { DraftRouteModel } from '../route'; export default class DraftRegistrationMetadataRoute extends Route { - @service analytics!: Analytics; @service store!: Store; @service router!: RouterService; @@ -21,9 +17,4 @@ export default class DraftRegistrationMetadataRoute extends Route { navigationManager.setPageAndRoute(DraftRoute.Metadata); return draftRouteModel; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/drafts/draft/page/route.ts b/lib/registries/addon/drafts/draft/page/route.ts index 20b3e972409..1207a5b55b7 100644 --- a/lib/registries/addon/drafts/draft/page/route.ts +++ b/lib/registries/addon/drafts/draft/page/route.ts @@ -1,9 +1,6 @@ import { assert } from '@ember/debug'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; import { getPageIndex } from 'ember-osf-web/utils/page-param'; import DraftRegistrationManager from 'registries/drafts/draft/draft-registration-manager'; @@ -18,8 +15,6 @@ export interface DraftPageRouteModel { } export default class DraftRegistrationPageRoute extends Route { - @service analytics!: Analytics; - model(params: { page: string }): DraftPageRouteModel { const { page } = params; const pageIndex = getPageIndex(page); @@ -36,9 +31,4 @@ export default class DraftRegistrationPageRoute extends Route { page, }; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/drafts/draft/review/route.ts b/lib/registries/addon/drafts/draft/review/route.ts index 0eaa6b91ead..57b79a8bad6 100644 --- a/lib/registries/addon/drafts/draft/review/route.ts +++ b/lib/registries/addon/drafts/draft/review/route.ts @@ -1,15 +1,9 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; import { DraftRoute } from 'registries/drafts/draft/navigation-manager'; import { DraftRouteModel } from '../route'; export default class DraftRegistrationReview extends Route { - @service analytics!: Analytics; - model(): DraftRouteModel { const draftRouteModel = this.modelFor('drafts.draft') as DraftRouteModel; const { navigationManager } = draftRouteModel; @@ -18,9 +12,4 @@ export default class DraftRegistrationReview extends Route { return this.modelFor('drafts.draft') as DraftRouteModel; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/drafts/draft/route.ts b/lib/registries/addon/drafts/draft/route.ts index aac3a18990c..b7b4c653f2c 100644 --- a/lib/registries/addon/drafts/draft/route.ts +++ b/lib/registries/addon/drafts/draft/route.ts @@ -1,6 +1,5 @@ import Store from '@ember-data/store'; import { getOwner } from '@ember/application'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; @@ -12,7 +11,6 @@ import requireAuth from 'ember-osf-web/decorators/require-auth'; import DraftRegistration from 'ember-osf-web/models/draft-registration'; import ProviderModel from 'ember-osf-web/models/provider'; import SubjectModel from 'ember-osf-web/models/subject'; -import Analytics from 'ember-osf-web/services/analytics'; import captureException from 'ember-osf-web/utils/capture-exception'; import { notFoundURL } from 'ember-osf-web/utils/clean-url'; import DraftRegistrationManager, { LoadDraftModelTask } from 'registries/drafts/draft/draft-registration-manager'; @@ -25,7 +23,6 @@ export interface DraftRouteModel { @requireAuth() export default class DraftRegistrationRoute extends Route { - @service analytics!: Analytics; @service store!: Store; @service router!: RouterService; @@ -68,8 +65,11 @@ export default class DraftRegistrationRoute extends Route { }; } - @action - didTransition() { - this.analytics.trackPage(); + buildRouteInfoMetadata() { + return { + osfMetrics: { + providerId: this.controller.model.draftRegistrationManager.provider?.id, + }, + }; } } diff --git a/lib/registries/addon/drafts/index/route.ts b/lib/registries/addon/drafts/index/route.ts index 1a2172f99c2..5546b2d71f2 100644 --- a/lib/registries/addon/drafts/index/route.ts +++ b/lib/registries/addon/drafts/index/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class DraftsIndexRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/edit-revision/justification/route.ts b/lib/registries/addon/edit-revision/justification/route.ts index 11c21c3b1bb..34bed492991 100644 --- a/lib/registries/addon/edit-revision/justification/route.ts +++ b/lib/registries/addon/edit-revision/justification/route.ts @@ -1,15 +1,10 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; import { RevisionRoute } from 'registries/edit-revision/nav-manager'; import { EditRevisionRouteModel } from '../route'; export default class EditRevisionJustification extends Route { - @service analytics!: Analytics; - model(): EditRevisionRouteModel { const editRevisionRouteModel = this.modelFor('edit-revision') as EditRevisionRouteModel; const { navigationManager } = editRevisionRouteModel; @@ -18,9 +13,4 @@ export default class EditRevisionJustification extends Route { return this.modelFor('edit-revision') as EditRevisionRouteModel; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/edit-revision/page/route.ts b/lib/registries/addon/edit-revision/page/route.ts index d014608339a..7eeb57a908d 100644 --- a/lib/registries/addon/edit-revision/page/route.ts +++ b/lib/registries/addon/edit-revision/page/route.ts @@ -1,9 +1,6 @@ import { assert } from '@ember/debug'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; import { getPageIndex } from 'ember-osf-web/utils/page-param'; import RevisionNavigationManager, { RevisionRoute } from 'registries/edit-revision/nav-manager'; @@ -18,8 +15,6 @@ export interface RevisionPageRouteModel { } export default class RevisionPageRoute extends Route { - @service analytics!: Analytics; - model(params: { page: string }): RevisionPageRouteModel { const { page } = params; const pageIndex = getPageIndex(page); @@ -36,9 +31,4 @@ export default class RevisionPageRoute extends Route { page, }; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/edit-revision/review/route.ts b/lib/registries/addon/edit-revision/review/route.ts index 929e2f6cb62..d9103d895e7 100644 --- a/lib/registries/addon/edit-revision/review/route.ts +++ b/lib/registries/addon/edit-revision/review/route.ts @@ -1,14 +1,10 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; -import Analytics from 'ember-osf-web/services/analytics'; import { RevisionRoute } from 'registries/edit-revision/nav-manager'; import { EditRevisionRouteModel } from '../route'; export default class EditRevisionReview extends Route { - @service analytics!: Analytics; model(): EditRevisionRouteModel { const editRevisionRouteModel = this.modelFor('edit-revision') as EditRevisionRouteModel; @@ -18,9 +14,4 @@ export default class EditRevisionReview extends Route { return this.modelFor('edit-revision') as EditRevisionRouteModel; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/edit-revision/route.ts b/lib/registries/addon/edit-revision/route.ts index 4e50a492d31..f8fbd1a1e2e 100644 --- a/lib/registries/addon/edit-revision/route.ts +++ b/lib/registries/addon/edit-revision/route.ts @@ -1,6 +1,5 @@ import { getOwner } from '@ember/application'; import Store from '@ember-data/store'; -import { action } from '@ember/object'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; @@ -10,7 +9,6 @@ import { taskFor } from 'ember-concurrency-ts'; import requireAuth from 'ember-osf-web/decorators/require-auth'; import { RevisionReviewStates } from 'ember-osf-web/models/schema-response'; -import Analytics from 'ember-osf-web/services/analytics'; import captureException from 'ember-osf-web/utils/capture-exception'; import { notFoundURL } from 'ember-osf-web/utils/clean-url'; import RevisionNavigationManager from 'registries/edit-revision/nav-manager'; @@ -22,7 +20,6 @@ export interface EditRevisionRouteModel { @requireAuth() export default class EditRevisionRoute extends Route { - @service analytics!: Analytics; @service store!: Store; @service router!: RouterService; @@ -62,9 +59,4 @@ export default class EditRevisionRoute extends Route { revisionManager, }; } - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/forms/help/route.ts b/lib/registries/addon/forms/help/route.ts index 5afb91d9e50..a7f1671cc3c 100644 --- a/lib/registries/addon/forms/help/route.ts +++ b/lib/registries/addon/forms/help/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class FormsHelpRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/forms/index/route.ts b/lib/registries/addon/forms/index/route.ts index 6948c56cbee..0ee3efe3a35 100644 --- a/lib/registries/addon/forms/index/route.ts +++ b/lib/registries/addon/forms/index/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class FormsIndexRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/index/route.ts b/lib/registries/addon/index/route.ts index c665d4bce42..069bc91f429 100644 --- a/lib/registries/addon/index/route.ts +++ b/lib/registries/addon/index/route.ts @@ -1,14 +1,4 @@ -import { action } from '@ember/object'; import Route from '@ember/routing/route'; -import { inject as service } from '@ember/service'; - -import Analytics from 'ember-osf-web/services/analytics'; export default class RegistriesIndexRoute extends Route { - @service analytics!: Analytics; - - @action - didTransition() { - this.analytics.trackPage(); - } } diff --git a/lib/registries/addon/my-registrations/route.ts b/lib/registries/addon/my-registrations/route.ts index 4dd8fd8e234..aa97b60ea83 100644 --- a/lib/registries/addon/my-registrations/route.ts +++ b/lib/registries/addon/my-registrations/route.ts @@ -1,20 +1,7 @@ import Route from '@ember/routing/route'; -import RouterService from '@ember/routing/router-service'; -import { inject as service } from '@ember/service'; import requireAuth from 'ember-osf-web/decorators/require-auth'; -import Analytics from 'ember-osf-web/services/analytics'; @requireAuth() export default class RegistriesMyRegistrationsRoute extends Route { - @service analytics!: Analytics; - @service router!: RouterService; - - constructor(...args: any[]) { - super(...args); - - this.router.on('routeDidChange', () => { - this.analytics.trackPage(); - }); - } } diff --git a/lib/registries/addon/overview/route.ts b/lib/registries/addon/overview/route.ts index ff7c97d8237..f9e643e98eb 100644 --- a/lib/registries/addon/overview/route.ts +++ b/lib/registries/addon/overview/route.ts @@ -118,14 +118,6 @@ export default class Overview extends GuidRoute { } } - @action - async didTransition() { - const { taskInstance } = this.controller.model as GuidRouteModel; - await taskInstance; - const registration = taskInstance.value; - this.analytics.trackPage(registration ? registration.public : undefined, 'registrations'); - } - @action error() { this.replaceWith('page-not-found', notFoundURL(this.router.currentURL)); diff --git a/lib/registries/addon/services/share-search.ts b/lib/registries/addon/services/share-search.ts index e49ca20aeea..27e791429c1 100644 --- a/lib/registries/addon/services/share-search.ts +++ b/lib/registries/addon/services/share-search.ts @@ -111,7 +111,7 @@ export interface ShareRegistration { title: string; withdrawn: boolean; relatedResourceTypes?: RelatedResourceTypes; - sourceUniqueId: string; + sourceUniqueId?: string; } export interface SourceDescriptor { diff --git a/mirage/config.ts b/mirage/config.ts index 9da2ee93251..eb1045271fd 100644 --- a/mirage/config.ts +++ b/mirage/config.ts @@ -20,6 +20,7 @@ import { createFork, createRegistrationFork } from './views/fork'; import { guidDetail } from './views/guid'; import { identifierCreate } from './views/identifier'; import { summaryMetrics } from './views/institution'; +import { postCountedUsage } from './views/metrics'; import { addModerator } from './views/moderator'; import { createNode, storageStatus } from './views/node'; import { osfNestedResource, osfResource, osfToManyRelationship } from './views/osf-resource'; @@ -353,4 +354,7 @@ export default function(this: Server) { only: ['related'], path: '/meetings/:parentID/submissions/', }); + + // usage reporting + this.post('/metrics/events/counted_usage/', postCountedUsage); } diff --git a/mirage/models/counted-usage.js b/mirage/models/counted-usage.js new file mode 100644 index 00000000000..1486a724095 --- /dev/null +++ b/mirage/models/counted-usage.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/views/metrics.ts b/mirage/views/metrics.ts new file mode 100644 index 00000000000..ebb52127690 --- /dev/null +++ b/mirage/views/metrics.ts @@ -0,0 +1,6 @@ +import { HandlerContext, Schema, Response } from 'ember-cli-mirage'; + +export function postCountedUsage(this: HandlerContext, schema: Schema) { + schema.countedUsages.create(this.normalizedRequestAttrs('counted-usage') as any); + return new Response(201); +} diff --git a/tests/unit/metric-adapters/keen-test.ts b/tests/unit/metrics-adapters/keen-test.ts similarity index 92% rename from tests/unit/metric-adapters/keen-test.ts rename to tests/unit/metrics-adapters/keen-test.ts index ab3e4fbbf1e..c43a5847efa 100644 --- a/tests/unit/metric-adapters/keen-test.ts +++ b/tests/unit/metrics-adapters/keen-test.ts @@ -34,6 +34,9 @@ module('Unit | Metrics Adapter | keen ', hooks => { const trackPublic = this.sandbox.stub(adapter, 'trackPublicEvent'); const trackPrivate = this.sandbox.stub(adapter, 'trackPrivateEvent'); + this.owner.register('service:router', Service.extend({ + currentRouteName: 'foo', + })); const mirageNode = server.create('node', { public: true }); const node = await store.findRecord('node', mirageNode.id); @@ -52,6 +55,9 @@ module('Unit | Metrics Adapter | keen ', hooks => { const trackPublic = this.sandbox.stub(adapter, 'trackPublicEvent'); const trackPrivate = this.sandbox.stub(adapter, 'trackPrivateEvent'); + this.owner.register('service:router', Service.extend({ + currentRouteName: 'foo', + })); const mirageNode = server.create('node', { public: false }); const node = await store.findRecord('node', mirageNode.id); @@ -69,6 +75,9 @@ module('Unit | Metrics Adapter | keen ', hooks => { const trackPublic = this.sandbox.stub(adapter, 'trackPublicEvent'); const trackPrivate = this.sandbox.stub(adapter, 'trackPrivateEvent'); + this.owner.register('service:router', Service.extend({ + currentRouteName: 'foo', + })); this.sandbox.stub(adapter, 'getCurrentNode').resolves(undefined); diff --git a/tests/unit/services/analytics-test.ts b/tests/unit/services/analytics-test.ts index 36f110d5f03..649200b7d24 100644 --- a/tests/unit/services/analytics-test.ts +++ b/tests/unit/services/analytics-test.ts @@ -1,12 +1,19 @@ +import { settled } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupTest } from 'ember-qunit'; import { module, test } from 'qunit'; module('Unit | Service | analytics', hooks => { setupTest(hooks); + setupMirage(hooks); - // Replace this with your real tests. - test('it exists', function(assert) { + test('trackPage', async function(assert) { const service = this.owner.lookup('service:analytics'); - assert.ok(service); + assert.strictEqual(server.schema.countedUsages.all().length, 0, 'start with no counted usage'); + await service.trackPage(); + await settled(); + assert.strictEqual(server.schema.countedUsages.all().length, 1, 'end with one counted usage'); + const savedCountedUsage = server.schema.countedUsages.first(); + assert.strictEqual(savedCountedUsage.pageviewInfo.page_url, document.URL, 'correct url'); }); });