diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 825171493e..e812595ca4 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -190,7 +190,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request; VersionLoadConfig config = versionLoadRequest.getConfig(); ListenableFuture future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId()); - Futures.transform(future, entityData -> { + return Futures.transform(future, entityData -> { EntityImportResult importResult = transactionTemplate.execute(status -> { try { return exportImportService.importEntity(user, entityData, EntityImportSettings.builder() diff --git a/ui-ngx/src/app/core/http/entities-version-control.service.ts b/ui-ngx/src/app/core/http/entities-version-control.service.ts index eba229de67..86674f0569 100644 --- a/ui-ngx/src/app/core/http/entities-version-control.service.ts +++ b/ui-ngx/src/app/core/http/entities-version-control.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { combineLatest, Observable, of } from 'rxjs'; -import { BranchInfo, EntityVersion, VersionCreateRequest, VersionCreationResult } from '@shared/models/vc.models'; +import { + BranchInfo, + EntityVersion, + VersionCreateRequest, + VersionCreationResult, + VersionLoadRequest, VersionLoadResult +} from '@shared/models/vc.models'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; @@ -95,4 +101,8 @@ export class EntitiesVersionControlService { return this.http.get>(`/api/entities/vc/version/${branch}${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); } + + public loadEntitiesVersion(request: VersionLoadRequest, config?: RequestConfig): Observable> { + return this.http.post>('/api/entities/vc/entity', request, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 38cf85e72c..6134708176 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -157,6 +157,7 @@ import { VersionControlSettingsComponent } from '@home/components/vc/version-con import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { EntityVersionsTableComponent } from '@home/components/vc/entity-versions-table.component'; import { EntityVersionExportComponent } from '@home/components/vc/entity-version-export.component'; +import { EntityVersionRestoreComponent } from '@home/components/vc/entity-version-restore.component'; @NgModule({ declarations: @@ -284,7 +285,8 @@ import { EntityVersionExportComponent } from '@home/components/vc/entity-version VersionControlSettingsComponent, VersionControlComponent, EntityVersionsTableComponent, - EntityVersionExportComponent + EntityVersionExportComponent, + EntityVersionRestoreComponent ], imports: [ CommonModule, @@ -406,7 +408,8 @@ import { EntityVersionExportComponent } from '@home/components/vc/entity-version VersionControlSettingsComponent, VersionControlComponent, EntityVersionsTableComponent, - EntityVersionExportComponent + EntityVersionExportComponent, + EntityVersionRestoreComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.html index 5791790b37..3838ea111d 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.html @@ -16,45 +16,58 @@ -->
- -

{{ 'version-control.create-entity-version' | translate }}

- -
- - -
-
-
- - - - version-control.version-name - - - {{ 'version-control.version-name-required' | translate }} - - - - {{ 'version-control.export-entity-relations' | translate }} - -
-
-
-
- - -
+
+ +

{{ 'version-control.create-entity-version' | translate }}

+ +
+ + +
+
+
+ + + + version-control.version-name + + + {{ 'version-control.version-name-required' | translate }} + + + + {{ 'version-control.export-entity-relations' | translate }} + +
+
+
+
+ + +
+
+
+
{{ resultMessage }}
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.scss b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.scss new file mode 100644 index 0000000000..46e55cf4d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.scss @@ -0,0 +1,21 @@ +/** + * 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. + */ +:host { + .export-result-message { + padding: 48px 8px 8px; + text-align: center; + } +} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.ts index 368a4bf441..2aa3123858 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.ts @@ -26,11 +26,12 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { EntityId } from '@shared/models/id/entity-id'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-entity-version-export', templateUrl: './entity-version-export.component.html', - styleUrls: [] + styleUrls: ['./entity-version-export.component.scss'] }) export class EntityVersionExportComponent extends PageComponent implements OnInit { @@ -43,10 +44,16 @@ export class EntityVersionExportComponent extends PageComponent implements OnIni @Input() onClose: (result: VersionCreationResult | null, branch: string | null) => void; + @Input() + onContentUpdated: () => void; + exportFormGroup: FormGroup; + resultMessage: string; + constructor(protected store: Store, private entitiesVersionControlService: EntitiesVersionControlService, + private translate: TranslateService, private fb: FormBuilder) { super(store); } @@ -76,7 +83,12 @@ export class EntityVersionExportComponent extends PageComponent implements OnIni type: VersionCreateRequestType.SINGLE_ENTITY }; this.entitiesVersionControlService.saveEntitiesVersion(request).subscribe((result) => { - if (this.onClose) { + if (!result.added && !result.modified) { + this.resultMessage = this.translate.instant('version-control.nothing-to-commit'); + if (this.onContentUpdated) { + this.onContentUpdated(); + } + } else if (this.onClose) { this.onClose(result, request.branch); } }); diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html new file mode 100644 index 0000000000..cb71696b4e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html @@ -0,0 +1,49 @@ + +
+ +

{{ 'version-control.restore-entity-from-version' | translate: {versionName} }}

+ +
+ + +
+
+
+ + {{ 'version-control.load-entity-relations' | translate }} + +
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts new file mode 100644 index 0000000000..2cc615c011 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts @@ -0,0 +1,86 @@ +/// +/// 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 { Component, Input, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { SingleEntityVersionLoadRequest, VersionLoadRequestType, VersionLoadResult } from '@shared/models/vc.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-entity-version-restore', + templateUrl: './entity-version-restore.component.html', + styleUrls: [] +}) +export class EntityVersionRestoreComponent extends PageComponent implements OnInit { + + @Input() + branch: string; + + @Input() + versionName: string; + + @Input() + versionId: string; + + @Input() + externalEntityId: EntityId; + + @Input() + onClose: (result: Array | null) => void; + + restoreFormGroup: FormGroup; + + constructor(protected store: Store, + private entitiesVersionControlService: EntitiesVersionControlService, + private translate: TranslateService, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.restoreFormGroup = this.fb.group({ + loadRelations: [false, []] + }); + } + + cancel(): void { + if (this.onClose) { + this.onClose(null); + } + } + + restore(): void { + const request: SingleEntityVersionLoadRequest = { + branch: this.branch, + versionId: this.versionId, + externalEntityId: this.externalEntityId, + config: { + loadRelations: this.restoreFormGroup.get('loadRelations').value + }, + type: VersionLoadRequestType.SINGLE_ENTITY + }; + this.entitiesVersionControlService.loadEntitiesVersion(request).subscribe((result) => { + if (this.onClose) { + this.onClose(result); + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html index 5acfe3b0ba..2e5302881e 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html @@ -99,6 +99,21 @@ {{ entityVersion.name }} + + + + +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts index b60665f2b4..06c778d278 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts @@ -18,10 +18,10 @@ import { AfterViewInit, ChangeDetectorRef, Component, - ElementRef, + ElementRef, EventEmitter, Input, OnDestroy, - OnInit, Renderer2, + OnInit, Output, Renderer2, ViewChild, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; @@ -33,7 +33,7 @@ import { BehaviorSubject, fromEvent, merge, Observable, of, ReplaySubject } from 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'; -import { EntityVersion, VersionCreationResult } from '@shared/models/vc.models'; +import { EntityVersion, VersionCreationResult, VersionLoadResult } from '@shared/models/vc.models'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; @@ -46,6 +46,7 @@ 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'; +import { EntityVersionRestoreComponent } from '@home/components/vc/entity-version-restore.component'; @Component({ selector: 'tb-entity-versions-table', @@ -59,7 +60,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni @Input() singleEntityMode = false; - displayedColumns = ['timestamp', 'id', 'name']; + displayedColumns = ['timestamp', 'id', 'name', 'actions']; pageLink: PageLink; textSearchMode = false; dataSource: EntityVersionsDatasource; @@ -73,8 +74,6 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni viewsInited = false; - vcExportPopover: TbPopoverComponent; - private componentResize$: ResizeObserver; @Input() @@ -104,6 +103,9 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni @Input() entityId: EntityId; + @Output() + versionRestored = new EventEmitter(); + @ViewChild('searchInput') searchInputField: ElementRef; @ViewChild(MatPaginator) paginator: MatPaginator; @@ -182,13 +184,13 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - this.vcExportPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityVersionExportComponent, 'bottom', true, null, + 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) => { - this.vcExportPopover.hide(); + vcExportPopover.hide(); if (result) { if (this.branch !== branch) { this.branchChanged(branch); @@ -196,13 +198,39 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni this.updateData(); } } + }, + 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 | null) => { + restoreVersionPopover.hide(); + if (result && result.length) { + this.versionRestored.emit(); + } } }, {}, {}, {}, false); - this.vcExportPopover.tbVisibleChange.subscribe((visible: boolean) => { - if (!visible) { - this.vcExportPopover = null; - } - }); } } diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html index c631819128..e5e650ac85 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html @@ -22,5 +22,6 @@ + [externalEntityId]="externalEntityId" + (versionRestored)="versionRestored.emit()"> diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.ts b/ui-ngx/src/app/modules/home/components/vc/version-control.component.ts index 2ca68255ae..da6828c4a0 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { selectHasVersionControl } from '@core/auth/auth.selectors'; @@ -47,6 +47,9 @@ export class VersionControlComponent implements OnInit, HasConfirmForm { @Input() entityId: EntityId; + @Output() + versionRestored = new EventEmitter(); + hasVersionControl$ = this.store.pipe(select(selectHasVersionControl)); constructor(private store: Store) { diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html index 3a3f2dda92..c115a43ab6 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html @@ -52,5 +52,6 @@ diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html b/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html index 7524fd59f7..634443a397 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html @@ -52,5 +52,6 @@ diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html index 21b20bcb82..61a030ac16 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html @@ -22,5 +22,6 @@ diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index e713e70690..effac9a7c8 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -75,8 +75,9 @@ label="{{ 'audit-log.audit-logs' | translate }}"> - diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index d84306d029..78d494d8ae 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -52,5 +52,6 @@ diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html index c60910b565..49a2d83372 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html @@ -55,5 +55,6 @@ diff --git a/ui-ngx/src/app/shared/models/vc.models.ts b/ui-ngx/src/app/shared/models/vc.models.ts index d574a1b55f..4975b8ea2b 100644 --- a/ui-ngx/src/app/shared/models/vc.models.ts +++ b/ui-ngx/src/app/shared/models/vc.models.ts @@ -15,16 +15,26 @@ /// import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; export interface VersionCreateConfig { saveRelations: boolean; } +export interface VersionLoadConfig { + loadRelations: boolean; +} + export enum VersionCreateRequestType { SINGLE_ENTITY = 'SINGLE_ENTITY', COMPLEX = 'COMPLEX' } +export enum VersionLoadRequestType { + SINGLE_ENTITY = 'SINGLE_ENTITY', + ENTITY_TYPE = 'ENTITY_TYPE' +} + export interface VersionCreateRequest { versionName: string; branch: string; @@ -37,6 +47,18 @@ export interface SingleEntityVersionCreateRequest extends VersionCreateRequest { type: VersionCreateRequestType.SINGLE_ENTITY; } +export interface VersionLoadRequest { + branch: string; + versionId: string; + type: VersionLoadRequestType; +} + +export interface SingleEntityVersionLoadRequest extends VersionLoadRequest { + externalEntityId: EntityId; + config: VersionLoadConfig; + type: VersionLoadRequestType.SINGLE_ENTITY; +} + export interface BranchInfo { name: string; default: boolean; @@ -54,3 +76,10 @@ export interface VersionCreationResult { modified: number; removed: number; } + +export interface VersionLoadResult { + entityType: EntityType; + created: number; + updated: number; + deleted: number; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 202597ade1..0a2f39bcd4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -57,7 +57,8 @@ "download": "Download", "next-with-label": "Next: {{label}}", "read-more": "Read more", - "hide": "Hide" + "hide": "Hide", + "restore": "Restore" }, "aggregation": { "aggregation": "Aggregation", @@ -3126,7 +3127,11 @@ "no-entity-versions-text": "No entity versions found", "no-versions-text": "No versions found", "copy-full-version-id": "Copy full version id", - "create-version": "Create version" + "create-version": "Create version", + "nothing-to-commit": "Nothing to commit", + "restore-version": "Restore version", + "restore-entity-from-version": "Restore entity from version '{{versionName}}'", + "load-entity-relations": "Load entity relations" }, "widget": { "widget-library": "Widgets Library",