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


##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/canvas/layers/process-group-layer/process-group-renderer.ts:
##########
@@ -0,0 +1,1367 @@
+/*
+ * 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 * as d3 from 'd3';
+import { CanvasProcessGroup } from '../../canvas.types';
+import { ProcessGroupRenderContext } from '../render-context.types';
+import { ConnectionRenderer } from '../connection-layer/connection-renderer';
+import { VersionControlTip } from 
'../../../tooltips/version-control-tip/version-control-tip.component';
+import { CanvasConstants } from '../../canvas.constants';
+
+/**
+ * ProcessGroupRenderer
+ *
+ * Responsibilities:
+ * - Render process group SVG elements using D3's data join pattern
+ * - Render banner with name
+ * - Render statistics table (queued, in, read/write, out, tasks/time)
+ * - Render component status counts (transmitting, running, stopped, invalid, 
disabled)
+ * - Render version control status indicators
+ * - Handle process group visual updates (selection, authorization)
+ * - NO business logic or state management
+ *
+ * Pattern:
+ * - Static utility class with pure rendering functions
+ * - render(): Main entry point accepting ProcessGroupRenderContext
+ * - appendProcessGroupElements(): Create initial SVG structure
+ * - updateProcessGroupElements(): Update existing elements
+ * - Uses D3's enter/update/exit pattern for efficiency
+ */
+export class ProcessGroupRenderer {
+    /**
+     * Main render method - orchestrates the D3 data join pattern
+     */
+    public static render(context: ProcessGroupRenderContext): void {
+        const { containerSelection, processGroups, canSelect, callbacks } = 
context;
+
+        // D3 data join
+        const selection = containerSelection
+            .selectAll<SVGGElement, CanvasProcessGroup>('g.process-group')
+            .data(processGroups, (d: CanvasProcessGroup) => d.entity.id);
+
+        // Enter: create new PG elements
+        const entered: any = selection.enter();
+        const appendedGroups: any = 
ProcessGroupRenderer.appendProcessGroupElements(entered);
+
+        // Update existing and newly entered process groups
+        const merged: any = selection.merge(appendedGroups);
+        ProcessGroupRenderer.updateProcessGroupElements(merged, context);
+
+        // Attach event listeners if selection is enabled
+        // Use mousedown for selection to ensure single, deterministic event 
firing
+        if (canSelect && callbacks) {
+            if (callbacks.onClick) {
+                merged.on('mousedown.selection', function (event: MouseEvent, 
d: CanvasProcessGroup) {
+                    // Only handle left mouse button
+                    if (event.button !== 0) {
+                        return;
+                    }
+
+                    event.stopPropagation();
+                    callbacks.onClick!(d, event);
+                });
+            }
+            if (callbacks.onDoubleClick) {
+                merged.on('dblclick', function (event: MouseEvent, d: 
CanvasProcessGroup) {
+                    event.stopPropagation();
+                    callbacks.onDoubleClick!(d, event);
+                });
+            }
+            // Attach drag behavior for position updates if editing is allowed
+            if (callbacks.onDragEnd && context.getCanEdit()) {
+                ProcessGroupRenderer.attachDragBehavior(merged, context);
+            }
+        }
+
+        // Exit: remove PGs that are no longer in data
+        const exited: any = selection.exit();
+        ProcessGroupRenderer.removeProcessGroupElements(exited);
+    }
+
+    /**
+     * Append process group SVG elements (enter selection)
+     */
+    private static appendProcessGroupElements(
+        entered: d3.Selection<any, CanvasProcessGroup, any, any>
+    ): d3.Selection<any, CanvasProcessGroup, any, any> {
+        // Create group for each process group
+        const pgGroups = entered
+            .append('g')
+            .attr('id', (d) => `id-${d.entity.id}`)
+            .attr('class', 'process-group component')
+            .attr('transform', (d) => `translate(${d.entity.position.x}, 
${d.entity.position.y})`);
+
+        // Process group border (selection/authorization indicator)
+        pgGroups
+            .append('rect')
+            .attr('class', 'border')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', (d) => d.ui.dimensions.height)
+            .attr('fill', 'transparent')
+            .attr('stroke', 'transparent');
+
+        // Process group body
+        pgGroups
+            .append('rect')
+            .attr('class', 'body')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', (d) => d.ui.dimensions.height)
+            .attr('filter', 'url(#component-drop-shadow)')
+            .attr('stroke-width', 0);
+
+        // Process group banner (name background)
+        pgGroups
+            .append('rect')
+            .attr('class', 'process-group-banner')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', CanvasConstants.PROCESS_GROUP_BANNER_HEIGHT);
+
+        // Process group name
+        pgGroups
+            .append('text')
+            .attr('class', 'process-group-name secondary-contrast 
font-semibold')

Review Comment:
   I don't think we want the `font-semibold` here. The flow designer canvas 
doesn't have this class and it makes a fairly noticeable difference between the 
2.
   



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/canvas/layers/remote-process-group-layer/remote-process-group-renderer.ts:
##########
@@ -0,0 +1,757 @@
+/*
+ * 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 * as d3 from 'd3';
+import { CanvasRemoteProcessGroup } from '../../canvas.types';
+import { RemoteProcessGroupRenderContext } from '../render-context.types';
+import { ValidationErrorsTip } from 
'../../../tooltips/validation-errors-tip/validation-errors-tip.component';
+import { ConnectionRenderer } from '../connection-layer/connection-renderer';
+import { CanvasConstants } from '../../canvas.constants';
+
+export class RemoteProcessGroupRenderer {
+    public static render(context: RemoteProcessGroupRenderContext): void {
+        const { containerSelection, remoteProcessGroups, canSelect, callbacks 
} = context;
+
+        // D3 data join
+        const selection = containerSelection
+            .selectAll<SVGGElement, 
CanvasRemoteProcessGroup>('g.remote-process-group')
+            .data(remoteProcessGroups, (d: CanvasRemoteProcessGroup) => 
d.entity.id);
+
+        // Enter: create new RPG elements
+        const entered: any = selection.enter();
+        const appendedGroups: any = 
RemoteProcessGroupRenderer.appendRemoteProcessGroupElements(entered);
+
+        // Update existing and newly entered remote process groups
+        const merged: any = selection.merge(appendedGroups);
+        RemoteProcessGroupRenderer.updateRemoteProcessGroupElements(merged, 
context);
+
+        // Attach event listeners if selection is enabled
+        // Use mousedown for selection to ensure single, deterministic event 
firing
+        if (canSelect && callbacks) {
+            if (callbacks.onClick) {
+                merged.on('mousedown.selection', function (event: MouseEvent, 
d: CanvasRemoteProcessGroup) {
+                    // Only handle left mouse button
+                    if (event.button !== 0) {
+                        return;
+                    }
+
+                    event.stopPropagation();
+                    callbacks.onClick!(d, event);
+                });
+            }
+            if (callbacks.onDoubleClick) {
+                merged.on('dblclick', function (event: MouseEvent, d: 
CanvasRemoteProcessGroup) {
+                    event.stopPropagation();
+                    callbacks.onDoubleClick!(d, event);
+                });
+            }
+            // Attach drag behavior for position updates (filter will check 
canEdit)
+            if (callbacks.onDragEnd) {
+                RemoteProcessGroupRenderer.attachDragBehavior(merged, context);
+            }
+        }
+
+        // Exit: remove RPGs that are no longer in data
+        const exited: any = selection.exit();
+        RemoteProcessGroupRenderer.removeRemoteProcessGroupElements(exited);
+    }
+
+    private static appendRemoteProcessGroupElements(
+        entered: d3.Selection<any, CanvasRemoteProcessGroup, any, any>
+    ): d3.Selection<any, CanvasRemoteProcessGroup, any, any> {
+        // Create group for each remote process group
+        const rpgGroups = entered
+            .append('g')
+            .attr('id', (d) => `id-${d.entity.id}`)
+            .attr('class', 'remote-process-group component')
+            .attr('transform', (d) => `translate(${d.entity.position.x}, 
${d.entity.position.y})`);
+
+        // Remote process group border (selection/authorization indicator)
+        rpgGroups
+            .append('rect')
+            .attr('class', 'border')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', (d) => d.ui.dimensions.height)
+            .attr('fill', 'transparent')
+            .attr('stroke', 'transparent');
+
+        // Remote process group body
+        rpgGroups
+            .append('rect')
+            .attr('class', 'body')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', (d) => d.ui.dimensions.height)
+            .attr('filter', 'url(#component-drop-shadow)')
+            .attr('stroke-width', 0);
+
+        // Remote process group banner (name background)
+        rpgGroups
+            .append('rect')
+            .attr('class', 'remote-process-group-banner')
+            .attr('width', (d) => d.ui.dimensions.width)
+            .attr('height', 
CanvasConstants.REMOTE_PROCESS_GROUP_BANNER_HEIGHT);
+
+        // Remote process group name
+        rpgGroups
+            .append('text')
+            .attr('class', 'remote-process-group-name secondary-contrast 
font-semibold')

Review Comment:
   I don't think we want the `font-semibold` here. The flow designer canvas 
doesn't have this class.
   



-- 
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