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 96c4fcb75e NIFI-14961: Rendering the retried relationships in the
Connection label. (#10299)
96c4fcb75e is described below
commit 96c4fcb75e2983eafc004b1fa6d77b2121d6c851
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Sep 16 11:39:45 2025 -0400
NIFI-14961: Rendering the retried relationships in the Connection label.
(#10299)
This closes #10299
---
.../org/apache/nifi/web/api/dto/ConnectionDTO.java | 15 ++
.../cluster/manager/ConnectionEntityMerger.java | 4 +
.../org/apache/nifi/web/api/dto/DtoFactory.java | 9 +
.../apache/nifi/web/api/dto/DtoFactoryTest.java | 263 +++++++++++++++++++++
.../manager/connection-manager.service.spec.ts | 53 +++++
.../service/manager/connection-manager.service.ts | 101 ++++++--
.../flow-designer/ui/canvas/canvas.component.scss | 9 +-
7 files changed, 423 insertions(+), 31 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectionDTO.java
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectionDTO.java
index 5fa1ab5e1f..47b5dc229f 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectionDTO.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectionDTO.java
@@ -38,6 +38,7 @@ public class ConnectionDTO extends ComponentDTO {
private Long zIndex;
private Set<String> selectedRelationships;
private Set<String> availableRelationships;
+ private Set<String> retriedRelationships;
private Long backPressureObjectThreshold;
private String backPressureDataSizeThreshold;
@@ -161,6 +162,20 @@ public class ConnectionDTO extends ComponentDTO {
this.availableRelationships = availableRelationships;
}
+ /**
+ * @return relationships that are configured to be retried from the source
of the connection. This property is read only
+ */
+ @Schema(description = "The relationships from the source of the connection
that are configured to be retried.",
+ accessMode = Schema.AccessMode.READ_ONLY
+ )
+ public Set<String> getRetriedRelationships() {
+ return retriedRelationships;
+ }
+
+ public void setRetriedRelationships(Set<String> retriedRelationships) {
+ this.retriedRelationships = retriedRelationships;
+ }
+
/**
* The object count threshold for determining when back pressure is
applied. Updating this value is a passive change in the sense that it won't
impact whether existing files over the limit are
* affected but it does help feeder processors to stop pushing too much
into this work queue.
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectionEntityMerger.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectionEntityMerger.java
index 9885b95ac6..ef6b5afe89 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectionEntityMerger.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectionEntityMerger.java
@@ -61,6 +61,10 @@ public class ConnectionEntityMerger implements
ComponentEntityMerger<ConnectionE
if (selectedRelationships != null) {
clientEntity.getComponent().setSelectedRelationships(new
TreeSet<>(selectedRelationships));
}
+ final Set<String> retriedRelationships = clientEntity.getComponent()
== null ? null : clientEntity.getComponent().getRetriedRelationships();
+ if (retriedRelationships != null) {
+ clientEntity.getComponent().setRetriedRelationships(new
TreeSet<>(retriedRelationships));
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 51a87885e8..9a7d7b5a82 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -752,6 +752,14 @@ public final class DtoFactory {
}
dto.getSelectedRelationships().add(selectedRelationship.getName());
+
+ // Check if this selected relationship is configured to be
retried from the source
+ if
(connection.getSource().isRelationshipRetried(selectedRelationship)) {
+ if (dto.getRetriedRelationships() == null) {
+ dto.setRetriedRelationships(new
TreeSet<>(Collator.getInstance(Locale.US)));
+ }
+
dto.getRetriedRelationships().add(selectedRelationship.getName());
+ }
}
}
@@ -4502,6 +4510,7 @@ public final class DtoFactory {
copy.setName(original.getName());
copy.setParentGroupId(original.getParentGroupId());
copy.setSelectedRelationships(copy(original.getSelectedRelationships()));
+ copy.setRetriedRelationships(copy(original.getRetriedRelationships()));
copy.setFlowFileExpiration(original.getFlowFileExpiration());
copy.setBackPressureObjectThreshold(original.getBackPressureObjectThreshold());
copy.setBackPressureDataSizeThreshold(original.getBackPressureDataSizeThreshold());
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
index cb307412ef..1d1c00bc96 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryTest.java
@@ -22,10 +22,17 @@ import org.apache.nifi.bundle.BundleDetails;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.validation.ValidationStatus;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.ConnectableType;
+import org.apache.nifi.connectable.Connection;
import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.LoadBalanceCompression;
+import org.apache.nifi.controller.queue.LoadBalanceStrategy;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarManifest;
@@ -34,6 +41,7 @@ import org.apache.nifi.nar.NarSource;
import org.apache.nifi.nar.NarState;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
+import org.apache.nifi.processor.Relationship;
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.junit.jupiter.api.Test;
@@ -51,6 +59,7 @@ import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -396,4 +405,258 @@ public class DtoFactoryTest {
final FlowRegistryClientDTO dto =
dtoFactory.createRegistryDto(clientNode);
assertTrue(dto.getMultipleVersionsAvailable(), "Ghost registry client
with one compatible bundle should allow change version");
}
+
+ @Test
+ void testCreateConnectionDtoWithRetriedRelationships() {
+ // Set up test relationships
+ final Relationship successRelationship = new
Relationship.Builder().name("success").build();
+ final Relationship failureRelationship = new
Relationship.Builder().name("failure").build();
+ final Relationship retryRelationship = new
Relationship.Builder().name("retry").build();
+
+ // Mock the process group
+ final ProcessGroup processGroup = mock(ProcessGroup.class);
+ when(processGroup.getIdentifier()).thenReturn("group-id");
+
+ // Mock the source connectable
+ final Connectable sourceConnectable = mock(Connectable.class);
+
when(sourceConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(sourceConnectable.getIdentifier()).thenReturn("source-id");
+ when(sourceConnectable.getName()).thenReturn("Source Processor");
+
when(sourceConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+ when(sourceConnectable.getProcessGroup()).thenReturn(processGroup);
+ // Configure retry settings: only "failure" and "retry" relationships
are retried
+
when(sourceConnectable.isRelationshipRetried(successRelationship)).thenReturn(false);
+
when(sourceConnectable.isRelationshipRetried(failureRelationship)).thenReturn(true);
+
when(sourceConnectable.isRelationshipRetried(retryRelationship)).thenReturn(true);
+
+ // Mock the destination connectable
+ final Connectable destinationConnectable = mock(Connectable.class);
+
when(destinationConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(destinationConnectable.getIdentifier()).thenReturn("dest-id");
+ when(destinationConnectable.getName()).thenReturn("Destination
Processor");
+
when(destinationConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+
when(destinationConnectable.getProcessGroup()).thenReturn(processGroup);
+
+ // Mock the flow file queue
+ final FlowFileQueue flowFileQueue = mock(FlowFileQueue.class);
+
when(flowFileQueue.getBackPressureObjectThreshold()).thenReturn(10000L);
+ when(flowFileQueue.getBackPressureDataSizeThreshold()).thenReturn("1
GB");
+ when(flowFileQueue.getFlowFileExpiration()).thenReturn("0 sec");
+
when(flowFileQueue.getPriorities()).thenReturn(Collections.emptyList());
+
when(flowFileQueue.getLoadBalanceStrategy()).thenReturn(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE);
+ when(flowFileQueue.getPartitioningAttribute()).thenReturn(null);
+
when(flowFileQueue.getLoadBalanceCompression()).thenReturn(LoadBalanceCompression.DO_NOT_COMPRESS);
+ when(flowFileQueue.isActivelyLoadBalancing()).thenReturn(false);
+
+ // Mock the connection - only "failure" and "retry" are selected
(success is not selected)
+ final Connection connection = mock(Connection.class);
+ when(connection.getIdentifier()).thenReturn("connection-id");
+ when(connection.getName()).thenReturn("Test Connection");
+ when(connection.getSource()).thenReturn(sourceConnectable);
+ when(connection.getDestination()).thenReturn(destinationConnectable);
+ when(connection.getProcessGroup()).thenReturn(processGroup);
+ when(connection.getFlowFileQueue()).thenReturn(flowFileQueue);
+
when(connection.getRelationships()).thenReturn(Arrays.asList(failureRelationship,
retryRelationship));
+ when(connection.getBendPoints()).thenReturn(Collections.emptyList());
+ when(connection.getLabelIndex()).thenReturn(0);
+ when(connection.getZIndex()).thenReturn(0L);
+
when(connection.getVersionedComponentId()).thenReturn(java.util.Optional.empty());
+
+ // Create the DTO factory
+ final DtoFactory dtoFactory = new DtoFactory();
+
+ // Test the creation of ConnectionDTO
+ final ConnectionDTO connectionDto =
dtoFactory.createConnectionDto(connection);
+
+ // Assertions
+ assertEquals("connection-id", connectionDto.getId());
+ assertEquals("Test Connection", connectionDto.getName());
+
+ // Verify selected relationships
+ assertEquals(2, connectionDto.getSelectedRelationships().size());
+
assertTrue(connectionDto.getSelectedRelationships().contains("failure"));
+ assertTrue(connectionDto.getSelectedRelationships().contains("retry"));
+
+ // Verify retried relationships - should only contain the selected
relationships that are also retried
+ assertEquals(2, connectionDto.getRetriedRelationships().size());
+
assertTrue(connectionDto.getRetriedRelationships().contains("failure"));
+ assertTrue(connectionDto.getRetriedRelationships().contains("retry"));
+ }
+
+ @Test
+ void testCreateConnectionDtoWithPartiallyRetriedRelationships() {
+ // Set up test relationships
+ final Relationship successRelationship = new
Relationship.Builder().name("success").build();
+ final Relationship failureRelationship = new
Relationship.Builder().name("failure").build();
+
+ // Mock the process group
+ final ProcessGroup processGroup = mock(ProcessGroup.class);
+ when(processGroup.getIdentifier()).thenReturn("group-id");
+
+ // Mock the source connectable
+ final Connectable sourceConnectable = mock(Connectable.class);
+
when(sourceConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(sourceConnectable.getIdentifier()).thenReturn("source-id");
+ when(sourceConnectable.getName()).thenReturn("Source Processor");
+
when(sourceConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+ when(sourceConnectable.getProcessGroup()).thenReturn(processGroup);
+ // Configure retry settings: only "failure" relationship is retried,
"success" is not
+
when(sourceConnectable.isRelationshipRetried(successRelationship)).thenReturn(false);
+
when(sourceConnectable.isRelationshipRetried(failureRelationship)).thenReturn(true);
+
+ // Mock the destination connectable
+ final Connectable destinationConnectable = mock(Connectable.class);
+
when(destinationConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(destinationConnectable.getIdentifier()).thenReturn("dest-id");
+ when(destinationConnectable.getName()).thenReturn("Destination
Processor");
+
when(destinationConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+
when(destinationConnectable.getProcessGroup()).thenReturn(processGroup);
+
+ // Mock the flow file queue
+ final FlowFileQueue flowFileQueue = mock(FlowFileQueue.class);
+
when(flowFileQueue.getBackPressureObjectThreshold()).thenReturn(10000L);
+ when(flowFileQueue.getBackPressureDataSizeThreshold()).thenReturn("1
GB");
+ when(flowFileQueue.getFlowFileExpiration()).thenReturn("0 sec");
+
when(flowFileQueue.getPriorities()).thenReturn(Collections.emptyList());
+
when(flowFileQueue.getLoadBalanceStrategy()).thenReturn(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE);
+ when(flowFileQueue.getPartitioningAttribute()).thenReturn(null);
+
when(flowFileQueue.getLoadBalanceCompression()).thenReturn(LoadBalanceCompression.DO_NOT_COMPRESS);
+ when(flowFileQueue.isActivelyLoadBalancing()).thenReturn(false);
+
+ // Mock the connection - both relationships are selected
+ final Connection connection = mock(Connection.class);
+ when(connection.getIdentifier()).thenReturn("connection-id");
+ when(connection.getName()).thenReturn("Test Connection");
+ when(connection.getSource()).thenReturn(sourceConnectable);
+ when(connection.getDestination()).thenReturn(destinationConnectable);
+ when(connection.getProcessGroup()).thenReturn(processGroup);
+ when(connection.getFlowFileQueue()).thenReturn(flowFileQueue);
+
when(connection.getRelationships()).thenReturn(Arrays.asList(successRelationship,
failureRelationship));
+ when(connection.getBendPoints()).thenReturn(Collections.emptyList());
+ when(connection.getLabelIndex()).thenReturn(0);
+ when(connection.getZIndex()).thenReturn(0L);
+
when(connection.getVersionedComponentId()).thenReturn(java.util.Optional.empty());
+
+ // Create the DTO factory
+ final DtoFactory dtoFactory = new DtoFactory();
+
+ // Test the creation of ConnectionDTO
+ final ConnectionDTO connectionDto =
dtoFactory.createConnectionDto(connection);
+
+ // Assertions
+ assertEquals("connection-id", connectionDto.getId());
+ assertEquals("Test Connection", connectionDto.getName());
+
+ // Verify selected relationships - should contain both
+ assertEquals(2, connectionDto.getSelectedRelationships().size());
+
assertTrue(connectionDto.getSelectedRelationships().contains("success"));
+
assertTrue(connectionDto.getSelectedRelationships().contains("failure"));
+
+ // Verify retried relationships - should only contain "failure" since
"success" is not retried
+ assertEquals(1, connectionDto.getRetriedRelationships().size());
+
assertTrue(connectionDto.getRetriedRelationships().contains("failure"));
+
assertFalse(connectionDto.getRetriedRelationships().contains("success"));
+ }
+
+ @Test
+ void testCreateConnectionDtoWithNoRetriedRelationships() {
+ // Set up test relationships
+ final Relationship successRelationship = new
Relationship.Builder().name("success").build();
+ final Relationship failureRelationship = new
Relationship.Builder().name("failure").build();
+
+ // Mock the process group
+ final ProcessGroup processGroup = mock(ProcessGroup.class);
+ when(processGroup.getIdentifier()).thenReturn("group-id");
+
+ // Mock the source connectable
+ final Connectable sourceConnectable = mock(Connectable.class);
+
when(sourceConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(sourceConnectable.getIdentifier()).thenReturn("source-id");
+ when(sourceConnectable.getName()).thenReturn("Source Processor");
+
when(sourceConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+ when(sourceConnectable.getProcessGroup()).thenReturn(processGroup);
+ // Configure retry settings: no relationships are retried
+
when(sourceConnectable.isRelationshipRetried(successRelationship)).thenReturn(false);
+
when(sourceConnectable.isRelationshipRetried(failureRelationship)).thenReturn(false);
+
+ // Mock the destination connectable
+ final Connectable destinationConnectable = mock(Connectable.class);
+
when(destinationConnectable.getConnectableType()).thenReturn(ConnectableType.PROCESSOR);
+ when(destinationConnectable.getIdentifier()).thenReturn("dest-id");
+ when(destinationConnectable.getName()).thenReturn("Destination
Processor");
+
when(destinationConnectable.getProcessGroupIdentifier()).thenReturn("group-id");
+
when(destinationConnectable.getProcessGroup()).thenReturn(processGroup);
+
+ // Mock the flow file queue
+ final FlowFileQueue flowFileQueue = mock(FlowFileQueue.class);
+
when(flowFileQueue.getBackPressureObjectThreshold()).thenReturn(10000L);
+ when(flowFileQueue.getBackPressureDataSizeThreshold()).thenReturn("1
GB");
+ when(flowFileQueue.getFlowFileExpiration()).thenReturn("0 sec");
+
when(flowFileQueue.getPriorities()).thenReturn(Collections.emptyList());
+
when(flowFileQueue.getLoadBalanceStrategy()).thenReturn(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE);
+ when(flowFileQueue.getPartitioningAttribute()).thenReturn(null);
+
when(flowFileQueue.getLoadBalanceCompression()).thenReturn(LoadBalanceCompression.DO_NOT_COMPRESS);
+ when(flowFileQueue.isActivelyLoadBalancing()).thenReturn(false);
+
+ // Mock the connection
+ final Connection connection = mock(Connection.class);
+ when(connection.getIdentifier()).thenReturn("connection-id");
+ when(connection.getName()).thenReturn("Test Connection");
+ when(connection.getSource()).thenReturn(sourceConnectable);
+ when(connection.getDestination()).thenReturn(destinationConnectable);
+ when(connection.getProcessGroup()).thenReturn(processGroup);
+ when(connection.getFlowFileQueue()).thenReturn(flowFileQueue);
+
when(connection.getRelationships()).thenReturn(Arrays.asList(successRelationship,
failureRelationship));
+ when(connection.getBendPoints()).thenReturn(Collections.emptyList());
+ when(connection.getLabelIndex()).thenReturn(0);
+ when(connection.getZIndex()).thenReturn(0L);
+
when(connection.getVersionedComponentId()).thenReturn(java.util.Optional.empty());
+
+ // Create the DTO factory
+ final DtoFactory dtoFactory = new DtoFactory();
+
+ // Test the creation of ConnectionDTO
+ final ConnectionDTO connectionDto =
dtoFactory.createConnectionDto(connection);
+
+ // Assertions
+ assertEquals("connection-id", connectionDto.getId());
+ assertEquals("Test Connection", connectionDto.getName());
+
+ // Verify selected relationships
+ assertEquals(2, connectionDto.getSelectedRelationships().size());
+
assertTrue(connectionDto.getSelectedRelationships().contains("success"));
+
assertTrue(connectionDto.getSelectedRelationships().contains("failure"));
+
+ // Verify retried relationships - should be null since no
relationships are retried
+ assertNull(connectionDto.getRetriedRelationships());
+ }
+
+ @Test
+ void testConnectionDtoCopy() {
+ // Create original ConnectionDTO with retried relationships
+ final ConnectionDTO original = new ConnectionDTO();
+ original.setId("connection-id");
+ original.setName("Test Connection");
+ original.setSelectedRelationships(new
HashSet<>(Arrays.asList("success", "failure", "retry")));
+ original.setAvailableRelationships(new
HashSet<>(Arrays.asList("success", "failure", "retry")));
+ original.setRetriedRelationships(new
HashSet<>(Arrays.asList("failure", "retry")));
+
+ // Create the DTO factory
+ final DtoFactory dtoFactory = new DtoFactory();
+
+ // Test the copy method
+ final ConnectionDTO copy = dtoFactory.copy(original);
+
+ // Assertions
+ assertEquals(original.getId(), copy.getId());
+ assertEquals(original.getName(), copy.getName());
+ assertEquals(original.getSelectedRelationships(),
copy.getSelectedRelationships());
+ assertEquals(original.getAvailableRelationships(),
copy.getAvailableRelationships());
+ assertEquals(original.getRetriedRelationships(),
copy.getRetriedRelationships());
+
+ // Verify they are separate objects (deep copy)
+ assertNotSame(original.getSelectedRelationships(),
copy.getSelectedRelationships());
+ assertNotSame(original.getAvailableRelationships(),
copy.getAvailableRelationships());
+ assertNotSame(original.getRetriedRelationships(),
copy.getRetriedRelationships());
+ }
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts
index f4b0970bbc..fea4e539fe 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.spec.ts
@@ -90,4 +90,57 @@ describe('ConnectionManager', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
+
+ describe('isRetryConfigured', () => {
+ it('should return true when retriedRelationships exist and have length
> 0', () => {
+ const connection = {
+ retriedRelationships: ['failure', 'retry']
+ };
+
+ // Access private method via bracket notation for testing
+ const result = (service as any).isRetryConfigured(connection);
+
+ expect(result).toBe(true);
+ });
+
+ it('should return false when retriedRelationships is null', () => {
+ const connection = {
+ retriedRelationships: null
+ };
+
+ const result = (service as any).isRetryConfigured(connection);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false when retriedRelationships is undefined', () =>
{
+ const connection = {
+ retriedRelationships: undefined
+ };
+
+ const result = (service as any).isRetryConfigured(connection);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false when retriedRelationships is empty array', ()
=> {
+ const connection = {
+ retriedRelationships: []
+ };
+
+ const result = (service as any).isRetryConfigured(connection);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return true when retriedRelationships has one
relationship', () => {
+ const connection = {
+ retriedRelationships: ['failure']
+ };
+
+ const result = (service as any).isRetryConfigured(connection);
+
+ expect(result).toBe(true);
+ });
+ });
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts
index 633f830b83..f82d74dc6b 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts
@@ -59,7 +59,7 @@ export class ConnectionManager implements OnDestroy {
private destroyed$: Subject<boolean> = new Subject();
private static readonly DIMENSIONS: Dimension = {
- width: 224,
+ width: 240,
height: 0
};
@@ -615,6 +615,16 @@ export class ConnectionManager implements OnDestroy {
return connection.loadBalanceStrategy != null && 'DO_NOT_LOAD_BALANCE'
!== connection.loadBalanceStrategy;
}
+ /**
+ * Determines whether retried relationships are configured for the
specified connection.
+ *
+ * @param {object} connection
+ * @return {boolean} Whether retried relationships are configured
+ */
+ private isRetryConfigured(connection: any): boolean {
+ return connection.retriedRelationships != null &&
connection.retriedRelationships.length > 0;
+ }
+
/**
* Sorts the specified connections according to the z index.
*
@@ -1221,14 +1231,6 @@ export class ConnectionManager implements OnDestroy {
self.quickSelectBehavior.activate(connectionLabelContainer);
- // connection label
- connectionLabelContainer
- .append('rect')
- .attr('class', 'body')
- .attr('width', ConnectionManager.DIMENSIONS.width)
- .attr('x', 0)
- .attr('y', 0);
-
// processor border
connectionLabelContainer
.append('rect')
@@ -1236,6 +1238,14 @@ export class ConnectionManager implements OnDestroy {
.attr('width', ConnectionManager.DIMENSIONS.width)
.attr('fill', 'transparent')
.attr('stroke', 'transparent');
+
+ // connection label
+ connectionLabelContainer
+ .append('rect')
+ .attr('class', 'body')
+ .attr('width', ConnectionManager.DIMENSIONS.width)
+ .attr('x', 0)
+ .attr('y', 0);
}
let labelCount = 0;
@@ -1290,12 +1300,12 @@ export class ConnectionManager implements OnDestroy {
.attr('class', 'stats-value
connection-from')
.attr('x', 43)
.attr('y', 14)
- .attr('width', 130);
+ .attr('width', 146);
connectionFrom
.append('text')
.attr('class',
'connection-from-run-status')
- .attr('x', 208)
+ .attr('x', 224)
.attr('y', 14);
} else {
backgrounds.push(connectionFrom.select('rect.connection-label-background'));
@@ -1405,12 +1415,12 @@ export class ConnectionManager implements OnDestroy {
.attr('class', 'stats-value connection-to')
.attr('x', 25)
.attr('y', 14)
- .attr('width', 145);
+ .attr('width', 161);
connectionTo
.append('text')
.attr('class', 'connection-to-run-status')
- .attr('x', 208)
+ .attr('x', 224)
.attr('y', 14);
} else {
backgrounds.push(connectionTo.select('rect.connection-label-background'));
@@ -1523,7 +1533,7 @@ export class ConnectionManager implements OnDestroy {
.attr('class', 'stats-value
connection-name')
.attr('x', 45)
.attr('y', 14)
- .attr('width', 142);
+ .attr('width', 158);
} else {
backgrounds.push(connectionName.select('rect.connection-label-background'));
borders.push(connectionName.select('rect.connection-label-border'));
@@ -1618,11 +1628,21 @@ export class ConnectionManager implements OnDestroy {
})
.append('title');
+ // retry icon
+ queued
+ .append('text')
+ .attr('class', 'retry-icon')
+ .attr('y', 14)
+ .text(function () {
+ return '\uf021';
+ })
+ .append('title');
+
// expiration icon
queued
.append('text')
.attr('class', 'expiration-icon primary-color')
- .attr('x', 208)
+ .attr('x', 224)
.attr('y', 14)
.text(function () {
return '\uf017';
@@ -1795,6 +1815,36 @@ export class ConnectionManager implements OnDestroy {
}
});
+ // determine whether or not to show the retry icon
+ connectionLabelContainer
+ .select('text.retry-icon')
+ .classed('hidden', function () {
+ if (d.permissions.canRead) {
+ return !self.isRetryConfigured(d.component);
+ } else {
+ return true;
+ }
+ })
+ .classed('primary-color', function () {
+ return d.permissions.canRead;
+ })
+ .attr('x', function () {
+ let offset = 224;
+ if (d.permissions.canRead &&
self.isExpirationConfigured(d.component)) {
+ offset -= 16;
+ }
+ // retry icon comes before load-balance icon, so
no additional offset needed here
+ return offset;
+ })
+ .select('title')
+ .text(function () {
+ if (d.permissions.canRead &&
self.isRetryConfigured(d.component)) {
+ return `Relationships configured to be
retried: ${d.component.retriedRelationships.join(', ')}`;
+ } else {
+ return '';
+ }
+ });
+
// determine whether or not to show the load-balance icon
connectionLabelContainer
.select('text.load-balance-icon')
@@ -1811,14 +1861,16 @@ export class ConnectionManager implements OnDestroy {
.classed('primary-color', function (d: any) {
return d.permissions.canRead &&
d.component.loadBalanceStatus !== 'LOAD_BALANCE_ACTIVE';
})
- .classed('load-balance-icon-184', function () {
- return d.permissions.canRead &&
self.isExpirationConfigured(d.component);
- })
- .classed('load-balance-icon-200', function () {
- return d.permissions.canRead &&
!self.isExpirationConfigured(d.component);
- })
.attr('x', function () {
- return d.permissions.canRead &&
self.isExpirationConfigured(d.component) ? 192 : 208;
+ let offset = 224;
+ if (d.permissions.canRead &&
self.isExpirationConfigured(d.component)) {
+ offset -= 16;
+ }
+ if (d.permissions.canRead &&
self.isRetryConfigured(d.component)) {
+ offset -= 16;
+ }
+ // load-balance icon is leftmost, so it gets
additional offset
+ return offset;
})
.select('title')
.text(function () {
@@ -1977,7 +2029,10 @@ export class ConnectionManager implements OnDestroy {
if
(!connectionLabelContainer.select('text.load-balance-icon').classed('hidden')) {
offset += 16;
}
- return 208 - offset;
+ if
(!connectionLabelContainer.select('text.retry-icon').classed('hidden')) {
+ offset += 16;
+ }
+ return 224 - offset;
})
.select('title')
.text(function () {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.scss
index e4ed4efaba..5fb106d35c 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.scss
@@ -245,19 +245,12 @@
text.connection-to-run-status,
text.expiration-icon,
text.load-balance-icon,
+ text.retry-icon,
text.penalized-icon {
font-family: FontAwesome;
font-size: 12px;
}
- text.load-balance-icon-184 {
- transform-origin: 197px 10px 0;
- }
-
- text.load-balance-icon-200 {
- transform-origin: 213px 10px 0;
- }
-
g.connection rect.backpressure-object > title > tspan,
g.connection rect.backpressure-data-size > title > tspan {
display: block;