From d2be2ef4c5e9f44e99db45d8230743280f06d57a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 3 May 2017 22:34:41 +0200 Subject: [PATCH] API calls in UI --- .../Api/Apps/AppUsageController.cs | 4 +- src/Squidex/app/features/dashboard/module.ts | 2 + .../pages/dashboard-page.component.html | 16 ++++- .../pages/dashboard-page.component.scss | 11 +++- .../pages/dashboard-page.component.ts | 43 ++++++++++++- src/Squidex/app/shared/declarations-base.ts | 1 + src/Squidex/app/shared/module.ts | 2 + .../shared/services/usages.service.spec.ts | 62 +++++++++++++++++++ .../app/shared/services/usages.service.ts | 50 +++++++++++++++ src/Squidex/package.json | 1 + .../BackgroundUsageTrackerTests.cs | 4 +- 11 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 src/Squidex/app/shared/services/usages.service.spec.ts create mode 100644 src/Squidex/app/shared/services/usages.service.ts diff --git a/src/Squidex/Controllers/Api/Apps/AppUsageController.cs b/src/Squidex/Controllers/Api/Apps/AppUsageController.cs index 2da1751e9..16c6b8004 100644 --- a/src/Squidex/Controllers/Api/Apps/AppUsageController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppUsageController.cs @@ -58,11 +58,11 @@ namespace Squidex.Controllers.Api.Apps return BadRequest(); } - var entities = await usageTracker.FindAsync(App.Id.ToString(), fromDate, toDate); + var entities = await usageTracker.FindAsync(App.Id.ToString(), fromDate.Date, toDate.Date); var models = entities.Select(x => { - var averageMs = x.TotalElapsedMs / x.TotalCount; + var averageMs = x.TotalCount == 0 ? 0 : x.TotalElapsedMs / x.TotalCount; return new UsageDto { Date = x.Date, Count = x.TotalCount, AverageMs = averageMs }; }).ToList(); diff --git a/src/Squidex/app/features/dashboard/module.ts b/src/Squidex/app/features/dashboard/module.ts index 1126b92d3..c201bb375 100644 --- a/src/Squidex/app/features/dashboard/module.ts +++ b/src/Squidex/app/features/dashboard/module.ts @@ -7,6 +7,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { ChartModule } from 'angular2-chartjs'; import { SqxFrameworkModule } from 'shared'; @@ -23,6 +24,7 @@ const routes: Routes = [ @NgModule({ imports: [ + ChartModule, SqxFrameworkModule, RouterModule.forChild(routes) ], diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html index 3863512bd..f7f8e16b1 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html @@ -9,7 +9,7 @@ -
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss index 76eb1450b..5470b902d 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss @@ -21,9 +21,14 @@ h1 { color: $color-title; } +a { + &.card { + cursor: pointer; + } +} + .card { & { - cursor: pointer; margin-right: 1rem; margin-bottom: 1rem; width: 16rem; @@ -43,6 +48,10 @@ h1 { color: $color-title; } + &-big { + width: 33rem; + } + &-block { min-height: 14.5rem; } diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts index 4f2fe8081..0f61ca3c9 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts @@ -12,8 +12,10 @@ import { AppComponentBase, AppsStoreService, AuthService, + DateTime, fadeAnimation, - NotificationService + NotificationService, + UsagesService } from 'shared'; declare var _urq: any; @@ -31,8 +33,13 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit, public profileDisplayName = ''; + public chartCount: any; + public chartPerformance: any; + public chartOptions = { }; + constructor(apps: AppsStoreService, notifications: NotificationService, - private readonly auth: AuthService + private readonly auth: AuthService, + private readonly usagesService: UsagesService ) { super(notifications, apps); } @@ -42,6 +49,38 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit, } public ngOnInit() { + this.appName() + .switchMap(app => this.usagesService.getUsages(app, DateTime.today().addDays(-30), DateTime.today())) + .subscribe(dtos => { + const usages: any[] = dtos.map(x => { return { date: x.date.toStringFormat('L'), count: x.count, averageMs: x.averageMs }; }); + + this.chartCount = { + labels: usages.map(x => x.date), + datasets: [ + { + label: 'Number of API Calls', + backgroundColor: 'rgba(61, 135, 213, 0.6)', + borderColor: 'rgba(61, 135, 213, 1)', + borderWidth: 1, + data: usages.map(x => x.count) + } + ] + }; + + this.chartPerformance = { + labels: usages.map(x => x.date), + datasets: [ + { + label: 'API Performance (Milliseconds)', + backgroundColor: 'rgba(61, 135, 213, 0.6)', + borderColor: 'rgba(61, 135, 213, 1)', + borderWidth: 1, + data: usages.map(x => x.averageMs) + } + ] + }; + }); + this.authenticationSubscription = this.auth.isAuthenticated.subscribe(() => { const user = this.auth.user; diff --git a/src/Squidex/app/shared/declarations-base.ts b/src/Squidex/app/shared/declarations-base.ts index 7c45966bc..323dfac29 100644 --- a/src/Squidex/app/shared/declarations-base.ts +++ b/src/Squidex/app/shared/declarations-base.ts @@ -26,6 +26,7 @@ export * from './services/help.service'; export * from './services/history.service'; export * from './services/languages.service'; export * from './services/schemas.service'; +export * from './services/usages.service'; export * from './services/users-provider.service'; export * from './services/users.service'; diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index c2f2a39f1..feceeadae 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -40,6 +40,7 @@ import { ResolvePublishedSchemaGuard, ResolveSchemaGuard, SchemasService, + UsagesService, UserEmailPipe, UserEmailRefPipe, UserNamePipe, @@ -113,6 +114,7 @@ export class SqxSharedModule { ResolvePublishedSchemaGuard, ResolveSchemaGuard, SchemasService, + UsagesService, UserManagementService, UsersProviderService, UsersService diff --git a/src/Squidex/app/shared/services/usages.service.spec.ts b/src/Squidex/app/shared/services/usages.service.spec.ts new file mode 100644 index 000000000..ad55ad324 --- /dev/null +++ b/src/Squidex/app/shared/services/usages.service.spec.ts @@ -0,0 +1,62 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Response, ResponseOptions } from '@angular/http'; +import { Observable } from 'rxjs'; +import { IMock, Mock, Times } from 'typemoq'; + +import { + ApiUrlConfig, + AuthService, + DateTime, + UsageDto, + UsagesService +} from './../'; + +describe('UsagesService', () => { + let authService: IMock; + let usagesService: UsagesService; + + beforeEach(() => { + authService = Mock.ofType(AuthService); + usagesService = new UsagesService(authService.object, new ApiUrlConfig('http://service/p/')); + }); + + it('should make get request to get usages', () => { + authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/2017-10-12/2017-10-13')) + .returns(() => Observable.of( + new Response( + new ResponseOptions({ + body: [{ + date: '2017-10-12', + count: 1, + averageMs: 130 + }, { + date: '2017-10-13', + count: 13, + averageMs: 170 + }] + }) + ) + )) + .verifiable(Times.once()); + + let usages: UsageDto[] | null = null; + + usagesService.getUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => { + usages = result; + }).unsubscribe(); + + expect(usages).toEqual( + [ + new UsageDto(DateTime.parseISO_UTC('2017-10-12'), 1, 130), + new UsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170) + ]); + + authService.verifyAll(); + }); +}); \ No newline at end of file diff --git a/src/Squidex/app/shared/services/usages.service.ts b/src/Squidex/app/shared/services/usages.service.ts new file mode 100644 index 000000000..360fe11dd --- /dev/null +++ b/src/Squidex/app/shared/services/usages.service.ts @@ -0,0 +1,50 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +import 'framework/angular/http-extensions'; + +import { ApiUrlConfig, DateTime } from 'framework'; +import { AuthService } from './auth.service'; + +export class UsageDto { + constructor( + public readonly date: DateTime, + public readonly count: number, + public readonly averageMs: number + ) { + } +} + +@Injectable() +export class UsagesService { + constructor( + private readonly authService: AuthService, + private readonly apiUrl: ApiUrlConfig + ) { + } + + public getUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/${fromDate.toStringFormat('YYYY-MM-DD')}/${toDate.toStringFormat('YYYY-MM-DD')}`); + + return this.authService.authGet(url) + .map(response => response.json()) + .map(response => { + const items: any[] = response; + + return items.map(item => { + return new UsageDto( + DateTime.parseISO_UTC(item.date), + item.count, + item.averageMs); + }); + }) + .catchError('Failed to load usage. Please reload.'); + } +} \ No newline at end of file diff --git a/src/Squidex/package.json b/src/Squidex/package.json index 333b83182..f8c62af5f 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -46,6 +46,7 @@ "@types/jasmine": "2.5.43", "@types/mousetrap": "1.5.33", "@types/node": "7.0.5", + "angular2-chartjs": "^0.2.0", "angular2-router-loader": "0.3.5", "angular2-template-loader": "0.6.2", "awesome-typescript-loader": "3.1.3", diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs index ef679af3b..3f145fb0b 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.UsageTracking new StoredUsage(dateFrom.AddDays(1), 10, 15), new StoredUsage(dateFrom.AddDays(3), 13, 18), new StoredUsage(dateFrom.AddDays(5), 15, 20), - new StoredUsage(dateFrom.AddDays(7), 17, 22), + new StoredUsage(dateFrom.AddDays(7), 17, 22) }; usageStore.Setup(x => x.FindAsync("key", dateFrom, dateTo)).Returns(Task.FromResult(originalDate)); @@ -69,7 +69,7 @@ namespace Squidex.Infrastructure.UsageTracking new StoredUsage(dateFrom.AddDays(4), 00, 00), new StoredUsage(dateFrom.AddDays(5), 15, 20), new StoredUsage(dateFrom.AddDays(6), 00, 00), - new StoredUsage(dateFrom.AddDays(7), 17, 22), + new StoredUsage(dateFrom.AddDays(7), 17, 22) }); }