thingsboard/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts

363 lines
12 KiB
TypeScript
Raw Normal View History

2022-05-25 12:26:23 +03:00
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
AfterViewInit,
ChangeDetectorRef,
Component,
2022-05-26 13:50:50 +03:00
ElementRef, EventEmitter,
2022-05-25 12:26:23 +03:00
Input,
OnDestroy,
2022-05-26 13:50:50 +03:00
OnInit, Output, Renderer2,
ViewChild, ViewContainerRef
2022-05-25 12:26:23 +03:00
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
2022-05-26 14:27:22 +03:00
import { EntityId, entityIdEquals } from '@shared/models/id/entity-id';
2022-05-25 12:26:23 +03:00
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, fromEvent, merge, Observable, of, ReplaySubject } from 'rxjs';
2022-05-25 12:26:23 +03:00
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { PageLink } from '@shared/models/page/page-link';
import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
2022-05-26 13:50:50 +03:00
import { EntityVersion, VersionCreationResult, VersionLoadResult } from '@shared/models/vc.models';
2022-05-25 12:26:23 +03:00
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { ResizeObserver } from '@juggle/resize-observer';
import { hidePageSizePixelValue } from '@shared/models/constants';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autocomplete.component';
import { isNotEmptyStr } from '@core/utils';
import { TbPopoverService } from '@shared/components/popover.service';
import { EntityVersionExportComponent } from '@home/components/vc/entity-version-export.component';
import { MatButton } from '@angular/material/button';
import { TbPopoverComponent } from '@shared/components/popover.component';
2022-05-26 13:50:50 +03:00
import { EntityVersionRestoreComponent } from '@home/components/vc/entity-version-restore.component';
2022-05-25 12:26:23 +03:00
@Component({
selector: 'tb-entity-versions-table',
templateUrl: './entity-versions-table.component.html',
styleUrls: ['./entity-versions-table.component.scss']
})
export class EntityVersionsTableComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('branchAutocompleteComponent') branchAutocompleteComponent: BranchAutocompleteComponent;
@Input()
singleEntityMode = false;
2022-05-26 13:50:50 +03:00
displayedColumns = ['timestamp', 'id', 'name', 'actions'];
2022-05-25 12:26:23 +03:00
pageLink: PageLink;
textSearchMode = false;
2022-05-25 12:26:23 +03:00
dataSource: EntityVersionsDatasource;
hidePageSize = false;
branch: string = null;
activeValue = false;
dirtyValue = false;
externalEntityIdValue: EntityId;
viewsInited = false;
private componentResize$: ResizeObserver;
@Input()
set active(active: boolean) {
if (this.activeValue !== active) {
this.activeValue = active;
if (this.activeValue && this.dirtyValue) {
this.dirtyValue = false;
if (this.viewsInited) {
this.initFromDefaultBranch();
}
}
}
}
@Input()
set externalEntityId(externalEntityId: EntityId) {
2022-05-26 14:27:22 +03:00
if (!entityIdEquals(this.externalEntityIdValue, externalEntityId)) {
2022-05-25 12:26:23 +03:00
this.externalEntityIdValue = externalEntityId;
this.resetSortAndFilter(this.activeValue);
if (!this.activeValue) {
this.dirtyValue = true;
}
}
}
@Input()
entityId: EntityId;
2022-05-26 13:50:50 +03:00
@Output()
versionRestored = new EventEmitter<void>();
@ViewChild('searchInput') searchInputField: ElementRef;
2022-05-25 12:26:23 +03:00
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private popoverService: TbPopoverService,
private renderer: Renderer2,
2022-05-25 12:26:23 +03:00
private cd: ChangeDetectorRef,
private viewContainerRef: ViewContainerRef,
2022-05-25 12:26:23 +03:00
private elementRef: ElementRef) {
super(store);
this.dirtyValue = !this.activeValue;
const sortOrder: SortOrder = { property: 'timestamp', direction: Direction.DESC };
this.pageLink = new PageLink(10, 0, null, sortOrder);
this.dataSource = new EntityVersionsDatasource(this.entitiesVersionControlService);
}
ngOnInit() {
this.componentResize$ = new ResizeObserver(() => {
const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue;
if (showHidePageSize !== this.hidePageSize) {
this.hidePageSize = showHidePageSize;
this.cd.markForCheck();
}
});
this.componentResize$.observe(this.elementRef.nativeElement);
}
ngOnDestroy() {
if (this.componentResize$) {
this.componentResize$.disconnect();
}
}
branchChanged(newBranch: string) {
if (isNotEmptyStr(newBranch) && this.branch !== newBranch) {
this.branch = newBranch;
this.paginator.pageIndex = 0;
if (this.activeValue) {
this.updateData();
}
2022-05-25 12:26:23 +03:00
}
}
ngAfterViewInit() {
fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe(
debounceTime(400),
distinctUntilChanged(),
tap(() => {
this.paginator.pageIndex = 0;
this.updateData();
})
)
.subscribe();
2022-05-25 12:26:23 +03:00
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.updateData())
)
.subscribe();
this.viewsInited = true;
if (!this.singleEntityMode || (this.activeValue && this.externalEntityIdValue)) {
2022-05-25 12:26:23 +03:00
this.initFromDefaultBranch();
}
}
toggleVcExport($event: Event, exportButton: MatButton) {
2022-05-25 12:26:23 +03:00
if ($event) {
$event.stopPropagation();
}
const trigger = exportButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
2022-05-26 13:50:50 +03:00
const vcExportPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionExportComponent, 'left', true, null,
{
branch: this.branch,
entityId: this.entityId,
onClose: (result: VersionCreationResult | null, branch: string | null) => {
2022-05-26 13:50:50 +03:00
vcExportPopover.hide();
if (result) {
if (this.branch !== branch) {
this.branchChanged(branch);
} else {
this.updateData();
}
}
2022-05-26 13:50:50 +03:00
},
onContentUpdated: () => {
vcExportPopover.updatePosition();
setTimeout(() => {
vcExportPopover.updatePosition();
});
}
}, {}, {}, {}, false);
}
}
toggleRestoreEntityVersion($event: Event, restoreVersionButton: MatButton, entityVersion: EntityVersion) {
if ($event) {
$event.stopPropagation();
}
const trigger = restoreVersionButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionRestoreComponent, 'left', true, null,
{
branch: this.branch,
versionName: entityVersion.name,
versionId: entityVersion.id,
externalEntityId: this.externalEntityIdValue,
onClose: (result: Array<VersionLoadResult> | null) => {
restoreVersionPopover.hide();
if (result && result.length) {
this.versionRestored.emit();
}
}
}, {}, {}, {}, false);
}
2022-05-25 12:26:23 +03:00
}
versionIdContent(entityVersion: EntityVersion): string {
let versionId = entityVersion.id;
if (versionId.length > 7) {
versionId = versionId.slice(0, 7);
}
return versionId;
}
enterFilterMode() {
this.textSearchMode = true;
this.pageLink.textSearch = '';
setTimeout(() => {
this.searchInputField.nativeElement.focus();
this.searchInputField.nativeElement.setSelectionRange(0, 0);
}, 10);
}
exitFilterMode() {
this.textSearchMode = false;
this.pageLink.textSearch = null;
this.paginator.pageIndex = 0;
this.updateData();
}
2022-05-25 12:26:23 +03:00
private initFromDefaultBranch() {
if (this.branchAutocompleteComponent.isDefaultBranchSelected()) {
this.paginator.pageIndex = 0;
if (this.activeValue) {
this.updateData();
}
} else {
this.branchAutocompleteComponent.selectDefaultBranchIfNeeded(true);
}
2022-05-25 12:26:23 +03:00
}
private updateData() {
this.pageLink.page = this.paginator.pageIndex;
this.pageLink.pageSize = this.paginator.pageSize;
this.pageLink.sortOrder.property = this.sort.active;
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
this.dataSource.loadEntityVersions(this.singleEntityMode, this.branch, this.externalEntityIdValue, this.pageLink);
}
private resetSortAndFilter(update: boolean) {
this.textSearchMode = false;
2022-05-25 12:26:23 +03:00
this.pageLink.textSearch = null;
if (this.viewsInited) {
this.paginator.pageIndex = 0;
const sortable = this.sort.sortables.get('timestamp');
this.sort.active = sortable.id;
this.sort.direction = 'desc';
if (update) {
this.initFromDefaultBranch();
}
}
}
}
class EntityVersionsDatasource implements DataSource<EntityVersion> {
private entityVersionsSubject = new BehaviorSubject<EntityVersion[]>([]);
private pageDataSubject = new BehaviorSubject<PageData<EntityVersion>>(emptyPageData<EntityVersion>());
public pageData$ = this.pageDataSubject.asObservable();
public dataLoading = true;
2022-05-25 12:26:23 +03:00
constructor(private entitiesVersionControlService: EntitiesVersionControlService) {}
connect(collectionViewer: CollectionViewer): Observable<EntityVersion[] | ReadonlyArray<EntityVersion>> {
return this.entityVersionsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.entityVersionsSubject.complete();
this.pageDataSubject.complete();
}
loadEntityVersions(singleEntityMode: boolean,
branch: string, externalEntityId: EntityId,
pageLink: PageLink): Observable<PageData<EntityVersion>> {
this.dataLoading = true;
2022-05-25 12:26:23 +03:00
const result = new ReplaySubject<PageData<EntityVersion>>();
this.fetchEntityVersions(singleEntityMode, branch, externalEntityId, pageLink).pipe(
catchError(() => of(emptyPageData<EntityVersion>())),
).subscribe(
(pageData) => {
this.entityVersionsSubject.next(pageData.data);
this.pageDataSubject.next(pageData);
result.next(pageData);
this.dataLoading = false;
2022-05-25 12:26:23 +03:00
}
);
return result;
}
fetchEntityVersions(singleEntityMode: boolean,
branch: string, externalEntityId: EntityId,
pageLink: PageLink): Observable<PageData<EntityVersion>> {
if (!branch) {
return of(emptyPageData<EntityVersion>());
} else {
if (singleEntityMode) {
if (externalEntityId) {
return this.entitiesVersionControlService.listEntityVersions(pageLink, branch, externalEntityId, {ignoreErrors: true});
} else {
return of(emptyPageData<EntityVersion>());
}
} else {
return this.entitiesVersionControlService.listVersions(pageLink, branch, {ignoreErrors: true});
}
}
}
isEmpty(): Observable<boolean> {
return this.entityVersionsSubject.pipe(
map((entityVersions) => !entityVersions.length)
);
}
total(): Observable<number> {
return this.pageDataSubject.pipe(
map((pageData) => pageData.totalElements)
);
}
}