Browse Source

Track usage by category (in this case clients)

pull/315/head
Sebastian Stehle 7 years ago
parent
commit
288fb02d5a
  1. 2
      src/Squidex/Areas/Api/Controllers/Statistics/Models/CallsUsageDto.cs
  2. 5
      src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
  3. 3
      src/Squidex/Pipeline/ApiCostsFilter.cs
  4. 14
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  5. 14
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
  6. 94
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  7. 35
      src/Squidex/app/shared/services/usages.service.spec.ts
  8. 21
      src/Squidex/app/shared/services/usages.service.ts

2
src/Squidex/Areas/Api/Controllers/Statistics/Models/CallsUsageDto.cs

@ -27,7 +27,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics.Models
/// </summary>
public long AverageMs { get; set; }
public static CallsUsageDto FromUsage(StoredUsage usage)
public static CallsUsageDto FromUsage(DateUsage usage)
{
var averageMs = usage.TotalCount == 0 ? 0 : usage.TotalElapsedMs / usage.TotalCount;

5
src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@ -82,7 +83,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
/// </returns>
[HttpGet]
[Route("apps/{app}/usages/calls/{fromDate}/{toDate}/")]
[ProducesResponseType(typeof(CallsUsageDto[]), 200)]
[ProducesResponseType(typeof(Dictionary<string, CallsUsageDto[]>), 200)]
[ApiCosts(0)]
public async Task<IActionResult> GetUsages(string app, DateTime fromDate, DateTime toDate)
{
@ -93,7 +94,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
var entities = await usageTracker.QueryAsync(App.Id.ToString(), fromDate.Date, toDate.Date);
var response = entities.Select(CallsUsageDto.FromUsage);
var response = entities.ToDictionary(x => x.Key, x => x.Value.Select(CallsUsageDto.FromUsage).ToList());
return Ok(response);
}

3
src/Squidex/Pipeline/ApiCostsFilter.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Security;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Pipeline
@ -71,7 +72,7 @@ namespace Squidex.Pipeline
{
var elapsedMs = watch.Stop();
await usageTracker.TrackAsync(appFeature.App.Id.ToString(), FilterDefinition.Weight, elapsedMs);
await usageTracker.TrackAsync(appFeature.App.Id.ToString(), context.HttpContext.User.OpenIdClientId(), FilterDefinition.Weight, elapsedMs);
}
}
else

14
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -69,21 +69,24 @@
</a>
<div class="card card-lg">
<div class="card-header">API Calls</div>
<div class="card-body">
<chart type="bar" [data]="chartCallsCount" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card-lg">
<div class="card-header">API Performance (ms)</div>
<div class="card-body">
<chart type="bar" [data]="chartCallsPerformance" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card">
<div class="card-header">API Calls</div>
<div class="card-body">
<div class="aggregation" *ngIf="callsCurrent >= 0">
<div class="aggregation-label">API calls this month</div>
<div class="aggregation-label">This month</div>
<div class="aggregation-value">{{callsCurrent | sqxKNumber}}</div>
<div class="aggregation-label" *ngIf="callsMax > 0">Monthly limit: {{callsMax | sqxKNumber}}</div>
</div>
@ -91,15 +94,17 @@
</div>
<div class="card card-lg">
<div class="card-header">Assets Count</div>
<div class="card-body">
<chart type="line" [data]="chartStorageCount" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card">
<div class="card-header">Assets Size (MB)</div>
<div class="card-body">
<div class="aggregation" *ngIf="assetsCurrent >= 0">
<div class="aggregation-label">Asset size today</div>
<div class="aggregation-label">Total Size</div>
<div class="aggregation-value">{{assetsCurrent | sqxFileSize}}</div>
<div class="aggregation-label" *ngIf="assetsMax > 0">Total limit: {{assetsMax | sqxFileSize}}</div>
</div>
@ -107,15 +112,14 @@
</div>
<div class="card card-lg">
<div class="card-header">Assets Size (MB)</div>
<div class="card-body">
<chart type="line" [data]="chartStorageSize" [options]="chartOptions"></chart>
</div>
</div>
<div class="card card-lg">
<div class="card-header">
History
</div>
<div class="card-header">History</div>
<div class="card-body card-history card-body-scroll">
<sqx-history-list [events]="history"></sqx-history-list>
</div>

14
src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss

@ -20,7 +20,9 @@
}
:host /deep/ canvas {
height: 13rem !important;
height: 12rem !important;
margin-top: -1rem;
margin-bottom: 0;
}
.subtext {
@ -34,6 +36,8 @@
margin-right: 1rem;
margin-bottom: 1rem;
width: 16rem;
min-height: 16rem;
max-height: 16rem;
float: left;
}
@ -41,17 +45,11 @@
width: 33rem;
}
&-body {
min-height: 15.5rem;
}
&-image {
text-align: center;
}
&-history {
min-height: 12.4rem;
max-height: 12.4rem;
overflow-y: auto;
}
@ -100,7 +98,7 @@
&-value {
font-size: 3rem;
margin-top: 2rem;
margin-top: 1rem;
margin-bottom: .5rem;
}
}

94
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -20,6 +20,20 @@ import {
UsagesService
} from '@app/shared';
const COLORS = [
' 51, 137, 213',
'211, 50, 50',
'131, 211, 50',
' 50, 211, 131',
' 50, 211, 211',
' 50, 131, 211',
' 50, 50, 211',
' 50, 211, 50',
'131, 50, 211',
'211, 50, 211',
'211, 50, 131'
];
@Component({
selector: 'sqx-dashboard-page',
styleUrls: ['./dashboard-page.component.scss'],
@ -43,18 +57,16 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
public chartOptions = {
responsive: true,
scales: {
xAxes: [
{
display: true
}
],
yAxes: [
{
ticks: {
beginAtZero: true
}
}
]
xAxes: [{
display: true,
stacked: true
}],
yAxes: [{
ticks: {
beginAtZero: true
},
stacked: true
}]
},
maintainAspectRatio: false
};
@ -111,15 +123,17 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
this.app.pipe(
switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
.subscribe(dtos => {
const labels = createLabels(dtos);
this.chartStorageCount = {
labels: createLabels(dtos),
labels,
datasets: [
{
label: 'Number of Assets',
label: 'All',
lineTension: 0,
fill: false,
backgroundColor: 'rgba(51, 137, 213, 0.6)',
borderColor: 'rgba(51, 137, 213, 1)',
backgroundColor: `rgba(${COLORS[0]}, 0.6)`,
borderColor: `rgba(${COLORS[0]}, 1)`,
borderWidth: 1,
data: dtos.map(x => x.count)
}
@ -127,14 +141,14 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
};
this.chartStorageSize = {
labels: createLabels(dtos),
labels,
datasets: [
{
label: 'Size of Assets (MB)',
label: 'All',
lineTension: 0,
fill: false,
backgroundColor: 'rgba(51, 137, 213, 0.6)',
borderColor: 'rgba(51, 137, 213, 1)',
backgroundColor: `rgba(${COLORS[0]}, 0.6)`,
borderColor: `rgba(${COLORS[0]}, 1)`,
borderWidth: 1,
data: dtos.map(x => Math.round(10 * (x.size / (1024 * 1024))) / 10)
}
@ -146,36 +160,44 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
this.app.pipe(
switchMap(app => this.usagesService.getCallsUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
.subscribe(dtos => {
const labels = createLabelsFromSet(dtos);
this.chartCallsCount = {
labels: createLabels(dtos),
datasets: [
labels,
datasets: Object.keys(dtos).map((k, i) => (
{
label: 'Number of API Calls',
backgroundColor: 'rgba(51, 137, 213, 0.6)',
borderColor: 'rgba(51, 137, 213, 1)',
label: label(k),
backgroundColor: `rgba(${COLORS[i]}, 0.6)`,
borderColor: `rgba(${COLORS[i]}, 1)`,
borderWidth: 1,
data: dtos.map(x => x.count)
}
]
data: dtos[k].map(x => x.count)
}))
};
this.chartCallsPerformance = {
labels: createLabels(dtos),
datasets: [
labels,
datasets: Object.keys(dtos).map((k, i) => (
{
label: 'API Performance (Milliseconds)',
backgroundColor: 'rgba(51, 137, 213, 0.6)',
borderColor: 'rgba(51, 137, 213, 1)',
label: label(k),
backgroundColor: `rgba(${COLORS[i]}, 0.6)`,
borderColor: `rgba(${COLORS[i]}, 1)`,
borderWidth: 1,
data: dtos.map(x => x.averageMs)
}
]
data: dtos[k].map(x => x.averageMs)
}))
};
}));
}
}
function label(category: string) {
return category === '*' ? 'All Clients' : category;
}
function createLabels(dtos: { date: DateTime }[]): string[] {
return dtos.map(d => d.date.toStringFormat('M-DD'));
}
function createLabelsFromSet(dtos: { [category: string]: { date: DateTime }[] }): string[] {
return createLabels(dtos[Object.keys(dtos)[0]]);
}

35
src/Squidex/app/shared/services/usages.service.spec.ts

@ -38,7 +38,7 @@ describe('UsagesService', () => {
it('should make get request to get calls usages',
inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
let usages: CallsUsageDto[];
let usages: { [category: string]: CallsUsageDto[] };
usagesService.getCallsUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
usages = result;
@ -49,24 +49,27 @@ describe('UsagesService', () => {
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([
{
date: '2017-10-12',
count: 10,
averageMs: 130
},
{
date: '2017-10-13',
count: 13,
averageMs: 170
}
]);
req.flush({
category1: [
{
date: '2017-10-12',
count: 10,
averageMs: 130
},
{
date: '2017-10-13',
count: 13,
averageMs: 170
}
]
});
expect(usages!).toEqual(
[
expect(usages!).toEqual({
category1: [
new CallsUsageDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
new CallsUsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
]);
]
});
}));
it('should make get request to get month calls',

21
src/Squidex/app/shared/services/usages.service.ts

@ -83,21 +83,24 @@ export class UsagesService {
pretifyError('Failed to load todays storage size. Please reload.'));
}
public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<CallsUsageDto[]> {
public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<{ [category: string]: CallsUsageDto[] }> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
const items: any[] = body;
return items.map(item => {
return new CallsUsageDto(
DateTime.parseISO_UTC(item.date),
item.count,
item.averageMs);
});
const result: { [category: string]: CallsUsageDto[] } = {};
for (let category of Object.keys(body)) {
result[category] = body[category].map((item: any) => {
return new CallsUsageDto(
DateTime.parseISO_UTC(item.date),
item.count,
item.averageMs);
});
}
return result;
}),
pretifyError('Failed to load calls usage. Please reload.'));
}

Loading…
Cancel
Save