NIFI-13136: Allowing users to unset optional property values (#8734)

* NIFI-13136:
- Allowing users to unset optional property values.
- Only selecting value and applying focus if it is not read only.

* NIFI-13136:
- Addressing review feedback.
- Adding styles to disabled editor input.
- Fixing show hint/autocomplete in production build.

This closes #8734
This commit is contained in:
Matt Gilman 2024-05-03 15:18:27 -04:00 committed by GitHub
parent 37937ffa15
commit 914e2b1057
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 74 additions and 38 deletions

View File

@ -28,7 +28,6 @@ import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
import { ParameterContextEntity, SelectOption } from '../../../../../../../state/shared';
import { Client } from '../../../../../../../service/client.service';
import { PropertyTable } from '../../../../../../../ui/common/property-table/property-table.component';
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
import { NifiTooltipDirective } from '../../../../../../../ui/common/tooltips/nifi-tooltip.directive';
import { TextTip } from '../../../../../../../ui/common/tooltips/text-tip/text-tip.component';
@ -51,7 +50,6 @@ import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-b
MatOptionModule,
MatSelectModule,
AsyncPipe,
PropertyTable,
NifiSpinnerDirective,
NifiTooltipDirective,
FormsModule,

View File

@ -27,6 +27,8 @@ import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../../../state/error/error.reducer';
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
import 'codemirror/addon/hint/show-hint';
describe('EditProcessor', () => {
let component: EditProcessor;
let fixture: ComponentFixture<EditProcessor>;

View File

@ -27,6 +27,8 @@ import { initialState } from '../../../state/parameter-context-listing/parameter
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
import { ParameterContextEntity } from '../../../../../state/shared';
import 'codemirror/addon/hint/show-hint';
describe('EditParameterContext', () => {
let component: EditParameterContext;
let fixture: ComponentFixture<EditParameterContext>;

View File

@ -26,6 +26,8 @@ import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/error/error.reducer';
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
import 'codemirror/addon/hint/show-hint';
describe('EditFlowAnalysisRule', () => {
let component: EditFlowAnalysisRule;
let fixture: ComponentFixture<EditFlowAnalysisRule>;

View File

@ -26,6 +26,8 @@ import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/error/error.reducer';
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
import 'codemirror/addon/hint/show-hint';
describe('EditRegistryClient', () => {
let component: EditRegistryClient;
let fixture: ComponentFixture<EditRegistryClient>;

View File

@ -26,6 +26,8 @@ import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/error/error.reducer';
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
import 'codemirror/addon/hint/show-hint';
describe('EditReportingTask', () => {
let component: EditReportingTask;
let fixture: ComponentFixture<EditReportingTask>;

View File

@ -28,7 +28,6 @@ import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { PropertyTable } from '../../property-table/property-table.component';
import { ControllerServiceApi } from '../controller-service-api/controller-service-api.component';
import { ControllerServiceReferences } from '../controller-service-references/controller-service-references.component';
import { NifiSpinnerDirective } from '../../spinner/nifi-spinner.directive';
@ -59,7 +58,6 @@ import {
MatTabsModule,
MatOptionModule,
MatSelectModule,
PropertyTable,
ControllerServiceApi,
ControllerServiceReferences,
AsyncPipe,

View File

@ -26,6 +26,8 @@ import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../state/error/error.reducer';
import { ClusterConnectionService } from '../../../../service/cluster-connection.service';
import 'codemirror/addon/hint/show-hint';
describe('EditControllerService', () => {
let component: EditControllerService;
let fixture: ComponentFixture<EditControllerService>;

View File

@ -30,7 +30,6 @@ import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { PropertyTable } from '../../property-table/property-table.component';
import { ControllerServiceApi } from '../controller-service-api/controller-service-api.component';
import { ControllerServiceReferences } from '../controller-service-references/controller-service-references.component';
import { NifiSpinnerDirective } from '../../spinner/nifi-spinner.directive';
@ -67,7 +66,6 @@ import {
MatTabsModule,
MatOptionModule,
MatSelectModule,
PropertyTable,
ControllerServiceApi,
ControllerServiceReferences,
AsyncPipe,

View File

@ -61,17 +61,14 @@
@include mat.button-density(-1);
.nf-editor {
border-color: var(--mdc-outlined-text-field-label-text-color);
&.blank {
border-color: var(--mdc-outlined-text-field-disabled-label-text-color);
}
.CodeMirror {
background-color: if($is-dark, $nifi-theme-surface-palette-darker, $nifi-theme-surface-palette-lighter);
&.blank {
background: $material-theme-primary-palette-default;
color: if(
$is-dark,
$material-theme-primary-palette-darker,
$material-theme-primary-palette-lighter
);
}
}
.CodeMirror-code {

View File

@ -18,7 +18,6 @@
import { ComponentRef, Injectable, Renderer2, ViewContainerRef } from '@angular/core';
import * as CodeMirror from 'codemirror';
import { Editor, Hint, Hints, StringStream } from 'codemirror';
import 'codemirror/addon/hint/show-hint';
import { ElFunction, Parameter } from '../../../../../../state/shared';
import { ParameterTip } from '../../../../tooltips/parameter-tip/parameter-tip.component';
import { ElService } from './el.service';

View File

@ -21,7 +21,7 @@
cdkDrag
resizable
(resized)="resized()">
<form class="h-full" [formGroup]="nfEditorForm" cdkTrapFocus cdkTrapFocusAutoCapture>
<form class="h-full" [formGroup]="nfEditorForm" cdkTrapFocus [cdkTrapFocusAutoCapture]="!readonly">
<div class="flex flex-col gap-y-3 h-full">
<div class="flex justify-end">
<div
@ -47,10 +47,9 @@
</ng-template>
</div>
<div class="flex flex-col gap-y-0.5 flex-1">
<div class="nf-editor flex-1" #nfEditorContainer>
<div class="nf-editor flex-1" [class.blank]="blank">
<ngx-codemirror
[options]="getOptions()"
[autoFocus]="true"
formControlName="value"
(mousedown)="preventDrag($event)"
(codeMirrorLoaded)="codeMirrorLoaded($event)"></ngx-codemirror>

View File

@ -31,7 +31,8 @@
.nf-editor {
min-height: 100px;
min-width: 210px;
border: 1px solid;
border-width: 1px;
border-style: solid;
cursor: default;
.CodeMirror {
@ -40,11 +41,6 @@
font-family: monospace;
cursor: default;
line-height: normal;
&.blank {
border-width: 1px;
border-style: solid;
}
}
.CodeMirror-scroll {

View File

@ -21,6 +21,8 @@ import { NfEditor } from './nf-editor.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { PropertyItem } from '../../property-table.component';
import 'codemirror/addon/hint/show-hint';
describe('NfEditor', () => {
let component: NfEditor;
let fixture: ComponentFixture<NfEditor>;

View File

@ -57,15 +57,16 @@ import { Resizable } from '../../../resizable/resizable.component';
export class NfEditor implements OnDestroy {
@Input() set item(item: PropertyItem) {
this.nfEditorForm.get('value')?.setValue(item.value);
const isEmptyString: boolean = item.value == '';
this.nfEditorForm.get('setEmptyString')?.setValue(isEmptyString);
if (isEmptyString) {
this.nfEditorForm.get('value')?.disable();
if (item.descriptor.required) {
this.nfEditorForm.get('value')?.addValidators(Validators.required);
} else {
this.nfEditorForm.get('value')?.enable();
this.nfEditorForm.get('value')?.removeValidators(Validators.required);
}
const isEmptyString: boolean = item.value === '';
this.nfEditorForm.get('setEmptyString')?.setValue(isEmptyString);
this.setEmptyStringChanged();
this.supportsEl = item.descriptor.supportsEl;
this.sensitive = item.descriptor.sensitive;
this.mode = this.supportsEl ? this.nfel.getLanguageId() : this.nfpr.getLanguageId();
@ -83,7 +84,7 @@ export class NfEditor implements OnDestroy {
@Input() width!: number;
@Input() readonly: boolean = false;
@Output() ok: EventEmitter<string> = new EventEmitter<string>();
@Output() ok: EventEmitter<string | null> = new EventEmitter<string | null>();
@Output() cancel: EventEmitter<void> = new EventEmitter<void>();
protected readonly PropertyHintTip = PropertyHintTip;
@ -95,6 +96,7 @@ export class NfEditor implements OnDestroy {
sensitive = false;
supportsEl = false;
supportsParameters = false;
blank = false;
mode!: string;
_parameters!: Parameter[];
@ -109,7 +111,7 @@ export class NfEditor implements OnDestroy {
private nfpr: NfPr
) {
this.nfEditorForm = this.formBuilder.group({
value: new FormControl('', Validators.required),
value: new FormControl(''),
setEmptyString: new FormControl(false)
});
}
@ -117,7 +119,17 @@ export class NfEditor implements OnDestroy {
codeMirrorLoaded(codeEditor: any): void {
this.editor = codeEditor.codeMirror;
this.editor.setSize('100%', '100%');
this.editor.execCommand('selectAll');
if (!this.readonly) {
this.editor.execCommand('selectAll');
}
// disabling of the input through the form isn't supported until codemirror
// has loaded so we must disable again if the value is an empty string
if (this.nfEditorForm.get('setEmptyString')?.value) {
this.nfEditorForm.get('value')?.disable();
this.editor.setOption('readOnly', 'nocursor');
}
}
loadParameters(): void {
@ -164,7 +176,9 @@ export class NfEditor implements OnDestroy {
extraKeys: {
'Ctrl-Space': 'autocomplete',
Enter: () => {
this.okClicked();
if (this.nfEditorForm.dirty && this.nfEditorForm.valid) {
this.okClicked();
}
}
}
};
@ -188,6 +202,8 @@ export class NfEditor implements OnDestroy {
setEmptyStringChanged(): void {
const emptyStringChecked: AbstractControl | null = this.nfEditorForm.get('setEmptyString');
if (emptyStringChecked) {
this.blank = emptyStringChecked.value;
if (emptyStringChecked.value) {
this.nfEditorForm.get('value')?.setValue('');
this.nfEditorForm.get('value')?.disable();
@ -207,8 +223,18 @@ export class NfEditor implements OnDestroy {
okClicked(): void {
const valueControl: AbstractControl | null = this.nfEditorForm.get('value');
if (valueControl) {
this.ok.next(valueControl.value);
const emptyStringChecked: AbstractControl | null = this.nfEditorForm.get('setEmptyString');
if (valueControl && emptyStringChecked) {
const value = valueControl.value;
if (value === '') {
if (emptyStringChecked.value) {
this.ok.next('');
} else {
this.ok.next(null);
}
} else {
this.ok.next(value);
}
}
}

View File

@ -19,6 +19,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PropertyTable } from './property-table.component';
import 'codemirror/addon/hint/show-hint';
describe('PropertyTable', () => {
let component: PropertyTable;
let fixture: ComponentFixture<PropertyTable>;

View File

@ -554,7 +554,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
}
}
savePropertyValue(item: PropertyItem, newValue: string): void {
savePropertyValue(item: PropertyItem, newValue: string | null): void {
if (item.value != newValue) {
item.value = newValue;
item.dirty = true;

View File

@ -19,6 +19,15 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/fold/foldcode';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/markdown-fold';
import 'codemirror/addon/fold/xml-fold';
import 'codemirror/addon/hint/show-hint';
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));