This is an automated email from the ASF dual-hosted git repository.
scottyaslan 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 8e0c4aeb33 NIFI-13248: Add Flow Analysis report menu to new ui (#8974)
8e0c4aeb33 is described below
commit 8e0c4aeb333168fec568917778ac9ab2dc3098fe
Author: Shane Ardell <[email protected]>
AuthorDate: Mon Aug 19 09:04:52 2024 -0400
NIFI-13248: Add Flow Analysis report menu to new ui (#8974)
* NIFI-13248: Add Flow Analysis report menu to new ui
* NIFI-13248: use ngrx to manage state for flow analysis component
* NIFI-13248: update types used in flow analysis drawer
* NIFI-13248: add violation details dialog
* NIFI-13248: add go to component functionality
* NIFI-13248: use Tailwind classes
* NIFI-13248: update analysis status icon based on response
* NIFI-13248: fix broken unit tests
* NIFI-13248: add missing license headers
* NIFI-13248: refactor styling
patch provided by @scottyaslan
* NIFI-13248: fix broken styling
* NIFI-13248: further style refactoring
patch provided by @scottyaslan
* NIFI-13248: binding and spacing fixes
* NIFI-13248: wire up pg name and id
* NIFI-13248: use breadcrumb selector to obtain process group name
* NIFI-13248: remove border color classes
* NIFI-13248: update drawer button icon
---
.../flow-designer/feature/flow-designer.module.ts | 4 +-
.../behavior/connectable-behavior.service.spec.ts | 5 +-
.../behavior/draggable-behavior.service.spec.ts | 5 +-
.../behavior/editable-behavior.service.spec.ts | 5 +-
.../behavior/quick-select-behavior.service.spec.ts | 5 +-
.../behavior/selectable-behavior.service.spec.ts | 5 +-
.../service/birdseye-view.service.spec.ts | 5 +-
.../service/canvas-utils.service.spec.ts | 5 +-
.../service/canvas-view.service.spec.ts | 5 +-
.../flow-analysis.service.spec.ts} | 34 ++-
.../flow-analysis.service.ts} | 20 +-
.../manager/connection-manager.service.spec.ts | 5 +-
.../service/manager/funnel-manager.service.spec.ts | 5 +-
.../service/manager/label-manager.service.spec.ts | 5 +-
.../service/manager/port-manager.service.spec.ts | 5 +-
.../manager/process-group-manager.service.spec.ts | 5 +-
.../manager/processor-manager.service.spec.ts | 5 +-
.../remote-process-group-manager.service.spec.ts | 5 +-
.../state/flow-analysis/flow-analysis.actions.ts | 44 ++++
.../state/flow-analysis/flow-analysis.effects.ts | 120 ++++++++++
.../flow-analysis/flow-analysis.reducer.ts} | 33 ++-
.../flow-analysis/flow-analysis.selectors.ts} | 18 +-
.../flow-designer/state/flow-analysis/index.ts | 68 ++++++
.../pages/flow-designer/state/flow/flow.actions.ts | 5 +
.../pages/flow-designer/state/flow/flow.effects.ts | 2 +
.../pages/flow-designer/state/flow/flow.reducer.ts | 6 +
.../flow-designer/state/flow/flow.selectors.ts | 2 +
.../app/pages/flow-designer/state/flow/index.ts | 1 +
.../src/app/pages/flow-designer/state/index.ts | 6 +-
.../ui/canvas/_canvas.component-theme.scss | 8 +
.../flow-designer/ui/canvas/canvas.component.html | 19 +-
.../ui/canvas/canvas.component.spec.ts | 12 +-
.../flow-designer/ui/canvas/canvas.component.ts | 3 +
.../pages/flow-designer/ui/canvas/canvas.module.ts | 6 +-
.../flow-analysis-drawer.component.html | 258 +++++++++++++++++++++
.../flow-analysis-drawer.component.scss} | 13 --
.../flow-analysis-drawer.component.spec.ts | 41 ++++
.../flow-analysis-drawer.component.ts | 167 +++++++++++++
..._violation-details-dialog.component-theme.scss} | 51 ++--
.../violation-details-dialog.component.html} | 35 ++-
.../violation-details-dialog.component.scss} | 13 --
.../violation-details-dialog.component.spec.ts | 104 +++++++++
.../violation-details-dialog.component.ts | 74 ++++++
.../flow-status/_flow-status.component-theme.scss | 71 ++++--
.../header/flow-status/flow-status.component.html | 6 +
.../header/flow-status/flow-status.component.scss | 5 -
.../header/flow-status/flow-status.component.ts | 56 ++++-
.../ui/canvas/header/header.component.html | 4 +-
.../ui/canvas/header/header.component.ts | 4 +
.../src/main/frontend/apps/nifi/src/styles.scss | 4 +
50 files changed, 1234 insertions(+), 158 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/feature/flow-designer.module.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/feature/flow-designer.module.ts
index 0aa5219148..b065fe30a7 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/feature/flow-designer.module.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/feature/flow-designer.module.ts
@@ -30,6 +30,7 @@ import { ControllerServicesEffects } from
'../state/controller-services/controll
import { ParameterEffects } from '../state/parameter/parameter.effects';
import { QueueEffects } from '../state/queue/queue.effects';
import { BannerText } from
'../../../ui/common/banner-text/banner-text.component';
+import { FlowAnalysisEffects } from
'../state/flow-analysis/flow-analysis.effects';
@NgModule({
declarations: [FlowDesigner, VersionControlTip],
@@ -43,7 +44,8 @@ import { BannerText } from
'../../../ui/common/banner-text/banner-text.component
TransformEffects,
ControllerServicesEffects,
ParameterEffects,
- QueueEffects
+ QueueEffects,
+ FlowAnalysisEffects
),
NgOptimizedImage,
MatDialogModule,
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts
index e7273d5db2..91b1988fce 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.service.spec.ts
@@ -35,6 +35,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('ConnectableBehavior', () => {
let service: ConnectableBehavior;
@@ -45,7 +47,8 @@ describe('ConnectableBehavior', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts
index d28a4c4168..601625d608 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('DraggableBehavior', () => {
let service: DraggableBehavior;
@@ -46,7 +48,8 @@ describe('DraggableBehavior', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts
index 8a8074a875..153387fdfd 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/editable-behavior.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('EditableBehaviorService', () => {
let service: EditableBehavior;
@@ -45,7 +47,8 @@ describe('EditableBehaviorService', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
beforeEach(() => {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts
index e157963476..9faed60018 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/quick-select-behavior.service.spec.ts
@@ -35,6 +35,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('QuickSelectBehavior', () => {
let service: QuickSelectBehavior;
@@ -45,7 +47,8 @@ describe('QuickSelectBehavior', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts
index 52d05081a3..ebad15236a 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/behavior/selectable-behavior.service.spec.ts
@@ -34,6 +34,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('SelectableBehavior', () => {
let service: SelectableBehavior;
@@ -44,7 +46,8 @@ describe('SelectableBehavior', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts
index 3545e43e46..66c331e309 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/birdseye-view.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../state/flow-configuration/flow-
import * as fromFlowConfiguration from
'../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../queue/state';
import * as fromQueue from '../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../state/flow-analysis';
+import * as fromFlowAnalysis from
'../state/flow-analysis/flow-analysis.reducer';
describe('BirdseyeView', () => {
let service: BirdseyeView;
@@ -46,7 +48,8 @@ describe('BirdseyeView', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts
index 45013d9b60..3c82d29c98 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.spec.ts
@@ -35,6 +35,8 @@ import { selectFlowConfiguration } from
'../../../state/flow-configuration/flow-
import * as fromFlowConfiguration from
'../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../queue/state';
import * as fromQueue from '../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../state/flow-analysis';
+import * as fromFlowAnalysis from
'../state/flow-analysis/flow-analysis.reducer';
describe('CanvasUtils', () => {
let service: CanvasUtils;
@@ -45,7 +47,8 @@ describe('CanvasUtils', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts
index dffdec8902..de8f8d9370 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-view.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../state/flow-configuration/flow-
import * as fromFlowConfiguration from
'../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../queue/state';
import * as fromQueue from '../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../state/flow-analysis';
+import * as fromFlowAnalysis from
'../state/flow-analysis/flow-analysis.reducer';
describe('CanvasView', () => {
let service: CanvasView;
@@ -46,7 +48,8 @@ describe('CanvasView', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.spec.ts
similarity index 53%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.spec.ts
index 5414141b19..d85a11dcf0 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.spec.ts
@@ -15,15 +15,29 @@
* limitations under the License.
*/
-.flow-status {
- box-sizing: content-box;
+import { TestBed } from '@angular/core/testing';
- .fa,
- .icon {
- font-style: normal;
- }
+import { FlowAnalysisService } from './flow-analysis.service';
+import { provideMockStore } from '@ngrx/store/testing';
+import { HttpClient } from '@angular/common/http';
- .controller-bulletins {
- cursor: default;
- }
-}
+describe('FlowAnalysisService', () => {
+ let service: FlowAnalysisService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ provideMockStore({}),
+ {
+ provide: HttpClient,
+ useValue: {}
+ }
+ ]
+ });
+ service = TestBed.inject(FlowAnalysisService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.ts
similarity index 62%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.ts
index 5414141b19..571d91534f 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.ts
@@ -15,15 +15,19 @@
* limitations under the License.
*/
-.flow-status {
- box-sizing: content-box;
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
- .fa,
- .icon {
- font-style: normal;
- }
+@Injectable({
+ providedIn: 'root'
+})
+export class FlowAnalysisService {
+ private static readonly API: string = '../nifi-api';
+
+ constructor(private httpClient: HttpClient) {}
- .controller-bulletins {
- cursor: default;
+ getResults(processGroupId: string): Observable<any> {
+ return
this.httpClient.get(`${FlowAnalysisService.API}/flow/flow-analysis/results/${processGroupId}`);
}
}
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 c334a35f86..f4b0970bbc 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
@@ -37,6 +37,8 @@ import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flo
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
import { ClusterConnectionService } from
'../../../../service/cluster-connection.service';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('ConnectionManager', () => {
let service: ConnectionManager;
@@ -47,7 +49,8 @@ describe('ConnectionManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts
index a48d1ac593..1dd171f002 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/funnel-manager.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('FunnelManager', () => {
let service: FunnelManager;
@@ -46,7 +48,8 @@ describe('FunnelManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts
index 517ae0dc3f..f7dfbc41b4 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.spec.ts
@@ -37,6 +37,8 @@ import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flo
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
import { ClusterConnectionService } from
'../../../../service/cluster-connection.service';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('LabelManager', () => {
let service: LabelManager;
@@ -47,7 +49,8 @@ describe('LabelManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts
index 009f256fd7..3ca730eec6 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/port-manager.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('PortManager', () => {
let service: PortManager;
@@ -46,7 +48,8 @@ describe('PortManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts
index cc01af6a99..53a5451268 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/process-group-manager.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('ProcessGroupManager', () => {
let service: ProcessGroupManager;
@@ -46,7 +48,8 @@ describe('ProcessGroupManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts
index 677b4d2bad..1e6a3876ab 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/processor-manager.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('ProcessorManager', () => {
let service: ProcessorManager;
@@ -46,7 +48,8 @@ describe('ProcessorManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts
index 0ac01b7d2b..6e0e2f12d7 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/manager/remote-process-group-manager.service.spec.ts
@@ -36,6 +36,8 @@ import { selectFlowConfiguration } from
'../../../../state/flow-configuration/fl
import * as fromFlowConfiguration from
'../../../../state/flow-configuration/flow-configuration.reducer';
import { queueFeatureKey } from '../../../queue/state';
import * as fromQueue from '../../state/queue/queue.reducer';
+import { flowAnalysisFeatureKey } from '../../state/flow-analysis';
+import * as fromFlowAnalysis from
'../../state/flow-analysis/flow-analysis.reducer';
describe('RemoteProcessGroupManager', () => {
let service: RemoteProcessGroupManager;
@@ -46,7 +48,8 @@ describe('RemoteProcessGroupManager', () => {
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]:
fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState,
- [queueFeatureKey]: fromQueue.initialState
+ [queueFeatureKey]: fromQueue.initialState,
+ [flowAnalysisFeatureKey]: fromFlowAnalysis.initialState
};
TestBed.configureTestingModule({
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.actions.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.actions.ts
new file mode 100644
index 0000000000..e5adce0adc
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.actions.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { FlowAnalysisRequestResponse, FlowAnalysisRule,
FlowAnalysisRuleViolation } from '.';
+
+export const startPollingFlowAnalysis = createAction('[Flow Analysis] Start
Polling Flow Analysis');
+
+export const stopPollingFlowAnalysis = createAction('[Flow Analysis] Stop
Polling Flow Analysis');
+
+export const pollFlowAnalysis = createAction(`[Flow Analysis] Poll Flow
Analysis`);
+
+export const pollFlowAnalysisSuccess = createAction(
+ `[Flow Analysis] Poll Flow Analysis Success`,
+ props<{ response: FlowAnalysisRequestResponse }>()
+);
+
+export const flowAnalysisApiError = createAction('[Flow Analysis] API Error',
props<{ error: string }>());
+
+export const resetPollingFlowAnalysis = createAction(`[Flow Analysis] Reset
Polling Flow Analysis`);
+
+export const navigateToEditFlowAnalysisRule = createAction(
+ '[Flow Analysis Rules] Navigate To Edit Flow Analysis Rule',
+ props<{ id: string }>()
+);
+
+export const openRuleDetailsDialog = createAction(
+ '[Flow Analysis Rules] Open Flow Analysis Rule Details Dialog',
+ props<{ violation: FlowAnalysisRuleViolation; rule: FlowAnalysisRule }>()
+);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.effects.ts
new file mode 100644
index 0000000000..c8b808d17e
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.effects.ts
@@ -0,0 +1,120 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { concatLatestFrom } from '@ngrx/operators';
+import { NiFiState } from '../../../../state';
+import { Store } from '@ngrx/store';
+import { asyncScheduler, catchError, from, interval, map, of, startWith,
switchMap, takeUntil, tap } from 'rxjs';
+import * as FlowAnalysisActions from './flow-analysis.actions';
+import { HttpErrorResponse } from '@angular/common/http';
+import { FlowAnalysisService } from '../../service/flow-analysis.service';
+import { ErrorHelper } from 'apps/nifi/src/app/service/error-helper.service';
+import { selectCurrentProcessGroupId } from '../flow/flow.selectors';
+import { Router } from '@angular/router';
+import { MatDialog } from '@angular/material/dialog';
+import { LARGE_DIALOG } from '@nifi/shared';
+import { ViolationDetailsDialogComponent } from
'../../ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component';
+
+@Injectable()
+export class FlowAnalysisEffects {
+ constructor(
+ private actions$: Actions,
+ private store: Store<NiFiState>,
+ private flowAnalysisService: FlowAnalysisService,
+ private errorHelper: ErrorHelper,
+ private router: Router,
+ private dialog: MatDialog
+ ) {}
+
+ startPollingFlowAnalysis$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisActions.startPollingFlowAnalysis),
+ switchMap(() =>
+ interval(30000, asyncScheduler).pipe(
+ startWith(0),
+
takeUntil(this.actions$.pipe(ofType(FlowAnalysisActions.stopPollingFlowAnalysis)))
+ )
+ ),
+ switchMap(() => of(FlowAnalysisActions.pollFlowAnalysis()))
+ )
+ );
+
+ resetPollingFlowAnalysis$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisActions.resetPollingFlowAnalysis),
+ switchMap(() => {
+
this.store.dispatch(FlowAnalysisActions.stopPollingFlowAnalysis());
+ return of(FlowAnalysisActions.pollFlowAnalysis());
+ })
+ )
+ );
+
+ pollFlowAnalysis$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisActions.pollFlowAnalysis),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ switchMap(([, pgId]) => {
+ return from(this.flowAnalysisService.getResults(pgId)).pipe(
+ map((response) =>
+ FlowAnalysisActions.pollFlowAnalysisSuccess({
+ response: response
+ })
+ ),
+ catchError((errorResponse: HttpErrorResponse) => {
+
this.store.dispatch(FlowAnalysisActions.stopPollingFlowAnalysis());
+ return of(
+ FlowAnalysisActions.flowAnalysisApiError({
+ error:
this.errorHelper.getErrorString(errorResponse)
+ })
+ );
+ })
+ );
+ })
+ )
+ );
+
+ navigateToEditFlowAnalysisRule$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisActions.navigateToEditFlowAnalysisRule),
+ map((action) => action.id),
+ tap((id) => {
+ this.router.navigate(['/settings', 'flow-analysis-rules',
id, 'edit']);
+ })
+ ),
+ { dispatch: false }
+ );
+
+ openRuleDetailsDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisActions.openRuleDetailsDialog),
+ tap(({ violation, rule }) => {
+ this.dialog.open(ViolationDetailsDialogComponent, {
+ ...LARGE_DIALOG,
+ data: {
+ violation,
+ rule
+ }
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.reducer.ts
similarity index 51%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.reducer.ts
index 5414141b19..486fb32757 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.reducer.ts
@@ -15,15 +15,28 @@
* limitations under the License.
*/
-.flow-status {
- box-sizing: content-box;
+import { createReducer, on } from '@ngrx/store';
+import { FlowAnalysisState } from './index';
+import { pollFlowAnalysis, pollFlowAnalysisSuccess } from
'./flow-analysis.actions';
- .fa,
- .icon {
- font-style: normal;
- }
+export const initialState: FlowAnalysisState = {
+ rules: [],
+ ruleViolations: [],
+ flowAnalysisPending: false,
+ status: 'pending'
+};
- .controller-bulletins {
- cursor: default;
- }
-}
+export const flowAnalysisReducer = createReducer(
+ initialState,
+ on(pollFlowAnalysis, (state) => ({
+ ...state,
+ status: 'loading' as const
+ })),
+ on(pollFlowAnalysisSuccess, (state, { response }) => ({
+ ...state,
+ rules: response.rules,
+ ruleViolations: response.ruleViolations,
+ flowAnalysisPending: response.flowAnalysisPending,
+ status: 'success' as const
+ }))
+);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.selectors.ts
similarity index 73%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.selectors.ts
index 5414141b19..7fd8035793 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.selectors.ts
@@ -15,15 +15,11 @@
* limitations under the License.
*/
-.flow-status {
- box-sizing: content-box;
+import { createSelector } from '@ngrx/store';
+import { flowAnalysisFeatureKey } from './index';
+import { CanvasState, selectCanvasState } from '../index';
- .fa,
- .icon {
- font-style: normal;
- }
-
- .controller-bulletins {
- cursor: default;
- }
-}
+export const selectFlowAnalysisState = createSelector(
+ selectCanvasState,
+ (state: CanvasState) => state[flowAnalysisFeatureKey]
+);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/index.ts
new file mode 100644
index 0000000000..49deb49658
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/index.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { PropertyDescriptor, Bundle, Permissions } from
'../../../../state/shared';
+
+export const flowAnalysisFeatureKey = 'flowAnalysis';
+
+export interface FlowAnalysisRule {
+ id: string;
+ name: string;
+ type: string;
+ bundle: Bundle;
+ state: string;
+ comments?: string;
+ persistsState: boolean;
+ restricted: boolean;
+ deprecated: boolean;
+ extensionMissing: boolean;
+ multipleVersionsAvailable: boolean;
+ supportsSensitiveDynamicProperties: boolean;
+ enforcementPolicy: string;
+ properties: { [key: string]: string };
+ descriptors: { [key: string]: PropertyDescriptor };
+ sensitiveDynamicPropertyNames: string[];
+ validationErrors: string[];
+ validationStatus: 'VALID' | 'INVALID' | 'VALIDATING';
+}
+
+export interface FlowAnalysisRuleViolation {
+ enforcementPolicy: string;
+ scope: string;
+ subjectId: string;
+ subjectDisplayName: string;
+ groupId: string;
+ ruleId: string;
+ issueId: string;
+ violationMessage: string;
+ subjectComponentType: string;
+ subjectPermissionDto: Permissions;
+ enabled: boolean;
+}
+
+export interface FlowAnalysisRequestResponse {
+ rules: FlowAnalysisRule[];
+ ruleViolations: FlowAnalysisRuleViolation[];
+ flowAnalysisPending: boolean;
+}
+
+export interface FlowAnalysisState {
+ rules: FlowAnalysisRule[];
+ ruleViolations: FlowAnalysisRuleViolation[];
+ flowAnalysisPending: boolean;
+ status: 'pending' | 'loading' | 'success';
+}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
index 9a41e2cb9d..96d8386d08 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
@@ -573,6 +573,11 @@ export const setOperationCollapsed = createAction(
props<{ operationCollapsed: boolean }>()
);
+export const setFlowAnalysisOpen = createAction(
+ `${CANVAS_PREFIX} Set Flow Analysis Open`,
+ props<{ flowAnalysisOpen: boolean }>()
+);
+
/*
General
*/
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
index fba867dc01..da3d62e3c8 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
@@ -153,6 +153,7 @@ import {
import { VerifyPropertiesRequestContext } from
'../../../../state/property-verification';
import { BackNavigation } from '../../../../state/navigation';
import { Storage, NiFiCommon } from '@nifi/shared';
+import { resetPollingFlowAnalysis } from
'../flow-analysis/flow-analysis.actions';
@Injectable()
export class FlowEffects {
@@ -212,6 +213,7 @@ export class FlowEffects {
this.flowService.getControllerBulletins()
]).pipe(
map(([flow, flowStatus, controllerBulletins]) => {
+ this.store.dispatch(resetPollingFlowAnalysis());
return FlowActions.loadProcessGroupSuccess({
response: {
id: request.id,
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
index 1744866759..b05d79baab 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
@@ -60,6 +60,7 @@ import {
saveToFlowRegistrySuccess,
setAllowTransition,
setDragging,
+ setFlowAnalysisOpen,
setNavigationCollapsed,
setOperationCollapsed,
setSkipTransform,
@@ -164,6 +165,7 @@ export const initialState: FlowState = {
allowTransition: false,
navigationCollapsed: false,
operationCollapsed: false,
+ flowAnalysisOpen: false,
status: 'pending'
};
@@ -541,6 +543,10 @@ export const flowReducer = createReducer(
...state,
operationCollapsed
})),
+ on(setFlowAnalysisOpen, (state, { flowAnalysisOpen }) => ({
+ ...state,
+ flowAnalysisOpen
+ })),
on(
startComponentSuccess,
stopComponentSuccess,
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
index d8164c3e94..3f282eafdf 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
@@ -257,3 +257,5 @@ export const selectMaxZIndex = (componentType:
ComponentType.Connection | Compon
);
}
};
+
+export const selectFlowAnalysisOpen = createSelector(selectFlowState, (state:
FlowState) => state.flowAnalysisOpen);
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 2f586351d4..d01516b90b 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
@@ -638,6 +638,7 @@ export interface FlowState {
saving: boolean;
navigationCollapsed: boolean;
operationCollapsed: boolean;
+ flowAnalysisOpen: boolean;
versionSaving: boolean;
changeVersionRequest: FlowUpdateRequestEntity | null;
copiedSnippet: CopiedSnippet | null;
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/index.ts
index 077b7c92da..7c69e18b74 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/index.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/index.ts
@@ -31,6 +31,8 @@ import { parameterReducer } from
'./parameter/parameter.reducer';
import { queueFeatureKey } from '../../queue/state';
import { QueueState } from './queue';
import { queueReducer } from './queue/queue.reducer';
+import { FlowAnalysisState, flowAnalysisFeatureKey } from './flow-analysis';
+import { flowAnalysisReducer } from './flow-analysis/flow-analysis.reducer';
export const canvasFeatureKey = 'canvas';
@@ -40,6 +42,7 @@ export interface CanvasState {
[controllerServicesFeatureKey]: ControllerServicesState;
[parameterFeatureKey]: ParameterState;
[queueFeatureKey]: QueueState;
+ [flowAnalysisFeatureKey]: FlowAnalysisState;
}
export function reducers(state: CanvasState | undefined, action: Action) {
@@ -48,7 +51,8 @@ export function reducers(state: CanvasState | undefined,
action: Action) {
[transformFeatureKey]: transformReducer,
[controllerServicesFeatureKey]: controllerServicesReducer,
[parameterFeatureKey]: parameterReducer,
- [queueFeatureKey]: queueReducer
+ [queueFeatureKey]: queueReducer,
+ [flowAnalysisFeatureKey]: flowAnalysisReducer
})(state, action);
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/_canvas.component-theme.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/_canvas.component-theme.scss
index b7933733f3..364043c91c 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/_canvas.component-theme.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/_canvas.component-theme.scss
@@ -104,6 +104,14 @@
);
}
+ mat-sidenav {
+ background-color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-darker,
+ $supplemental-theme-surface-palette-lighter
+ );
+ }
+
/* svg styles */
svg.canvas-svg {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
index 802e15c2fa..2f5b308b42 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
@@ -18,12 +18,19 @@
<div class="flex flex-col h-full">
<fd-header></fd-header>
<div class="flex-1">
- <graph-controls></graph-controls>
- <div
- id="canvas-container"
- class="canvas-background select-none h-full"
- [cdkContextMenuTriggerFor]="contextMenu.menu"></div>
- <fd-context-menu #contextMenu [menuProvider]="canvasContextMenu"
menuId="root"></fd-context-menu>
+ <mat-sidenav-container class="h-full">
+ <mat-sidenav mode="side" [opened]="flowAnalysisOpen()"
position="end">
+ <flow-analysis-drawer></flow-analysis-drawer>
+ </mat-sidenav>
+ <mat-sidenav-content>
+ <graph-controls></graph-controls>
+ <div
+ id="canvas-container"
+ class="canvas-background select-none h-full w-full"
+ [cdkContextMenuTriggerFor]="contextMenu.menu"></div>
+ <fd-context-menu #contextMenu
[menuProvider]="canvasContextMenu" menuId="root"></fd-context-menu>
+ </mat-sidenav-content>
+ </mat-sidenav-container>
</div>
<fd-footer></fd-footer>
</div>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.spec.ts
index 26c8de4e9e..7ae42d3448 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.spec.ts
@@ -30,6 +30,10 @@ import { HeaderComponent } from './header/header.component';
import { FooterComponent } from './footer/footer.component';
import { canvasFeatureKey } from '../../state';
import { flowFeatureKey } from '../../state/flow';
+import { MatSidenavModule } from '@angular/material/sidenav';
+import { FlowAnalysisDrawerComponent } from
'./header/flow-analysis-drawer/flow-analysis-drawer.component';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { CanvasActionsService } from '../../service/canvas-actions.service';
describe('Canvas', () => {
let component: Canvas;
@@ -54,9 +58,12 @@ describe('Canvas', () => {
imports: [
CdkContextMenuTrigger,
ContextMenu,
+ MatSidenavModule,
+ NoopAnimationsModule,
MockComponent(GraphControls),
MockComponent(HeaderComponent),
- MockComponent(FooterComponent)
+ MockComponent(FooterComponent),
+ FlowAnalysisDrawerComponent
],
providers: [
provideMockStore({
@@ -71,7 +78,8 @@ describe('Canvas', () => {
value: breadcrumbEntity
}
]
- })
+ }),
+ CanvasActionsService
]
});
fixture = TestBed.createComponent(Canvas);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.ts
index 0eeef0f8d5..ee923e4d69 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.ts
@@ -43,6 +43,7 @@ import {
selectConnection,
selectCurrentProcessGroupId,
selectEditedCurrentProcessGroup,
+ selectFlowAnalysisOpen,
selectFunnel,
selectInputPort,
selectLabel,
@@ -80,6 +81,8 @@ export class Canvas implements OnInit, OnDestroy {
private scale: number = INITIAL_SCALE;
private canvasClicked = false;
+ flowAnalysisOpen = this.store.selectSignal(selectFlowAnalysisOpen);
+
constructor(
private store: Store<CanvasState>,
private canvasView: CanvasView,
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.module.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.module.ts
index 4f09f38986..b459f1e665 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.module.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.module.ts
@@ -17,6 +17,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
+import { MatSidenavModule } from '@angular/material/sidenav';
import { Canvas } from './canvas.component';
import { ContextMenu } from
'../../../../ui/common/context-menu/context-menu.component';
import { CdkContextMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuTrigger } from
'@angular/cdk/menu';
@@ -24,6 +25,7 @@ import { GraphControls } from
'./graph-controls/graph-controls.component';
import { CanvasRoutingModule } from './canvas-routing.module';
import { HeaderComponent } from './header/header.component';
import { FooterComponent } from './footer/footer.component';
+import { FlowAnalysisDrawerComponent } from
'./header/flow-analysis-drawer/flow-analysis-drawer.component';
@NgModule({
declarations: [Canvas],
@@ -38,7 +40,9 @@ import { FooterComponent } from './footer/footer.component';
GraphControls,
ContextMenu,
HeaderComponent,
- FooterComponent
+ FooterComponent,
+ MatSidenavModule,
+ FlowAnalysisDrawerComponent
]
})
export class CanvasModule {}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.html
new file mode 100644
index 0000000000..3493603e37
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.html
@@ -0,0 +1,258 @@
+<!--
+ ~ 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.
+ -->
+<div class="flow-analysis-drawer h-full w-96 p-5">
+ <component-context type="ProcessGroup" [name]="processGroupName"
[id]="currentProcessGroupId"></component-context>
+ <div class="flex items-center w-full">
+ <ng-container *ngIf="isAnalysisPending">
+ <span *nifiSpinner="isAnalysisPending"></span>
+ <div class="ml-1">Rules analysis pending...</div>
+ </ng-container>
+ </div>
+ <div class="mt-5">
+ <div class="flex items-center justify-between">
+ <div>
+ <div>
+ <mat-checkbox class="text-sm" color="primary"
[(ngModel)]="showEnforcedViolations">
+ Show enforced violations
+ </mat-checkbox>
+ </div>
+ <div>
+ <mat-checkbox color="primary"
[(ngModel)]="showWarningViolations">
+ Show warning violations
+ </mat-checkbox>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="mt-5 mb-2" [hidden]="showEnforcedViolations() ||
showWarningViolations()">
+ <div class="flex flex-col gap-y-2">
+ <mat-expansion-panel hideToggle>
+ <mat-expansion-panel-header>
+ <mat-panel-title>
+ <div class="flex flex-1 justify-start">
+ <div>Enforced Rules ({{ enforcedRules.length
}})</div>
+ </div>
+ </mat-panel-title>
+ </mat-expansion-panel-header>
+
+ @if (rules) {
+ @for (rule of enforcedRules; track rule.id) {
+ <div class="mb-2">
+ <div class="flex justify-between">
+ <div class="flex items-center">{{ rule.name
}}</div>
+ <button
+ mat-icon-button
+ type="button"
+ [matMenuTriggerFor]="menu"
+ class="h-16 w-16 flex items-center
justify-center">
+ <i class="fa fa-ellipsis-v"></i>
+ </button>
+ <mat-menu #menu="matMenu">
+ <button mat-menu-item
(click)="openDocumentation(rule)">
+ <i class="fa fa-book mr-2
primary-color"></i>
+ View Documentation
+ </button>
+ <button mat-menu-item
(click)="openRule(rule)">
+ <i class="fa fa-cog mr-2
primary-color"></i>
+ Edit Rule
+ </button>
+ </mat-menu>
+ </div>
+ @if (violationsMap.size > 0 &&
violationsMap.get(rule.id)) {
+ <div class="warn-color-darker text-sm">
+ <ng-container
[ngPlural]="violationsMap.get(rule.id).length">
+ <ng-template ngPluralCase="=1"
+ >{{
violationsMap.get(rule.id).length }} violation</ng-template
+ >
+ <ng-template ngPluralCase="other"
+ >{{
violationsMap.get(rule.id).length }} violations</ng-template
+ >
+ </ng-container>
+ </div>
+ @for (violation of violationsMap.get(rule.id);
track violation.scope) {
+ <div class="flex align-center
justify-between mt-2">
+ <div class="flex flex-col items-start
ml-2">
+ <div
*ngIf="violation?.subjectPermissionDto?.canRead; else unauthorized">
+ {{
violation.subjectDisplayName }}
+ </div>
+ <span class="text-sm">
+ {{ violation.subjectId }}
+ </span>
+ </div>
+
+ <ng-template
+
[ngTemplateOutlet]="violationMenuTemplate"
+ [ngTemplateOutletContext]="{
violation: violation }"></ng-template>
+ </div>
+ }
+ }
+ </div>
+ }
+ }
+ </mat-expansion-panel>
+ <mat-expansion-panel hideToggle>
+ <mat-expansion-panel-header>
+ <mat-panel-title>
+ <div class="flex flex-1 justify-start">
+ <div>Warning Rules ({{ warningRules.length
}})</div>
+ </div>
+ </mat-panel-title>
+ </mat-expansion-panel-header>
+
+ @if (rules) {
+ @for (rule of warningRules; track rule.id) {
+ <div>
+ <div class="flex justify-between">
+ <div class="flex items-center">{{ rule.name
}}</div>
+ <button
+ mat-icon-button
+ type="button"
+ [matMenuTriggerFor]="menu"
+ class="h-16 w-16 flex items-center
justify-center">
+ <i class="fa fa-ellipsis-v"></i>
+ </button>
+ <mat-menu #menu="matMenu" class="rule-menu
w-52 shadow-lg">
+ <button mat-menu-item
(click)="openDocumentation(rule)">
+ <i class="fa fa-book mr-2
primary-color"></i>
+ View Documentation
+ </button>
+ <button mat-menu-item
(click)="openRule(rule)">
+ <i class="fa fa-cog mr-2
primary-color"></i>
+ Edit Rule
+ </button>
+ </mat-menu>
+ </div>
+ @if (violationsMap.size > 0 &&
violationsMap.get(rule.id)) {
+ <div class="primary-color text-sm">
+ <ng-container
[ngPlural]="violationsMap.get(rule.id).length">
+ <ng-template ngPluralCase="=1"
+ >{{
violationsMap.get(rule.id).length }} violation</ng-template
+ >
+ <ng-template ngPluralCase="other"
+ >{{
violationsMap.get(rule.id).length }} violations</ng-template
+ >
+ </ng-container>
+ </div>
+ <ul>
+ @for (violation of
violationsMap.get(rule.id); track violation.scope) {
+ <li class="flex align-center
justify-between mt-2">
+ <div class="flex flex-col
items-start ml-2">
+ <div
*ngIf="violation?.subjectPermissionDto?.canRead; else unauthorized">
+ {{
violation.subjectDisplayName }}
+ </div>
+ <span class="text-sm">
+ {{ violation.subjectId }}
+ </span>
+ </div>
+
+ <ng-template
+
[ngTemplateOutlet]="violationMenuTemplate"
+ [ngTemplateOutletContext]="{
violation: violation }"></ng-template>
+ </li>
+ }
+ </ul>
+ }
+ </div>
+ }
+ }
+ </mat-expansion-panel>
+ </div>
+ </div>
+
+ <div class="mt-5 mb-2" [hidden]="!showEnforcedViolations()"
[class.mb-5]="!showWarningViolations()">
+ <div class="border-b pb-2">
+ <div>
+ Enforced Violations
+ <span>({{ enforcedViolations.length }})</span>
+ </div>
+ </div>
+
+ <ul>
+ @for (violation of enforcedViolations; track violation.scope) {
+ <li class="mt-2 pb-2 border-b last-of-type:border-0">
+ <div class="warn-color-darker">{{
getRuleName(violation.ruleId) }}</div>
+ <div class="flex align-center justify-between ml-2">
+ <div class="flex flex-col items-start">
+ <div
*ngIf="violation?.subjectPermissionDto?.canRead; else unauthorized">
+ {{ violation.subjectDisplayName }}
+ </div>
+ <span class="text-sm">{{ violation.subjectId
}}</span>
+ </div>
+
+ <ng-template [ngTemplateOutlet]="violationMenuTemplate"
+ [ngTemplateOutletContext]="{ violation:
violation }"></ng-template>
+ </div>
+ </li>
+ }
+ </ul>
+ </div>
+
+ <div class="mt-5 mb-2" [hidden]="!showWarningViolations()">
+ <div class="border-b pb-2">
+ <div>
+ Warning Violations
+ <span>({{ warningViolations.length }})</span>
+ </div>
+ </div>
+
+ <ul>
+ @for (violation of warningViolations; track violation.scope) {
+ <li class="mt-2 pb-2 border-b last-of-type:border-0">
+ <div class="warn-color-darker">{{
getRuleName(violation.ruleId) }}</div>
+ <div class="flex align-center justify-between ml-2">
+ <div class="flex flex-col items-start">
+ <div
*ngIf="violation?.subjectPermissionDto?.canRead; else unauthorized">
+ {{ violation.subjectDisplayName }}
+ </div>
+ <span class="text-sm">{{ violation.subjectId
}}</span>
+ </div>
+
+ <ng-template
+ [ngTemplateOutlet]="violationMenuTemplate"
+ [ngTemplateOutletContext]="{ violation: violation
}"></ng-template>
+ </div>
+ </li>
+ }
+ </ul>
+ </div>
+</div>
+
+<ng-template #violationMenuTemplate let-violation="violation">
+ <button
+ mat-icon-button
+ type="button"
+ [matMenuTriggerFor]="violationMenu"
+ class="h-16 w-16 flex items-center justify-center">
+ <i class="fa fa-ellipsis-v"></i>
+ </button>
+ <mat-menu #violationMenu="matMenu">
+ <button
+ mat-menu-item
+ (click)="viewViolationDetails(violation)"
+ [disabled]="!violation?.subjectPermissionDto?.canRead">
+ <i class="fa fa-info-circle mr-2 primary-color"></i>Violation
details
+ </button>
+ <button
+ mat-menu-item
+ [routerLink]="getProcessorLink(violation)"
+ *ngIf="violation?.subjectComponentType === 'PROCESSOR' &&
violation?.subjectPermissionDto?.canRead">
+ <i class="fa mr-2 fa-long-arrow-right primary-color"></i>Go to
component
+ </button>
+ </mat-menu>
+</ng-template>
+
+<ng-template #unauthorized> Unauthorized </ng-template>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.scss
similarity index 83%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.scss
index 5414141b19..2944f98194 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.scss
@@ -14,16 +14,3 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-.flow-status {
- box-sizing: content-box;
-
- .fa,
- .icon {
- font-style: normal;
- }
-
- .controller-bulletins {
- cursor: default;
- }
-}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.spec.ts
new file mode 100644
index 0000000000..dcd357b816
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { FlowAnalysisDrawerComponent } from './flow-analysis-drawer.component';
+import { provideMockStore } from '@ngrx/store/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('FlowAnalysisDrawerComponent', () => {
+ let component: FlowAnalysisDrawerComponent;
+ let fixture: ComponentFixture<FlowAnalysisDrawerComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [FlowAnalysisDrawerComponent, NoopAnimationsModule],
+ providers: [provideMockStore({})]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(FlowAnalysisDrawerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.ts
new file mode 100644
index 0000000000..ed5283eb2c
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/flow-analysis-drawer.component.ts
@@ -0,0 +1,167 @@
+/*
+ * 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, model } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { Store } from '@ngrx/store';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatIconModule } from '@angular/material/icon';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { navigateToComponentDocumentation } from
'../../../../../../state/documentation/documentation.actions';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { FormsModule } from '@angular/forms';
+import { selectFlowAnalysisState } from
'../../../../state/flow-analysis/flow-analysis.selectors';
+import {
+ navigateToEditFlowAnalysisRule,
+ startPollingFlowAnalysis,
+ openRuleDetailsDialog
+} from '../../../../state/flow-analysis/flow-analysis.actions';
+import { FlowAnalysisRule, FlowAnalysisRuleViolation } from
'../../../../state/flow-analysis';
+import {
+ selectBreadcrumbs,
+ selectCurrentProcessGroupId
+} from '../../../../state/flow/flow.selectors';
+import { RouterLink } from '@angular/router';
+import { NifiSpinnerDirective } from
'../../../../../../ui/common/spinner/nifi-spinner.directive';
+import { MatIconButton } from '@angular/material/button';
+import { ComponentContext } from '@nifi/shared';
+import { BreadcrumbEntity } from '../../../../state/shared';
+
+@Component({
+ selector: 'flow-analysis-drawer',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatMenuModule,
+ MatIconModule,
+ MatExpansionModule,
+ MatCheckboxModule,
+ FormsModule,
+ RouterLink,
+ NifiSpinnerDirective,
+ MatIconButton,
+ ComponentContext
+ ],
+ templateUrl: './flow-analysis-drawer.component.html',
+ styleUrl: './flow-analysis-drawer.component.scss'
+})
+export class FlowAnalysisDrawerComponent {
+ violationsMap = new Map();
+ warningRules: FlowAnalysisRule[] = [];
+ enforcedRules: FlowAnalysisRule[] = [];
+ warningViolations: FlowAnalysisRuleViolation[] = [];
+ enforcedViolations: FlowAnalysisRuleViolation[] = [];
+ rules: FlowAnalysisRule[] = [];
+ currentProcessGroupId = '';
+ isAnalysisPending = false;
+ showEnforcedViolations = model(false);
+ showWarningViolations = model(false);
+ flowAnalysisState$ = this.store.select(selectFlowAnalysisState);
+ currentProcessGroupId$ = this.store.select(selectCurrentProcessGroupId);
+ processGroupName = '';
+
+ constructor(private store: Store) {
+ this.store.dispatch(startPollingFlowAnalysis());
+ this.flowAnalysisState$.pipe(takeUntilDestroyed()).subscribe((res) => {
+ this.clearRulesTracking();
+ this.isAnalysisPending = res.flowAnalysisPending;
+ this.rules = res.rules;
+
+ res.rules.forEach((rule: FlowAnalysisRule) => {
+ if (rule.enforcementPolicy === 'WARN') {
+ this.warningRules.push(rule);
+ } else {
+ this.enforcedRules.push(rule);
+ }
+ });
+ res.ruleViolations.forEach((violation: FlowAnalysisRuleViolation)
=> {
+ if (this.violationsMap.has(violation.ruleId)) {
+ this.violationsMap.get(violation.ruleId).push(violation);
+ } else {
+ this.violationsMap.set(violation.ruleId, [violation]);
+ }
+ });
+ this.enforcedViolations = res.ruleViolations.filter(function
(violation: FlowAnalysisRuleViolation) {
+ return violation.enforcementPolicy === 'ENFORCE';
+ });
+ this.warningViolations = res.ruleViolations.filter(function
(violation: FlowAnalysisRuleViolation) {
+ return violation.enforcementPolicy === 'WARN';
+ });
+ });
+ this.currentProcessGroupId$.subscribe((pgId) => {
+ this.currentProcessGroupId = pgId;
+ });
+ this.store
+ .select(selectBreadcrumbs)
+ .pipe(takeUntilDestroyed())
+ .subscribe((breadcrumbs: BreadcrumbEntity) => {
+ this.processGroupName = breadcrumbs.breadcrumb.name;
+ });
+ }
+
+ openRule(rule: FlowAnalysisRule) {
+ this.store.dispatch(
+ navigateToEditFlowAnalysisRule({
+ id: rule.id
+ })
+ );
+ }
+
+ clearRulesTracking() {
+ this.enforcedRules = [];
+ this.warningRules = [];
+ this.violationsMap.clear();
+ }
+
+ openDocumentation(rule: FlowAnalysisRule) {
+ this.store.dispatch(
+ navigateToComponentDocumentation({
+ request: {
+ backNavigation: {
+ route: ['/process-groups', this.currentProcessGroupId],
+ routeBoundary: ['/documentation'],
+ context: 'Canvas'
+ },
+ parameters: {
+ select: rule.type,
+ group: rule.bundle.group,
+ artifact: rule.bundle.artifact,
+ version: rule.bundle.version
+ }
+ }
+ })
+ );
+ }
+
+ viewViolationDetails(violation: FlowAnalysisRuleViolation) {
+ const ruleTest: FlowAnalysisRule = this.rules.find((rule) => rule.id
=== violation.ruleId)!;
+ this.store.dispatch(openRuleDetailsDialog({ violation, rule: ruleTest
}));
+ }
+
+ getProcessorLink(violation: FlowAnalysisRuleViolation): string[] {
+ return ['/process-groups', violation.groupId,
violation.subjectComponentType, violation.subjectId];
+ }
+
+ getRuleName(id: string) {
+ const rule = this.rules.find(function (rule: FlowAnalysisRule) {
+ return rule.id === id;
+ });
+
+ return rule ? rule.name : '';
+ }
+}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/_violation-details-dialog.component-theme.scss
similarity index 63%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/_violation-details-dialog.component-theme.scss
index 4c4e73ca53..c47dd4dee9 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/_violation-details-dialog.component-theme.scss
@@ -29,18 +29,10 @@
$supplemental-theme-surface-palette:
map.get($supplemental-theme-color-config, 'primary');
// Get hues from palette
- $is-dark: map-get($supplemental-theme-color-config, is-dark);
+ $is-dark: map-get($material-theme-color-config, is-dark);
$material-theme-primary-palette-default:
mat.m2-get-color-from-palette($material-theme-primary-palette);
$material-theme-primary-palette-lighter:
mat.m2-get-color-from-palette($material-theme-primary-palette, lighter);
$material-theme-warn-palette-darker:
mat.m2-get-color-from-palette($material-theme-warn-palette, darker);
- $supplemental-theme-surface-palette-lighter: mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- lighter
- );
- $supplemental-theme-surface-palette-darker: mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- darker
- );
$supplemental-theme-surface-palette-darker-contrast:
mat.m2-get-color-from-palette(
$supplemental-theme-surface-palette,
darker-contrast
@@ -50,32 +42,25 @@
lighter-contrast
);
- .flow-status {
- background: if(
+ .pill.enforce {
+ background-color: $material-theme-warn-palette-darker;
+ color: if(
$is-dark,
- $supplemental-theme-surface-palette-darker,
- $supplemental-theme-surface-palette-lighter
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
);
+ }
- .controller-bulletins {
- background-color: if(
- $is-dark,
- $material-theme-primary-palette-lighter,
- $material-theme-primary-palette-default
- );
-
- .fa {
- // invert the contrast colors since the surface is dark in
light mode and light in dark mode
- color: if(
- $is-dark,
- $supplemental-theme-surface-palette-lighter-contrast,
- $supplemental-theme-surface-palette-darker-contrast
- );
- }
- }
-
- .controller-bulletins.has-bulletins {
- background-color: $material-theme-warn-palette-darker;
- }
+ .pill.warn {
+ background-color: if(
+ $is-dark,
+ $material-theme-primary-palette-lighter,
+ $material-theme-primary-palette-default
+ );
+ color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
+ );
}
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.html
similarity index 50%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.html
index 802e15c2fa..96bd40d4df 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/canvas.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.html
@@ -15,15 +15,28 @@
~ limitations under the License.
-->
-<div class="flex flex-col h-full">
- <fd-header></fd-header>
- <div class="flex-1">
- <graph-controls></graph-controls>
- <div
- id="canvas-container"
- class="canvas-background select-none h-full"
- [cdkContextMenuTriggerFor]="contextMenu.menu"></div>
- <fd-context-menu #contextMenu [menuProvider]="canvasContextMenu"
menuId="root"></fd-context-menu>
+<h2 mat-dialog-title>
+ <div class="flex justify-between items-baseline">
+ <div>Violation Information</div>
</div>
- <fd-footer></fd-footer>
-</div>
+</h2>
+<mat-dialog-content>
+ <div>
+ <div class="flex justify-between mt-4 mb-4">
+ <div>Violation</div>
+ <div class="py-1 px-2 rounded-xl pill"
[ngClass]="violation.enforcementPolicy | lowercase">
+ {{ violation.enforcementPolicy }}
+ </div>
+ </div>
+
+ <p>{{ violation.violationMessage }}</p>
+
+ <button mat-menu-item (click)="viewDocumentation()">
+ <i class="fa fa-book primary-color mr-2"></i>
+ View Documentation
+ </button>
+ </div>
+ <mat-dialog-actions align="end">
+ <button mat-button mat-dialog-close color="primary">Close</button>
+ </mat-dialog-actions>
+</mat-dialog-content>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.scss
similarity index 83%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.scss
index 5414141b19..2944f98194 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.scss
@@ -14,16 +14,3 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-.flow-status {
- box-sizing: content-box;
-
- .fa,
- .icon {
- font-style: normal;
- }
-
- .controller-bulletins {
- cursor: default;
- }
-}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.spec.ts
new file mode 100644
index 0000000000..e25954b911
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.spec.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 { ViolationDetailsDialogComponent } from
'./violation-details-dialog.component';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { provideMockStore } from '@ngrx/store/testing';
+
+describe('ViolationDetailsDialogComponent', () => {
+ let component: ViolationDetailsDialogComponent;
+ let fixture: ComponentFixture<ViolationDetailsDialogComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ViolationDetailsDialogComponent],
+ providers: [
+ {
+ provide: MAT_DIALOG_DATA,
+ useValue: {
+ violation: {
+ enforcementPolicy: 'ENFORCE',
+ scope: '4b4cfdf2-0190-1000-500d-b170055a0a34',
+ subjectId: '4b4cfdf2-0190-1000-500d-b170055a0a34',
+ subjectDisplayName: 'AttributeRollingWindow',
+ groupId: '36890551-0190-1000-cfaa-27b854604d18',
+ ruleId: '369c8d4e-0190-1000-99b5-8c4e0144f1a9',
+ issueId: 'default',
+ violationMessage: "'AttributeRollingWindow' is not
allowed",
+ subjectComponentType: 'PROCESSOR',
+ subjectPermissionDto: {
+ canRead: true,
+ canWrite: true
+ },
+ enabled: false
+ },
+ rule: {
+ id: '369c8d4e-0190-1000-99b5-8c4e0144f1a9',
+ name: 'DisallowComponentType',
+ type:
'org.apache.nifi.flowanalysis.rules.DisallowComponentType',
+ bundle: {
+ group: 'org.apache.nifi',
+ artifact: 'nifi-standard-nar',
+ version: '2.0.0-SNAPSHOT'
+ },
+ state: 'ENABLED',
+ persistsState: false,
+ restricted: false,
+ deprecated: false,
+ multipleVersionsAvailable: false,
+ supportsSensitiveDynamicProperties: false,
+ enforcementPolicy: 'ENFORCE',
+ properties: {
+ 'component-type': 'AttributeRollingWindow'
+ },
+ descriptors: {
+ 'component-type': {
+ name: 'component-type',
+ displayName: 'Component Type',
+ description:
+ "Components of the given type will
produce a rule violation (i.e. they shouldn't exist). Either the simple or the
fully qualified name of the type should be provided.",
+ required: true,
+ sensitive: false,
+ dynamic: false,
+ supportsEl: false,
+ expressionLanguageScope: 'Not Supported',
+ dependencies: []
+ }
+ },
+ validationStatus: 'VALID',
+ extensionMissing: false
+ }
+ }
+ },
+ {
+ provide: MatDialogRef,
+ useValue: {}
+ },
+ provideMockStore({})
+ ]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ViolationDetailsDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.ts
new file mode 100644
index 0000000000..f76ae28325
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 { CommonModule } from '@angular/common';
+import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { FlowAnalysisRule, FlowAnalysisRuleViolation } from
'../../../../../state/flow-analysis';
+import { Store } from '@ngrx/store';
+import { navigateToComponentDocumentation } from
'apps/nifi/src/app/state/documentation/documentation.actions';
+import { selectCurrentProcessGroupId } from
'../../../../../state/flow/flow.selectors';
+
+interface Data {
+ violation: FlowAnalysisRuleViolation;
+ rule: FlowAnalysisRule;
+}
+
+@Component({
+ selector: 'app-violation-details-dialog',
+ standalone: true,
+ imports: [CommonModule, MatDialogModule],
+ templateUrl: './violation-details-dialog.component.html',
+ styleUrl: './violation-details-dialog.component.scss'
+})
+export class ViolationDetailsDialogComponent {
+ violation: FlowAnalysisRuleViolation;
+ rule: FlowAnalysisRule;
+ currentProcessGroupId$ = this.store.select(selectCurrentProcessGroupId);
+ currentProcessGroupId = '';
+
+ constructor(
+ @Inject(MAT_DIALOG_DATA) public data: Data,
+ private store: Store
+ ) {
+ this.violation = data.violation;
+ this.rule = data.rule;
+ this.currentProcessGroupId$.subscribe((pgId) => {
+ this.currentProcessGroupId = pgId;
+ });
+ }
+
+ viewDocumentation() {
+ this.store.dispatch(
+ navigateToComponentDocumentation({
+ request: {
+ backNavigation: {
+ route: ['/process-groups', this.currentProcessGroupId],
+ routeBoundary: ['/documentation'],
+ context: 'Canvas'
+ },
+ parameters: {
+ select: this.rule.type,
+ group: this.rule.bundle.group,
+ artifact: this.rule.bundle.artifact,
+ version: this.rule.bundle.version
+ }
+ }
+ })
+ );
+ }
+}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
index 4c4e73ca53..0e11c021d2 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/_flow-status.component-theme.scss
@@ -34,48 +34,89 @@
$material-theme-primary-palette-lighter:
mat.m2-get-color-from-palette($material-theme-primary-palette, lighter);
$material-theme-warn-palette-darker:
mat.m2-get-color-from-palette($material-theme-warn-palette, darker);
$supplemental-theme-surface-palette-lighter: mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- lighter
+ $supplemental-theme-surface-palette,
+ lighter
);
$supplemental-theme-surface-palette-darker: mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- darker
+ $supplemental-theme-surface-palette,
+ darker
);
$supplemental-theme-surface-palette-darker-contrast:
mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- darker-contrast
+ $supplemental-theme-surface-palette,
+ darker-contrast
);
$supplemental-theme-surface-palette-lighter-contrast:
mat.m2-get-color-from-palette(
- $supplemental-theme-surface-palette,
- lighter-contrast
+ $supplemental-theme-surface-palette,
+ lighter-contrast
);
.flow-status {
background: if(
- $is-dark,
- $supplemental-theme-surface-palette-darker,
- $supplemental-theme-surface-palette-lighter
+ $is-dark,
+ $supplemental-theme-surface-palette-darker,
+ $supplemental-theme-surface-palette-lighter
);
.controller-bulletins {
background-color: if(
- $is-dark,
- $material-theme-primary-palette-lighter,
- $material-theme-primary-palette-default
+ $is-dark,
+ $material-theme-primary-palette-lighter,
+ $material-theme-primary-palette-default
);
.fa {
// invert the contrast colors since the surface is dark in
light mode and light in dark mode
color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
+ );
+ }
+ }
+
+ .controller-bulletins.has-bulletins {
+ background-color: $material-theme-warn-palette-darker;
+ }
+
+ .flow-analysis-notifications.warn {
+ background-color: if(
+ $is-dark,
+ $material-theme-primary-palette-lighter,
+ $material-theme-primary-palette-default
+ );
+ border-right-color: if(
$is-dark,
$supplemental-theme-surface-palette-lighter-contrast,
$supplemental-theme-surface-palette-darker-contrast
+ );
+
+
+ .fa {
+ // invert the contrast colors since the surface is dark in
light mode and light in dark mode
+ color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
);
}
}
- .controller-bulletins.has-bulletins {
+ .flow-analysis-notifications.enforce {
background-color: $material-theme-warn-palette-darker;
+ border-right-color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
+ );
+
+ .fa {
+ // invert the contrast colors since the surface is dark in
light mode and light in dark mode
+ color: if(
+ $is-dark,
+ $supplemental-theme-surface-palette-lighter-contrast,
+ $supplemental-theme-surface-palette-darker-contrast
+ );
+ }
}
}
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.html
index 1903fa5d2c..d2b1261dbc 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.html
@@ -127,6 +127,12 @@
</div>
<div class="flex">
<search [currentProcessGroupId]="currentProcessGroupId"></search>
+ <button
+ class="flow-analysis-notifications w-8 border-l border-r flex
justify-center items-center pointer"
+ (click)="toggleFlowAnalysis()"
+ [ngClass]="flowAnalysisNotificationClass">
+ <i class="fa fa-medkit"></i>
+ </button>
@if (hasBulletins()) {
<button
nifiTooltip
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
index 5414141b19..af36ba0a8f 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.scss
@@ -18,11 +18,6 @@
.flow-status {
box-sizing: content-box;
- .fa,
- .icon {
- font-style: normal;
- }
-
.controller-bulletins {
cursor: default;
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
index 42b9424eb8..95f71cc4c7 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
@@ -22,23 +22,44 @@ import { BulletinsTip } from
'../../../../../../ui/common/tooltips/bulletins-tip
import { BulletinEntity, BulletinsTipInput } from
'../../../../../../state/shared';
import { Search } from '../search/search.component';
-import { NifiTooltipDirective } from '@nifi/shared';
+import { NifiTooltipDirective, Storage } from '@nifi/shared';
import { ClusterSummary } from '../../../../../../state/cluster-summary';
import { ConnectedPosition } from '@angular/cdk/overlay';
+import { FlowAnalysisState } from '../../../../state/flow-analysis';
+import { CommonModule } from '@angular/common';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../../../state';
+import { setFlowAnalysisOpen } from '../../../../state/flow/flow.actions';
@Component({
selector: 'flow-status',
standalone: true,
templateUrl: './flow-status.component.html',
- imports: [Search, NifiTooltipDirective],
+ imports: [Search, NifiTooltipDirective, CommonModule],
styleUrls: ['./flow-status.component.scss']
})
export class FlowStatus {
+ private static readonly FLOW_ANALYSIS_VISIBILITY_KEY: string =
'flow-analysis-visibility';
+ private static readonly FLOW_ANALYSIS_KEY: string = 'flow-analysis';
+ public flowAnalysisNotificationClass: string = '';
@Input() controllerStatus: ControllerStatus =
initialState.flowStatus.controllerStatus;
@Input() lastRefreshed: string =
initialState.flow.processGroupFlow.lastRefreshed;
@Input() clusterSummary: ClusterSummary | null = null;
@Input() currentProcessGroupId: string = initialState.id;
@Input() loadingStatus = false;
+ @Input() flowAnalysisOpen = initialState.flowAnalysisOpen;
+ @Input() set flowAnalysisState(state: FlowAnalysisState) {
+ if (!state.ruleViolations.length) {
+ this.flowAnalysisNotificationClass = 'primary-color';
+ } else {
+ const isEnforcedRuleViolated = state.ruleViolations.find((v) => {
+ return v.enforcementPolicy === 'ENFORCE';
+ });
+ isEnforcedRuleViolated
+ ? (this.flowAnalysisNotificationClass = 'enforce')
+ : (this.flowAnalysisNotificationClass = 'warn');
+ }
+ }
@Input() set bulletins(bulletins: BulletinEntity[]) {
if (bulletins) {
@@ -52,6 +73,23 @@ export class FlowStatus {
protected readonly BulletinsTip = BulletinsTip;
+ constructor(
+ private store: Store<NiFiState>,
+ private storage: Storage
+ ) {
+ try {
+ const item: { [key: string]: boolean } | null =
this.storage.getItem(
+ FlowStatus.FLOW_ANALYSIS_VISIBILITY_KEY
+ );
+ if (item) {
+ const flowAnalysisOpen = item[FlowStatus.FLOW_ANALYSIS_KEY]
=== true;
+ this.store.dispatch(setFlowAnalysisOpen({ flowAnalysisOpen }));
+ }
+ } catch (e) {
+ // likely could not parse item... ignoring
+ }
+ }
+
hasTerminatedThreads(): boolean {
return this.controllerStatus.terminatedThreadCount > 0;
}
@@ -141,4 +179,18 @@ export class FlowStatus {
offsetY: 8
};
}
+
+ toggleFlowAnalysis(): void {
+ const flowAnalysisOpen = !this.flowAnalysisOpen;
+ this.store.dispatch(setFlowAnalysisOpen({ flowAnalysisOpen }));
+
+ // update the current value in storage
+ let item: { [key: string]: boolean } | null =
this.storage.getItem(FlowStatus.FLOW_ANALYSIS_VISIBILITY_KEY);
+ if (item == null) {
+ item = {};
+ }
+
+ item[FlowStatus.FLOW_ANALYSIS_KEY] = flowAnalysisOpen;
+ this.storage.setItem(FlowStatus.FLOW_ANALYSIS_VISIBILITY_KEY, item);
+ }
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html
index d77493993a..7c320341e3 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.html
@@ -69,11 +69,13 @@
}
</navigation>
<flow-status
+ [flowAnalysisOpen]="(flowAnalysisOpen$ | async)!"
[controllerStatus]="(controllerStatus$ | async)!"
[lastRefreshed]="(lastRefreshed$ | async)!"
[clusterSummary]="(clusterSummary$ | async)!"
[bulletins]="(controllerBulletins$ | async)!"
[currentProcessGroupId]="(currentProcessGroupId$ | async)!"
- [loadingStatus]="(loadingService.status$ | async)!">
+ [loadingStatus]="(loadingService.status$ | async)!"
+ [flowAnalysisState]="(flowAnalysisState$ | async)!">
</flow-status>
</header>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.ts
index a438299367..4ec1e91f72 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.ts
@@ -24,6 +24,7 @@ import {
selectControllerBulletins,
selectControllerStatus,
selectCurrentProcessGroupId,
+ selectFlowAnalysisOpen,
selectLastRefreshed
} from '../../../state/flow/flow.selectors';
import { LoadingService } from '../../../../../service/loading.service';
@@ -36,6 +37,7 @@ import { RouterLink } from '@angular/router';
import { FlowStatus } from './flow-status/flow-status.component';
import { Navigation } from
'../../../../../ui/common/navigation/navigation.component';
import { selectClusterSummary } from
'../../../../../state/cluster-summary/cluster-summary.selectors';
+import { selectFlowAnalysisState } from
'../../../state/flow-analysis/flow-analysis.selectors';
@Component({
selector: 'fd-header',
@@ -63,6 +65,8 @@ export class HeaderComponent {
controllerBulletins$ = this.store.select(selectControllerBulletins);
currentProcessGroupId$ = this.store.select(selectCurrentProcessGroupId);
canvasPermissions$ = this.store.select(selectCanvasPermissions);
+ flowAnalysisState$ = this.store.select(selectFlowAnalysisState);
+ flowAnalysisOpen$ = this.store.select(selectFlowAnalysisOpen);
constructor(
private store: Store<CanvasState>,
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/styles.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/styles.scss
index 7db0432493..8bdcf1fe58 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/styles.scss
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/styles.scss
@@ -34,6 +34,8 @@
@use
'app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component-theme'
as flow-status;
@use
'app/pages/flow-designer/ui/canvas/header/new-canvas-item/new-canvas-item.component-theme'
as new-canvas-item;
@use 'app/pages/flow-designer/ui/canvas/header/search/search.component-theme'
as search;
+@use
'app/pages/flow-designer/ui/canvas/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.component-theme'
+ as violation-details-dialog;
@use 'app/pages/login/feature/login.component-theme' as login;
@use 'app/pages/logout/feature/logout.component-theme' as logout;
@use 'app/pages/provenance/feature/provenance.component-theme' as provenance;
@@ -107,6 +109,7 @@
@include processor-status-table.generate-theme($supplemental-theme-light);
@include change-color-dialog.generate-theme($supplemental-theme-light);
@include text-editor.generate-theme($material-theme-light,
$supplemental-theme-light);
+@include violation-details-dialog.generate-theme($material-theme-light,
$supplemental-theme-light);
.dark-theme {
// Include the dark theme color styles.
@@ -140,4 +143,5 @@
@include processor-status-table.generate-theme($supplemental-theme-dark);
@include change-color-dialog.generate-theme($supplemental-theme-dark);
@include text-editor.generate-theme($material-theme-dark,
$supplemental-theme-dark);
+ @include violation-details-dialog.generate-theme($material-theme-dark,
$supplemental-theme-dark);
}