rfellows commented on code in PR #11582:
URL: https://github.com/apache/nifi/pull/11582#discussion_r3982018111


##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.spec.ts:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { provideMockStore, MockStore } from '@ngrx/store/testing';
+import { of } from 'rxjs';
+import { ComponentType } from '@nifi/shared';
+
+import { ComponentConnectionsDialog } from 
'./component-connections-dialog.component';
+import { ComponentConnectionsDialogRequest, ConnectionDirection, 
ConnectionEntity } from '../../../state/flow';
+import { navigateToComponent } from '../../../state/flow/flow.actions';
+import { CanvasUtils } from '../../../service/canvas-utils.service';
+
+const COMPONENT_ID = 'a1b2c3d4-0000-0000-0000-000000000000';
+const GROUP_ID = 'e5f6a7b8-0000-0000-0000-000000000000';
+const CHILD_GROUP_ID = 'c9d0e1f2-0000-0000-0000-000000000000';
+
+interface ConnectableStub {
+    id: string;
+    name: string;
+}
+
+/**
+ * Builds a readable connection between the two supplied endpoints. The 
endpoints are reported as
+ * living in the group being viewed, which is the common case; {@link 
connectionIntoChildGroup}
+ * covers an endpoint inside a child group.
+ */
+function connection(
+    id: string,
+    source: ConnectableStub,
+    destination: ConnectableStub,
+    component: { name?: string; selectedRelationships?: string[] } = {}
+): ConnectionEntity {
+    return {
+        id,
+        permissions: { canRead: true, canWrite: true },
+        position: { x: 0, y: 0 },
+        revision: { version: 0 },
+        sourceId: source.id,
+        sourceGroupId: GROUP_ID,
+        sourceType: 'PROCESSOR',
+        destinationId: destination.id,
+        destinationGroupId: GROUP_ID,
+        destinationType: 'INPUT_PORT',
+        component: {
+            id,
+            source,
+            destination,
+            ...component
+        }
+    };
+}
+
+/**
+ * Builds a connection the current user cannot read. The API omits the 
component entirely in that case
+ * but still reports the endpoints at the top level of the entity.
+ */
+function unreadableConnection(id: string, sourceId: string, destinationId: 
string): ConnectionEntity {
+    return {
+        id,
+        permissions: { canRead: false, canWrite: false },
+        position: { x: 0, y: 0 },
+        revision: { version: 0 },
+        sourceId,
+        sourceGroupId: GROUP_ID,
+        sourceType: 'PROCESSOR',
+        destinationId,
+        destinationGroupId: GROUP_ID,
+        destinationType: 'INPUT_PORT',
+        component: null
+    };
+}
+
+/**
+ * Builds a connection from a component in the viewed group to an Input Port 
inside a child group.
+ * The canvas draws this as terminating at the child group, but the entity 
names the port.
+ */
+function connectionIntoChildGroup(
+    id: string,
+    source: ConnectableStub,
+    innerPort: ConnectableStub,
+    name: string
+): ConnectionEntity {
+    return {
+        id,
+        permissions: { canRead: true, canWrite: true },
+        position: { x: 0, y: 0 },
+        revision: { version: 0 },
+        sourceId: source.id,
+        sourceGroupId: GROUP_ID,
+        sourceType: 'PROCESSOR',
+        destinationId: innerPort.id,
+        destinationGroupId: CHILD_GROUP_ID,
+        destinationType: 'INPUT_PORT',
+        component: {
+            id,
+            name,
+            source,
+            destination: innerPort
+        }
+    };
+}
+
+interface CreatedDialog {
+    component: ComponentConnectionsDialog;
+    fixture: ComponentFixture<ComponentConnectionsDialog>;
+    store: MockStore;
+    dialogRef: { close: ReturnType<typeof vi.fn>; keydownEvents: () => 
ReturnType<typeof of> };
+}
+
+function createDialog(
+    direction: ConnectionDirection,
+    connections: ConnectionEntity[],
+    componentName = 'In'
+): CreatedDialog {
+    const dialogRequest: ComponentConnectionsDialogRequest = {
+        componentName,
+        groupId: GROUP_ID,
+        direction,
+        connections
+    };
+    const dialogRef = { close: vi.fn(), keydownEvents: () => of() };
+
+    // only formatConnectionName is exercised; the real CanvasUtils subscribes 
to canvas state on
+    // construction, which this dialog has no need of
+    const canvasUtils = {
+        formatConnectionName: (component: any): string => {
+            if (component.name) {
+                return component.name;
+            }
+            if (component.selectedRelationships) {
+                return component.selectedRelationships.join(', ');
+            }
+            return '';
+        }
+    };
+
+    TestBed.resetTestingModule();
+    TestBed.configureTestingModule({
+        imports: [ComponentConnectionsDialog],
+        providers: [
+            { provide: MAT_DIALOG_DATA, useValue: dialogRequest },
+            { provide: MatDialogRef, useValue: dialogRef },
+            { provide: CanvasUtils, useValue: canvasUtils },
+            provideMockStore({})

Review Comment:
   These tests currently fail (`npx nx test nifi`: 6 failed in this file).
   
   `provideMockStore({})` does not satisfy `selectProcessGroupIdToNameMap`, so 
`detectChanges()` throws `Cannot read properties of undefined (reading 
'flowState')` from `flow.selectors.ts`. The row assertions also omit 
`groupId`/`type`, and the navigation test still calls `component.goTo(...)`, 
which this component no longer has (`navigateTo` is the method).
   
   Please override `selectProcessGroupIdToNameMap` before `detectChanges()`, 
update the expected row shape, and call `navigateTo(...)` for the connection 
(and ideally the other clickable cells).



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.ts:
##########
@@ -0,0 +1,203 @@
+/*
+ * 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, inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from 
'@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTableModule } from '@angular/material/table';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { Store } from '@ngrx/store';
+import { CloseOnEscapeDialog, ComponentType } from '@nifi/shared';
+import { CanvasState } from '../../../state';
+import { ComponentConnectionsDialogRequest, ConnectionEntity } from 
'../../../state/flow';
+import { CanvasUtils } from '../../../service/canvas-utils.service';
+import { navigateToComponent } from '../../../state/flow/flow.actions';
+import { selectProcessGroupIdToNameMap } from 
'../../../state/flow/flow.selectors';
+
+/**
+ * One end of a connection, with enough information to render a cell and 
navigate to it.
+ * - {@code id}: the component's own id.
+ * - {@code groupId}: the id of the process group that directly contains the 
component.
+ * - {@code type}: the component type, used to tell {@code 
navigateToComponent} what it's looking at.
+ * - {@code name}: the component name, or {@code null} when the current user 
cannot read the
+ *   connection, in which case the cell renders an "Unauthorized" placeholder 
and is not clickable.
+ */
+export interface ConnectionEndpoint {
+    id: string;
+    groupId: string;
+    type: ComponentType;
+    name: string | null;
+}
+
+/**
+ * Row in the connections table.
+ * - {@code id}: the connection id.
+ * - {@code name}: the connection name, or the relationships it carries when 
it has no name.
+ *   {@code null} when it has neither, so the cell renders an "Unnamed" 
placeholder.
+ *
+ * Both ends are listed, along with each end's process group, rather than only 
the far end. When the
+ * selected component is a Process Group or Remote Process Group the 
connection actually terminates at
+ * a port inside it, and which group and port that is matters as much as the 
component on the other side.
+ */
+export interface ComponentConnectionRow {
+    id: string;
+    name: string | null;
+    source: ConnectionEndpoint;
+    destination: ConnectionEndpoint;
+}
+
+/**
+ * Lists the connections attached to a component in one direction. For most 
components those
+ * connections are already drawn on the canvas, so this is a way to reach one 
whose other end sits
+ * somewhere else entirely. For an Input Port's upstream connections and an 
Output Port's downstream
+ * connections it is the only way, since those are defined in the parent 
process group and are not drawn
+ * alongside the port at all. Each of the 5 cells in a row is independently 
clickable and navigates
+ * to the process group, component, or connection it represents.
+ */
+@Component({
+    selector: 'component-connections-dialog',
+    imports: [MatButtonModule, MatDialogModule, MatTableModule, 
MatTooltipModule],
+    templateUrl: './component-connections-dialog.component.html',
+    styleUrls: ['./component-connections-dialog.component.scss']
+})
+export class ComponentConnectionsDialog extends CloseOnEscapeDialog {
+    private dialogRequest = 
inject<ComponentConnectionsDialogRequest>(MAT_DIALOG_DATA);
+    private componentConnectionsDialogRef = 
inject<MatDialogRef<ComponentConnectionsDialog>>(MatDialogRef);
+    private store = inject<Store<CanvasState>>(Store);
+    private canvasUtils = inject(CanvasUtils);
+    // Signal-based snapshot — reads current value synchronously, no manual 
subscribe/unsubscribe.
+    private groupIdToName = 
this.store.selectSignal(selectProcessGroupIdToNameMap);
+
+    // Maps the string type returned by the NiFi API to the ComponentType enum 
used for navigation.
+    private static readonly TYPE_MAP: Record<string, ComponentType> = {
+        PROCESSOR: ComponentType.Processor,
+        INPUT_PORT: ComponentType.InputPort,
+        OUTPUT_PORT: ComponentType.OutputPort,
+        REMOTE_INPUT_PORT: ComponentType.RemoteProcessGroup,
+        REMOTE_OUTPUT_PORT: ComponentType.RemoteProcessGroup,
+        FUNNEL: ComponentType.Funnel
+    };

Review Comment:
   `REMOTE_INPUT_PORT` / `REMOTE_OUTPUT_PORT` are mapped to 
`ComponentType.RemoteProcessGroup`, but `buildRow` still stores 
`connection.sourceId` / `destinationId` (the remote *port* id). The 
source/destination cells then call `navigateTo(row.*.id, row.*.groupId, 
row.*.type)`, which becomes 
`/process-groups/{rpgId}/RemoteProcessGroup/{remotePortId}`.
   
   Existing connectable mapping in 
`apps/nifi/src/app/ui/common/utils/component-state.utils.ts` 
(`getComponentTypeForSource` / `getComponentTypeForDestination`) treats those 
types as the containing RPG. Canvas endpoint resolution 
(`CanvasUtils.getConnectionSourceComponentId` / 
`getConnectionDestinationComponentId`) likewise collapses to the group/RPG id.
   
   For remote ports, navigate with the RPG id as `id` and the process group 
that contains that RPG as `processGroupId` (or open Manage Remote Ports). 
Please add a remote-port navigation test.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.html:
##########
@@ -0,0 +1,134 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>{{ title }}</h2>
+<mat-dialog-content>
+    <div class="flex flex-col gap-y-4">
+        <div class="tertiary-color font-medium">Selected Component<br><i 
class="icon component-type-icon" [class]="componentIcon(componentType)"></i>{{ 
componentName }}</div>
+        @if (rows.length === 0) {
+            <div class="unset neutral-color">{{ emptyMessage }}</div>
+        } @else {
+            <div class="listing-table component-connections-table">
+                <table mat-table [dataSource]="rows">
+                    <ng-container matColumnDef="sourceProcessGroup">
+                        <th mat-header-cell 
*matHeaderCellDef>Source<br>Process Group</th>
+                        <td mat-cell *matCellDef="let row">
+                            <!-- TODO: translate row.source.groupId to process 
group name -->
+                            <button
+                                type="button"
+                                mat-button
+                                class="link-cell"
+                                
[matTooltip]="resolveGroupName(row.source.groupId)"
+                                (click)="navigateTo(row.source.groupId, 
dialogRequestGroupId, processGroupType)">
+                                <i class="icon component-type-icon" 
[class]="componentIcon(processGroupType)"></i>
+                                {{ resolveGroupName(row.source.groupId) }}
+                            </button>
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="sourceComponent">
+                        <th mat-header-cell 
*matHeaderCellDef>Source<br>Component</th>
+                        <td mat-cell *matCellDef="let row">
+                            @if (row.source.name === null) {
+                                <span class="unset neutral-color" 
[matTooltip]="row.source.id">
+                                    <i class="icon component-type-icon" 
[class]="componentIcon(row.source.type)"></i>
+                                    Unauthorized
+                                </span>
+                            } @else {
+                                <button
+                                    type="button"
+                                    mat-button
+                                    class="link-cell"
+                                    [matTooltip]="row.source.name"
+                                    (click)="navigateTo(row.source.id, 
row.source.groupId, row.source.type)">
+                                    <i class="icon component-type-icon" 
[class]="componentIcon(row.source.type)"></i>
+                                    {{ row.source.name }}
+                                </button>

Review Comment:
   Listing tables don’t put Material buttons in data cells. `mat-button` adds 
padded chrome and fights the ellipsis / `table-layout: fixed` treatment that 
`.listing-table` already applies to cell text.
   
   When a name is itself the navigation target, existing UIs use an `<a>`:
   
   - 
`apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.html`
   - 
`apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.html`
   - 
`apps/nifi/src/app/ui/common/process-group-references/process-group-references.component.html`
   
   Row actions belong in an overflow menu (`local-changes-table.html`). Please 
replace all five `mat-button` cells with `<a>` (or `routerLink`) and drop 
`class="link-cell"`.
   
   ```suggestion
                                   <a
                                       [matTooltip]="row.source.name"
                                       (click)="navigateTo(row.source.id, 
row.source.groupId, row.source.type)">
                                       <i class="icon component-type-icon" 
[class]="componentIcon(row.source.type)"></i>
                                       {{ row.source.name }}
                                   </a>
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.html:
##########
@@ -0,0 +1,134 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>{{ title }}</h2>
+<mat-dialog-content>
+    <div class="flex flex-col gap-y-4">
+        <div class="tertiary-color font-medium">Selected Component<br><i 
class="icon component-type-icon" [class]="componentIcon(componentType)"></i>{{ 
componentName }}</div>
+        @if (rows.length === 0) {
+            <div class="unset neutral-color">{{ emptyMessage }}</div>
+        } @else {
+            <div class="listing-table component-connections-table">
+                <table mat-table [dataSource]="rows">
+                    <ng-container matColumnDef="sourceProcessGroup">
+                        <th mat-header-cell 
*matHeaderCellDef>Source<br>Process Group</th>
+                        <td mat-cell *matCellDef="let row">
+                            <!-- TODO: translate row.source.groupId to process 
group name -->
+                            <button
+                                type="button"
+                                mat-button
+                                class="link-cell"
+                                
[matTooltip]="resolveGroupName(row.source.groupId)"
+                                (click)="navigateTo(row.source.groupId, 
dialogRequestGroupId, processGroupType)">

Review Comment:
   This always navigates with `(id, processGroupId, ProcessGroup) = 
(row.source.groupId, dialogRequestGroupId, ProcessGroup)`. For connections 
whose endpoint lives in the group currently on the canvas, those two ids are 
the same, so `navigateToComponent` routes to 
`/process-groups/{groupId}/ProcessGroup/{groupId}`.
   
   The current process group is not a child in that group’s `processGroups` 
collection, so nothing is selected or centered. The destination process-group 
cell (lines 87–92) has the same problem.
   
   Please treat “this is the group we’re already in” as non-interactive, or 
navigate to `/process-groups/{groupId}` without a selected component. Also drop 
the leftover TODO on line 30 — `resolveGroupName` already does that lookup.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts:
##########
@@ -294,3 +295,35 @@ export const selectOverlappingConnections = createSelector(
         return detectOverlappingConnections(connections, processGroupId);
     }
 );
+
+// maps the id of every group whose name is known in the current flow to that 
group's name: the current
+// process group, its ancestors via the breadcrumb chain, and the child 
process groups and remote process
+// groups it contains. Remote process groups are included alongside process 
groups because a connection to
+// one reports the remote process group's own id as the source/destination 
group id of the remote port it
+// terminates at, so the two kinds of id are looked up the same way.
+export const selectProcessGroupIdToNameMap = createSelector(

Review Comment:
   This map only includes the current breadcrumb chain plus *children of the 
currently loaded canvas group*. For Input Port upstream / Output Port 
downstream, `viewComponentConnections$` fetches the *parent* flow 
(`flow.effects.ts` ~3213–3221) and never puts that parent’s child groups/RPGs 
into canvas state.
   
   `resolveGroupName()` then falls back to the raw UUID 
(`component-connections-dialog.component.ts` 147–148). That is exactly the 
cross-boundary case this feature is meant to clarify.
   
   Please build the name map from the fetched `ProcessGroupFlowEntity` (plus 
breadcrumbs as needed) and pass it into the dialog request, rather than reading 
only current canvas state.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.html:
##########
@@ -0,0 +1,134 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>{{ title }}</h2>
+<mat-dialog-content>
+    <div class="flex flex-col gap-y-4">
+        <div class="tertiary-color font-medium">Selected Component<br><i 
class="icon component-type-icon" [class]="componentIcon(componentType)"></i>{{ 
componentName }}</div>
+        @if (rows.length === 0) {
+            <div class="unset neutral-color">{{ emptyMessage }}</div>
+        } @else {
+            <div class="listing-table component-connections-table">

Review Comment:
   This table is an unbounded `listing-table` with no scroll viewport, no 
sticky header, and no striped rows. With more than a handful of connections, 
the header and “Selected Component” block scroll away.
   
   Please match the existing dialog/listing-table pattern:
   
   - Bounded scroll + sticky header: 
`apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.html`
 (44–45, 104) and 
`apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/change-version-dialog/change-version-dialog.html`
 (71–72, 124). Pattern: `listing-table flex-1 relative` wrapping `absolute 
inset-0 overflow-y-auto`, and `*matHeaderRowDef="displayedColumns; sticky: 
true"`.
   - Striped rows (`let even = even` + `[class.even]="even"`), same two files 
plus 
`apps/nifi/src/app/pages/summary/ui/common/cluster-summary-dialog/connection-cluster-table/connection-cluster-table.component.html`
 (18–19, 121–125).
   
   Keep truncation/tooltips; put the table in that scroll container.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.html.orig:
##########
@@ -0,0 +1,90 @@
+<!--

Review Comment:
   This looks like a leftover backup of an earlier template (Go To column, no 
per-cell navigation). Please remove it from the PR so it is not shipped.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.html:
##########
@@ -0,0 +1,134 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>{{ title }}</h2>
+<mat-dialog-content>
+    <div class="flex flex-col gap-y-4">
+        <div class="tertiary-color font-medium">Selected Component<br><i 
class="icon component-type-icon" [class]="componentIcon(componentType)"></i>{{ 
componentName }}</div>
+        @if (rows.length === 0) {
+            <div class="unset neutral-color">{{ emptyMessage }}</div>
+        } @else {
+            <div class="listing-table component-connections-table">
+                <table mat-table [dataSource]="rows">
+                    <ng-container matColumnDef="sourceProcessGroup">
+                        <th mat-header-cell 
*matHeaderCellDef>Source<br>Process Group</th>
+                        <td mat-cell *matCellDef="let row">
+                            <!-- TODO: translate row.source.groupId to process 
group name -->
+                            <button
+                                type="button"
+                                mat-button
+                                class="link-cell"
+                                
[matTooltip]="resolveGroupName(row.source.groupId)"
+                                (click)="navigateTo(row.source.groupId, 
dialogRequestGroupId, processGroupType)">
+                                <i class="icon component-type-icon" 
[class]="componentIcon(processGroupType)"></i>
+                                {{ resolveGroupName(row.source.groupId) }}
+                            </button>
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="sourceComponent">
+                        <th mat-header-cell 
*matHeaderCellDef>Source<br>Component</th>
+                        <td mat-cell *matCellDef="let row">
+                            @if (row.source.name === null) {
+                                <span class="unset neutral-color" 
[matTooltip]="row.source.id">
+                                    <i class="icon component-type-icon" 
[class]="componentIcon(row.source.type)"></i>
+                                    Unauthorized
+                                </span>
+                            } @else {
+                                <button
+                                    type="button"
+                                    mat-button
+                                    class="link-cell"
+                                    [matTooltip]="row.source.name"
+                                    (click)="navigateTo(row.source.id, 
row.source.groupId, row.source.type)">
+                                    <i class="icon component-type-icon" 
[class]="componentIcon(row.source.type)"></i>
+                                    {{ row.source.name }}
+                                </button>
+                            }
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="connection">
+                        <th mat-header-cell *matHeaderCellDef>Connection</th>
+                        <td mat-cell *matCellDef="let row">
+                            <button
+                                type="button"
+                                mat-button
+                                class="link-cell"
+                                [matTooltip]="row.name"
+                                (click)="navigateTo(row.id, 
dialogRequestGroupId, connectionType)">
+                                <i class="icon component-type-icon" 
[class]="componentIcon(connectionType)"></i>
+                                @if (row.name === null) {
+                                    <span class="unset 
neutral-color">Unnamed</span>
+                                } @else {
+                                    {{ row.name }}
+                                }
+                            </button>
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="destinationProcessGroup">
+                        <th mat-header-cell 
*matHeaderCellDef>Destination<br>Process Group</th>
+                        <td mat-cell *matCellDef="let row">
+                            <button
+                                type="button"
+                                mat-button
+                                class="link-cell"
+                                
[matTooltip]="resolveGroupName(row.destination.groupId)"
+                                (click)="navigateTo(row.destination.groupId, 
dialogRequestGroupId, processGroupType)">
+                                <i class="icon component-type-icon" 
[class]="componentIcon(processGroupType)"></i>
+                                {{ resolveGroupName(row.destination.groupId) }}
+                            </button>
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="destinationComponent">
+                        <th mat-header-cell 
*matHeaderCellDef>Destination<br>Component</th>
+                        <td mat-cell *matCellDef="let row">
+                            @if (row.destination.name === null) {
+                                <span class="unset neutral-color" 
[matTooltip]="row.destination.id">
+                                    <i
+                                        class="icon component-type-icon"
+                                        
[class]="componentIcon(row.destination.type)"></i>
+                                    Unauthorized
+                                </span>
+                            } @else {
+                                <button
+                                    type="button"
+                                    mat-button
+                                    class="link-cell"
+                                    [matTooltip]="row.destination.name"
+                                    (click)="navigateTo(row.destination.id, 
row.destination.groupId, row.destination.type)">
+                                    <i
+                                        class="icon component-type-icon"
+                                        
[class]="componentIcon(row.destination.type)"></i>
+                                    {{ row.destination.name }}
+                                </button>
+                            }
+                        </td>
+                    </ng-container>
+
+                    <tr mat-header-row 
*matHeaderRowDef="displayedColumns"></tr>
+                    <tr mat-row *matRowDef="let row; columns: 
displayedColumns"></tr>

Review Comment:
   Add the sticky header and striped rows used by other listing tables.
   
   ```suggestion
                       <tr mat-header-row *matHeaderRowDef="displayedColumns; 
sticky: true"></tr>
                       <tr mat-row *matRowDef="let row; let even = even; 
columns: displayedColumns" [class.even]="even"></tr>
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.scss:
##########
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+.component-connections-table {
+    width: 100%;
+
+    table {
+        table-layout: fixed;
+        width: 100%;
+    }
+
+    // flexible columns: truncate instead of pushing siblings out
+    .mat-column-source,
+    .mat-column-destination,
+    .mat-column-name {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        max-width: 0; // works with table-layout: fixed to force shrinking
+    }

Review Comment:
   These selectors (`.mat-column-source`, `.mat-column-destination`, 
`.mat-column-name`) do not match `displayedColumns` (`sourceProcessGroup`, 
`sourceComponent`, `connection`, `destinationProcessGroup`, 
`destinationComponent`), so the truncation rules never apply.
   
   Global `.listing-table` in 
`libs/shared/src/assets/themes/components/_table.scss` already sets 
`table-layout: fixed` and cell ellipsis. Either retarget the real 
`mat-column-*` classes or drop the dead rules. The commented-out `.link-cell` / 
`.cell-text` remnants below should go too.
   
   ```suggestion
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to