From 098881f1382f30f072ce37d40e9a895f2c436d93 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Singh Date: Mon, 10 Aug 2026 18:55:33 +0530 Subject: [PATCH] NIFI-16167 Focus referenced parameter when navigating from a property Clicking "Go to" on a processor or controller service property that references a parameter (e.g. #{my-param}) opened the Parameter Context edit dialog but did not focus the referenced parameter, requiring users to manually search the parameter list to find and update it. This restores the NiFi 1.x behavior of automatically selecting and scrolling to the referenced parameter. - PropertyTable now extracts the parameter name from the #{...} reference syntax before invoking the goToParameter callback, instead of passing the raw reference literal. - The goToParameter callbacks in the processor and controller service effects forward the parameter name as router navigation state alongside the existing back-navigation state. - The Parameter Context listing effect reads the parameter name from navigation state when opening the Edit Parameter Context dialog, switches to the Parameters tab, and passes it to the dialog. - EditParameterContext forwards the selected parameter name to ParameterTable, which selects the matching row and scrolls it into view once the table data is populated. --- .../controller-services.effects.ts | 25 +++++++++++++----- .../flow-designer/state/flow/flow.effects.ts | 17 ++++++++---- .../parameter-context-listing.effects.ts | 11 ++++++++ .../parameter-table.component.html | 3 ++- .../parameter-table.component.ts | 26 ++++++++++++++++++- .../edit-parameter-context.component.html | 3 ++- .../edit-parameter-context.component.ts | 1 + .../property-table.component.spec.ts | 4 +-- .../property-table.component.ts | 15 ++++++++--- 9 files changed, 84 insertions(+), 21 deletions(-) diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts index f0fc80574cb9..7c8fa4d22344 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts @@ -406,7 +406,12 @@ export class ControllerServicesEffects { selectPropertyVerificationStatus ); - const goTo = (commands: string[], destination: string, commandBoundary?: string[]): void => { + const goTo = ( + commands: string[], + destination: string, + commandBoundary?: string[], + additionalState?: Record + ): void => { if (editDialogReference.componentInstance.editControllerServiceForm.dirty) { const saveChangesDialogReference = this.dialog.open(YesNoDialog, { ...SMALL_DIALOG, @@ -434,11 +439,14 @@ export class ControllerServicesEffects { ], routeBoundary: commandBoundary, context: 'Controller Service' - } as BackNavigation + } as BackNavigation, + ...additionalState } }); } else { - this.router.navigate(commands); + this.router.navigate(commands, { + state: { ...additionalState } + }); } }); } else { @@ -455,11 +463,14 @@ export class ControllerServicesEffects { ], routeBoundary: commandBoundary, context: 'Controller Service' - } as BackNavigation + } as BackNavigation, + ...additionalState } }); } else { - this.router.navigate(commands); + this.router.navigate(commands, { + state: { ...additionalState } + }); } } }; @@ -473,12 +484,12 @@ export class ControllerServicesEffects { if (parameterContext != null) { editDialogReference.componentInstance.parameterContext = parameterContext; - editDialogReference.componentInstance.goToParameter = () => { + editDialogReference.componentInstance.goToParameter = (parameterName: string) => { this.storage.setItem(NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID, 1); const commandBoundary: string[] = ['/parameter-contexts']; const commands: string[] = [...commandBoundary, parameterContext.id, 'edit']; - goTo(commands, 'Parameter', commandBoundary); + goTo(commands, 'Parameter', commandBoundary, { parameterName }); }; editDialogReference.componentInstance.convertToParameter = diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts index a66564ab5bc6..cab067529cbb 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts @@ -1564,7 +1564,12 @@ export class FlowEffects { selectPropertyVerificationStatus ); - const goTo = (commands: string[], commandBoundary: string[], destination: string): void => { + const goTo = ( + commands: string[], + commandBoundary: string[], + destination: string, + additionalState?: Record + ): void => { if (editDialogReference.componentInstance.editProcessorForm.dirty) { const saveChangesDialogReference = this.dialog.open(YesNoDialog, { ...SMALL_DIALOG, @@ -1591,7 +1596,8 @@ export class FlowEffects { ], routeBoundary: commandBoundary, context: 'Processor' - } as BackNavigation + } as BackNavigation, + ...additionalState } }); }); @@ -1608,7 +1614,8 @@ export class FlowEffects { ], routeBoundary: commandBoundary, context: 'Processor' - } as BackNavigation + } as BackNavigation, + ...additionalState } }); } @@ -1616,12 +1623,12 @@ export class FlowEffects { if (parameterContext != null) { editDialogReference.componentInstance.parameterContext = parameterContext; - editDialogReference.componentInstance.goToParameter = () => { + editDialogReference.componentInstance.goToParameter = (parameterName: string) => { this.storage.setItem(NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID, 1); const commandBoundary: string[] = ['/parameter-contexts']; const commands: string[] = [...commandBoundary, parameterContext.id, 'edit']; - goTo(commands, commandBoundary, 'Parameter'); + goTo(commands, commandBoundary, 'Parameter', { parameterName }); }; editDialogReference.componentInstance.convertToParameter = diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts index 74b9a005cb8b..5cd46d246eb5 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts @@ -16,6 +16,7 @@ */ import { Injectable, inject } from '@angular/core'; +import { Location } from '@angular/common'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { concatLatestFrom } from '@ngrx/operators'; import * as ParameterContextListingActions from './parameter-context-listing.actions'; @@ -67,6 +68,7 @@ export class ParameterContextListingEffects { private parameterContextService = inject(ParameterContextService); private dialog = inject(MatDialog); private router = inject(Router); + private location = inject(Location); private errorHelper = inject(ErrorHelper); loadParameterContexts$ = createEffect(() => @@ -323,6 +325,9 @@ export class ParameterContextListingEffects { this.storage.setItem(NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID, 1); + const navigationState = this.location.getState() as { parameterName?: string } | null; + const selectedParameterName = navigationState?.parameterName; + const editDialogReference = this.dialog.open(EditParameterContext, { ...XL_DIALOG, data: { @@ -330,6 +335,12 @@ export class ParameterContextListingEffects { } }); + if (selectedParameterName) { + // Parameters tab + editDialogReference.componentInstance.selectedIndex = 1; + editDialogReference.componentInstance.selectedParameterName = selectedParameterName; + } + editDialogReference.componentInstance.updateRequest = this.store.select(selectUpdateRequest); editDialogReference.componentInstance.availableParameterContexts$ = this.store diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html index eadeaaf17e93..8de520312d7e 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html @@ -172,7 +172,8 @@ (click)="selectParameter(row)" (dblclick)="doubleClicked(row)" [class.selected]="isSelected(row)" - [class.even]="even"> + [class.even]="even" + [attr.data-parameter-name]="row.originalEntity.parameter.name"> diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts index 70f615a1e710..a45002d79fa6 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AfterViewInit, ChangeDetectorRef, Component, forwardRef, Input, inject } from '@angular/core'; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, inject } from '@angular/core'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; @@ -74,11 +74,13 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor { private store = inject>(Store); private changeDetector = inject(ChangeDetectorRef); private nifiCommon = inject(NiFiCommon); + private elementRef = inject(ElementRef); @Input() createNewParameter!: (existingParameters: string[]) => Observable; @Input() editParameter!: (parameter: Parameter) => Observable; @Input() canAddParameters = true; @Input() inheritsParameters = false; + @Input() selectedParameterName?: string; protected readonly TextTip = TextTip; @@ -165,6 +167,28 @@ export class ParameterTable implements AfterViewInit, ControlValueAccessor { private setPropertyItems(parameterItems: ParameterItem[]): void { this.dataSource.data = this.sortEntities(parameterItems, this.activeSort); this.initFilter(); + this.selectAndScrollToParameter(this.selectedParameterName); + } + + private selectAndScrollToParameter(parameterName: string | undefined): void { + if (!parameterName) { + return; + } + + const item = this.dataSource.data.find((i) => i.originalEntity.parameter.name === parameterName); + if (!item) { + return; + } + + this.selectParameter(item); + + // wait for the table rows to render before attempting to scroll + setTimeout(() => { + const row: HTMLElement | null = this.elementRef.nativeElement.querySelector( + `tr[data-parameter-name="${parameterName}"]` + ); + row?.scrollIntoView({ block: 'center' }); + }); } private sortEntities(parameters: ParameterItem[], sort: Sort): ParameterItem[] { diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html index 9ef15caaf6de..a7fcecfd5827 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html @@ -157,7 +157,8 @@

[canAddParameters]="!request.parameterContext?.component?.parameterProviderConfiguration" [inheritsParameters]="inheritsParameters(request.parameterContext?.component?.parameters)" [createNewParameter]="createNewParameter" - [editParameter]="editParameter"> + [editParameter]="editParameter" + [selectedParameterName]="selectedParameterName"> diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts index 59e3e75279d8..32d1a2525427 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts @@ -96,6 +96,7 @@ export class EditParameterContext extends TabbedDialog { @Input() updateRequest!: Observable; @Input() availableParameterContexts$!: Observable; @Input() saving$!: Observable; + @Input() selectedParameterName?: string; @Output() addParameterContext: EventEmitter = new EventEmitter(); @Output() editParameterContext: EventEmitter = new EventEmitter(); diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.spec.ts index 20779e9a0f02..0dd5c547467e 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.spec.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/property-table.component.spec.ts @@ -74,13 +74,13 @@ describe('PropertyTable', () => { expect(() => component.goToParameterClicked(mockItem)).not.toThrow(); }); - it('should invoke goToParameter callback when supplied and item.value is non-null', () => { + it('should invoke goToParameter callback with the extracted parameter name when supplied and item.value is non-null', () => { const goToParameterSpy = vi.fn(); component.goToParameter = goToParameterSpy; component.goToParameterClicked(mockItem); - expect(goToParameterSpy).toHaveBeenCalledWith('#{some-param}'); + expect(goToParameterSpy).toHaveBeenCalledWith('some-param'); }); it('should not invoke goToParameter callback when item.value is null', () => { 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 4abc9dcc8310..ef4f47cd78c4 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 @@ -108,6 +108,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor { @Input() supportsParameters = true; private static readonly PARAM_REF_REGEX: RegExp = /#{(['"]?)[a-zA-Z0-9-_. ]+\1}/; + private static readonly PARAM_REF_NAME_REGEX: RegExp = /#{(['"]?)([a-zA-Z0-9-_. ]+)\1}/; private destroyRef = inject(DestroyRef); @@ -517,9 +518,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor { } canGoToParameter(item: PropertyItem): boolean { - // TODO - currently parameter context route does not support navigating - // directly to a specific parameter so the parameter context link - // is not item specific. if (this.parameterContext && this.goToParameter && item.value) { return this.parameterContext.permissions.canRead && PropertyTable.PARAM_REF_REGEX.test(item.value); } @@ -531,7 +529,16 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor { if (!this.goToParameter || item.value == null) { return; } - this.goToParameter(item.value); + + const parameterName = this.extractParameterName(item.value); + if (parameterName) { + this.goToParameter(parameterName); + } + } + + private extractParameterName(value: string): string | null { + const match = PropertyTable.PARAM_REF_NAME_REGEX.exec(value); + return match ? match[2] : null; } canConvertToParameter(item: PropertyItem): boolean {