UI: Implement complex version create action.
This commit is contained in:
parent
0be0df6015
commit
b6f964a5eb
@ -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,
|
||||
|
||||
@ -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>
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
:host {
|
||||
.export-result-message {
|
||||
.create-result-message {
|
||||
padding: 48px 8px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
@ -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()"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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…
x
Reference in New Issue
Block a user