Browse Source

History in dashboard.

pull/248/head
Sebastian Stehle 8 years ago
parent
commit
b4a6970d75
  1. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  2. 4
      src/Squidex/app/features/content/pages/content/content-history.component.html
  3. 38
      src/Squidex/app/features/content/pages/content/content-history.component.ts
  4. 3
      src/Squidex/app/features/dashboard/module.ts
  5. 19
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  6. 50
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss
  7. 23
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  8. 4
      src/Squidex/app/shared/components/history.component.html
  9. 38
      src/Squidex/app/shared/components/history.component.ts
  10. 2
      src/Squidex/app/shared/components/pipes.ts
  11. 43
      src/Squidex/app/shared/services/history.service.ts
  12. 2
      src/Squidex/app/shared/services/users-provider.service.ts

13
src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -66,9 +66,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count) public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count)
{ {
var historyEventEntities = List<MongoHistoryEventEntity> historyEventEntities;
if (!string.IsNullOrWhiteSpace(channelPrefix))
{
historyEventEntities =
await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count) await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
.ToListAsync(); .ToListAsync();
}
else
{
historyEventEntities =
await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
.ToListAsync();
}
return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList(); return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList();
} }

4
src/Squidex/app/features/content/pages/content/content-history.component.html

@ -13,11 +13,11 @@
<div class="panel-content panel-content-blank"> <div class="panel-content panel-content-blank">
<div *ngFor="let event of events | async" class="event"> <div *ngFor="let event of events | async" class="event">
<div class="event-left"> <div class="event-left">
<img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:'I'" [attr.src]="event.actor | sqxUserPictureRef" /> <img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:null" [attr.src]="event.actor | sqxUserPictureRef" />
</div> </div>
<div class="event-main"> <div class="event-main">
<div class="event-message"> <div class="event-message">
<span class="event-actor user-ref">{{event.actor | sqxUserNameRef:'I'}}</span> <span [innerHTML]="format(event.message) | async"></span> <span class="event-actor user-ref">{{event.actor | sqxUserNameRef:null}}</span> <span [innerHTML]="format(event.message) | async"></span>
</div> </div>
<div class="event-created">{{event.created | sqxFromNow}}</div> <div class="event-created">{{event.created | sqxFromNow}}</div>

38
src/Squidex/app/features/content/pages/content/content-history.component.ts

@ -11,6 +11,7 @@ import { Observable } from 'rxjs';
import { import {
allParams, allParams,
AppContext, AppContext,
formatHistoryMessage,
HistoryChannelUpdated, HistoryChannelUpdated,
HistoryEventDto, HistoryEventDto,
HistoryService, HistoryService,
@ -19,8 +20,6 @@ import {
import { ContentVersionSelected } from './../messages'; import { ContentVersionSelected } from './../messages';
const REPLACEMENT_TEMP = '$TEMP$';
@Component({ @Component({
selector: 'sqx-history', selector: 'sqx-history',
styleUrls: ['./content-history.component.scss'], styleUrls: ['./content-history.component.scss'],
@ -58,44 +57,11 @@ export class ContentHistoryComponent {
) { ) {
} }
private userName(userId: string): Observable<string> {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return this.users.getUser(parts[1], 'Me').map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
}
public loadVersion(version: number) { public loadVersion(version: number) {
this.ctx.bus.emit(new ContentVersionSelected(version)); this.ctx.bus.emit(new ContentVersionSelected(version));
} }
public format(message: string): Observable<string> { public format(message: string): Observable<string> {
let foundUserId: string | null = null; return formatHistoryMessage(message, this.users);
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => {
if (type === 'user') {
foundUserId = id;
return REPLACEMENT_TEMP;
} else {
return id;
}
});
message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => {
return `<span class="marker-ref">${marker}</span>`;
});
if (foundUserId) {
return this.userName(foundUserId).map(t => message.replace(REPLACEMENT_TEMP, `<span class="user-ref">${t}</span>`));
}
return Observable.of(message);
} }
} }

3
src/Squidex/app/features/dashboard/module.ts

@ -9,7 +9,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { ChartModule } from 'angular2-chartjs'; import { ChartModule } from 'angular2-chartjs';
import { SqxFrameworkModule } from 'shared'; import { SqxFrameworkModule, SqxSharedModule } from 'shared';
import { import {
DashboardPageComponent DashboardPageComponent
@ -26,6 +26,7 @@ const routes: Routes = [
imports: [ imports: [
ChartModule, ChartModule,
SqxFrameworkModule, SqxFrameworkModule,
SqxSharedModule,
RouterModule.forChild(routes) RouterModule.forChild(routes)
], ],
declarations: [ declarations: [

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

@ -111,5 +111,24 @@
<chart type="line" [data]="chartStorageSize" [options]="chartOptions"></chart> <chart type="line" [data]="chartStorageSize" [options]="chartOptions"></chart>
</div> </div>
</div> </div>
<div class="card card-lg">
<div class="card-header">
History
</div>
<div class="card-body card-history card-body-scroll">
<div *ngFor="let event of history" class="event">
<div class="event-left">
<img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:null" [attr.src]="event.actor | sqxUserPictureRef" />
</div>
<div class="event-main">
<div class="event-message">
<span class="event-actor user-ref">{{event.actor | sqxUserNameRef:null}}</span> <span [innerHTML]="format(event.message) | async"></span>
</div>
<div class="event-created">{{event.created | sqxFromNow}}</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>

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

@ -49,17 +49,16 @@
text-align: center; text-align: center;
} }
&-text { &-history {
color: $color-text-decent; min-height: 12.4rem;
font-weight: normal; max-height: 12.4rem;
font-size: .9rem; overflow-y: auto;
} }
&-more { &-text {
color: $color-text-decent; color: $color-text-decent;
font-weight: normal; font-weight: normal;
font-size: .8rem; font-size: .9rem;
margin-top: .4rem;
} }
&-title { &-title {
@ -109,3 +108,40 @@
.app-name { .app-name {
color: $color-title; color: $color-title;
} }
.event {
& {
@include flex-box;
margin-bottom: 1rem;
}
&-main {
@include flex-grow(1);
}
&-load {
& {
font-size: .9rem;
font-weight: normal;
cursor: pointer;
color: $color-theme-blue !important;
}
&:focus,
&:hover {
text-decoration: underline !important;
}
}
&-left {
min-width: 2.8rem;
max-width: 2.8rem;
margin-top: .3rem;
}
&-created {
font-size: .65rem;
font-weight: normal;
color: $color-text-decent;
}
}

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

@ -6,14 +6,18 @@
*/ */
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { import {
AppContext, AppContext,
AppDto, AppDto,
DateTime, DateTime,
fadeAnimation, fadeAnimation,
UsagesService formatHistoryMessage,
HistoryEventDto,
HistoryService,
UsagesService,
UsersProviderService
} from 'shared'; } from 'shared';
declare var _urq: any; declare var _urq: any;
@ -60,6 +64,8 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
maintainAspectRatio: false maintainAspectRatio: false
}; };
public history: HistoryEventDto[] = [];
public assetsCurrent = 0; public assetsCurrent = 0;
public assetsMax = 0; public assetsMax = 0;
@ -67,6 +73,8 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
public callsMax = 0; public callsMax = 0;
constructor(public readonly ctx: AppContext, constructor(public readonly ctx: AppContext,
private readonly historyService: HistoryService,
private readonly users: UsersProviderService,
private readonly usagesService: UsagesService private readonly usagesService: UsagesService
) { ) {
} }
@ -96,6 +104,13 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
this.callsMax = dto.maxAllowed; this.callsMax = dto.maxAllowed;
})); }));
this.subscriptions.push(
this.app
.switchMap(app => this.historyService.getHistory(app.name, ''))
.subscribe(dto => {
this.history = dto;
}));
this.subscriptions.push( this.subscriptions.push(
this.app this.app
.switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today())) .switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today()))
@ -163,6 +178,10 @@ export class DashboardPageComponent implements OnDestroy, OnInit {
})); }));
} }
public format(message: string): Observable<string> {
return formatHistoryMessage(message, this.users);
}
public showForum() { public showForum() {
_urq.push(['Feedback_Open']); _urq.push(['Feedback_Open']);
} }

4
src/Squidex/app/shared/components/history.component.html

@ -13,11 +13,11 @@
<div class="panel-content panel-content-blank"> <div class="panel-content panel-content-blank">
<div *ngFor="let event of events | async" class="event"> <div *ngFor="let event of events | async" class="event">
<div class="event-left"> <div class="event-left">
<img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:'I'" [attr.src]="event.actor | sqxUserPictureRef" /> <img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:null" [attr.src]="event.actor | sqxUserPictureRef" />
</div> </div>
<div class="event-main"> <div class="event-main">
<div class="event-message"> <div class="event-message">
<span class="event-actor user-ref">{{event.actor | sqxUserNameRef:'I'}}</span> <span [innerHTML]="format(event.message) | async"></span> <span class="event-actor user-ref">{{event.actor | sqxUserNameRef:null}}</span> <span [innerHTML]="format(event.message) | async"></span>
</div> </div>
<div class="event-created">{{event.created | sqxFromNow}}</div> <div class="event-created">{{event.created | sqxFromNow}}</div>
</div> </div>

38
src/Squidex/app/shared/components/history.component.ts

@ -12,14 +12,13 @@ import { AppContext } from './app-context';
import { import {
allParams, allParams,
formatHistoryMessage,
HistoryChannelUpdated, HistoryChannelUpdated,
HistoryEventDto, HistoryEventDto,
HistoryService, HistoryService,
UsersProviderService UsersProviderService
} from './../declarations-base'; } from './../declarations-base';
const REPLACEMENT_TEMP = '$TEMP$';
@Component({ @Component({
selector: 'sqx-history', selector: 'sqx-history',
styleUrls: ['./history.component.scss'], styleUrls: ['./history.component.scss'],
@ -57,40 +56,7 @@ export class HistoryComponent {
) { ) {
} }
private userName(userId: string): Observable<string> {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return this.users.getUser(parts[1], 'Me').map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
}
public format(message: string): Observable<string> { public format(message: string): Observable<string> {
let foundUserId: string | null = null; return formatHistoryMessage(message, this.users);
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => {
if (type === 'user') {
foundUserId = id;
return REPLACEMENT_TEMP;
} else {
return id;
}
});
message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => {
return `<span class="marker-ref">${marker}</span>`;
});
if (foundUserId) {
return this.userName(foundUserId).map(t => message.replace(REPLACEMENT_TEMP, `<span class="user-ref">${t}</span>`));
}
return Observable.of(message);
} }
} }

2
src/Squidex/app/shared/components/pipes.ts

@ -71,7 +71,7 @@ export class UserNameRefPipe extends UserAsyncPipe implements PipeTransform {
super(users, changeDetector); super(users, changeDetector);
} }
public transform(userId: string, placeholder = 'Me'): string | null { public transform(userId: string, placeholder: string | null = 'Me'): string | null {
return super.transformInternal(userId, users => { return super.transformInternal(userId, users => {
const parts = userId.split(':'); const parts = userId.split(':');

43
src/Squidex/app/shared/services/history.service.ts

@ -11,6 +11,8 @@ import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { UsersProviderService } from './users-provider.service';
import { import {
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
@ -28,6 +30,47 @@ export class HistoryEventDto {
} }
} }
const REPLACEMENT_TEMP = '$TEMP$';
export function formatHistoryMessage(message: string, users: UsersProviderService): Observable<string> {
const userName = (userId: string) => {
const parts = userId.split(':');
if (parts.length === 1) {
return users.getUser(parts[0], null).map(u => u.displayName);
} else if (parts[0] === 'subject') {
return users.getUser(parts[1], null).map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
};
let foundUserId: string | null = null;
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => {
if (type === 'user') {
foundUserId = id;
return REPLACEMENT_TEMP;
} else {
return id;
}
});
message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => {
return `<span class="marker-ref">${marker}</span>`;
});
if (foundUserId) {
return userName(foundUserId).map(t => message.replace(REPLACEMENT_TEMP, `<span class="user-ref">${t}</span>`));
}
return Observable.of(message);
}
@Injectable() @Injectable()
export class HistoryService { export class HistoryService {
constructor( constructor(

2
src/Squidex/app/shared/services/users-provider.service.ts

@ -22,7 +22,7 @@ export class UsersProviderService {
) { ) {
} }
public getUser(id: string, me = 'Me'): Observable<UserDto> { public getUser(id: string, me: string | null = 'Me'): Observable<UserDto> {
let result = this.caches[id]; let result = this.caches[id];
if (!result) { if (!result) {

Loading…
Cancel
Save