This is an automated email from the ASF dual-hosted git repository.
rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new f6d88ae610 NIFI-14965: Adding the property value tooltip that helps
see embedded… (#10304)
f6d88ae610 is described below
commit f6d88ae610a75c50d4bbc3638e12287bf17f669f
Author: Matt Gilman <[email protected]>
AuthorDate: Mon Sep 15 14:26:44 2025 -0400
NIFI-14965: Adding the property value tooltip that helps see embedded…
(#10304)
* NIFI-14965: Adding the property value tooltip that helps see embedded
parameter values.
* NIFI-14965: Addressing review feedback.
This closes #10304
---
.../apps/nifi/src/app/state/shared/index.ts | 5 +
.../property-table/property-table.component.html | 6 +-
.../property-table/property-table.component.ts | 22 ++-
.../property-value-tip.component.html} | 25 ++--
.../property-value-tip.component.scss | 16 +++
.../property-value-tip.component.spec.ts | 159 +++++++++++++++++++++
.../property-value-tip.component.ts | 83 +++++++++++
.../parameter-tip/parameter-tip.component.html | 9 ++
8 files changed, 314 insertions(+), 11 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
index 5ebac560e6..a32b6ce6f9 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
@@ -245,6 +245,11 @@ export interface BulletinsTipInput {
bulletins: BulletinEntity[];
}
+export interface PropertyValueTipInput {
+ parameters: ParameterEntity[];
+ property: Property;
+}
+
export interface PropertyTipInput {
descriptor: PropertyDescriptor;
propertyHistory?: PropertyHistory;
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.html
index e790128d74..144425b0f9 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.html
@@ -84,7 +84,11 @@
<div class="flex justify-between
items-center">
<div
class="whitespace-nowrap
overflow-hidden text-ellipsis"
- [title]="resolvedValue">
+ nifiTooltip
+
[tooltipComponentType]="PropertyValueTip"
+
[tooltipInputData]="getPropertyValueTipData(item)"
+ [position]="tooltipPosition"
+ [delayClose]="false">
{{ resolvedValue }}
</div>
@if
(hasExtraWhitespace(resolvedValue)) {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.ts
index 230040f1d5..34ae54f000 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.ts
@@ -42,13 +42,15 @@ import {
Property,
PropertyDependency,
PropertyDescriptor,
- PropertyTipInput
+ PropertyTipInput,
+ PropertyValueTipInput
} from '../../../state/shared';
import { PropertyTip } from '../tooltips/property-tip/property-tip.component';
import { NfEditor } from './editors/nf-editor/nf-editor.component';
import {
CdkConnectedOverlay,
CdkOverlayOrigin,
+ ConnectedPosition,
ConnectionPositionPair,
OriginConnectionPosition,
OverlayConnectionPosition
@@ -59,6 +61,7 @@ import { takeUntilDestroyed } from
'@angular/core/rxjs-interop';
import { ConvertToParameterResponse } from
'../../../pages/flow-designer/service/parameter-helper.service';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { PropertyItem } from './property-item';
+import { PropertyValueTip } from
'../tooltips/property-value-tip/property-value-tip.component';
@Component({
selector: 'property-table',
@@ -138,6 +141,14 @@ export class PropertyTable implements AfterViewInit,
ControlValueAccessor {
};
public editorPositions: ConnectionPositionPair[] = [];
+ tooltipPosition: ConnectedPosition = {
+ originX: 'start',
+ originY: 'bottom',
+ overlayX: 'start',
+ overlayY: 'top',
+ offsetY: 4
+ };
+
constructor(
private changeDetector: ChangeDetectorRef,
private nifiCommon: NiFiCommon
@@ -431,6 +442,13 @@ export class PropertyTable implements AfterViewInit,
ControlValueAccessor {
};
}
+ getPropertyValueTipData(item: PropertyItem): PropertyValueTipInput {
+ return {
+ property: item,
+ parameters: this.parameterContext?.component?.parameters || []
+ };
+ }
+
hasAllowableValues(item: PropertyItem): boolean {
return Array.isArray(item.descriptor.allowableValues);
}
@@ -617,4 +635,6 @@ export class PropertyTable implements AfterViewInit,
ControlValueAccessor {
}
return false;
}
+
+ protected readonly PropertyValueTip = PropertyValueTip;
}
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.html
similarity index 52%
copy from
nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.html
index c70f094731..a98907f3ea 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.html
@@ -15,15 +15,22 @@
~ limitations under the License.
-->
-<div class="parameter-tip">
- @if (data?.parameter; as parameter) {
- <div class="flex flex-col gap-y-3">
- <div class="parameter-name text-lg font-bold">{{ parameter.name
}}</div>
- @if (hasDescription(parameter)) {
- <div>{{ parameter.description }}</div>
- } @else {
- <div class="unset neutral-color">No description provided</div>
+<div class="tooltip property-value-tip overflow-hidden">
+ <div class="line-clamp-[10] font-mono text-sm whitespace-pre max-h-52
truncate">{{ data?.property?.value }}</div>
+
+ @if (parameterReferences.length > 0) {
+ <div class="mt-4">Parameter values:</div>
+ <table class="w-full min-w-72">
+ @for (param of parameterReferences; track param.name) {
+ <tr>
+ <td class="font-bold pr-4 leading-4">{{ param.name }}</td>
+ <td class="line-clamp-[10] leading-4 pl-4 whitespace-pre
font-mono text-sm">
+ <div class="truncate max-w-xs">
+ {{ param.value }}
+ </div>
+ </td>
+ </tr>
}
- </div>
+ </table>
}
</div>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.scss
new file mode 100644
index 0000000000..2944f98194
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.scss
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.spec.ts
new file mode 100644
index 0000000000..118cf59883
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.spec.ts
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 { ComponentFixture, TestBed } from '@angular/core/testing';
+import { PropertyValueTip } from './property-value-tip.component';
+import { PropertyValueTipInput } from '../../../../state/shared';
+
+describe('PropertyValueTip', () => {
+ let component: PropertyValueTip;
+ let fixture: ComponentFixture<PropertyValueTip>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [PropertyValueTip]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(PropertyValueTip);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('extractParameterReferences', () => {
+ function buildDescriptor(overrides: Partial<any> = {}) {
+ return {
+ name: 'prop',
+ displayName: 'Prop',
+ description: 'desc',
+ required: false,
+ sensitive: false,
+ dynamic: false,
+ supportsEl: true,
+ expressionLanguageScope: '',
+ dependencies: [],
+ ...overrides
+ };
+ }
+
+ it('should return early when property is sensitive', () => {
+ const data: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: "#{'PARAM_A'}",
+ descriptor: buildDescriptor({ sensitive: true })
+ },
+ parameters: [{ parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: '1' } }]
+ };
+
+ component.data = data;
+ fixture.detectChanges();
+
+ expect(component.parameterReferences.length).toBe(0);
+ });
+
+ it('should match quoted and unquoted parameter references', () => {
+ const data: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: 'start #{PARAM_A} mid #{\'PARAM_B\'} end
#{"PARAM_C"}',
+ descriptor: buildDescriptor()
+ },
+ parameters: [
+ { parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: 'a' } },
+ { parameter: { name: 'PARAM_B', description: '',
sensitive: false, value: 'b' } },
+ { parameter: { name: 'PARAM_C', description: '',
sensitive: false, value: 'c' } }
+ ]
+ };
+
+ component.data = data;
+ fixture.detectChanges();
+
+ const names = component.parameterReferences.map((p) => p.name);
+ expect(names).toEqual(['PARAM_A', 'PARAM_B', 'PARAM_C']);
+ });
+
+ it('should ignore sensitive parameters in regex construction', () => {
+ const data: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: '#{PARAM_A} #{PARAM_SEC}',
+ descriptor: buildDescriptor()
+ },
+ parameters: [
+ { parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: 'a' } },
+ { parameter: { name: 'PARAM_SEC', description: '',
sensitive: true, value: 'secret' } }
+ ]
+ };
+
+ component.data = data;
+ fixture.detectChanges();
+
+ const names = component.parameterReferences.map((p) => p.name);
+ expect(names).toEqual(['PARAM_A']);
+ });
+
+ it('should capture multiple occurrences of the same parameter', () => {
+ const data: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: "#{PARAM_A} and again #{'PARAM_A'}",
+ descriptor: buildDescriptor()
+ },
+ parameters: [{ parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: 'a' } }]
+ };
+
+ component.data = data;
+ fixture.detectChanges();
+
+ expect(component.parameterReferences.length).toBe(2);
+ expect(component.parameterReferences[0].name).toBe('PARAM_A');
+ expect(component.parameterReferences[1].name).toBe('PARAM_A');
+ });
+
+ it('should handle null or empty property values', () => {
+ const dataNull: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: null,
+ descriptor: buildDescriptor()
+ },
+ parameters: [{ parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: 'a' } }]
+ };
+
+ component.data = dataNull;
+ fixture.detectChanges();
+ expect(component.parameterReferences.length).toBe(0);
+
+ const dataEmpty: PropertyValueTipInput = {
+ property: {
+ property: 'prop',
+ value: '',
+ descriptor: buildDescriptor()
+ },
+ parameters: [{ parameter: { name: 'PARAM_A', description: '',
sensitive: false, value: 'a' } }]
+ };
+
+ component.data = dataEmpty;
+ fixture.detectChanges();
+ expect(component.parameterReferences.length).toBe(0);
+ });
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.ts
new file mode 100644
index 0000000000..b02bda9cf3
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tooltips/property-value-tip/property-value-tip.component.ts
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 } from '@angular/core';
+import { PropertyValueTipInput } from '../../../../state/shared';
+import { Parameter } from '@nifi/shared';
+
+@Component({
+ selector: 'property-value-tip',
+ standalone: true,
+ templateUrl: './property-value-tip.component.html',
+ styleUrl: './property-value-tip.component.scss'
+})
+export class PropertyValueTip {
+ private _data: PropertyValueTipInput | undefined;
+ private parameterRegex = new RegExp('^$');
+
+ @Input() set data(data: PropertyValueTipInput | undefined) {
+ this._data = data;
+ this.extractParameterReferences();
+ }
+ get data(): PropertyValueTipInput | undefined {
+ return this._data;
+ }
+
+ parameterReferences: Parameter[] = [];
+
+ private extractParameterReferences() {
+ if (this._data?.property.descriptor.sensitive) {
+ return;
+ }
+
+ const propertyValue = this._data?.property.value || null;
+
+ // get all the non-sensitive parameters
+ const parameters = this.data?.parameters
+ .filter((parameter) => !parameter.parameter.sensitive)
+ .map((parameter) => parameter.parameter);
+
+ if (propertyValue && parameters && parameters.length > 0) {
+ this.parameterReferences = [];
+
+ // build up the regex that will match any parameter in a string,
even if it is quoted
+ const allParamsRegex = parameters.reduce((regex, param, idx) => {
+ if (idx > 0) {
+ regex += '|';
+ }
+ const quoteCaptureGroupIndex = idx * 2 + 1;
+ regex +=
`#{(['"]?)(${param.name})\\${quoteCaptureGroupIndex}}`;
+ return regex;
+ }, '');
+ this.parameterRegex = new RegExp(allParamsRegex, 'gm');
+
+ let matched;
+ while ((matched = this.parameterRegex.exec(propertyValue)) !==
null) {
+ // pull out the parameter name matched from the capturing
groups, ignore any quote group
+ const paramName = matched.splice(1).find((match) => !!match &&
match !== "'" && match !== '"');
+
+ // get the Parameter object that was matched
+ const param = parameters.find((param) => param.name ===
paramName);
+
+ // if matched, add it to the list of parameter references
+ if (param) {
+ this.parameterReferences.push(param);
+ }
+ }
+ }
+ }
+}
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
b/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
index c70f094731..5aca0f3d24 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/components/codemirror/autocomplete/parameter-tip/parameter-tip.component.html
@@ -19,6 +19,15 @@
@if (data?.parameter; as parameter) {
<div class="flex flex-col gap-y-3">
<div class="parameter-name text-lg font-bold">{{ parameter.name
}}</div>
+ @if (!parameter.sensitive) {
+ @if (parameter.value === null) {
+ <div class="unset neutral-color">No value set</div>
+ } @else if (parameter.value === '') {
+ <div class="unset neutral-color">Empty string set</div>
+ } @else {
+ <div class="line-clamp-[20] leading-4 whitespace-pre
font-mono text-sm truncate">{{ parameter.value }}</div>
+ }
+ }
@if (hasDescription(parameter)) {
<div>{{ parameter.description }}</div>
} @else {