Browse Source

UI: Implement complex version create action.

pull/6615/head
Igor Kulikov 4 years ago
parent
commit
b6f964a5eb
  1. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  2. 82
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html
  3. 2
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.scss
  4. 104
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts
  5. 111
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html
  6. 64
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.scss
  7. 247
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts
  8. 8
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html
  9. 21
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.scss
  10. 18
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts
  11. 11
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html
  12. 50
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts
  13. 26
      ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts
  14. 47
      ui-ngx/src/app/shared/models/vc.models.ts
  15. 14
      ui-ngx/src/assets/locale/locale.constant-en_US.json

16
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -156,9 +156,11 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component';
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 { EntityVersionCreateComponent } from '@home/components/vc/entity-version-create.component';
import { EntityVersionRestoreComponent } from '@home/components/vc/entity-version-restore.component';
import { EntityVersionDiffComponent } from '@home/components/vc/entity-version-diff.component';
import { ComplexVersionCreateComponent } from '@home/components/vc/complex-version-create.component';
import { EntityTypesVersionCreateComponent } from '@home/components/vc/entity-types-version-create.component';
@NgModule({
declarations:
@ -286,9 +288,11 @@ import { EntityVersionDiffComponent } from '@home/components/vc/entity-version-d
VersionControlSettingsComponent,
VersionControlComponent,
EntityVersionsTableComponent,
EntityVersionExportComponent,
EntityVersionCreateComponent,
EntityVersionRestoreComponent,
EntityVersionDiffComponent
EntityVersionDiffComponent,
ComplexVersionCreateComponent,
EntityTypesVersionCreateComponent
],
imports: [
CommonModule,
@ -410,9 +414,11 @@ import { EntityVersionDiffComponent } from '@home/components/vc/entity-version-d
VersionControlSettingsComponent,
VersionControlComponent,
EntityVersionsTableComponent,
EntityVersionExportComponent,
EntityVersionCreateComponent,
EntityVersionRestoreComponent,
EntityVersionDiffComponent
EntityVersionDiffComponent,
ComplexVersionCreateComponent,
EntityTypesVersionCreateComponent
],
providers: [
WidgetComponentService,

82
ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html

@ -0,0 +1,82 @@
<!--
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.
-->
<section style="min-width: 800px;">
<section *ngIf="!resultMessage">
<mat-toolbar>
<h2>{{ 'version-control.create-entities-version' | translate }}</h2>
<span fxFlex></span>
</mat-toolbar>
<mat-progress-bar color="warn" style="z-index: 10; width: 100%; margin-bottom: -4px;" mode="indeterminate"
*ngIf="isLoading$ | async">
</mat-progress-bar>
<form [formGroup]="createVersionFormGroup" style="padding-top: 16px;">
<fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column">
<tb-branch-autocomplete
fxFlex
required
formControlName="branch">
</tb-branch-autocomplete>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>version-control.version-name</mat-label>
<input required matInput formControlName="versionName">
<mat-error *ngIf="createVersionFormGroup.get('versionName').hasError('required')">
{{ 'version-control.version-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>version-control.default-sync-strategy</mat-label>
<mat-select required formControlName="syncStrategy">
<mat-option *ngFor="let strategy of syncStrategies" [value]="strategy">
{{syncStrategyTranslations.get(strategy) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<tb-entity-types-version-create
formControlName="entityTypes">
</tb-entity-types-version-create>
</div>
</fieldset>
</form>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="button"
(click)="export()"
[disabled]="(isLoading$ | async) || createVersionFormGroup.invalid || !createVersionFormGroup.dirty">
{{ 'action.create' | translate }}
</button>
</div>
</section>
<section *ngIf="resultMessage">
<div class="mat-title create-result-message">{{ resultMessage }}</div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
</div>
</section>
</section>

2
ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.scss → ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.scss

@ -14,7 +14,7 @@
* limitations under the License.
*/
:host {
.export-result-message {
.create-result-message {
padding: 48px 8px 8px;
text-align: center;
}

104
ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts

@ -0,0 +1,104 @@
///
/// 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, Validators } from '@angular/forms';
import {
ComplexVersionCreateRequest,
createDefaultEntityTypesVersionCreate,
SyncStrategy, syncStrategyTranslationMap,
VersionCreateRequestType,
VersionCreationResult
} 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 { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-complex-version-create',
templateUrl: './complex-version-create.component.html',
styleUrls: ['./complex-version-create.component.scss']
})
export class ComplexVersionCreateComponent extends PageComponent implements OnInit {
@Input()
branch: string;
@Input()
onClose: (result: VersionCreationResult | null, branch: string | null) => void;
@Input()
onContentUpdated: () => void;
createVersionFormGroup: FormGroup;
syncStrategies = Object.values(SyncStrategy);
syncStrategyTranslations = syncStrategyTranslationMap;
resultMessage: string;
versionCreateResult: VersionCreationResult = null;
versionCreateBranch: string = null;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private translate: TranslateService,
private fb: FormBuilder) {
super(store);
}
ngOnInit(): void {
this.createVersionFormGroup = this.fb.group({
branch: [this.branch, [Validators.required]],
versionName: [null, [Validators.required]],
syncStrategy: [SyncStrategy.MERGE, Validators.required],
entityTypes: [createDefaultEntityTypesVersionCreate(), []],
});
}
cancel(): void {
if (this.onClose) {
this.onClose(this.versionCreateResult, this.versionCreateBranch);
}
}
export(): void {
const request: ComplexVersionCreateRequest = {
branch: this.createVersionFormGroup.get('branch').value,
versionName: this.createVersionFormGroup.get('versionName').value,
syncStrategy: this.createVersionFormGroup.get('syncStrategy').value,
entityTypes: this.createVersionFormGroup.get('entityTypes').value,
type: VersionCreateRequestType.COMPLEX
};
this.entitiesVersionControlService.saveEntitiesVersion(request).subscribe((result) => {
if (!result.added && !result.modified && !result.removed) {
this.resultMessage = this.translate.instant('version-control.nothing-to-commit');
} else {
this.resultMessage = this.translate.instant('version-control.version-create-result',
{added: result.added, modified: result.modified, removed: result.removed});
}
this.versionCreateResult = result;
this.versionCreateBranch = request.branch;
if (this.onContentUpdated) {
this.onContentUpdated();
}
});
}
}

111
ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html

@ -0,0 +1,111 @@
<!--
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.
-->
<section class="entity-types-version-create" [formGroup]="entityTypesVersionCreateFormGroup" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>version-control.entity-types</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let entityTypeFormGroup of entityTypesFormGroupArray(); trackBy: trackByEntityType;
let $index = index; last as isLast;"
fxLayout="row" fxLayoutAlign="start center" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<mat-expansion-panel class="entity-type-config" fxFlex [formGroup]="entityTypeFormGroup" [expanded]="entityTypesFormGroupExpanded(entityTypeFormGroup)">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div>{{ entityTypeText(entityTypeFormGroup) }}</div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeEntityType($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="entity-type-config-content" fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<div fxLayout="row" fxLayoutGap="16px">
<tb-entity-type-select
showLabel
formControlName="entityType"
required
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="allowedEntityTypes(entityTypeFormGroup)">
</tb-entity-type-select>
<div fxFlex fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="start center" formGroupName="config">
<mat-form-field class="mat-block">
<mat-label translate>version-control.sync-strategy</mat-label>
<mat-select formControlName="syncStrategy">
<mat-option [value]="'default'">
{{ 'version-control.default' | translate }}
</mat-option>
<mat-option *ngFor="let strategy of syncStrategies" [value]="strategy">
{{syncStrategyTranslations.get(strategy) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
</div>
</div>
<div fxLayout="row" fxLayoutGap="16px" fxLayoutAlign="start center"
formGroupName="config" style="height: 60px;">
<mat-slide-toggle formControlName="allEntities">
{{ 'version-control.all-entities' | translate }}
</mat-slide-toggle>
<tb-entity-list
fxFlex
[fxShow]="!entityTypeFormGroup.get('config').get('allEntities').value"
[entityType]="entityTypeFormGroup.get('entityType').value"
required
formControlName="entityIds">
</tb-entity-list>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</div>
<div *ngIf="!entityTypesFormGroupArray().length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">version-control.no-entity-types</span>
</div>
<div style="padding-top: 16px;" fxLayout="row">
<button mat-raised-button color="primary"
type="button"
[disabled]="!addEnabled()"
(click)="addEntityType()">
<span translate>version-control.add-entity-type</span>
</button>
<span fxFlex></span>
<button mat-raised-button color="primary"
type="button"
[disabled]="!entityTypesFormGroupArray().length"
(click)="removeAll()">
<span translate>version-control.remove-all</span>
</button>
</div>
</div>
</fieldset>
</section>

64
ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.scss

@ -0,0 +1,64 @@
/**
* 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 {
.entity-types-version-create {
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
color: rgba(0, 0, 0, .7);
width: fit-content;
}
legend + * {
display: block;
margin-top: 16px;
}
}
.tb-control-list {
overflow-y: auto;
max-height: 600px;
}
.tb-prompt {
margin: 30px 0;
}
mat-expansion-panel.entity-type-config {
box-shadow: none;
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
height: 48px;
}
.entity-type-config-content {
padding: 0 8px 8px;
}
}
}
}
:host ::ng-deep {
.mat-expansion-panel.entity-type-config {
.mat-expansion-panel-body {
padding: 0;
}
}
}

247
ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts

@ -0,0 +1,247 @@
///
/// 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, forwardRef, Input, OnInit } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormArray,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import {
EntityTypeVersionCreateConfig,
exportableEntityTypes,
SyncStrategy,
syncStrategyTranslationMap
} from '@shared/models/vc.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { isDefinedAndNotNull } from '@core/utils';
@Component({
selector: 'tb-entity-types-version-create',
templateUrl: './entity-types-version-create.component.html',
styleUrls: ['./entity-types-version-create.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EntityTypesVersionCreateComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => EntityTypesVersionCreateComponent),
multi: true
}
]
})
export class EntityTypesVersionCreateComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator {
@Input()
disabled: boolean;
private modelValue: {[entityType: string]: EntityTypeVersionCreateConfig};
private propagateChange = null;
public entityTypesVersionCreateFormGroup: FormGroup;
syncStrategies = Object.values(SyncStrategy);
syncStrategyTranslations = syncStrategyTranslationMap;
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private fb: FormBuilder) {
super(store);
}
ngOnInit(): void {
this.entityTypesVersionCreateFormGroup = this.fb.group({
entityTypes: [this.fb.array([]), [Validators.min(1)]]
});
this.entityTypesVersionCreateFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.entityTypesVersionCreateFormGroup.disable({emitEvent: false});
} else {
this.entityTypesVersionCreateFormGroup.enable({emitEvent: false});
}
}
writeValue(value: {[entityType: string]: EntityTypeVersionCreateConfig} | undefined): void {
this.modelValue = value;
this.entityTypesVersionCreateFormGroup.setControl('entityTypes',
this.prepareEntityTypesFormArray(value), {emitEvent: false});
}
public validate(c: FormControl) {
return this.entityTypesVersionCreateFormGroup.valid && this.entityTypesFormGroupArray().length ? null : {
entityTypes: {
valid: false,
},
};
}
private prepareEntityTypesFormArray(entityTypes: {[entityType: string]: EntityTypeVersionCreateConfig} | undefined): FormArray {
const entityTypesControls: Array<AbstractControl> = [];
if (entityTypes) {
for (const entityType of Object.keys(entityTypes)) {
const config = entityTypes[entityType];
entityTypesControls.push(this.createEntityTypeControl(entityType as EntityType, config));
}
}
return this.fb.array(entityTypesControls);
}
private createEntityTypeControl(entityType: EntityType, config: EntityTypeVersionCreateConfig): AbstractControl {
const entityTypeControl = this.fb.group(
{
entityType: [entityType, [Validators.required]],
config: this.fb.group({
syncStrategy: [config.syncStrategy === null ? 'default' : config.syncStrategy, []],
saveRelations: [config.saveRelations, []],
allEntities: [config.allEntities, []],
entityIds: [config.entityIds, [Validators.required]]
})
}
);
this.updateEntityTypeValidators(entityTypeControl);
entityTypeControl.get('config').get('allEntities').valueChanges.subscribe(() => {
this.updateEntityTypeValidators(entityTypeControl);
});
return entityTypeControl;
}
private updateEntityTypeValidators(entityTypeControl: AbstractControl): void {
const allEntities: boolean = entityTypeControl.get('config').get('allEntities').value;
if (allEntities) {
entityTypeControl.get('config').get('entityIds').disable({emitEvent: false});
} else {
entityTypeControl.get('config').get('entityIds').enable({emitEvent: false});
}
entityTypeControl.get('config').get('entityIds').updateValueAndValidity({emitEvent: false});
}
entityTypesFormGroupArray(): FormGroup[] {
return (this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray).controls as FormGroup[];
}
entityTypesFormGroupExpanded(entityTypeControl: AbstractControl): boolean {
return !!(entityTypeControl as any).expanded;
}
public trackByEntityType(index: number, entityTypeControl: AbstractControl): any {
return entityTypeControl;
}
public removeEntityType(index: number) {
(this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray).removeAt(index);
}
public addEnabled(): boolean {
const entityTypesArray = this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray;
return entityTypesArray.length < exportableEntityTypes.length;
}
public addEntityType() {
const entityTypesArray = this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray;
const config: EntityTypeVersionCreateConfig = {
syncStrategy: null,
saveRelations: false,
allEntities: true,
entityIds: []
};
const allowed = this.allowedEntityTypes();
let entityType: EntityType = null;
if (allowed.length) {
entityType = allowed[0];
}
const entityTypeControl = this.createEntityTypeControl(entityType, config);
(entityTypeControl as any).expanded = true;
entityTypesArray.push(entityTypeControl);
this.entityTypesVersionCreateFormGroup.updateValueAndValidity();
}
public removeAll() {
const entityTypesArray = this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray;
entityTypesArray.clear();
this.entityTypesVersionCreateFormGroup.updateValueAndValidity();
}
entityTypeText(entityTypeControl: AbstractControl): string {
const entityType: EntityType = entityTypeControl.get('entityType').value;
const config: EntityTypeVersionCreateConfig = entityTypeControl.get('config').value;
let count = config?.entityIds?.length;
if (!isDefinedAndNotNull(count)) {
count = 0;
}
if (entityType) {
return this.translate.instant((config?.allEntities ? entityTypeTranslations.get(entityType).typePlural
: entityTypeTranslations.get(entityType).list), { count });
} else {
return 'Undefined';
}
}
allowedEntityTypes(entityTypeControl?: AbstractControl): Array<EntityType> {
let res = [...exportableEntityTypes];
const currentEntityType: EntityType = entityTypeControl?.get('entityType')?.value;
const value: [{entityType: string, config: EntityTypeVersionCreateConfig}] =
this.entityTypesVersionCreateFormGroup.get('entityTypes').value || [];
const usedEntityTypes = value.map(val => val.entityType).filter(val => val);
res = res.filter(entityType => !usedEntityTypes.includes(entityType) || entityType === currentEntityType);
return res;
}
private updateModel() {
const value: [{entityType: string, config: EntityTypeVersionCreateConfig}] =
this.entityTypesVersionCreateFormGroup.get('entityTypes').value || [];
let modelValue: {[entityType: string]: EntityTypeVersionCreateConfig} = null;
if (value && value.length) {
modelValue = {};
value.forEach((val) => {
modelValue[val.entityType] = val.config;
if ((modelValue[val.entityType].syncStrategy as any) === 'default') {
modelValue[val.entityType].syncStrategy = null;
}
});
}
this.modelValue = modelValue;
this.propagateChange(this.modelValue);
}
}

8
ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.html → ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html

@ -24,7 +24,7 @@
<mat-progress-bar color="warn" style="z-index: 10; width: 100%; margin-bottom: -4px;" mode="indeterminate"
*ngIf="isLoading$ | async">
</mat-progress-bar>
<form [formGroup]="exportFormGroup" style="padding-top: 16px;">
<form [formGroup]="createVersionFormGroup" style="padding-top: 16px;">
<fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column">
<tb-branch-autocomplete
@ -34,7 +34,7 @@
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>version-control.version-name</mat-label>
<input required matInput formControlName="versionName">
<mat-error *ngIf="exportFormGroup.get('versionName').hasError('required')">
<mat-error *ngIf="createVersionFormGroup.get('versionName').hasError('required')">
{{ 'version-control.version-name-required' | translate }}
</mat-error>
</mat-form-field>
@ -54,13 +54,13 @@
<button mat-raised-button color="primary"
type="button"
(click)="export()"
[disabled]="(isLoading$ | async) || exportFormGroup.invalid || !exportFormGroup.dirty">
[disabled]="(isLoading$ | async) || createVersionFormGroup.invalid || !createVersionFormGroup.dirty">
{{ 'action.create' | translate }}
</button>
</div>
</section>
<section *ngIf="resultMessage">
<div class="mat-title export-result-message">{{ resultMessage }}</div>
<div class="mat-title create-result-message">{{ resultMessage }}</div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"

21
ui-ngx/src/app/modules/home/components/vc/entity-version-create.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 {
.create-result-message {
padding: 48px 8px 8px;
text-align: center;
}
}

18
ui-ngx/src/app/modules/home/components/vc/entity-version-export.component.ts → ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts

@ -29,11 +29,11 @@ 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: ['./entity-version-export.component.scss']
selector: 'tb-entity-version-create',
templateUrl: './entity-version-create.component.html',
styleUrls: ['./entity-version-create.component.scss']
})
export class EntityVersionExportComponent extends PageComponent implements OnInit {
export class EntityVersionCreateComponent extends PageComponent implements OnInit {
@Input()
branch: string;
@ -47,7 +47,7 @@ export class EntityVersionExportComponent extends PageComponent implements OnIni
@Input()
onContentUpdated: () => void;
exportFormGroup: FormGroup;
createVersionFormGroup: FormGroup;
resultMessage: string;
@ -59,7 +59,7 @@ export class EntityVersionExportComponent extends PageComponent implements OnIni
}
ngOnInit(): void {
this.exportFormGroup = this.fb.group({
this.createVersionFormGroup = this.fb.group({
branch: [this.branch, [Validators.required]],
versionName: [null, [Validators.required]],
saveRelations: [false, []]
@ -75,10 +75,10 @@ export class EntityVersionExportComponent extends PageComponent implements OnIni
export(): void {
const request: SingleEntityVersionCreateRequest = {
entityId: this.entityId,
branch: this.exportFormGroup.get('branch').value,
versionName: this.exportFormGroup.get('versionName').value,
branch: this.createVersionFormGroup.get('branch').value,
versionName: this.createVersionFormGroup.get('versionName').value,
config: {
saveRelations: this.exportFormGroup.get('saveRelations').value
saveRelations: this.createVersionFormGroup.get('saveRelations').value
},
type: VersionCreateRequestType.SINGLE_ENTITY
};

11
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html

@ -32,12 +32,19 @@
</div>
<span fxFlex></span>
<button *ngIf="singleEntityMode" mat-stroked-button color="primary"
#exportButton
#createVersionButton
[disabled]="(isLoading$ | async)"
(click)="toggleVcExport($event, exportButton)">
(click)="toggleCreateVersion($event, createVersionButton)">
<mat-icon>update</mat-icon>
{{'version-control.create-version' | translate }}
</button>
<button *ngIf="!singleEntityMode" mat-stroked-button color="primary"
#complexCreateVersionButton
[disabled]="(isLoading$ | async)"
(click)="toggleComplexCreateVersion($event, complexCreateVersionButton)">
<mat-icon>update</mat-icon>
{{'version-control.create-entities-version' | translate }}
</button>
<button mat-icon-button
[disabled]="isLoading$ | async"
(click)="enterFilterMode()"

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

@ -43,11 +43,11 @@ 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 { EntityVersionCreateComponent } from '@home/components/vc/entity-version-create.component';
import { MatButton } from '@angular/material/button';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { EntityVersionRestoreComponent } from '@home/components/vc/entity-version-restore.component';
import { EntityVersionDiffComponent } from '@home/components/vc/entity-version-diff.component';
import { ComplexVersionCreateComponent } from '@home/components/vc/complex-version-create.component';
@Component({
selector: 'tb-entity-versions-table',
@ -177,21 +177,21 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
}
}
toggleVcExport($event: Event, exportButton: MatButton) {
toggleCreateVersion($event: Event, createVersionButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = exportButton._elementRef.nativeElement;
const trigger = createVersionButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const vcExportPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionExportComponent, 'left', true, null,
const createVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionCreateComponent, 'left', true, null,
{
branch: this.branch,
entityId: this.entityId,
onClose: (result: VersionCreationResult | null, branch: string | null) => {
vcExportPopover.hide();
createVersionPopover.hide();
if (result) {
if (this.branch !== branch) {
this.branchChanged(branch);
@ -201,9 +201,41 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
}
},
onContentUpdated: () => {
vcExportPopover.updatePosition();
createVersionPopover.updatePosition();
setTimeout(() => {
vcExportPopover.updatePosition();
createVersionPopover.updatePosition();
});
}
}, {}, {}, {}, false);
}
}
toggleComplexCreateVersion($event: Event, complexCreateVersionButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = complexCreateVersionButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const complexCreateVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ComplexVersionCreateComponent, 'leftTop', true, null,
{
branch: this.branch,
onClose: (result: VersionCreationResult | null, branch: string | null) => {
complexCreateVersionPopover.hide();
if (result) {
if (this.branch !== branch) {
this.branchChanged(branch);
} else {
this.updateData();
}
}
},
onContentUpdated: () => {
complexCreateVersionPopover.updatePosition();
setTimeout(() => {
complexCreateVersionPopover.updatePosition();
});
}
}, {}, {}, {}, false);

26
ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { AfterViewInit, Component, forwardRef, Input, OnInit } from '@angular/core';
import { AfterViewInit, Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
@ -33,7 +33,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
multi: true
}]
})
export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit {
export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges {
entityTypeFormGroup: FormGroup;
@ -45,6 +45,9 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit,
@Input()
useAliasEntityTypes: boolean;
@Input()
filterAllowedEntityTypes = true;
private showLabelValue: boolean;
get showLabel(): boolean {
return this.showLabelValue;
@ -87,7 +90,8 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit,
}
ngOnInit() {
this.entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes);
this.entityTypes = this.filterAllowedEntityTypes ?
this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes) : this.allowedEntityTypes;
this.entityTypeFormGroup.get('entityType').valueChanges.subscribe(
(value) => {
let modelValue;
@ -101,6 +105,22 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit,
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'allowedEntityTypes') {
this.entityTypes = this.filterAllowedEntityTypes ?
this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes) : this.allowedEntityTypes;
const currentEntityType: EntityType | AliasEntityType = this.entityTypeFormGroup.get('entityType').value;
if (currentEntityType && !this.entityTypes.includes(currentEntityType)) {
this.entityTypeFormGroup.get('entityType').patchValue(null, {emitEvent: true});
}
}
}
}
}
ngAfterViewInit(): void {
}

47
ui-ngx/src/app/shared/models/vc.models.ts

@ -21,6 +21,15 @@ import { EntityRelation } from '@shared/models/relation.models';
import { Device, DeviceCredentials } from '@shared/models/device.models';
import { RuleChain, RuleChainMetaData } from '@shared/models/rule-chain.models';
export const exportableEntityTypes: Array<EntityType> = [
EntityType.ASSET,
EntityType.DEVICE,
EntityType.DASHBOARD,
EntityType.CUSTOMER,
EntityType.DEVICE_PROFILE,
EntityType.RULE_CHAIN
];
export interface VersionCreateConfig {
saveRelations: boolean;
}
@ -51,6 +60,43 @@ export interface SingleEntityVersionCreateRequest extends VersionCreateRequest {
type: VersionCreateRequestType.SINGLE_ENTITY;
}
export enum SyncStrategy {
MERGE = 'MERGE',
OVERWRITE = 'OVERWRITE'
}
export const syncStrategyTranslationMap = new Map<SyncStrategy, string>(
[
[SyncStrategy.MERGE, 'version-control.sync-strategy-merge'],
[SyncStrategy.OVERWRITE, 'version-control.sync-strategy-overwrite']
]
);
export interface EntityTypeVersionCreateConfig extends VersionCreateConfig {
syncStrategy: SyncStrategy;
entityIds: string[];
allEntities: boolean;
}
export interface ComplexVersionCreateRequest extends VersionCreateRequest {
syncStrategy: SyncStrategy;
entityTypes: {[entityType: string]: EntityTypeVersionCreateConfig};
type: VersionCreateRequestType.COMPLEX;
}
export function createDefaultEntityTypesVersionCreate(): {[entityType: string]: EntityTypeVersionCreateConfig} {
const res: {[entityType: string]: EntityTypeVersionCreateConfig} = {};
for (const entityType of exportableEntityTypes) {
res[entityType] = {
syncStrategy: null,
saveRelations: false,
allEntities: true,
entityIds: []
};
}
return res;
}
export interface VersionLoadRequest {
branch: string;
versionId: string;
@ -63,6 +109,7 @@ export interface SingleEntityVersionLoadRequest extends VersionLoadRequest {
type: VersionLoadRequestType.SINGLE_ENTITY;
}
export interface BranchInfo {
name: string;
default: boolean;

14
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3137,7 +3137,19 @@
"previous-difference": "Previous Difference",
"next-difference": "Next Difference",
"current": "Current",
"differences": "{ count, plural, 1 {1 difference} other {# differences} }"
"differences": "{ count, plural, 1 {1 difference} other {# differences} }",
"create-entities-version": "Create entities version",
"default-sync-strategy": "Default sync strategy",
"sync-strategy-merge": "Merge",
"sync-strategy-overwrite": "Overwrite",
"entity-types": "Entity types",
"sync-strategy": "Sync strategy",
"default": "Default",
"all-entities": "All entities",
"no-entity-types": "No entity types configured",
"add-entity-type": "Add entity type",
"remove-all": "Remove all",
"version-create-result": "{ added, plural, 0 {No entities} 1 {1 entity} other {# entities} } added.\n{ modified, plural, 0 {No entities} 1 {1 entity} other {# entities} } modified.\n{ removed, plural, 0 {No entities} 1 {1 entity} other {# entities} } removed."
},
"widget": {
"widget-library": "Widgets Library",

Loading…
Cancel
Save