This is an automated email from the ASF dual-hosted git repository.
mcgilman 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 f6d146f314d NIFI-15381 - Improve UX for the view Show/Revert Local
Changes to account for environmental changes (#10681)
f6d146f314d is described below
commit f6d146f314d7885a53bba4eb0dbd3fd19fa4fb62
Author: Pierre Villard <[email protected]>
AuthorDate: Fri Feb 27 15:08:42 2026 +0100
NIFI-15381 - Improve UX for the view Show/Revert Local Changes to account
for environmental changes (#10681)
* NIFI-15381 - Improve UX for the view Show/Revert Local Changes to account
for environmental changes
Signed-off-by: Pierre Villard <[email protected]>
* review
---------
Signed-off-by: Pierre Villard <[email protected]>
---
.../org/apache/nifi/web/api/dto/DifferenceDTO.java | 15 +-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 26 ++--
.../org/apache/nifi/web/api/dto/DtoFactory.java | 42 ++++--
.../apache/nifi/web/api/dto/DtoFactoryTest.java | 59 ++++++++
.../app/pages/flow-designer/state/flow/index.ts | 1 +
.../local-changes-dialog/local-changes-dialog.html | 1 +
.../local-changes-table/local-changes-table.html | 13 +-
.../local-changes-table.spec.ts | 162 +++++++++++++++++++++
.../local-changes-table/local-changes-table.ts | 77 +++++++---
9 files changed, 353 insertions(+), 43 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
index 8c3d5e88d81..b144f82a184 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
@@ -26,6 +26,7 @@ import java.util.Objects;
public class DifferenceDTO {
private String differenceType;
private String difference;
+ private Boolean isEnvironmental;
@Schema(description = "The type of difference")
public String getDifferenceType() {
@@ -45,6 +46,16 @@ public class DifferenceDTO {
this.difference = difference;
}
+ @Schema(description = "Whether this difference is environmental (e.g.,
bundle version change due to NiFi upgrade) " +
+ "rather than a user-initiated change. Environmental changes are
typically not reverted when reverting local changes.")
+ public Boolean getEnvironmental() {
+ return isEnvironmental;
+ }
+
+ public void setEnvironmental(Boolean environmental) {
+ isEnvironmental = environmental;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) {
@@ -54,11 +65,11 @@ public class DifferenceDTO {
return false;
}
final DifferenceDTO that = (DifferenceDTO) o;
- return Objects.equals(differenceType, that.differenceType) &&
Objects.equals(difference, that.difference);
+ return Objects.equals(differenceType, that.differenceType) &&
Objects.equals(difference, that.difference) && Objects.equals(isEnvironmental,
that.isEnvironmental);
}
@Override
public int hashCode() {
- return Objects.hash(differenceType, difference);
+ return Objects.hash(differenceType, difference, isEnvironmental);
}
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index e204fde501b..55f9f6af02f 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -5615,18 +5615,20 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
+ " but cannot find a Flow Registry with that identifier");
}
- VersionedProcessGroup registryGroup =
versionControlInfo.getFlowSnapshot();
- if (registryGroup == null) {
- try {
- final FlowVersionLocation flowVersionLocation = new
FlowVersionLocation(versionControlInfo.getBranch(),
versionControlInfo.getBucketIdentifier(),
- versionControlInfo.getFlowIdentifier(),
versionControlInfo.getVersion());
- final FlowSnapshotContainer flowSnapshotContainer =
flowRegistry.getFlowContents(FlowRegistryClientContextFactory.getContextForUser(NiFiUserUtils.getNiFiUser()),
- flowVersionLocation, true);
- final RegisteredFlowSnapshot versionedFlowSnapshot =
flowSnapshotContainer.getFlowSnapshot();
- registryGroup = versionedFlowSnapshot.getFlowContents();
- } catch (final IOException | FlowRegistryException e) {
- throw new NiFiCoreException("Failed to retrieve flow with Flow
Registry in order to calculate local differences due to " + e.getMessage(), e);
- }
+ // Always fetch the flow from the registry to get the original bundle
versions.
+ // The cached snapshot (versionControlInfo.getFlowSnapshot()) may have
been mutated
+ // during import/revert operations by discoverCompatibleBundles(),
which would cause
+ // bundle version differences to not be detected.
+ final VersionedProcessGroup registryGroup;
+ try {
+ final FlowVersionLocation flowVersionLocation = new
FlowVersionLocation(versionControlInfo.getBranch(),
versionControlInfo.getBucketIdentifier(),
+ versionControlInfo.getFlowIdentifier(),
versionControlInfo.getVersion());
+ final FlowSnapshotContainer flowSnapshotContainer =
flowRegistry.getFlowContents(FlowRegistryClientContextFactory.getContextForUser(NiFiUserUtils.getNiFiUser()),
+ flowVersionLocation, true);
+ final RegisteredFlowSnapshot versionedFlowSnapshot =
flowSnapshotContainer.getFlowSnapshot();
+ registryGroup = versionedFlowSnapshot.getFlowContents();
+ } catch (final IOException | FlowRegistryException e) {
+ throw new NiFiCoreException("Failed to retrieve flow with Flow
Registry in order to calculate local differences due to " + e.getMessage(), e);
}
final NiFiRegistryFlowMapper mapper =
makeNiFiRegistryFlowMapper(controllerFacade.getExtensionManager());
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 e9c9c34851c..6954dc6607a 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
@@ -2810,7 +2810,7 @@ public final class DtoFactory {
if (FlowDifferenceFilters.isBundleChange(difference)) {
final ComponentDifferenceDTO componentDiff =
createBundleDifference(difference);
final Set<DifferenceDTO> differences =
bundleDifferencesByComponent.computeIfAbsent(componentDiff, key -> new
HashSet<>());
- differences.add(createDifferenceDto(difference));
+ differences.add(createBundleDifferenceDto(difference));
}
// Ignore any environment-specific change
@@ -2829,17 +2829,13 @@ public final class DtoFactory {
differences.add(createDifferenceDto(difference));
}
- if (!differencesByComponent.isEmpty()) {
- // differences were found, so now let's add back in any
BUNDLE_CHANGED differences
- // since they were initially filtered out as an
environment-specific change
- bundleDifferencesByComponent.forEach((key, value) -> {
- List<DifferenceDTO> values = value.stream().toList();
- differencesByComponent.merge(key, values, (v1, v2) -> {
- v1.addAll(v2);
- return v1;
- });
+ bundleDifferencesByComponent.forEach((key, value) -> {
+ final List<DifferenceDTO> values = value.stream().toList();
+ differencesByComponent.merge(key, values, (v1, v2) -> {
+ v1.addAll(v2);
+ return v1;
});
- }
+ });
for (final Map.Entry<ComponentDifferenceDTO, List<DifferenceDTO>>
entry : differencesByComponent.entrySet()) {
entry.getKey().setDifferences(entry.getValue());
@@ -2858,6 +2854,30 @@ public final class DtoFactory {
return dto;
}
+ /**
+ * Creates a DifferenceDTO for bundle changes, determining whether the
change is environmental
+ * (due to NiFi upgrade where the original bundle version is not
available) or user-initiated
+ * (user manually changed the bundle version when multiple versions are
available).
+ */
+ DifferenceDTO createBundleDifferenceDto(final FlowDifference difference) {
+ final DifferenceDTO dto = createDifferenceDto(difference);
+
+ final Object valueA = difference.getValueA();
+ if (valueA instanceof org.apache.nifi.flow.Bundle registryBundle) {
+ final BundleCoordinate registryCoordinate = new BundleCoordinate(
+ registryBundle.getGroup(),
+ registryBundle.getArtifact(),
+ registryBundle.getVersion()
+ );
+
+
dto.setEnvironmental(extensionManager.getBundle(registryCoordinate) == null);
+ } else {
+ dto.setEnvironmental(true);
+ }
+
+ return dto;
+ }
+
private Map<String, VersionedProcessGroup> flattenProcessGroups(final
VersionedProcessGroup group) {
final Map<String, VersionedProcessGroup> flattened = new HashMap<>();
flattenProcessGroups(group, flattened);
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 1d1c00bc961..e4549bdd501 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
@@ -43,6 +43,8 @@ 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.registry.flow.diff.DifferenceType;
+import org.apache.nifi.registry.flow.diff.FlowDifference;
import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -659,4 +661,61 @@ public class DtoFactoryTest {
assertNotSame(original.getAvailableRelationships(),
copy.getAvailableRelationships());
assertNotSame(original.getRetriedRelationships(),
copy.getRetriedRelationships());
}
+
+ @Test
+ void testCreateBundleDifferenceDtoWhenRegistryBundleAvailable() {
+ final org.apache.nifi.flow.Bundle registryBundle = new
org.apache.nifi.flow.Bundle("com.example", "my-nar", "1.0.0");
+ final BundleCoordinate expectedCoordinate = new
BundleCoordinate("com.example", "my-nar", "1.0.0");
+
+ final FlowDifference difference = mock(FlowDifference.class);
+
when(difference.getDifferenceType()).thenReturn(DifferenceType.BUNDLE_CHANGED);
+ when(difference.getDescription()).thenReturn("Bundle changed from
1.0.0 to 2.0.0");
+ when(difference.getValueA()).thenReturn(registryBundle);
+
+ final ExtensionManager extensionManager = mock(ExtensionManager.class);
+
when(extensionManager.getBundle(eq(expectedCoordinate))).thenReturn(createBundle("com.example",
"my-nar", "1.0.0"));
+
+ final DtoFactory dtoFactory = new DtoFactory();
+ dtoFactory.setExtensionManager(extensionManager);
+
+ final DifferenceDTO dto =
dtoFactory.createBundleDifferenceDto(difference);
+ assertEquals(DifferenceType.BUNDLE_CHANGED.getDescription(),
dto.getDifferenceType());
+ assertFalse(dto.getEnvironmental());
+ }
+
+ @Test
+ void testCreateBundleDifferenceDtoWhenRegistryBundleNotAvailable() {
+ final org.apache.nifi.flow.Bundle registryBundle = new
org.apache.nifi.flow.Bundle("com.example", "my-nar", "1.0.0");
+
+ final FlowDifference difference = mock(FlowDifference.class);
+
when(difference.getDifferenceType()).thenReturn(DifferenceType.BUNDLE_CHANGED);
+ when(difference.getDescription()).thenReturn("Bundle changed from
1.0.0 to 2.0.0");
+ when(difference.getValueA()).thenReturn(registryBundle);
+
+ final ExtensionManager extensionManager = mock(ExtensionManager.class);
+
when(extensionManager.getBundle(any(BundleCoordinate.class))).thenReturn(null);
+
+ final DtoFactory dtoFactory = new DtoFactory();
+ dtoFactory.setExtensionManager(extensionManager);
+
+ final DifferenceDTO dto =
dtoFactory.createBundleDifferenceDto(difference);
+ assertEquals(DifferenceType.BUNDLE_CHANGED.getDescription(),
dto.getDifferenceType());
+ assertTrue(dto.getEnvironmental());
+ }
+
+ @Test
+ void testCreateBundleDifferenceDtoWhenValueIsNotBundle() {
+ final FlowDifference difference = mock(FlowDifference.class);
+
when(difference.getDifferenceType()).thenReturn(DifferenceType.BUNDLE_CHANGED);
+ when(difference.getDescription()).thenReturn("Bundle changed");
+ when(difference.getValueA()).thenReturn("not-a-bundle");
+
+ final ExtensionManager extensionManager = mock(ExtensionManager.class);
+
+ final DtoFactory dtoFactory = new DtoFactory();
+ dtoFactory.setExtensionManager(extensionManager);
+
+ final DifferenceDTO dto =
dtoFactory.createBundleDifferenceDto(difference);
+ assertTrue(dto.getEnvironmental());
+ }
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/index.ts
index da79c1c19b5..832c608d620 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/index.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/index.ts
@@ -865,6 +865,7 @@ export interface FlowUpdateRequestEntity {
export interface Difference {
differenceType: string;
difference: string;
+ environmental?: boolean;
}
export interface ComponentDifference {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-dialog.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-dialog.html
index 93fdfae04cf..343a7a015e5 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-dialog.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-dialog.html
@@ -34,6 +34,7 @@
</div>
<div class="flex-1">
<local-changes-table
+ [mode]="mode"
[differences]="localModifications.componentDifferences"
(goToChange)="goToChange.next($event)"></local-changes-table>
</div>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.html
index 121a245abb5..8f95bc2845e 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.html
@@ -19,13 +19,21 @@
<div>
<div>
<form [formGroup]="filterForm" class="my-2">
- <div class="flex pt-2">
+ <div class="flex pt-2 items-center">
<div class="mr-2">
<mat-form-field subscriptSizing="dynamic">
<mat-label>Filter</mat-label>
<input matInput type="text" class="small"
formControlName="filterTerm" />
</mat-form-field>
</div>
+ @if (mode === 'SHOW') {
+ <mat-checkbox
+ [checked]="showEnvironmentalChanges"
+ (change)="toggleEnvironmentalChanges()"
+ class="ml-4">
+ Show environmental changes ({{ environmentalCount
}})
+ </mat-checkbox>
+ }
</div>
</form>
<div class="my-2 tertiary-color leading-none font-medium">
@@ -55,6 +63,9 @@
<ng-container matColumnDef="changeType">
<th mat-header-cell *matHeaderCellDef
mat-sort-header>Change Type</th>
<td mat-cell *matCellDef="let item"
[title]="formatChangeType(item)">
+ @if (isEnvironmental(item)) {
+ <i class="fa fa-info-circle neutral-color mr-2"
title="Environmental change"></i>
+ }
{{ formatChangeType(item) }}
</td>
</ng-container>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.spec.ts
index 105ebf9d26a..305588c3f1e 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.spec.ts
@@ -20,11 +20,42 @@ import { ComponentFixture, TestBed } from
'@angular/core/testing';
import { LocalChangesTable } from './local-changes-table';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentType } from '@nifi/shared';
+import { ComponentDifference } from '../../../../../../state/flow';
describe('LocalChangesTable', () => {
let component: LocalChangesTable;
let fixture: ComponentFixture<LocalChangesTable>;
+ const mixedDifferences: ComponentDifference[] = [
+ {
+ componentType: ComponentType.Processor,
+ componentId: '1',
+ processGroupId: 'pg-1',
+ componentName: 'GenerateFlowFile',
+ differences: [
+ { differenceType: 'Property Value Changed', difference: 'Batch
Size changed from 1 to 10' },
+ {
+ differenceType: 'Component Bundle Changed',
+ difference: 'Bundle changed from 1.0 to 2.0',
+ environmental: true
+ }
+ ]
+ },
+ {
+ componentType: ComponentType.ControllerService,
+ componentId: '2',
+ processGroupId: 'pg-1',
+ componentName: 'DBCPService',
+ differences: [
+ {
+ differenceType: 'Component Bundle Changed',
+ difference: 'Bundle changed from 1.0 to 2.0',
+ environmental: false
+ }
+ ]
+ }
+ ];
+
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LocalChangesTable, NoopAnimationsModule]
@@ -102,4 +133,135 @@ describe('LocalChangesTable', () => {
});
});
});
+
+ describe('isEnvironmental', () => {
+ it('should return true when environmental is true', () => {
+ expect(
+ component.isEnvironmental({
+ componentType: 'Processor',
+ componentId: '1',
+ componentName: 'P',
+ processGroupId: 'pg',
+ differenceType: 'Bundle Changed',
+ difference: 'diff',
+ environmental: true
+ })
+ ).toBe(true);
+ });
+
+ it('should return false when environmental is false', () => {
+ expect(
+ component.isEnvironmental({
+ componentType: 'Processor',
+ componentId: '1',
+ componentName: 'P',
+ processGroupId: 'pg',
+ differenceType: 'Bundle Changed',
+ difference: 'diff',
+ environmental: false
+ })
+ ).toBe(false);
+ });
+
+ it('should return false when environmental is undefined', () => {
+ expect(
+ component.isEnvironmental({
+ componentType: 'Processor',
+ componentId: '1',
+ componentName: 'P',
+ processGroupId: 'pg',
+ differenceType: 'Changed',
+ difference: 'diff'
+ })
+ ).toBe(false);
+ });
+ });
+
+ describe('environmentalCount', () => {
+ it('should count environmental changes from input differences', () => {
+ component.differences = mixedDifferences;
+ expect(component.environmentalCount).toBe(1);
+ });
+ });
+
+ describe('SHOW mode filtering', () => {
+ beforeEach(() => {
+ component.mode = 'SHOW';
+ });
+
+ it('should hide environmental changes by default', () => {
+ component.differences = mixedDifferences;
+ expect(component.showEnvironmentalChanges).toBe(false);
+ expect(component.dataSource.data.length).toBe(2);
+ expect(component.dataSource.data.every((d) => d.environmental !==
true)).toBe(true);
+ });
+
+ it('should include environmental changes when toggle is enabled', ()
=> {
+ component.differences = mixedDifferences;
+ component.toggleEnvironmentalChanges();
+ expect(component.showEnvironmentalChanges).toBe(true);
+ expect(component.dataSource.data.length).toBe(3);
+ });
+
+ it('should set totalCount to all changes including environmental', ()
=> {
+ component.differences = mixedDifferences;
+ expect(component.totalCount).toBe(3);
+ });
+
+ it('should set filteredCount to displayed changes only', () => {
+ component.differences = mixedDifferences;
+ expect(component.filteredCount).toBe(2);
+
+ component.toggleEnvironmentalChanges();
+ expect(component.filteredCount).toBe(3);
+ });
+ });
+
+ describe('REVERT mode filtering', () => {
+ beforeEach(() => {
+ component.mode = 'REVERT';
+ });
+
+ it('should always filter out environmental changes', () => {
+ component.differences = mixedDifferences;
+ expect(component.dataSource.data.length).toBe(2);
+ expect(component.dataSource.data.every((d) => d.environmental !==
true)).toBe(true);
+ });
+
+ it('should not include environmental changes even after toggle', () =>
{
+ component.differences = mixedDifferences;
+ component.showEnvironmentalChanges = true;
+ component.differences = mixedDifferences;
+ expect(component.dataSource.data.length).toBe(2);
+ });
+
+ it('should set totalCount to non-environmental changes only', () => {
+ component.differences = mixedDifferences;
+ expect(component.totalCount).toBe(2);
+ });
+
+ it('should set filteredCount equal to totalCount', () => {
+ component.differences = mixedDifferences;
+ expect(component.filteredCount).toBe(2);
+ expect(component.totalCount).toBe(component.filteredCount);
+ });
+ });
+
+ describe('toggleEnvironmentalChanges', () => {
+ it('should toggle the flag and re-filter', () => {
+ component.mode = 'SHOW';
+ component.differences = mixedDifferences;
+
+ expect(component.showEnvironmentalChanges).toBe(false);
+ expect(component.dataSource.data.length).toBe(2);
+
+ component.toggleEnvironmentalChanges();
+ expect(component.showEnvironmentalChanges).toBe(true);
+ expect(component.dataSource.data.length).toBe(3);
+
+ component.toggleEnvironmentalChanges();
+ expect(component.showEnvironmentalChanges).toBe(false);
+ expect(component.dataSource.data.length).toBe(2);
+ });
+ });
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.ts
index f42b94314fb..1041eb14721 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/flow/local-changes-dialog/local-changes-table/local-changes-table.ts
@@ -27,6 +27,7 @@ import { debounceTime } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatIconButton } from '@angular/material/button';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
+import { MatCheckbox } from '@angular/material/checkbox';
interface LocalChange {
componentType: string;
@@ -35,6 +36,7 @@ interface LocalChange {
processGroupId: string;
differenceType: string;
difference: string;
+ environmental?: boolean;
}
@Component({
@@ -49,7 +51,8 @@ interface LocalChange {
MatIconButton,
MatMenu,
MatMenuTrigger,
- MatMenuItem
+ MatMenuItem,
+ MatCheckbox
],
templateUrl: './local-changes-table.html',
styleUrl: './local-changes-table.scss'
@@ -65,6 +68,8 @@ export class LocalChangesTable implements AfterViewInit {
filterTerm = '';
totalCount = 0;
filteredCount = 0;
+ environmentalCount = 0;
+ showEnvironmentalChanges = false;
activeSort: Sort = {
active: this.initialSortColumn,
@@ -75,8 +80,51 @@ export class LocalChangesTable implements AfterViewInit {
dataSource: MatTableDataSource<LocalChange> = new
MatTableDataSource<LocalChange>();
filterForm: FormGroup;
+ private allLocalChanges: LocalChange[] = [];
+ private _mode: 'SHOW' | 'REVERT' = 'SHOW';
+
+ @Input() set mode(value: 'SHOW' | 'REVERT') {
+ this._mode = value;
+ // Re-apply filtering when mode changes (important for REVERT mode to
filter environmental changes)
+ if (this.allLocalChanges.length > 0) {
+ this.updateDataSource();
+ }
+ }
+
+ get mode(): 'SHOW' | 'REVERT' {
+ return this._mode;
+ }
+
@Input() set differences(differences: ComponentDifference[]) {
- const localChanges: LocalChange[] =
this.explodeDifferences(differences);
+ this.allLocalChanges = this.explodeDifferences(differences);
+ this.environmentalCount = this.allLocalChanges.filter((change) =>
change.environmental === true).length;
+ this.updateDataSource();
+ }
+
+ @Output() goToChange: EventEmitter<NavigateToComponentRequest> = new
EventEmitter<NavigateToComponentRequest>();
+
+ constructor() {
+ this.filterForm = this.formBuilder.group({ filterTerm: '',
filterColumn: 'componentName' });
+ }
+
+ ngAfterViewInit(): void {
+ this.filterForm
+ .get('filterTerm')
+ ?.valueChanges.pipe(debounceTime(500),
takeUntilDestroyed(this.destroyRef))
+ .subscribe((filterTerm: string) => {
+ this.applyFilter(filterTerm);
+ });
+ }
+
+ private updateDataSource(): void {
+ let localChanges = this.allLocalChanges;
+
+ // In REVERT mode, always filter out environmental changes as they
cannot be reverted
+ // In SHOW mode, filter based on user preference
+ if (this.mode === 'REVERT' || !this.showEnvironmentalChanges) {
+ localChanges = localChanges.filter((change) =>
change.environmental !== true);
+ }
+
this.dataSource.data = this.sortEntities(localChanges,
this.activeSort);
this.dataSource.filterPredicate = (data: LocalChange, filter: string)
=> {
const { filterTerm } = JSON.parse(filter);
@@ -86,7 +134,7 @@ export class LocalChangesTable implements AfterViewInit {
this.nifiCommon.stringContains(data.differenceType,
filterTerm, true)
);
};
- this.totalCount = localChanges.length;
+ this.totalCount = this.mode === 'REVERT' ? localChanges.length :
this.allLocalChanges.length;
this.filteredCount = localChanges.length;
// apply any filtering to the new data
@@ -96,19 +144,9 @@ export class LocalChangesTable implements AfterViewInit {
}
}
- @Output() goToChange: EventEmitter<NavigateToComponentRequest> = new
EventEmitter<NavigateToComponentRequest>();
-
- constructor() {
- this.filterForm = this.formBuilder.group({ filterTerm: '',
filterColumn: 'componentName' });
- }
-
- ngAfterViewInit(): void {
- this.filterForm
- .get('filterTerm')
- ?.valueChanges.pipe(debounceTime(500),
takeUntilDestroyed(this.destroyRef))
- .subscribe((filterTerm: string) => {
- this.applyFilter(filterTerm);
- });
+ toggleEnvironmentalChanges(): void {
+ this.showEnvironmentalChanges = !this.showEnvironmentalChanges;
+ this.updateDataSource();
}
applyFilter(filterTerm: string) {
@@ -132,6 +170,10 @@ export class LocalChangesTable implements AfterViewInit {
return item.difference;
}
+ isEnvironmental(item: LocalChange): boolean {
+ return item.environmental === true;
+ }
+
sortData(sort: Sort) {
this.activeSort = sort;
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
@@ -216,7 +258,8 @@ export class LocalChangesTable implements AfterViewInit {
componentType: currentValue.componentType,
processGroupId: currentValue.processGroupId,
differenceType: diff.differenceType,
- difference: diff.difference
+ difference: diff.difference,
+ environmental: diff.environmental
}) as LocalChange
);
return [...accumulator, ...diffs];