From 8f2a698250a7c6958c63c1680219e410bdbc0054 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Sep 2017 14:27:39 +0200 Subject: [PATCH] Google Analytis Support. --- src/Squidex/app/app.module.ts | 6 +++ src/Squidex/app/framework/configurations.ts | 4 ++ src/Squidex/app/framework/declarations.ts | 3 +- src/Squidex/app/framework/module.ts | 6 ++- src/Squidex/app/framework/plattform.ts | 14 ----- .../framework/services/analytics.service.ts | 51 +++++++++++++++++++ .../services/app-clients.service.spec.ts | 4 +- .../shared/services/app-clients.service.ts | 13 ++++- .../services/app-contributors.service.spec.ts | 4 +- .../services/app-contributors.service.ts | 10 +++- .../services/app-languages.service.spec.ts | 5 +- .../shared/services/app-languages.service.ts | 13 ++++- .../app/shared/services/apps.service.spec.ts | 4 +- .../app/shared/services/apps.service.ts | 7 ++- .../shared/services/assets.service.spec.ts | 4 +- .../app/shared/services/assets.service.ts | 12 +++++ .../shared/services/contents.service.spec.ts | 4 +- .../app/shared/services/contents.service.ts | 34 ++++++++++--- .../app/shared/services/plans.service.spec.ts | 4 +- .../app/shared/services/plans.service.ts | 7 ++- .../shared/services/schemas.service.spec.ts | 4 +- .../app/shared/services/schemas.service.ts | 46 +++++++++++++++++ .../shared/services/webhooks.service.spec.ts | 4 +- .../app/shared/services/webhooks.service.ts | 16 +++++- 24 files changed, 238 insertions(+), 41 deletions(-) delete mode 100644 src/Squidex/app/framework/plattform.ts create mode 100644 src/Squidex/app/framework/services/analytics.service.ts diff --git a/src/Squidex/app/app.module.ts b/src/Squidex/app/app.module.ts index 1055e5c57..a5839cf6e 100644 --- a/src/Squidex/app/app.module.ts +++ b/src/Squidex/app/app.module.ts @@ -13,6 +13,7 @@ import { DndModule } from 'ng2-dnd'; import { AppComponent } from './app.component'; import { + AnalyticsIdConfig, ApiUrlConfig, CurrencyConfig, DecimalSeparatorConfig, @@ -34,6 +35,10 @@ export function configTitles() { return new TitlesConfig({}, undefined, 'Squidex Headless CMS'); } +export function configAnalyticsId() { + return new AnalyticsIdConfig('UA-99989790-2'); +} + export function configDecimalSeparator() { return new DecimalSeparatorConfig('.'); } @@ -60,6 +65,7 @@ export function configUserReport() { AppComponent ], providers: [ + { provide: AnalyticsIdConfig, useFactory: configAnalyticsId }, { provide: ApiUrlConfig, useFactory: configApiUrl }, { provide: CurrencyConfig, useFactory: configCurrency }, { provide: DecimalSeparatorConfig, useFactory: configDecimalSeparator }, diff --git a/src/Squidex/app/framework/configurations.ts b/src/Squidex/app/framework/configurations.ts index 7030f432a..703a5f9ca 100644 --- a/src/Squidex/app/framework/configurations.ts +++ b/src/Squidex/app/framework/configurations.ts @@ -34,6 +34,10 @@ export class CurrencyConfig { } } +export class AnalyticsIdConfig { + constructor(public value: string) { } +} + export class DecimalSeparatorConfig { constructor(public readonly value: string) { } } diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index e150b3563..50ea748ed 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -51,6 +51,7 @@ export * from './angular/user-report.component'; export * from './angular/validators'; export * from './configurations'; +export * from './services/analytics.service'; export * from './services/clipboard.service'; export * from './services/dialog.service'; export * from './services/local-store.service'; @@ -61,8 +62,6 @@ export * from './services/root-view.service'; export * from './services/shortcut.service'; export * from './services/title.service'; -export * from './plattform'; - export * from './utils/date-helper'; export * from './utils/date-time'; export * from './utils/duration'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 3205ba722..422e3931f 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -6,12 +6,13 @@ */ import { CommonModule } from '@angular/common'; -import { HttpClientModule } from '@angular/common/http'; import { ModuleWithProviders, NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { + AnalyticsService, AutocompleteComponent, CanDeactivateGuard, ClipboardService, @@ -185,6 +186,7 @@ export class SqxFrameworkModule { return { ngModule: SqxFrameworkModule, providers: [ + AnalyticsService, CanDeactivateGuard, ClipboardService, DialogService, diff --git a/src/Squidex/app/framework/plattform.ts b/src/Squidex/app/framework/plattform.ts deleted file mode 100644 index 28670a5ad..000000000 --- a/src/Squidex/app/framework/plattform.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -import { Observable } from 'rxjs/Observable'; - -export abstract class AppStore { - public abstract select(pathOrMapFn: any, ...paths: string[]): Observable; - - public abstract next(action: any): void; -} \ No newline at end of file diff --git a/src/Squidex/app/framework/services/analytics.service.ts b/src/Squidex/app/framework/services/analytics.service.ts new file mode 100644 index 000000000..ba365921b --- /dev/null +++ b/src/Squidex/app/framework/services/analytics.service.ts @@ -0,0 +1,51 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Injectable } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; + +import { AnalyticsIdConfig } from '../configurations'; +import { ResourceLoaderService } from './resource-loader.service'; + +// tslint:disable:only-arrow-functions + +export const AnalyticsServiceFactory = (analyticsId: AnalyticsIdConfig, router: Router, resourceLoader: ResourceLoaderService) => { + return new AnalyticsService(analyticsId, router, resourceLoader); +} + +@Injectable() +export class AnalyticsService { + private readonly gtag: any; + + constructor(analyticsId?: AnalyticsIdConfig, router?: Router, resourceLoader?: ResourceLoaderService) { + window['dataLayer'] = window['dataLayer'] || []; + + this.gtag = function () { + window['dataLayer'].push(arguments); + }; + + if (analyticsId && router && resourceLoader && window.location.hostname !== 'localhost') { + this.gtag('config', analyticsId.value); + + router.events.filter(e => e instanceof NavigationEnd) + .subscribe(() => { + this.gtag('config', analyticsId.value, { page_path: window.location.pathname }); + }); + + resourceLoader.loadScript(`https://www.googletagmanager.com/gtag/js?id=${analyticsId.value}`); + } + } + + public trackEvent(category: string, action: string, label?: string, value?: number) { + this.gtag('event', 'user-action', { + event_category: category, + event_action: action, + event_label: label, + value: value + }); + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/services/app-clients.service.spec.ts b/src/Squidex/app/shared/services/app-clients.service.spec.ts index 6b85eff1a..ce7610cde 100644 --- a/src/Squidex/app/shared/services/app-clients.service.spec.ts +++ b/src/Squidex/app/shared/services/app-clients.service.spec.ts @@ -10,6 +10,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { AccessTokenDto, + AnalyticsService, ApiUrlConfig, AppClientDto, AppClientsService, @@ -44,7 +45,8 @@ describe('AppClientsService', () => { ], providers: [ AppClientsService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/app-clients.service.ts b/src/Squidex/app/shared/services/app-clients.service.ts index 478581c55..c6a208be1 100644 --- a/src/Squidex/app/shared/services/app-clients.service.ts +++ b/src/Squidex/app/shared/services/app-clients.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, HTTP, Version @@ -62,7 +63,8 @@ export class AccessTokenDto { export class AppClientsService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -95,6 +97,9 @@ export class AppClientsService { response.secret, response.isReader); }) + .do(() => { + this.analytics.trackEvent('Client', 'Created', appName); + }) .pretifyError('Failed to add client. Please reload.'); } @@ -102,6 +107,9 @@ export class AppClientsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Client', 'Updated', appName); + }) .pretifyError('Failed to revoke client. Please reload.'); } @@ -109,6 +117,9 @@ export class AppClientsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); return HTTP.deleteVersioned(this.http, url, version) + .do(() => { + this.analytics.trackEvent('Client', 'Deleted', appName); + }) .pretifyError('Failed to revoke client. Please reload.'); } diff --git a/src/Squidex/app/shared/services/app-contributors.service.spec.ts b/src/Squidex/app/shared/services/app-contributors.service.spec.ts index 7ce016002..a75b66fa0 100644 --- a/src/Squidex/app/shared/services/app-contributors.service.spec.ts +++ b/src/Squidex/app/shared/services/app-contributors.service.spec.ts @@ -9,6 +9,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, ApiUrlConfig, AppContributorDto, AppContributorsDto, @@ -35,7 +36,8 @@ describe('AppContributorsService', () => { ], providers: [ AppContributorsService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/app-contributors.service.ts b/src/Squidex/app/shared/services/app-contributors.service.ts index 7ccc9f0d6..35aa95f5a 100644 --- a/src/Squidex/app/shared/services/app-contributors.service.ts +++ b/src/Squidex/app/shared/services/app-contributors.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, HTTP, Version @@ -41,7 +42,8 @@ export class AppContributorsDto { export class AppContributorsService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -67,6 +69,9 @@ export class AppContributorsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); return HTTP.postVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Contributor', 'Configured', appName); + }) .pretifyError('Failed to add contributors. Please reload.'); } @@ -74,6 +79,9 @@ export class AppContributorsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`); return HTTP.deleteVersioned(this.http, url, version) + .do(() => { + this.analytics.trackEvent('Contributor', 'Deleted', appName); + }) .pretifyError('Failed to delete contributors. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/app-languages.service.spec.ts b/src/Squidex/app/shared/services/app-languages.service.spec.ts index 395487868..a5182d683 100644 --- a/src/Squidex/app/shared/services/app-languages.service.spec.ts +++ b/src/Squidex/app/shared/services/app-languages.service.spec.ts @@ -10,6 +10,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { AddAppLanguageDto, + AnalyticsService, ApiUrlConfig, AppLanguageDto, AppLanguagesService, @@ -17,7 +18,6 @@ import { Version } from './../'; - describe('AppLanguageDto', () => { it('should update properties when updating', () => { const language_1 = new AppLanguageDto('de', 'English', false, false, []); @@ -39,7 +39,8 @@ describe('AppLanguagesService', () => { ], providers: [ AppLanguagesService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/app-languages.service.ts b/src/Squidex/app/shared/services/app-languages.service.ts index 3197562ff..7a4546d95 100644 --- a/src/Squidex/app/shared/services/app-languages.service.ts +++ b/src/Squidex/app/shared/services/app-languages.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, HTTP, Version @@ -52,7 +53,8 @@ export class UpdateAppLanguageDto { export class AppLanguagesService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -87,6 +89,9 @@ export class AppLanguagesService { response.isOptional === true, response.fallback || []); }) + .do(() => { + this.analytics.trackEvent('Language', 'Added', appName); + }) .pretifyError('Failed to add language. Please reload.'); } @@ -94,6 +99,9 @@ export class AppLanguagesService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Language', 'Updated', appName); + }) .pretifyError('Failed to change language. Please reload.'); } @@ -101,6 +109,9 @@ export class AppLanguagesService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); return HTTP.deleteVersioned(this.http, url, version) + .do(() => { + this.analytics.trackEvent('Language', 'Deleted', appName); + }) .pretifyError('Failed to add language. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/apps.service.spec.ts b/src/Squidex/app/shared/services/apps.service.spec.ts index 30b05c152..4f29d6075 100644 --- a/src/Squidex/app/shared/services/apps.service.spec.ts +++ b/src/Squidex/app/shared/services/apps.service.spec.ts @@ -9,6 +9,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, ApiUrlConfig, AppDto, AppsService, @@ -26,7 +27,8 @@ describe('AppsService', () => { ], providers: [ AppsService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/apps.service.ts b/src/Squidex/app/shared/services/apps.service.ts index b9a94ada4..28dda20d3 100644 --- a/src/Squidex/app/shared/services/apps.service.ts +++ b/src/Squidex/app/shared/services/apps.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, DateTime, HTTP @@ -39,7 +40,8 @@ export class CreateAppDto { export class AppsService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -71,6 +73,9 @@ export class AppsService { return new AppDto(response.id, dto.name, 'Owner', now, now); }) + .do(() => { + this.analytics.trackEvent('App', 'Created', dto.name); + }) .pretifyError('Failed to create app. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/assets.service.spec.ts b/src/Squidex/app/shared/services/assets.service.spec.ts index a56a4581e..8bbb0f518 100644 --- a/src/Squidex/app/shared/services/assets.service.spec.ts +++ b/src/Squidex/app/shared/services/assets.service.spec.ts @@ -9,6 +9,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, ApiUrlConfig, AssetsDto, AssetDto, @@ -66,7 +67,8 @@ describe('AssetsService', () => { providers: [ AssetsService, LocalCacheService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index f1c2a1e51..041b7d7ff 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -10,6 +10,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { + AnalyticsService, ApiUrlConfig, DateTime, LocalCacheService, @@ -102,6 +103,7 @@ export class AssetsService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService, private readonly localCache: LocalCacheService ) { } @@ -193,6 +195,8 @@ export class AssetsService { } }) .do(dto => { + this.analytics.trackEvent('Asset', 'Uploaded', appName); + if (dto instanceof AssetDto) { this.localCache.set(`asset.${dto.id}`, dto, 5000); } @@ -266,6 +270,9 @@ export class AssetsService { return dto; } }) + .do(() => { + this.analytics.trackEvent('Analytics', 'Replaced', appName); + }) .pretifyError('Failed to replace asset. Please reload.'); } @@ -274,6 +281,8 @@ export class AssetsService { return HTTP.deleteVersioned(this.http, url, version) .do(() => { + this.analytics.trackEvent('Analytics', 'Deleted', appName); + this.localCache.remove(`asset.${id}`); }) .pretifyError('Failed to delete asset. Please reload.'); @@ -283,6 +292,9 @@ export class AssetsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Analytics', 'Updated', appName); + }) .pretifyError('Failed to delete asset. Please reload.'); } } diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index 9e0bc33e5..a4ba51572 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -9,6 +9,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, ApiUrlConfig, ContentDto, ContentsDto, @@ -91,7 +92,8 @@ describe('ContentsService', () => { providers: [ ContentsService, LocalCacheService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 14b6afda3..c01637faf 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, DateTime, LocalCacheService, @@ -94,6 +95,7 @@ export class ContentsService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService, private readonly localCache: LocalCacheService ) { } @@ -179,6 +181,13 @@ export class ContentsService { .pretifyError('Failed to load content. Please reload.'); } + public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable { + const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`); + + return HTTP.getVersioned(this.http, url, version) + .pretifyError('Failed to load data. Please reload.'); + } + public postContent(appName: string, schemaName: string, dto: any, publish: boolean): Observable { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`); @@ -195,6 +204,8 @@ export class ContentsService { new Version(response.version.toString())); }) .do(content => { + this.analytics.trackEvent('Content', 'Created', appName); + this.localCache.set(`content.${content.id}`, content, 5000); }) .pretifyError('Failed to create content. Please reload.'); @@ -205,6 +216,8 @@ export class ContentsService { return HTTP.putVersioned(this.http, url, dto, version) .do(() => { + this.analytics.trackEvent('Content', 'Updated', appName); + this.localCache.set(`content.${id}`, dto, 5000); }) .pretifyError('Failed to update content. Please reload.'); @@ -215,22 +228,20 @@ export class ContentsService { return HTTP.deleteVersioned(this.http, url, version) .do(() => { + this.analytics.trackEvent('Content', 'Deleted', appName); + this.localCache.remove(`content.${id}`); }) .pretifyError('Failed to delete content. Please reload.'); } - public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable { - const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`); - - return HTTP.getVersioned(this.http, url, version) - .pretifyError('Failed to load data. Please reload.'); - } - public publishContent(appName: string, schemaName: string, id: string, version: Version): Observable { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Content', 'Published', appName); + }) .pretifyError('Failed to publish content. Please reload.'); } @@ -238,6 +249,9 @@ export class ContentsService { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Content', 'Unpublished', appName); + }) .pretifyError('Failed to unpublish content. Please reload.'); } @@ -245,6 +259,9 @@ export class ContentsService { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/archive`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Content', 'Archived', appName); + }) .pretifyError('Failed to archive content. Please reload.'); } @@ -252,6 +269,9 @@ export class ContentsService { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/restore`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Content', 'Restored', appName); + }) .pretifyError('Failed to restore content. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/plans.service.spec.ts b/src/Squidex/app/shared/services/plans.service.spec.ts index d70f497a4..196b3c8b6 100644 --- a/src/Squidex/app/shared/services/plans.service.spec.ts +++ b/src/Squidex/app/shared/services/plans.service.spec.ts @@ -9,6 +9,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, AppPlansDto, ApiUrlConfig, ChangePlanDto, @@ -28,7 +29,8 @@ describe('PlansService', () => { ], providers: [ PlansService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/plans.service.ts b/src/Squidex/app/shared/services/plans.service.ts index afb40fded..41bf67a42 100644 --- a/src/Squidex/app/shared/services/plans.service.ts +++ b/src/Squidex/app/shared/services/plans.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, HTTP, Version @@ -57,7 +58,8 @@ export class ChangePlanDto { export class PlansService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -92,6 +94,9 @@ export class PlansService { .map(response => { return new PlanChangedDto(response.redirectUri); }) + .do(() => { + this.analytics.trackEvent('Plan', 'Changed', appName); + }) .pretifyError('Failed to change plan. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 7d936d491..2088db9a8 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -10,6 +10,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { AddFieldDto, + AnalyticsService, ApiUrlConfig, CreateSchemaDto, createProperties, @@ -185,7 +186,8 @@ describe('SchemasService', () => { providers: [ LocalCacheService, SchemasService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index ec98da47d..b934d62fe 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -13,6 +13,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, DateTime, LocalCacheService, @@ -689,6 +690,7 @@ export class SchemasService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService, private readonly localCache: LocalCacheService ) { } @@ -795,6 +797,8 @@ export class SchemasService { response.scriptChange); }) .do(schema => { + this.analytics.trackEvent('Schema', 'Created', appName); + this.localCache.set(`schema.${appName}.${schema.id}`, schema, 5000); this.localCache.set(`schema.${appName}.${schema.name}`, schema, 5000); }) @@ -815,6 +819,9 @@ export class SchemasService { dto.partitioning, dto.properties); }) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldCreated', appName); + }) .pretifyError('Failed to add field. Please reload.'); } @@ -825,6 +832,9 @@ export class SchemasService { .do(() => { this.localCache.remove(`schema.${appName}.${schemaName}`); }) + .do(() => { + this.analytics.trackEvent('Schema', 'Deleted', appName); + }) .pretifyError('Failed to delete schema. Please reload.'); } @@ -832,6 +842,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Schema', 'ScriptsConfigured', appName); + }) .pretifyError('Failed to update schema scripts. Please reload.'); } @@ -839,6 +852,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Schema', 'Updated', appName); + }) .pretifyError('Failed to update schema. Please reload.'); } @@ -846,6 +862,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`); return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldsReordered', appName); + }) .pretifyError('Failed to reorder fields. Please reload.'); } @@ -853,6 +872,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'Published', appName); + }) .pretifyError('Failed to publish schema. Please reload.'); } @@ -860,6 +882,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'Unpublished', appName); + }) .pretifyError('Failed to unpublish schema. Please reload.'); } @@ -867,6 +892,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldUpdated', appName); + }) .pretifyError('Failed to update field. Please reload.'); } @@ -874,6 +902,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldEnabled', appName); + }) .pretifyError('Failed to enable field. Please reload.'); } @@ -881,6 +912,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldDisabled', appName); + }) .pretifyError('Failed to disable field. Please reload.'); } @@ -888,6 +922,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldLocked', appName); + }) .pretifyError('Failed to lock field. Please reload.'); } @@ -895,6 +932,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldShown', appName); + }) .pretifyError('Failed to show field. Please reload.'); } @@ -902,6 +942,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`); return HTTP.putVersioned(this.http, url, {}, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldHidden', appName); + }) .pretifyError('Failed to hide field. Please reload.'); } @@ -909,6 +952,9 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); return HTTP.deleteVersioned(this.http, url, version) + .do(() => { + this.analytics.trackEvent('Schema', 'FieldDeleted', appName); + }) .pretifyError('Failed to delete field. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/webhooks.service.spec.ts b/src/Squidex/app/shared/services/webhooks.service.spec.ts index a814ee7a0..ad7e151e0 100644 --- a/src/Squidex/app/shared/services/webhooks.service.spec.ts +++ b/src/Squidex/app/shared/services/webhooks.service.spec.ts @@ -10,6 +10,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { inject, TestBed } from '@angular/core/testing'; import { + AnalyticsService, ApiUrlConfig, CreateWebhookDto, DateTime, @@ -57,7 +58,8 @@ describe('WebhooksService', () => { ], providers: [ WebhooksService, - { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } ] }); }); diff --git a/src/Squidex/app/shared/services/webhooks.service.ts b/src/Squidex/app/shared/services/webhooks.service.ts index 3edcfbfa5..6e804968a 100644 --- a/src/Squidex/app/shared/services/webhooks.service.ts +++ b/src/Squidex/app/shared/services/webhooks.service.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import 'framework/angular/http-extensions'; import { + AnalyticsService, ApiUrlConfig, DateTime, HTTP, @@ -106,7 +107,8 @@ export class UpdateWebhookDto { export class WebhooksService { constructor( private readonly http: HttpClient, - private readonly apiUrl: ApiUrlConfig + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService ) { } @@ -162,6 +164,9 @@ export class WebhooksService { dto.url, 0, 0, 0, 0); }) + .do(() => { + this.analytics.trackEvent('Webhook', 'Created', appName); + }) .pretifyError('Failed to create webhook. Please reload.'); } @@ -169,6 +174,9 @@ export class WebhooksService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`); return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Webhook', 'Updated', appName); + }) .pretifyError('Failed to update webhook. Please reload.'); } @@ -176,6 +184,9 @@ export class WebhooksService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`); return HTTP.deleteVersioned(this.http, url, version) + .do(() => { + this.analytics.trackEvent('Webhook', 'Deleted', appName); + }) .pretifyError('Failed to delete webhook. Please reload.'); } @@ -206,6 +217,9 @@ export class WebhooksService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/events/${id}`); return HTTP.putVersioned(this.http, url, {}) + .do(() => { + this.analytics.trackEvent('Webhook', 'EventEnqueued', appName); + }) .pretifyError('Failed to enqueue webhook event. Please reload.'); } } \ No newline at end of file