Merge pull request #10306 from rusikv/relation_query_not_option-ui

Relation query 'Not' option and redesign
This commit is contained in:
Andrew Shvayka 2024-05-22 15:52:53 +03:00 committed by GitHub
commit 78d5a57468
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 101 additions and 107 deletions

View File

@ -28,10 +28,9 @@
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset class="tb-form-panel no-padding no-border no-gap" [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column"> <div class="tb-form-row column-xs align-start no-border no-gap no-padding tb-standard-fields">
<div fxLayout="row"> <mat-form-field class="flex">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alias.name</mat-label> <mat-label translate>alias.name</mat-label>
<input matInput formControlName="alias" required> <input matInput formControlName="alias" required>
<mat-error *ngIf="entityAliasFormGroup.get('alias').hasError('required')"> <mat-error *ngIf="entityAliasFormGroup.get('alias').hasError('required')">
@ -41,12 +40,11 @@
{{ 'alias.duplicate-alias' | translate }} {{ 'alias.duplicate-alias' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<section class="tb-resolve-multiple-switch" fxLayout="column" fxLayoutAlign="start center"> <div class="tb-resolve-multiple-switch" tb-hint-tooltip-icon="{{ 'alias.resolve-multiple-hint' | translate }}">
<label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label> <mat-slide-toggle class="mat-slide" formControlName="resolveMultiple">
<mat-slide-toggle class="resolve-multiple-switch" {{ 'alias.resolve-multiple' | translate }}
formControlName="resolveMultiple">
</mat-slide-toggle> </mat-slide-toggle>
</section> </div>
</div> </div>
<tb-entity-filter formControlName="filter" <tb-entity-filter formControlName="filter"
[resolveMultiple]="entityAliasFormGroup.get('resolveMultiple').value" [resolveMultiple]="entityAliasFormGroup.get('resolveMultiple').value"
@ -55,7 +53,6 @@
</tb-entity-filter> </tb-entity-filter>
<tb-error [error]="entityAliasFormGroup.hasError('noEntityMatched') <tb-error [error]="entityAliasFormGroup.hasError('noEntityMatched')
? translate.instant('alias.entity-filter-no-entity-matched') : ''"></tb-error> ? translate.instant('alias.entity-filter-no-entity-matched') : ''"></tb-error>
</div>
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">

View File

@ -13,16 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../../../../scss/constants';
:host { :host {
.tb-resolve-multiple-switch { .tb-resolve-multiple-switch {
padding-left: 10px; padding: 18px 0 0 18px;
@media #{$mat-xs} {
.resolve-multiple-switch { padding: 0 0 18px 0;
margin: 0;
}
.resolve-multiple-label {
margin: 5px 0;
} }
} }
} }

View File

@ -130,45 +130,27 @@
</mat-form-field> </mat-form-field>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="aliasFilterType.relationsQuery"> <ng-template [ngSwitchCase]="aliasFilterType.relationsQuery">
<section fxLayout="column" id="relationsQueryFilter"> <section class="tb-form-panel no-border no-padding">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label> <div class="tb-form-panel stroked no-gap">
<section class="tb-root-state-entity-switch" fxLayout="row" fxLayoutAlign="start center" style="padding-left: 0;"> <div class="tb-form-panel-title" translate>alias.root-entity</div>
<mat-slide-toggle class="root-state-entity-switch" <mat-slide-toggle class="mat-slide margin" formControlName="rootStateEntity">
formControlName="rootStateEntity"> {{ 'alias.root-state-entity' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> <div *ngIf="filterFormGroup.get('rootStateEntity').value" class="tb-form-panel no-border no-padding no-gap">
</section> <mat-form-field>
<div fxFlex fxLayout="row" *ngIf="!filterFormGroup.get('rootStateEntity').value"> <mat-label translate>alias.state-entity-parameter-name</mat-label>
<tb-entity-select fxFlex <input matInput formControlName="stateEntityParamName">
</mat-form-field>
<tb-entity-select useAliasEntityTypes="true" formControlName="defaultStateEntity">
</tb-entity-select>
</div>
<tb-entity-select *ngIf="!filterFormGroup.get('rootStateEntity').value"
required required
useAliasEntityTypes="true" useAliasEntityTypes="true"
formControlName="rootEntity"> formControlName="rootEntity">
</tb-entity-select> </tb-entity-select>
</div> <div class="tb-form-row no-border no-padding tb-standard-fields">
<div fxFlex fxLayout="column" fxLayout.gt-sm="row" *ngIf="filterFormGroup.get('rootStateEntity').value"> <mat-form-field class="flex">
<mat-form-field floatLabel="always" class="mat-block" style="margin-top: 24px; padding-right: 8px;">
<mat-label translate>alias.state-entity-parameter-name</mat-label>
<input matInput formControlName="stateEntityParamName"
placeholder="{{ 'alias.default-entity-parameter-name' | translate }}">
</mat-form-field>
<div fxFlex fxLayout="column">
<label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
<tb-entity-select fxFlex
useAliasEntityTypes="true"
formControlName="defaultStateEntity">
</tb-entity-select>
</div>
</div>
<div fxFlex fxLayout="row">
<section class="tb-root-state-entity-switch" fxLayout="row" fxLayoutAlign="start center" style="padding-left: 0;">
<mat-slide-toggle class="root-state-entity-switch"
formControlName="fetchLastLevelOnly">
</mat-slide-toggle>
<label class="tb-small root-state-entity-label" translate>alias.last-level-relation</label>
</section>
</div>
<div fxFlex fxLayoutGap="8px" fxLayout="row" fxLayout.xs="column">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select required formControlName="direction"> <mat-select required formControlName="direction">
<mat-option *ngFor="let type of directionTypes" [value]="type"> <mat-option *ngFor="let type of directionTypes" [value]="type">
@ -176,7 +158,7 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex floatLabel="always" class="mat-block"> <mat-form-field class="flex" floatLabel="always">
<mat-label translate>alias.max-relation-level</mat-label> <mat-label translate>alias.max-relation-level</mat-label>
<input matInput <input matInput
type="number" type="number"
@ -186,14 +168,19 @@
formControlName="maxLevel"> formControlName="maxLevel">
</mat-form-field> </mat-form-field>
</div> </div>
<mat-slide-toggle formControlName="negate" *ngIf="this.filterFormGroup.get('filters').value.length > 1"> <mat-slide-toggle *ngIf="filterFormGroup.get('maxLevel').value > 1 || !filterFormGroup.get('maxLevel').value"
{{ 'relation.negate-all-relations' | translate }} class="mat-slide" formControlName="fetchLastLevelOnly">
{{ 'alias.last-level-relation' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div class="mat-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div> </div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>relation.relation-filters</div>
<tb-relation-filters <tb-relation-filters
enableNotOption
[allowedEntityTypes]="allowedEntityTypes" [allowedEntityTypes]="allowedEntityTypes"
formControlName="filters"> formControlName="filters">
</tb-relation-filters> </tb-relation-filters>
</div>
</section> </section>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="entityFilterFormGroup.get('type').value === aliasFilterType.assetSearchQuery || <ng-template [ngSwitchCase]="entityFilterFormGroup.get('type').value === aliasFilterType.assetSearchQuery ||

View File

@ -197,8 +197,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit, OnDe
rootEntity: [filter ? filter.rootEntity : null, (filter && filter.rootStateEntity) ? [] : [Validators.required]], rootEntity: [filter ? filter.rootEntity : null, (filter && filter.rootStateEntity) ? [] : [Validators.required]],
direction: [filter ? filter.direction : EntitySearchDirection.FROM, [Validators.required]], direction: [filter ? filter.direction : EntitySearchDirection.FROM, [Validators.required]],
maxLevel: [filter ? filter.maxLevel : 1, []], maxLevel: [filter ? filter.maxLevel : 1, []],
fetchLastLevelOnly: [filter ? filter.fetchLastLevelOnly : false, []], fetchLastLevelOnly: [filter ? filter.fetchLastLevelOnly : false, []]
negate: [filter ? filter.negate : false, []]
}); });
const rootStateSubscription = this.filterFormGroup.get('rootStateEntity').valueChanges.subscribe((rootStateEntity: boolean) => { const rootStateSubscription = this.filterFormGroup.get('rootStateEntity').valueChanges.subscribe((rootStateEntity: boolean) => {
this.filterFormGroup.get('rootEntity').setValidators(rootStateEntity ? [] : [Validators.required]); this.filterFormGroup.get('rootEntity').setValidators(rootStateEntity ? [] : [Validators.required]);

View File

@ -18,13 +18,17 @@
<div class="tb-form-panel no-border no-padding"> <div class="tb-form-panel no-border no-padding">
<div class="tb-form-table" [formGroup]="relationFiltersFormGroup"> <div class="tb-form-table" [formGroup]="relationFiltersFormGroup">
<div class="tb-form-table-header"> <div class="tb-form-table-header">
<div *ngIf="enableNotOption" class="tb-form-table-header-cell flex-18"></div>
<div class="tb-form-table-header-cell flex-50">{{ 'relation.relation-type' | translate }}</div> <div class="tb-form-table-header-cell flex-50">{{ 'relation.relation-type' | translate }}</div>
<div class="tb-form-table-header-cell flex-50">{{ 'entity.entity-types' | translate }}</div> <div class="tb-form-table-header-cell flex-50">{{ 'entity.entity-types' | translate }}</div>
<div class="tb-form-table-header-cell actions-header"></div> <div class="tb-form-table-header-cell actions-header"></div>
</div> </div>
<div class="tb-form-table-body" formArrayName="relationFilters"> <div class="tb-form-table-body" formArrayName="relationFilters">
<div class="tb-form-table-row" <div class="tb-form-table-row align-start"
*ngFor="let relationFilterControl of relationFiltersFormArray.controls; let $index = index"> *ngFor="let relationFilterControl of relationFiltersFormArray.controls; let $index = index">
<mat-chip-listbox *ngIf="enableNotOption" class="flex-18 center-stretch" [formControl]="relationFilterControl.get('negate')">
<mat-chip-option color="primary" [value]="true">{{ 'relation.not' | translate}}</mat-chip-option>
</mat-chip-listbox>
<tb-relation-type-autocomplete subscriptSizing="dynamic" <tb-relation-type-autocomplete subscriptSizing="dynamic"
class="flex-50" showLabel="false" class="flex-50" showLabel="false"
[additionalClasses]="['tb-inline-field']" [additionalClasses]="['tb-inline-field']"

View File

@ -18,6 +18,10 @@
flex: 1 1 50%; flex: 1 1 50%;
} }
.flex-18 {
flex: 1 1 18%;
}
.actions-header { .actions-header {
width: 40px width: 40px
} }

View File

@ -30,6 +30,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-relation-filters', selector: 'tb-relation-filters',
@ -49,6 +50,10 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
@Input() allowedEntityTypes: Array<EntityType | AliasEntityType>; @Input() allowedEntityTypes: Array<EntityType | AliasEntityType>;
@Input()
@coerceBoolean()
enableNotOption = false;
relationFiltersFormGroup: UntypedFormGroup; relationFiltersFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
@ -112,18 +117,20 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
public addFilter() { public addFilter() {
const filter: RelationEntityTypeFilter = { const filter: RelationEntityTypeFilter = {
relationType: null, relationType: null,
entityTypes: [], entityTypes: []
negate: false
}; };
this.relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter)); this.relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter));
} }
private createRelationFilterFormGroup(filter: RelationEntityTypeFilter): AbstractControl { private createRelationFilterFormGroup(filter: RelationEntityTypeFilter): AbstractControl {
return this.fb.group({ const formGroup = this.fb.group({
relationType: [filter ? filter.relationType : null], relationType: [filter ? filter.relationType : null],
entityTypes: [filter ? filter.entityTypes : []], entityTypes: [filter ? filter.entityTypes : []]
negate: [filter ? filter.negate : false],
}); });
if (this.enableNotOption) {
formGroup.addControl('negate', this.fb.control(filter ? filter.negate : false));
}
return formGroup;
} }
private updateModel() { private updateModel() {

View File

@ -15,9 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" class="tb-entity-select" [formGroup]="entitySelectFormGroup"> <div [formGroup]="entitySelectFormGroup">
<tb-entity-type-select <tb-entity-type-select
style="min-width: 100px; padding-right: 8px;"
*ngIf="displayEntityTypeSelect" *ngIf="displayEntityTypeSelect"
[showLabel]="true" [showLabel]="true"
[required]="required" [required]="required"
@ -26,7 +25,6 @@
formControlName="entityType"> formControlName="entityType">
</tb-entity-type-select> </tb-entity-type-select>
<tb-entity-autocomplete <tb-entity-autocomplete
fxFlex
*ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT *ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT
&& modelValue.entityType !== AliasEntityType.CURRENT_USER && modelValue.entityType !== AliasEntityType.CURRENT_USER
&& modelValue.entityType !== AliasEntityType.CURRENT_USER_OWNER" && modelValue.entityType !== AliasEntityType.CURRENT_USER_OWNER"

View File

@ -17,8 +17,7 @@
--> -->
<mat-form-field [formGroup]="entityTypeFormGroup"> <mat-form-field [formGroup]="entityTypeFormGroup">
<mat-label *ngIf="showLabel">{{ 'entity.type' | translate }}</mat-label> <mat-label *ngIf="showLabel">{{ 'entity.type' | translate }}</mat-label>
<mat-select [required]="required" <mat-select [required]="required" matInput formControlName="entityType">
class="tb-entity-type-select" matInput formControlName="entityType">
<mat-option *ngFor="let type of entityTypes" [value]="type"> <mat-option *ngFor="let type of entityTypes" [value]="type">
{{ displayEntityTypeFn(type) }} {{ displayEntityTypeFn(type) }}
</mat-option> </mat-option>

View File

@ -130,7 +130,6 @@ export interface RelationsQueryFilter {
filters?: Array<RelationEntityTypeFilter>; filters?: Array<RelationEntityTypeFilter>;
maxLevel?: number; maxLevel?: number;
fetchLastLevelOnly?: boolean; fetchLastLevelOnly?: boolean;
negate?: boolean;
} }
export interface EntitySearchQueryFilter { export interface EntitySearchQueryFilter {

View File

@ -638,6 +638,7 @@
"filter-type-edge-search-query-description": "Edges with types {{edgeTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-edge-search-query-description": "Edges with types {{edgeTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"entity-filter": "Entity filter", "entity-filter": "Entity filter",
"resolve-multiple": "Resolve as multiple entities", "resolve-multiple": "Resolve as multiple entities",
"resolve-multiple-hint": "Enable to render data from all filtered entities simultaneously. \nIf disabled, the widget shows data from the selected entity only.",
"filter-type": "Filter type", "filter-type": "Filter type",
"filter-type-required": "Filter type is required.", "filter-type-required": "Filter type is required.",
"entity-filter-no-entity-matched": "No entities matching specified filter were found.", "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
@ -3830,8 +3831,7 @@
"additional-info": "Additional info (JSON)", "additional-info": "Additional info (JSON)",
"invalid-additional-info": "Unable to parse additional info json.", "invalid-additional-info": "Unable to parse additional info json.",
"no-relations-text": "No relations found", "no-relations-text": "No relations found",
"negate-all-relations": "Negate all relations (NOT condition)", "not": "Not"
"negate-relation": "Negate this relation (NOT condition)"
}, },
"resource": { "resource": {
"add": "Add resource", "add": "Add resource",

View File

@ -527,6 +527,9 @@
&.no-padding-right { &.no-padding-right {
padding-right: 0; padding-right: 0;
} }
&.align-start {
align-items: start;
}
@media #{$mat-gt-md} { @media #{$mat-gt-md} {
gap: 12px; gap: 12px;
padding-left: 12px; padding-left: 12px;