NIFI-13670: Improve sensitive value editing. (#9191)

* NIFI-13670: Improve sensitive value editing.
- Do not present the user with the masked value.
- Do not use the masked value when determining if a value has changed.
- Do not include the masked value in the payload if the value hasn't changed.
- Updates to ParameterItem model to more easily track changes.

* NIFI-13670: Not setting valueRemoved when deleting a parameter.

* NIFI-13670: Marking field as dirty following removal of sensitive value set text.
This commit is contained in:
Matt Gilman 2024-08-26 13:56:02 -04:00 committed by GitHub
parent cbdc53a565
commit 7945234f5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 314 additions and 135 deletions

View File

@ -113,7 +113,7 @@ export class ParameterContextListingEffects {
dialogReference.componentInstance.createNewParameter = (
existingParameters: string[]
): Observable<Parameter> => {
): Observable<EditParameterResponse> => {
const dialogRequest: EditParameterRequest = { existingParameters };
const newParameterDialogReference = this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
@ -128,13 +128,15 @@ export class ParameterContextListingEffects {
newParameterDialogReference.close();
return {
...dialogResponse.parameter
...dialogResponse
};
})
);
};
dialogReference.componentInstance.editParameter = (parameter: Parameter): Observable<Parameter> => {
dialogReference.componentInstance.editParameter = (
parameter: Parameter
): Observable<EditParameterResponse> => {
const dialogRequest: EditParameterRequest = {
parameter: {
...parameter
@ -153,7 +155,7 @@ export class ParameterContextListingEffects {
editParameterDialogReference.close();
return {
...dialogResponse.parameter
...dialogResponse
};
})
);
@ -317,7 +319,7 @@ export class ParameterContextListingEffects {
editDialogReference.componentInstance.createNewParameter = (
existingParameters: string[]
): Observable<Parameter> => {
): Observable<EditParameterResponse> => {
const dialogRequest: EditParameterRequest = { existingParameters };
const newParameterDialogReference = this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
@ -332,7 +334,7 @@ export class ParameterContextListingEffects {
newParameterDialogReference.close();
return {
...dialogResponse.parameter
...dialogResponse
};
})
);
@ -340,7 +342,7 @@ export class ParameterContextListingEffects {
editDialogReference.componentInstance.editParameter = (
parameter: Parameter
): Observable<Parameter> => {
): Observable<EditParameterResponse> => {
const dialogRequest: EditParameterRequest = {
parameter: {
...parameter
@ -359,7 +361,7 @@ export class ParameterContextListingEffects {
editParameterDialogReference.close();
return {
...dialogResponse.parameter
...dialogResponse
};
})
);

View File

@ -31,6 +31,7 @@ import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spin
import { Client } from '../../../../../service/client.service';
import { ParameterTable } from '../parameter-table/parameter-table.component';
import {
EditParameterResponse,
Parameter,
ParameterContextEntity,
ParameterContextUpdateRequestEntity,
@ -73,8 +74,8 @@ import { NiFiCommon, TextTip, NifiTooltipDirective } from '@nifi/shared';
styleUrls: ['./edit-parameter-context.component.scss']
})
export class EditParameterContext extends TabbedDialog {
@Input() createNewParameter!: (existingParameters: string[]) => Observable<Parameter>;
@Input() editParameter!: (parameter: Parameter) => Observable<Parameter>;
@Input() createNewParameter!: (existingParameters: string[]) => Observable<EditParameterResponse>;
@Input() editParameter!: (parameter: Parameter) => Observable<EditParameterResponse>;
@Input() updateRequest!: Observable<ParameterContextUpdateRequestEntity | null>;
@Input() availableParameterContexts$!: Observable<ParameterContextEntity[]>;
@Input() saving$!: Observable<boolean>;

View File

@ -41,15 +41,15 @@
<div class="flex justify-between items-center">
<div
class="whitespace-nowrap overflow-hidden text-ellipsis leading-normal"
[title]="item.entity.parameter.name">
{{ item.entity.parameter.name }}
[title]="item.originalEntity.parameter.name">
{{ item.originalEntity.parameter.name }}
</div>
@if (hasDescription(item)) {
<i
class="fa fa-info-circle primary-color"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="item.entity.parameter.description"
[tooltipInputData]="getDescription(item)"
[delayClose]="false"></i>
}
@if (canOverride(item)) {
@ -68,42 +68,46 @@
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef>Value</th>
<td mat-cell *matCellDef="let item">
@if (isNull(item.entity.parameter.value)) {
<div class="unset surface-color">No value set</div>
} @else {
<ng-container
*ngTemplateOutlet="
isSensitiveParameter(item) ? sensitive : nonSensitive;
context: { $implicit: item.entity.parameter.value }
"></ng-container>
<ng-template #sensitive>
<div class="sensitive surface-color">Sensitive value set</div>
</ng-template>
<ng-template #nonSensitive let-value>
<ng-container
*ngTemplateOutlet="renderValue; context: { $implicit: getValue(item) }"></ng-container>
<ng-template #renderValue let-parameterValue>
@if (isNull(parameterValue)) {
<div class="unset surface-color">No value set</div>
} @else {
<ng-container
*ngTemplateOutlet="
isEmptyString(value) ? blank : nonBlank;
context: { $implicit: value }
isSensitiveParameter(item) ? sensitive : nonSensitive;
context: { $implicit: parameterValue }
"></ng-container>
</ng-template>
<ng-template #blank>
<div class="empty surface-color">Empty string set</div>
</ng-template>
<ng-template #nonBlank let-value>
<div class="flex justify-between items-center">
<div class="whitespace-nowrap overflow-hidden text-ellipsis">
{{ value }}
<ng-template #sensitive>
<div class="sensitive surface-color">Sensitive value set</div>
</ng-template>
<ng-template #nonSensitive let-value>
<ng-container
*ngTemplateOutlet="
isEmptyString(value) ? blank : nonBlank;
context: { $implicit: value }
"></ng-container>
</ng-template>
<ng-template #blank>
<div class="empty surface-color">Empty string set</div>
</ng-template>
<ng-template #nonBlank let-value>
<div class="flex justify-between items-center">
<div class="whitespace-nowrap overflow-hidden text-ellipsis">
{{ value }}
</div>
@if (hasExtraWhitespace(value)) {
<div
class="fa fa-info-circle primary-color"
nifiTooltip
[tooltipComponentType]="TextTip"
tooltipInputData="The specified value contains leading and/or trailing whitespace character(s). This could produce unexpected results if it was not intentional."></div>
}
</div>
@if (hasExtraWhitespace(value)) {
<div
class="fa fa-info-circle primary-color"
nifiTooltip
[tooltipComponentType]="TextTip"
tooltipInputData="The specified value contains leading and/or trailing whitespace character(s). This could produce unexpected results if it was not intentional."></div>
}
</div>
</ng-template>
}
</ng-template>
}
</ng-template>
</td>
</ng-container>
@ -171,7 +175,7 @@
<div>Parameter</div>
<div class="accent-color font-medium">
@if (selectedItem) {
<span>{{ selectedItem.entity.parameter.name }}</span>
<span>{{ selectedItem.originalEntity.parameter.name }}</span>
} @else {
<span class="unset surface-color">None</span>
}
@ -191,7 +195,7 @@
@if (selectedItem) {
<parameter-references
[parameterReferences]="
selectedItem.entity.parameter.referencingComponents
selectedItem.originalEntity.parameter.referencingComponents
"></parameter-references>
}
</div>

View File

@ -23,7 +23,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
import { RouterLink } from '@angular/router';
import { Parameter, ParameterEntity } from '../../../../../state/shared';
import { EditParameterResponse, Parameter, ParameterEntity } from '../../../../../state/shared';
import { NifiTooltipDirective, NiFiCommon, TextTip } from '@nifi/shared';
import { Observable, take } from 'rxjs';
import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component';
@ -37,7 +37,8 @@ export interface ParameterItem {
deleted: boolean;
dirty: boolean;
added: boolean;
entity: ParameterEntity;
originalEntity: ParameterEntity; // either new or existing from server
updatedEntity?: ParameterEntity;
}
@Component({
@ -70,8 +71,8 @@ export interface ParameterItem {
]
})
export class ParameterTable implements AfterViewInit, ControlValueAccessor {
@Input() createNewParameter!: (existingParameters: string[]) => Observable<Parameter>;
@Input() editParameter!: (parameter: Parameter) => Observable<Parameter>;
@Input() createNewParameter!: (existingParameters: string[]) => Observable<EditParameterResponse>;
@Input() editParameter!: (parameter: Parameter) => Observable<EditParameterResponse>;
@Input() canAddParameters = true;
protected readonly TextTip = TextTip;
@ -128,10 +129,11 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
deleted: false,
added: false,
dirty: false,
entity: {
originalEntity: {
...entity,
parameter: {
...entity.parameter
...entity.parameter,
value: entity.parameter.value === undefined ? null : entity.parameter.value
}
}
};
@ -161,7 +163,10 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(a.entity.parameter.name, b.entity.parameter.name);
retVal = this.nifiCommon.compareString(
a.originalEntity.parameter.name,
b.originalEntity.parameter.name
);
break;
default:
return 0;
@ -176,25 +181,27 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
// unmarked for deletion if the user chooses to enter the same name
const existingParameters: string[] = this.dataSource.data
.filter((item) => !item.deleted)
.filter((item) => !item.entity.parameter.inherited)
.map((item) => item.entity.parameter.name);
.filter((item) => !item.originalEntity.parameter.inherited)
.map((item) => item.originalEntity.parameter.name);
this.createNewParameter(existingParameters)
.pipe(take(1))
.subscribe((parameter) => {
.subscribe((response: EditParameterResponse) => {
const parameter: Parameter = response.parameter;
const currentParameterItems: ParameterItem[] = this.dataSource.data;
// identify if a parameter with the same name already exists (must have been marked
// for deletion already)
const item: ParameterItem | undefined = currentParameterItems.find(
(item) => item.entity.parameter.name === parameter.name
(item) => item.originalEntity.parameter.name === parameter.name
);
if (item) {
// if the item is added that means it hasn't been saved yet. in this case, we
// can simply update the existing parameter. if the item has been saved, and the
// sensitivity has changed, the user must apply the changes first.
if (!item.added && item.entity.parameter.sensitive !== parameter.sensitive) {
if (!item.added && item.originalEntity.parameter.sensitive !== parameter.sensitive) {
this.store.dispatch(
showOkDialog({
title: 'Parameter Exists',
@ -208,15 +215,21 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
// update the existing item
item.deleted = false;
item.dirty = true;
item.entity.parameter = {
...parameter
};
// since the item is either new or the sensitivity is the same, accept the sensitivity
// from the response to be set on the entities
item.originalEntity.parameter.sensitive = parameter.sensitive;
if (item.updatedEntity) {
item.updatedEntity.parameter.sensitive = parameter.sensitive;
}
this.applyParameterEdit(item, response);
} else {
const newItem: ParameterItem = {
deleted: false,
added: true,
dirty: true,
entity: {
originalEntity: {
canWrite: true,
parameter: {
...parameter
@ -232,11 +245,36 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
}
hasDescription(item: ParameterItem): boolean {
return !this.nifiCommon.isBlank(item.entity.parameter.description);
return !this.nifiCommon.isBlank(this.getDescription(item));
}
getDescription(item: ParameterItem): string {
if (item.updatedEntity) {
return item.updatedEntity.parameter.description;
} else {
return item.originalEntity.parameter.description;
}
}
isSensitiveParameter(item: ParameterItem): boolean {
return item.entity.parameter.sensitive;
return item.originalEntity.parameter.sensitive;
}
getValue(item: ParameterItem): string | null {
if (item.updatedEntity) {
if (item.updatedEntity.parameter.valueRemoved) {
return null;
} else {
// if there is an updated value use that, otherwise the original
if (item.updatedEntity.parameter.value !== null) {
return item.updatedEntity.parameter.value;
} else {
return item.originalEntity.parameter.value;
}
}
} else {
return item.originalEntity.parameter.value;
}
}
isNull(value: string): boolean {
@ -253,35 +291,37 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
canGoToParameter(item: ParameterItem): boolean {
return (
item.entity.parameter.inherited === true &&
item.entity.parameter.parameterContext?.permissions.canRead == true
item.originalEntity.parameter.inherited === true &&
item.originalEntity.parameter.parameterContext?.permissions.canRead == true
);
}
getParameterLink(item: ParameterItem): string[] {
if (item.entity.parameter.parameterContext) {
if (item.originalEntity.parameter.parameterContext) {
// TODO - support routing directly to a parameter
return ['/parameter-contexts', item.entity.parameter.parameterContext.id, 'edit'];
return ['/parameter-contexts', item.originalEntity.parameter.parameterContext.id, 'edit'];
}
return [];
}
canOverride(item: ParameterItem): boolean {
return item.entity.parameter.inherited === true;
return item.originalEntity.parameter.inherited === true;
}
overrideParameter(item: ParameterItem): void {
const overriddenParameter: Parameter = {
...item.entity.parameter,
...item.originalEntity.parameter,
value: null
};
this.editParameter(overriddenParameter)
.pipe(take(1))
.subscribe((parameter) => {
.subscribe((response) => {
item.dirty = true;
item.entity.parameter = {
...parameter
item.updatedEntity = {
parameter: {
...response.parameter
}
};
this.handleChanged();
@ -289,46 +329,93 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
}
canEdit(item: ParameterItem): boolean {
const canWrite: boolean = item.entity.canWrite == true;
const provided: boolean = item.entity.parameter.provided == true;
const inherited: boolean = item.entity.parameter.inherited == true;
const canWrite: boolean = item.originalEntity.canWrite == true;
const provided: boolean = item.originalEntity.parameter.provided == true;
const inherited: boolean = item.originalEntity.parameter.inherited == true;
return canWrite && !provided && !inherited;
}
editClicked(item: ParameterItem): void {
this.editParameter(item.entity.parameter)
const parameterToEdit: Parameter = {
name: item.originalEntity.parameter.name,
sensitive: item.originalEntity.parameter.sensitive,
description: this.getDescription(item),
value: this.getValue(item)
};
this.editParameter(parameterToEdit)
.pipe(take(1))
.subscribe((parameter) => {
const valueChanged: boolean = item.entity.parameter.value != parameter.value;
const descriptionChanged: boolean = item.entity.parameter.description != parameter.description;
const valueRemovedChanged: boolean = item.entity.parameter.valueRemoved != parameter.valueRemoved;
if (valueChanged || descriptionChanged || valueRemovedChanged) {
item.entity.parameter.value = parameter.value;
item.entity.parameter.description = parameter.description;
item.entity.parameter.valueRemoved = parameter.valueRemoved;
item.dirty = true;
if (valueChanged) {
item.entity.parameter.referencedAssets = undefined;
}
this.handleChanged();
}
.subscribe((response) => {
this.applyParameterEdit(item, response);
});
}
private applyParameterEdit(item: ParameterItem, response: EditParameterResponse) {
const parameter: Parameter = response.parameter;
// initialize the updated entity if this is the first time this has been edited
if (!item.updatedEntity) {
item.updatedEntity = {
parameter: {
...item.originalEntity.parameter,
value: null
}
};
}
let hasChanged: boolean = response.valueChanged;
if (response.valueChanged) {
item.updatedEntity.parameter.value = parameter.value;
// a value has been specified so record it in the updated entity
if (parameter.value !== null) {
item.updatedEntity.parameter.valueRemoved = undefined;
item.updatedEntity.parameter.referencedAssets = undefined;
} else {
// the value is null, this means that the value should be unchanged
// or that the value has been removed. if removed we should
// clear our the value.
if (parameter.valueRemoved === true) {
item.updatedEntity.parameter.value = null;
item.updatedEntity.parameter.valueRemoved = true;
item.updatedEntity.parameter.referencedAssets = undefined;
}
}
}
if (item.updatedEntity.parameter.description !== parameter.description) {
hasChanged = true;
item.updatedEntity.parameter.description = parameter.description;
}
// if any aspect of the parameter has changed, mark it dirty and trigger handle changed
if (hasChanged) {
item.dirty = true;
this.handleChanged();
}
}
canDelete(item: ParameterItem): boolean {
const canWrite: boolean = item.entity.canWrite == true;
const provided: boolean = item.entity.parameter.provided == true;
const inherited: boolean = item.entity.parameter.inherited == true;
const canWrite: boolean = item.originalEntity.canWrite == true;
const provided: boolean = item.originalEntity.parameter.provided == true;
const inherited: boolean = item.originalEntity.parameter.inherited == true;
return canWrite && !provided && !inherited;
}
deleteClicked(item: ParameterItem): void {
if (!item.deleted) {
item.entity.parameter.value = null;
item.entity.parameter.valueRemoved = true;
if (!item.updatedEntity) {
item.updatedEntity = {
parameter: {
...item.originalEntity.parameter
}
};
}
item.updatedEntity.parameter.value = null;
item.updatedEntity.parameter.referencedAssets = undefined;
item.deleted = true;
item.dirty = true;
this.selectParameter(null);
@ -360,20 +447,34 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
.filter((item) => !(item.added && item.deleted))
.map((item) => {
if (item.deleted) {
// item is deleted
return {
parameter: {
name: item.entity.parameter.name
name: item.originalEntity.parameter.name
}
};
} else if (item.updatedEntity) {
// item has been edited
return {
parameter: {
name: item.originalEntity.parameter.name,
sensitive: item.originalEntity.parameter.sensitive,
description: item.updatedEntity.parameter.description,
value: item.updatedEntity.parameter.value,
valueRemoved: item.updatedEntity.parameter.valueRemoved,
referencedAssets: item.updatedEntity.parameter.referencedAssets
}
};
} else {
// item is added (but not subsequently edited)
return {
parameter: {
name: item.entity.parameter.name,
sensitive: item.entity.parameter.sensitive,
description: item.entity.parameter.description,
value: item.entity.parameter.value,
valueRemoved: item.entity.parameter.valueRemoved,
referencedAssets: item.entity.parameter.referencedAssets
name: item.originalEntity.parameter.name,
sensitive: item.originalEntity.parameter.sensitive,
description: item.originalEntity.parameter.description,
value: item.originalEntity.parameter.value,
valueRemoved: item.originalEntity.parameter.valueRemoved,
referencedAssets: item.originalEntity.parameter.referencedAssets
}
};
}
@ -386,7 +487,7 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor {
isSelected(item: ParameterItem): boolean {
if (this.selectedItem) {
return item.entity.parameter.name == this.selectedItem.entity.parameter.name;
return item.originalEntity.parameter.name == this.selectedItem.originalEntity.parameter.name;
}
return false;
}

View File

@ -50,6 +50,7 @@ export interface EditParameterRequest {
export interface EditParameterResponse {
parameter: Parameter;
valueChanged: boolean;
}
export interface AdvancedUiParams {

View File

@ -37,7 +37,12 @@
[tooltipComponentType]="TextTip"
tooltipInputData="Parameter values do not support Expression Language or embedded parameter references."></i>
</mat-label>
<textarea matInput formControlName="value" type="text"></textarea>
<textarea
matInput
formControlName="value"
type="text"
[class.sensitive]="showSensitiveHelperText"
(keydown)="clearSensitiveHelperText()"></textarea>
</mat-form-field>
</div>
<div class="-mt-4 mb-4">

View File

@ -17,7 +17,7 @@
import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { EditParameterRequest, EditParameterResponse, Parameter, ReferencedAsset } from '../../../state/shared';
import { EditParameterRequest, EditParameterResponse, Parameter } from '../../../state/shared';
import { MatButtonModule } from '@angular/material/button';
import {
AbstractControl,
@ -67,6 +67,8 @@ export class EditParameterDialog extends CloseOnEscapeDialog {
sensitive: FormControl;
editParameterForm: FormGroup;
isNew: boolean;
showSensitiveHelperText: boolean = false;
valueInputTriggered: boolean = false;
private originalParameter: Parameter | undefined = undefined;
@ -81,10 +83,14 @@ export class EditParameterDialog extends CloseOnEscapeDialog {
const parameter: Parameter | undefined = request.parameter;
this.originalParameter = parameter;
let value: string | null;
const validators: any[] = [Validators.required];
if (request.existingParameters) {
this.isNew = true;
value = parameter ? parameter.value : null;
// since there were existing parameters in the request, add the existing parameters validator because
// parameters names must be unique
validators.push(this.existingParameterValidator(request.existingParameters));
@ -100,20 +106,25 @@ export class EditParameterDialog extends CloseOnEscapeDialog {
} else {
this.isNew = false;
value = parameter ? parameter.value : null;
const sensitive = parameter ? parameter.sensitive : false;
if (sensitive && value !== null) {
value = 'Sensitive value set';
this.showSensitiveHelperText = true;
}
// without existingParameters, we are editing an existing parameter. in this case the name and sensitivity cannot be modified
this.name = new FormControl(
{ value: parameter ? parameter.name : '', disabled: true },
Validators.required
);
this.sensitive = new FormControl(
{ value: parameter ? parameter.sensitive : false, disabled: true },
Validators.required
);
this.sensitive = new FormControl({ value: sensitive, disabled: true }, Validators.required);
}
this.editParameterForm = this.formBuilder.group({
name: this.name,
value: new FormControl(parameter ? parameter.value : null),
value: new FormControl(value),
empty: new FormControl(parameter ? parameter.value == '' : false),
sensitive: this.sensitive,
description: new FormControl(parameter ? parameter.description : '')
@ -146,12 +157,23 @@ export class EditParameterDialog extends CloseOnEscapeDialog {
return this.name.hasError('existingParameter') ? 'A parameter with this name already exists.' : '';
}
clearSensitiveHelperText(): void {
if (this.showSensitiveHelperText) {
this.editParameterForm.get('value')?.setValue('');
this.showSensitiveHelperText = false;
}
this.valueInputTriggered = true;
}
setEmptyStringChanged(): void {
const emptyStringChecked: AbstractControl | null = this.editParameterForm.get('empty');
if (emptyStringChecked) {
if (emptyStringChecked.value) {
this.editParameterForm.get('value')?.setValue('');
this.editParameterForm.get('value')?.disable();
this.valueInputTriggered = true;
} else {
this.editParameterForm.get('value')?.enable();
}
@ -162,24 +184,50 @@ export class EditParameterDialog extends CloseOnEscapeDialog {
this.cancel.next();
}
okClicked(): void {
const value: string = this.editParameterForm.get('value')?.value;
const empty: boolean = this.editParameterForm.get('empty')?.value;
let referencedAssets: ReferencedAsset[] | undefined = undefined;
private valueChanged(enteredValue: string | null): boolean {
let valueChanged = true;
if (this.originalParameter) {
referencedAssets = this.originalParameter.referencedAssets;
if (this.originalParameter.sensitive) {
valueChanged = this.valueInputTriggered;
} else {
valueChanged = enteredValue !== this.originalParameter.value;
}
}
return valueChanged;
}
okClicked(): void {
const value: string | null = this.editParameterForm.get('value')?.value;
const empty: boolean = this.editParameterForm.get('empty')?.value;
const parameter: Parameter = {
name: this.editParameterForm.get('name')?.value,
value: null,
sensitive: this.editParameterForm.get('sensitive')?.value,
description: this.editParameterForm.get('description')?.value
};
// update the parameter value
let valueChanged = this.valueChanged(value);
if (valueChanged) {
parameter.value = value;
}
// indicate if the value has been removed
const valueRemoved = value === '' && !empty;
if (valueRemoved) {
valueChanged = true;
parameter.value = null;
// if this is a new parameter there is no need to indicate that the value is removed
if (!this.isNew) {
parameter.valueRemoved = true;
}
}
this.editParameter.next({
parameter: {
name: this.editParameterForm.get('name')?.value,
value: value === '' && !empty ? null : value,
valueRemoved: value === '' && !empty,
sensitive: this.editParameterForm.get('sensitive')?.value,
description: this.editParameterForm.get('description')?.value,
referencedAssets
}
parameter,
valueChanged
});
}

View File

@ -47,7 +47,7 @@
</ng-template>
</div>
<div class="flex flex-col gap-y-0.5 flex-1">
<div class="nf-editor flex-1" [class.blank]="blank">
<div class="nf-editor flex-1" [class.blank]="blank" [class.sensitive]="showSensitiveHelperText">
<ngx-codemirror
[options]="getOptions()"
formControlName="value"

View File

@ -55,7 +55,13 @@ import { Editor } from 'codemirror';
})
export class NfEditor implements OnDestroy {
@Input() set item(item: PropertyItem) {
this.nfEditorForm.get('value')?.setValue(item.value);
if (item.descriptor.sensitive && item.value !== null) {
this.nfEditorForm.get('value')?.setValue('Sensitive value set');
this.showSensitiveHelperText = true;
} else {
this.nfEditorForm.get('value')?.setValue(item.value);
}
if (item.descriptor.required) {
this.nfEditorForm.get('value')?.addValidators(Validators.required);
} else {
@ -67,7 +73,6 @@ export class NfEditor implements OnDestroy {
this.setEmptyStringChanged();
this.supportsEl = item.descriptor.supportsEl;
this.sensitive = item.descriptor.sensitive;
this.mode = this.supportsEl ? this.nfel.getLanguageId() : this.nfpr.getLanguageId();
this.itemSet = true;
@ -93,7 +98,7 @@ export class NfEditor implements OnDestroy {
getParametersSet = false;
nfEditorForm: FormGroup;
sensitive = false;
showSensitiveHelperText = false;
supportsEl = false;
supportsParameters = false;
blank = false;
@ -124,6 +129,18 @@ export class NfEditor implements OnDestroy {
this.editor.execCommand('selectAll');
}
if (this.showSensitiveHelperText) {
const clearSensitiveHelperText = () => {
if (this.showSensitiveHelperText) {
this.nfEditorForm.get('value')?.setValue('');
this.nfEditorForm.get('value')?.markAsDirty();
this.showSensitiveHelperText = false;
}
};
this.editor.on('keydown', clearSensitiveHelperText);
}
// 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) {

View File

@ -56,8 +56,8 @@
.unset,
.sensitive {
font-weight: normal !important;
font-style: italic;
opacity: 0.8;
font-style: italic !important;
opacity: 0.8 !important;
}
.context-logo {