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/service/flow-analysis.service.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.spec.ts new file mode 100644 index 0000000000..d85a11dcf0 --- /dev/null +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.spec.ts @@ -0,0 +1,43 @@ +/* + * 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 { TestBed } from '@angular/core/testing'; + +import { FlowAnalysisService } from './flow-analysis.service'; +import { provideMockStore } from '@ngrx/store/testing'; +import { HttpClient } from '@angular/common/http'; + +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/service/flow-analysis.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.ts new file mode 100644 index 0000000000..571d91534f --- /dev/null +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow-analysis.service.ts @@ -0,0 +1,33 @@ +/* + * 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 { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class FlowAnalysisService { + private static readonly API: string = '../nifi-api'; + + constructor(private httpClient: HttpClient) {} + + getResults(processGroupId: string): Observable { + 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, + 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/state/flow-analysis/flow-analysis.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.reducer.ts new file mode 100644 index 0000000000..486fb32757 --- /dev/null +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.reducer.ts @@ -0,0 +1,42 @@ +/* + * 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 { createReducer, on } from '@ngrx/store'; +import { FlowAnalysisState } from './index'; +import { pollFlowAnalysis, pollFlowAnalysisSuccess } from './flow-analysis.actions'; + +export const initialState: FlowAnalysisState = { + rules: [], + ruleViolations: [], + flowAnalysisPending: false, + status: 'pending' +}; + +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/state/flow-analysis/flow-analysis.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.selectors.ts new file mode 100644 index 0000000000..7fd8035793 --- /dev/null +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow-analysis/flow-analysis.selectors.ts @@ -0,0 +1,25 @@ +/* + * 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 { createSelector } from '@ngrx/store'; +import { flowAnalysisFeatureKey } from './index'; +import { CanvasState, selectCanvasState } from '../index'; + +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 @@
- -
- + + + + + + +
+ +
+
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, 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 @@ + +
+ +
+ + +
Rules analysis pending...
+
+
+
+
+
+
+ + Show enforced violations + +
+
+ + Show warning violations + +
+
+
+
+
+
+ + + +
+
Enforced Rules ({{ enforcedRules.length }})
+
+
+
+ + @if (rules) { + @for (rule of enforcedRules; track rule.id) { +
+
+
{{ rule.name }}
+ + + + + +
+ @if (violationsMap.size > 0 && violationsMap.get(rule.id)) { +
+ + {{ violationsMap.get(rule.id).length }} violation + {{ violationsMap.get(rule.id).length }} violations + +
+ @for (violation of violationsMap.get(rule.id); track violation.scope) { +
+
+
+ {{ violation.subjectDisplayName }} +
+ + {{ violation.subjectId }} + +
+ + +
+ } + } +
+ } + } +
+ + + +
+
Warning Rules ({{ warningRules.length }})
+
+
+
+ + @if (rules) { + @for (rule of warningRules; track rule.id) { +
+
+
{{ rule.name }}
+ + + + + +
+ @if (violationsMap.size > 0 && violationsMap.get(rule.id)) { +
+ + {{ violationsMap.get(rule.id).length }} violation + {{ violationsMap.get(rule.id).length }} violations + +
+
    + @for (violation of violationsMap.get(rule.id); track violation.scope) { +
  • +
    +
    + {{ violation.subjectDisplayName }} +
    + + {{ violation.subjectId }} + +
    + + +
  • + } +
+ } +
+ } + } +
+
+
+ +
+
+
+ Enforced Violations + ({{ enforcedViolations.length }}) +
+
+ +
    + @for (violation of enforcedViolations; track violation.scope) { +
  • +
    {{ getRuleName(violation.ruleId) }}
    +
    +
    +
    + {{ violation.subjectDisplayName }} +
    + {{ violation.subjectId }} +
    + + +
    +
  • + } +
+
+ +
+
+
+ Warning Violations + ({{ warningViolations.length }}) +
+
+ +
    + @for (violation of warningViolations; track violation.scope) { +
  • +
    {{ getRuleName(violation.ruleId) }}
    +
    +
    +
    + {{ violation.subjectDisplayName }} +
    + {{ violation.subjectId }} +
    + + +
    +
  • + } +
+
+
+ + + + + + + + + + Unauthorized 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.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 new file mode 100644 index 0000000000..2944f98194 --- /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.scss @@ -0,0 +1,16 @@ +/* + * 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. + */ 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; + + 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-analysis-drawer/violation-details-dialog/_violation-details-dialog.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 new file mode 100644 index 0000000000..c47dd4dee9 --- /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-theme.scss @@ -0,0 +1,66 @@ +/* + * 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. + */ + +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin generate-theme($material-theme, $supplemental-theme) { + // Get the color config from the theme. + $material-theme-color-config: mat.m2-get-color-config($material-theme); + $supplemental-theme-color-config: mat.m2-get-color-config($supplemental-theme); + + // Get the color palette from the color-config. + $material-theme-primary-palette: map.get($material-theme-color-config, 'primary'); + $material-theme-warn-palette: map.get($material-theme-color-config, 'warn'); + $supplemental-theme-surface-palette: map.get($supplemental-theme-color-config, 'primary'); + + // Get hues from palette + $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-darker-contrast: mat.m2-get-color-from-palette( + $supplemental-theme-surface-palette, + darker-contrast + ); + $supplemental-theme-surface-palette-lighter-contrast: mat.m2-get-color-from-palette( + $supplemental-theme-surface-palette, + lighter-contrast + ); + + .pill.enforce { + background-color: $material-theme-warn-palette-darker; + color: if( + $is-dark, + $supplemental-theme-surface-palette-lighter-contrast, + $supplemental-theme-surface-palette-darker-contrast + ); + } + + .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/header/flow-analysis-drawer/violation-details-dialog/violation-details-dialog.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 new file mode 100644 index 0000000000..96bd40d4df --- /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.html @@ -0,0 +1,42 @@ + + +

+
+
Violation Information
+
+

+ +
+
+
Violation
+
+ {{ violation.enforcementPolicy }} +
+
+ +

{{ violation.violationMessage }}

+ + +
+ + + +
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.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 new file mode 100644 index 0000000000..2944f98194 --- /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.scss @@ -0,0 +1,16 @@ +/* + * 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. + */ 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; + + 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,42 +34,42 @@ $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 + $is-dark, + $supplemental-theme-surface-palette-lighter-contrast, + $supplemental-theme-surface-palette-darker-contrast ); } } @@ -77,5 +77,46 @@ .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 + ); + } + } + + .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 @@
+ @if (hasBulletins()) {