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 78a68749e64 NIFI-16250 Treat an unconfigured SECRET_REFERENCE as unset 
(#11591)
78a68749e64 is described below

commit 78a68749e64b5c32af53886b10d757aa412467b6
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Aug 25 15:40:37 2026 -0400

    NIFI-16250 Treat an unconfigured SECRET_REFERENCE as unset (#11591)
    
    A SECRET property with no secret selected can be stored as a
    SECRET_REFERENCE carrying only a provider name. Both helpers relied on
    buildSecretKey, which never returns an empty string, so the select
    showed an unmatched key like "::Some Provider::" and hasPropertyValue
    reported the property as configured.
    
    Both now check fullyQualifiedSecretName and secretName directly.
---
 .../property-group-card.component.spec.ts          | 38 +++++++++++
 .../src/services/value-reference.helper.spec.ts    | 37 +++++++++++
 .../shared/src/services/value-reference.helper.ts  | 20 +++---
 .../src/utils/connector-validation.utils.spec.ts   | 73 ++++++++++++++++++++++
 .../shared/src/utils/connector-validation.utils.ts |  5 ++
 5 files changed, 166 insertions(+), 7 deletions(-)

diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
index da84c658280..a94e0e6bf77 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
@@ -199,6 +199,23 @@ describe('PropertyGroupCard', () => {
             expect(component.hasValue('Password')).toBe(true);
         });
 
+        it('should return false for a SECRET_REFERENCE that names a provider 
but selects no secret', async () => {
+            const { component } = await setup({
+                propertyGroup: makeGroup({
+                    propertyDescriptors: {
+                        Password: { name: 'Password', type: 'SECRET', 
required: true }
+                    },
+                    propertyValues: {
+                        Password: {
+                            valueType: 'SECRET_REFERENCE',
+                            secretProviderName: 'Vault'
+                        }
+                    }
+                })
+            });
+            expect(component.hasValue('Password')).toBe(false);
+        });
+
         it('should return false for a SECRET with only a descriptor default 
and no saved reference', async () => {
             const { component } = await setup({
                 propertyGroup: makeGroup({
@@ -424,6 +441,27 @@ describe('PropertyGroupCard', () => {
             expect(unsetLabels[0].nativeElement.textContent.trim()).toBe('No 
value set');
         });
 
+        it('should show "No value set" rather than the mask for a SECRET with 
no secret selected', async () => {
+            const { query } = await setup({
+                propertyGroup: makeGroup({
+                    propertyDescriptors: {
+                        Password: { name: 'Password', type: 'SECRET', 
required: true }
+                    },
+                    propertyValues: {
+                        Password: {
+                            valueType: 'SECRET_REFERENCE',
+                            secretProviderName: 'Vault'
+                        }
+                    }
+                })
+            });
+            const content = query('mat-card-content');
+            const unsetLabels = content.queryAll(By.css('.unset'));
+            expect(unsetLabels.length).toBe(1);
+            expect(unsetLabels[0].nativeElement.textContent.trim()).toBe('No 
value set');
+            
expect(content.nativeElement.textContent).not.toContain('••••••••');
+        });
+
         it('should display descriptor defaults when property values are 
absent', async () => {
             const { query } = await setup({
                 propertyGroup: makeGroup({
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
index df5894ea5a6..93749aa4ca4 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
@@ -44,5 +44,42 @@ describe('Value Reference Helper', () => {
         it('preserves a null BOOLEAN value so callers can use the descriptor 
default', () => {
             expect(fromValueReference({ value: null, valueType: 
'STRING_LITERAL' }, 'BOOLEAN')).toBeNull();
         });
+
+        describe('SECRET type', () => {
+            it('should return composite key for populated SECRET_REFERENCE', 
() => {
+                const valueRef = {
+                    valueType: 'SECRET_REFERENCE' as const,
+                    secretProviderId: 'provider-123',
+                    secretProviderName: 'My Provider',
+                    secretName: 'my-secret',
+                    fullyQualifiedSecretName: 'My Provider.group.my-secret'
+                };
+
+                const result = fromValueReference(valueRef, 'SECRET');
+
+                expect(result).toBe('provider-123::My Provider::My 
Provider.group.my-secret');
+            });
+
+            it('should return undefined for unconfigured SECRET_REFERENCE with 
providerName only', () => {
+                const valueRef = {
+                    valueType: 'SECRET_REFERENCE' as const,
+                    secretProviderName: 'Local Parameter Provider'
+                };
+
+                const result = fromValueReference(valueRef, 'SECRET');
+
+                expect(result).toBeUndefined();
+            });
+
+            it('should return undefined for SECRET_REFERENCE with all null 
fields', () => {
+                const valueRef = {
+                    valueType: 'SECRET_REFERENCE' as const
+                };
+
+                const result = fromValueReference(valueRef);
+
+                expect(result).toBeUndefined();
+            });
+        });
     });
 });
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
index 5b677403167..f78a6495878 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
@@ -172,13 +172,19 @@ export function fromValueReference(
                 break;
             }
             case 'SECRET_REFERENCE':
-                // Return composite key to uniquely identify the secret
-                // Format: providerId::providerName::fullyQualifiedName
-                rawValue = buildSecretKey(
-                    valueRef.secretProviderId,
-                    valueRef.secretProviderName,
-                    valueRef.fullyQualifiedSecretName
-                );
+                // If no secret has been selected yet (no FQN and no secret 
name),
+                // return undefined so the select shows placeholder instead of 
"(no longer available)"
+                if (!valueRef.fullyQualifiedSecretName && 
!valueRef.secretName) {
+                    rawValue = undefined;
+                } else {
+                    // Return composite key to uniquely identify the secret
+                    // Format: providerId::providerName::fullyQualifiedName
+                    rawValue = buildSecretKey(
+                        valueRef.secretProviderId,
+                        valueRef.secretProviderName,
+                        valueRef.fullyQualifiedSecretName
+                    );
+                }
                 break;
             default:
                 // Fallback for unknown types - try to get value
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
new file mode 100644
index 00000000000..42c58edc8b1
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { hasPropertyValue } from './connector-validation.utils';
+import { ConnectorValueReference } from '../types';
+
+describe('hasPropertyValue', () => {
+    describe('SECRET type', () => {
+        it('should return true for secret reference with value', () => {
+            const valueRef: ConnectorValueReference = {
+                valueType: 'SECRET_REFERENCE',
+                secretName: 'my-secret',
+                secretProviderId: 'provider-1',
+                secretProviderName: 'AWS Secrets Manager',
+                fullyQualifiedSecretName: 'group/my-secret'
+            };
+            expect(hasPropertyValue(valueRef, 'SECRET')).toBe(true);
+        });
+
+        it('should return false for unconfigured secret with providerName 
only', () => {
+            const valueRef: ConnectorValueReference = {
+                valueType: 'SECRET_REFERENCE',
+                secretProviderName: 'Local Parameter Provider'
+            };
+            expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+        });
+
+        it('should return false for secret reference with all empty strings', 
() => {
+            const valueRef: ConnectorValueReference = {
+                valueType: 'SECRET_REFERENCE',
+                secretProviderId: '',
+                secretProviderName: '',
+                fullyQualifiedSecretName: ''
+            };
+            expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+        });
+
+        it('should return true when secretName is set but 
fullyQualifiedSecretName is not', () => {
+            const valueRef: ConnectorValueReference = {
+                valueType: 'SECRET_REFERENCE',
+                secretProviderName: 'My Provider',
+                secretName: 'my-secret'
+            };
+            expect(hasPropertyValue(valueRef, 'SECRET')).toBe(true);
+        });
+
+        it('should return false for cleared secret (STRING_LITERAL with 
null)', () => {
+            const valueRef: ConnectorValueReference = {
+                valueType: 'STRING_LITERAL',
+                value: null
+            };
+            expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+        });
+
+        it('should return false for undefined value reference', () => {
+            expect(hasPropertyValue(undefined, 'SECRET')).toBe(false);
+        });
+    });
+});
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
index 15895d54f7b..18450a9e810 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
@@ -48,6 +48,11 @@ export function hasPropertyValue(
         if (valueRef.valueType !== 'SECRET_REFERENCE') {
             return false;
         }
+        // An unconfigured secret may carry a provider name but no secret 
selection,
+        // so it has neither a fullyQualifiedSecretName nor a secretName. 
Treat as unset.
+        if (!valueRef.fullyQualifiedSecretName && !valueRef.secretName) {
+            return false;
+        }
         const secretKey = buildSecretKey(
             valueRef.secretProviderId,
             valueRef.secretProviderName,

Reply via email to