Files
zitadel/console/src/app/modules/changes/changes.component.ts
T
Max PeintnerGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Livio Amstutz
56b527060b fix(console): table pagination, diverse optimizations, lint workspace (#1563)
* show app redirect issues, action layout

* refactor pagination

* tos, privacy

* new paginator everywhere

* layout width and table optim

* lint pt1

* lint config, ts

* stylelint

* chore(deps-dev): bump @angular/cli from 11.2.7 to 11.2.8 in /console (#1566)

* fix: remove app name check on ChangeAPIApplication (#1561)

* fix: remove app name check on ChangeAPIApplication

* fix APIConfigInvalid message

* chore(workflow): ignore tags for docs and codecov flows (#1564)

* chore(deps-dev): bump @angular/cli from 11.2.7 to 11.2.8 in /console

Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.2.7 to 11.2.8.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Commits](https://github.com/angular/angular-cli/compare/v11.2.7...v11.2.8)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump jasmine-spec-reporter from 6.0.0 to 7.0.0 in /console (#1573)

* fix: remove app name check on ChangeAPIApplication (#1561)

* fix: remove app name check on ChangeAPIApplication

* fix APIConfigInvalid message

* chore(workflow): ignore tags for docs and codecov flows (#1564)

* chore(deps-dev): bump jasmine-spec-reporter in /console

Bumps [jasmine-spec-reporter](https://github.com/bcaudan/jasmine-spec-reporter) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/bcaudan/jasmine-spec-reporter/releases)
- [Changelog](https://github.com/bcaudan/jasmine-spec-reporter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bcaudan/jasmine-spec-reporter/compare/v6.0.0...v7.0.0)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump @angular-devkit/build-angular from 0.1102.7 to 0.1102.8 in /console (#1574)

* fix: remove app name check on ChangeAPIApplication (#1561)

* fix: remove app name check on ChangeAPIApplication

* fix APIConfigInvalid message

* chore(workflow): ignore tags for docs and codecov flows (#1564)

* chore(deps-dev): bump @angular-devkit/build-angular in /console

Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1102.7 to 0.1102.8.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Commits](https://github.com/angular/angular-cli/commits)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <[email protected]>

* chore(deps): bump @types/file-saver from 2.0.1 to 2.0.2 in /console (#1576)

* fix: remove app name check on ChangeAPIApplication (#1561)

* fix: remove app name check on ChangeAPIApplication

* fix APIConfigInvalid message

* chore(workflow): ignore tags for docs and codecov flows (#1564)

* chore(deps): bump @types/file-saver from 2.0.1 to 2.0.2 in /console

Bumps [@types/file-saver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/file-saver) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/file-saver)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump @angular/language-service from 11.2.8 to 11.2.9 in /console (#1567)

* fix: remove app name check on ChangeAPIApplication (#1561)

* fix: remove app name check on ChangeAPIApplication

* fix APIConfigInvalid message

* chore(workflow): ignore tags for docs and codecov flows (#1564)

* chore(deps-dev): bump @angular/language-service in /console

Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.2.8 to 11.2.9.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/11.2.9/packages/language-service)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* update protobuf, libphone

* chore(deps): bump rxjs from 6.6.3 to 6.6.7 in /console (#1488)

Bumps [rxjs](https://github.com/reactivex/rxjs) from 6.6.3 to 6.6.7.
- [Release notes](https://github.com/reactivex/rxjs/releases)
- [Changelog](https://github.com/ReactiveX/rxjs/blob/6.6.7/CHANGELOG.md)
- [Commits](https://github.com/reactivex/rxjs/compare/6.6.3...6.6.7)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump @types/jasmine from 3.6.3 to 3.6.9 in /console (#1485)

Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.3 to 3.6.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <[email protected]>

* chore(deps-dev): bump jasmine-core from 3.6.0 to 3.7.1 in /console (#1445)

Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 3.6.0 to 3.7.1.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v3.6.0...v3.7.1)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Livio Amstutz <[email protected]>
2021-04-15 15:54:10 +02:00

260 lines
9.4 KiB
TypeScript

import { KeyValue } from '@angular/common';
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
import { catchError, debounceTime, scan, take, takeUntil, tap } from 'rxjs/operators';
import { ListMyUserChangesResponse } from 'src/app/proto/generated/zitadel/auth_pb';
import { Change } from 'src/app/proto/generated/zitadel/change_pb';
import {
ListAppChangesResponse,
ListOrgChangesResponse,
ListProjectChangesResponse,
ListUserChangesResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
export enum ChangeType {
MYUSER = 'myuser',
USER = 'user',
ORG = 'org',
PROJECT = 'project',
APP = 'app',
}
export interface MappedChange {
key: string;
values: Array<{
data: any[];
dates: Timestamp.AsObject[];
editorId: string;
editorName: string;
eventTypes: Array<{ key: string; localizedMessage: string; }>;
sequences: number[];
}>;
}
type ListChanges = ListMyUserChangesResponse.AsObject |
ListUserChangesResponse.AsObject |
ListProjectChangesResponse.AsObject |
ListOrgChangesResponse.AsObject |
ListAppChangesResponse.AsObject;
@Component({
selector: 'app-changes',
templateUrl: './changes.component.html',
styleUrls: ['./changes.component.scss'],
})
export class ChangesComponent implements OnInit, OnDestroy {
@Input() public changeType: ChangeType = ChangeType.USER;
@Input() public id: string = '';
@Input() public secId: string = '';
@Input() public sortDirectionAsc: boolean = true;
@Input() public refresh!: Observable<void>;
public bottom: boolean = false;
private _done: BehaviorSubject<any> = new BehaviorSubject(false);
private _loading: BehaviorSubject<any> = new BehaviorSubject(false);
private _data: BehaviorSubject<any> = new BehaviorSubject([]);
loading: Observable<boolean> = this._loading.asObservable();
public data!: Observable<MappedChange[]>;
public changes!: ListChanges;
private destroyed$: Subject<void> = new Subject();
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {
}
ngOnInit(): void {
this.init();
if (this.refresh) {
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
this.init();
});
}
}
ngOnDestroy(): void {
this.destroyed$.next();
}
public scrollHandler(e: any): void {
if (e === 'bottom') {
this.more();
}
}
public init(): void {
let first: Promise<ListChanges>;
switch (this.changeType) {
case ChangeType.MYUSER: first = this.authUserService.listMyUserChanges(20, 0);
break;
case ChangeType.USER: first = this.mgmtUserService.listUserChanges(this.id, 20, 0);
break;
case ChangeType.PROJECT: first = this.mgmtUserService.listProjectChanges(this.id, 20, 0);
break;
case ChangeType.ORG: first = this.mgmtUserService.listOrgChanges(20, 0);
break;
case ChangeType.APP: first = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, 0);
break;
}
this.mapAndUpdate(first);
// Create the observable array for consumption in components
this.data = this._data.asObservable().pipe(
scan((acc, val) => {
return false ? val.concat(acc) : acc.concat(val);
}));
}
private more(): void {
const cursor = this.getCursor();
let more: Promise<ListChanges>;
switch (this.changeType) {
case ChangeType.MYUSER: more = this.authUserService.listMyUserChanges(20, cursor);
break;
case ChangeType.USER: more = this.mgmtUserService.listUserChanges(this.id, 20, cursor);
break;
case ChangeType.PROJECT: more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
break;
case ChangeType.ORG: more = this.mgmtUserService.listOrgChanges(20, cursor);
break;
case ChangeType.APP: more = this.mgmtUserService.listAppChanges(this.id, this.secId, 20, cursor);
break;
}
this.mapAndUpdate(more);
}
// Determines the snapshot to paginate query
private getCursor(): number {
const current = this._data.value;
if (current.length) {
const lastElementValues = current[current.length - 1].values;
const seq = lastElementValues[lastElementValues.length - 1].sequences;
return seq[seq.length - 1];
}
return 0;
}
// Maps the snapshot to usable format the updates source
private mapAndUpdate(col: Promise<ListChanges>): any {
if (this._done.value || this._loading.value) { return; }
// Map snapshot with doc ref (needed for cursor)
if (!this.bottom) {
// loading
this._loading.next(true);
return from(col).pipe(
take(1),
tap((res: ListChanges) => {
const values = res.resultList;
const mapped = this.mapChanges(values);
// update source with new values, done loading
// this._data.next(values);
this._data.next(mapped);
this._loading.next(false);
// no more values, mark done
if (!values.length) {
this._done.next(true);
}
}),
catchError(_ => {
this._loading.next(false);
this.bottom = true;
return of([]);
}),
).subscribe();
}
}
private mapChanges(changes: Change.AsObject[]): {
key: string; values: any[];
}[] {
const splitted: { [editorId: string]: any[]; } = {};
changes.forEach((change) => {
if (change.changeDate) {
const index = `${this.getDateString(change.changeDate)}`;
// `${this.getDateString(change.changeDate)}:${change.editorId}`;
if (index) {
if (splitted[index]) {
const userData: any = {
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
};
const lastIndex = splitted[index].length - 1;
if (lastIndex > -1 && splitted[index][lastIndex].editor === change.editorDisplayName) {
splitted[index][lastIndex].dates.push(change.changeDate);
// splitted[index][lastIndex].data.push(change.data);
splitted[index][lastIndex].eventTypes.push(change.eventType);
splitted[index][lastIndex].sequences.push(change.sequence);
} else {
splitted[index].push(userData);
}
} else {
splitted[index] = [
{
editor: change.editorDisplayName,
editorId: change.editorId,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],
eventTypes: [change.eventType],
sequences: [change.sequence],
},
];
}
}
}
});
const arr = Object.keys(splitted).map(key => {
return { key: key, values: splitted[key] };
});
arr.sort((a, b) => {
return parseFloat(b.key) - parseFloat(a.key);
});
return arr;
}
getDateString(ts: Timestamp.AsObject): string {
const date = new Date(ts.seconds * 1000 + ts.nanos / 1000 / 1000);
return date.getUTCFullYear() + this.pad(date.getUTCMonth() + 1) + this.pad(date.getUTCDate());
}
getTimestampIndex(date: any): number {
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000 / 1000);
return ts.getTime();
}
pad(n: number): string {
return n < 10 ? '0' + n : n.toString();
}
// Order by ascending property value
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.value.localeCompare(b.value);
}
// Order by descending property key
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
}
}