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 {

Reply via email to