Browse Source

Another attempt to download logs.

pull/344/head
Sebastian Stehle 7 years ago
parent
commit
6f00f440ff
  1. 6
      src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs
  3. 17
      src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs
  4. 40
      src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
  5. 4
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  6. 4
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
  7. 13
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  8. 8
      src/Squidex/app/shared/services/usages.service.spec.ts
  9. 7
      src/Squidex/app/shared/services/usages.service.ts
  10. 2
      src/Squidex/package.json

6
src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs

@ -24,11 +24,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.logStore = logStore; this.logStore = logStore;
} }
public Task ReadLogAsync(IAppEntity app, DateTime from, DateTime to, Stream stream) public Task ReadLogAsync(string appId, DateTime from, DateTime to, Stream stream)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(appId, nameof(appId));
return logStore.ReadLogAsync(app.Id.ToString(), from, to, stream); return logStore.ReadLogAsync(appId, from, to, stream);
} }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/IAppLogStore.cs

@ -13,6 +13,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppLogStore public interface IAppLogStore
{ {
Task ReadLogAsync(IAppEntity app, DateTime from, DateTime to, Stream stream); Task ReadLogAsync(string appId, DateTime from, DateTime to, Stream stream);
} }
} }

17
src/Squidex/Areas/Api/Controllers/Statistics/Models/LogDownloadDto.cs

@ -0,0 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Areas.Api.Controllers.Statistics.Models
{
public sealed class LogDownloadDto
{
/// <summary>
/// The url to download the log.
/// </summary>
public string DownloadUrl { get; set; }
}
}

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

@ -9,8 +9,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Squidex.Areas.Api.Controllers.Statistics.Models; using Squidex.Areas.Api.Controllers.Statistics.Models;
using Squidex.Config;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
@ -31,13 +34,17 @@ namespace Squidex.Areas.Api.Controllers.Statistics
private readonly IAppLogStore appLogStore; private readonly IAppLogStore appLogStore;
private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlansProvider appPlansProvider;
private readonly IAssetUsageTracker assetStatsRepository; private readonly IAssetUsageTracker assetStatsRepository;
private readonly IDataProtector dataProtector;
private readonly MyUrlsOptions urlsOptions;
public UsagesController( public UsagesController(
ICommandBus commandBus, ICommandBus commandBus,
IUsageTracker usageTracker, IUsageTracker usageTracker,
IAppLogStore appLogStore, IAppLogStore appLogStore,
IAppPlansProvider appPlansProvider, IAppPlansProvider appPlansProvider,
IAssetUsageTracker assetStatsRepository) IAssetUsageTracker assetStatsRepository,
IDataProtectionProvider dataProtection,
IOptions<MyUrlsOptions> urlsOptions)
: base(commandBus) : base(commandBus)
{ {
this.usageTracker = usageTracker; this.usageTracker = usageTracker;
@ -45,6 +52,9 @@ namespace Squidex.Areas.Api.Controllers.Statistics
this.appLogStore = appLogStore; this.appLogStore = appLogStore;
this.appPlansProvider = appPlansProvider; this.appPlansProvider = appPlansProvider;
this.assetStatsRepository = assetStatsRepository; this.assetStatsRepository = assetStatsRepository;
this.urlsOptions = urlsOptions.Value;
dataProtector = dataProtection.CreateProtector("LogToken");
} }
/// <summary> /// <summary>
@ -57,16 +67,40 @@ namespace Squidex.Areas.Api.Controllers.Statistics
/// </returns> /// </returns>
[HttpGet] [HttpGet]
[Route("apps/{app}/usages/log/")] [Route("apps/{app}/usages/log/")]
[ProducesResponseType(typeof(CurrentCallsDto), 200)] [ProducesResponseType(typeof(LogDownloadDto), 200)]
[ApiPermission(Permissions.AppCommon)] [ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetLog(string app) public IActionResult GetLog(string app)
{ {
var token = dataProtector.Protect(App.Id.ToString());
var url = urlsOptions.BuildUrl($"/api/apps/log/{token}/");
var response = new LogDownloadDto { DownloadUrl = url };
return Ok(response);
}
/// <summary>
/// Get api calls as log file.
/// </summary>
/// <param name="token">The token for the log file.</param>
/// <returns>
/// 200 => Usage tracking results returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("apps/log/{token}/")]
[ApiCosts(0)]
public IActionResult GetLogFile(string token)
{
var appId = dataProtector.Unprotect(token);
var today = DateTime.Today; var today = DateTime.Today;
return new FileCallbackResult("text/csv", $"Usage-{today:yyy-MM-dd}.csv", false, stream => return new FileCallbackResult("text/csv", $"Usage-{today:yyy-MM-dd}.csv", false, stream =>
{ {
return appLogStore.ReadLogAsync(App, today.AddDays(-30), today, stream); return appLogStore.ReadLogAsync(appId, today.AddDays(-30), today, stream);
}); });
} }

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

@ -73,9 +73,7 @@
API Calls API Calls
<div class="float-right"> <div class="float-right">
<img *ngIf="isLoadingLog" class="loader mr-2" src="/images/loader.gif" /> <a class="force" (click)="downloadLog()">
<a class="force" (click)="downloadLog()" [class.disabled]="isLoadingLog">
<small>Download Log</small> <small>Download Log</small>
</a> </a>
</div> </div>

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

@ -31,10 +31,6 @@
color: $color-text-decent; color: $color-text-decent;
} }
.loader {
height: 12px;
}
.card { .card {
& { & {
margin-right: 1rem; margin-right: 1rem;

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

@ -6,7 +6,6 @@
*/ */
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { saveAs } from 'file-saver';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators'; import { filter, map, switchMap } from 'rxjs/operators';
@ -99,8 +98,6 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
public callsCurrent = 0; public callsCurrent = 0;
public callsMax = 0; public callsMax = 0;
public isLoadingLog = false;
constructor( constructor(
public readonly appsState: AppsState, public readonly appsState: AppsState,
public readonly authState: AuthService, public readonly authState: AuthService,
@ -211,15 +208,9 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
} }
public downloadLog() { public downloadLog() {
this.isLoadingLog = true;
this.usagesService.getLog(this.appsState.appName) this.usagesService.getLog(this.appsState.appName)
.subscribe(buffer => { .subscribe(url => {
saveAs(buffer, 'Log.csv'); window.open(url, '_blank');
}, () => {
this.isLoadingLog = false;
}, () => {
this.isLoadingLog = false;
}); });
} }
} }

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

@ -147,10 +147,10 @@ describe('UsagesService', () => {
it('should make get request to get log', it('should make get request to get log',
inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => { inject([UsagesService, HttpTestingController], (usagesService: UsagesService, httpMock: HttpTestingController) => {
let blob: Blob; let url: string;
usagesService.getLog('my-app').subscribe(result => { usagesService.getLog('my-app').subscribe(result => {
blob = result; url = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/log'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/usages/log');
@ -158,8 +158,8 @@ describe('UsagesService', () => {
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush(new Blob([])); req.flush({ downloadUrl: 'download/url' });
expect(blob!).toBeDefined(); expect(url!).toEqual('download/url');
})); }));
}); });

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

@ -59,10 +59,13 @@ export class UsagesService {
) { ) {
} }
public getLog(app: string): Observable<Blob> { public getLog(app: string): Observable<string> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/log`); const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/log`);
return this.http.get(url, { responseType: 'blob' }).pipe( return this.http.get<any>(url).pipe(
map(response => {
return response.downloadUrl;
}),
pretifyError('Failed to load monthly api calls. Please reload.')); pretifyError('Failed to load monthly api calls. Please reload.'));
} }

2
src/Squidex/package.json

@ -28,7 +28,6 @@
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "4.2.1", "bootstrap": "4.2.1",
"core-js": "2.6.3", "core-js": "2.6.3",
"file-saver": "2.0.0",
"graphiql": "0.12.0", "graphiql": "0.12.0",
"graphql": "14.1.1", "graphql": "14.1.1",
"marked": "0.6.0", "marked": "0.6.0",
@ -52,7 +51,6 @@
"@angular/compiler-cli": "7.2.2", "@angular/compiler-cli": "7.2.2",
"@ngtools/webpack": "7.2.3", "@ngtools/webpack": "7.2.3",
"@types/core-js": "2.5.0", "@types/core-js": "2.5.0",
"@types/file-saver": "2.0.0",
"@types/jasmine": "3.3.7", "@types/jasmine": "3.3.7",
"@types/marked": "0.6.0", "@types/marked": "0.6.0",
"@types/mousetrap": "1.6", "@types/mousetrap": "1.6",

Loading…
Cancel
Save