mcgilman commented on code in PR #8233:
URL: https://github.com/apache/nifi/pull/8233#discussion_r1449415743
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts:
##########
@@ -120,6 +120,11 @@ export interface UpdateReportingTaskRequest {
postUpdateNavigation?: string[];
}
+export interface UpdateFlowAnalysisRuleRequest {
Review Comment:
This interface definition (and the `UpdateReportingTaskRequest` above) could
be moved to their respective `pages`. `UpdateConfigurationServiceRequest` is
here because the Controller Service edit dialog is a shared component between
the Canvas and Controller Services. The Flow Analysis edit dialog and Reporting
Task edit dialog are not shared components.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts:
##########
@@ -0,0 +1,261 @@
+/*
+ * 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 { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild }
from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { RouterLink } from '@angular/router';
+import { NgClass, NgIf } from '@angular/common';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { MatSort, MatSortModule } from '@angular/material/sort';
+import { FlowAnalysisRuleEntity } from '../../../state/flow-analysis-rules';
+import { TextTip } from
'../../../../../ui/common/tooltips/text-tip/text-tip.component';
+import { BulletinsTip } from
'../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
+import { ValidationErrorsTip } from
'../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { BulletinsTipInput, TextTipInput, ValidationErrorsTipInput } from
'../../../../../state/shared';
+import { NifiTooltipDirective } from
'../../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { ReportingTaskEntity } from '../../../state/reporting-tasks';
+
+@Component({
+ selector: 'flow-analysis-rule-table',
+ standalone: true,
+ templateUrl: './flow-analysis-rule-table.component.html',
+ imports: [
+ MatButtonModule,
+ MatDialogModule,
+ MatTableModule,
+ MatSortModule,
+ NgIf,
+ NgClass,
+ NifiTooltipDirective,
+ RouterLink
+ ],
+ styleUrls: ['./flow-analysis-rule-table.component.scss',
'../../../../../../assets/styles/listing-table.scss']
+})
+export class FlowAnalysisRuleTable implements AfterViewInit {
+ @Input() set flowAnalysisRules(FlowAnalysisRuleEntities:
FlowAnalysisRuleEntity[]) {
+ this.dataSource = new
MatTableDataSource<FlowAnalysisRuleEntity>(FlowAnalysisRuleEntities);
+ this.dataSource.sort = this.sort;
+ this.dataSource.sortingDataAccessor = (data: FlowAnalysisRuleEntity,
displayColumn: string) => {
+ if (displayColumn == 'name') {
+ return this.formatType(data);
+ } else if (displayColumn == 'type') {
+ return this.formatType(data);
+ } else if (displayColumn == 'bundle') {
+ return this.formatBundle(data);
+ } else if (displayColumn == 'state') {
+ return this.formatState(data);
+ }
+ return '';
+ };
+ }
+ @Input() selectedFlowAnalysisRuleId!: string;
+ @Input() definedByCurrentGroup!: (entity: FlowAnalysisRuleEntity) =>
boolean;
+
+ @Output() selectFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() deleteFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() configureFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() enableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() disableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+
+ protected readonly TextTip = TextTip;
+ protected readonly BulletinsTip = BulletinsTip;
+ protected readonly ValidationErrorsTip = ValidationErrorsTip;
+
+ displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle',
'state', 'actions'];
+ dataSource: MatTableDataSource<FlowAnalysisRuleEntity> = new
MatTableDataSource<FlowAnalysisRuleEntity>();
+
+ @ViewChild(MatSort) sort!: MatSort;
+
+ constructor(private nifiCommon: NiFiCommon) {}
+
+ ngAfterViewInit(): void {
+ this.dataSource.sort = this.sort;
+ }
+
+ canRead(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canRead;
+ }
+
+ canWrite(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canWrite;
+ }
+
+ canOperate(entity: FlowAnalysisRuleEntity): boolean {
+ if (this.canWrite(entity)) {
+ return true;
+ }
+ return !!entity.operatePermissions?.canWrite;
+ }
+
+ hasComments(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isBlank(entity.component.comments);
+ }
+
+ getCommentsTipData(entity: FlowAnalysisRuleEntity): TextTipInput {
+ return {
+ text: entity.component.comments
+ };
+ }
+
+ hasErrors(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.component.validationErrors);
+ }
+
+ getValidationErrorsTipData(entity: FlowAnalysisRuleEntity):
ValidationErrorsTipInput {
+ return {
+ isValidating: entity.status.validationStatus === 'VALIDATING',
+ validationErrors: entity.component.validationErrors
+ };
+ }
+
+ hasBulletins(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.bulletins);
+ }
+
+ getBulletinsTipData(entity: FlowAnalysisRuleEntity): BulletinsTipInput {
+ return {
+ bulletins: entity.bulletins
+ };
+ }
+
+ getStateIcon(entity: FlowAnalysisRuleEntity): string {
+ if (entity.status.validationStatus === 'VALIDATING') {
+ return 'validating fa fa-spin fa-circle-o-notch';
+ } else if (entity.status.validationStatus === 'INVALID') {
+ return 'invalid fa fa-warning';
+ } else {
+ if (entity.status.runStatus === 'DISABLED') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'DISABLING') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'ENABLED') {
+ return 'enabled fa fa-flash';
+ } else if (entity.status.runStatus === 'ENABLING') {
Review Comment:
Compared to the existing NiFi UI, it doesn't appear that FlowAnalysisRules
support `ENABLING` and `DISABLING` transitional states.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html:
##########
@@ -0,0 +1,145 @@
+<!--
+ ~ 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="relative h-full border">
+ <div class="flow-analysis-rule-table listing-table absolute inset-0
overflow-y-auto">
+ <table mat-table [dataSource]="dataSource" matSort matSortDisableClear>
+ <!-- More Details Column -->
+ <ng-container matColumnDef="moreDetails">
+ <th mat-header-cell *matHeaderCellDef></th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ <div class="flex items-center gap-x-3">
+ <div class="pointer fa fa-book"
title="Usage"></div>
+ <!-- TODO - handle read only in configure
component? -->
+ <div *ngIf="hasComments(item)">
+ <div
+ class="pointer fa fa-comment"
+ [delayClose]="false"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+
[tooltipInputData]="getCommentsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasErrors(item)">
+ <div
+ class="pointer fa fa-warning has-errors"
+ [delayClose]="false"
+ nifiTooltip
+
[tooltipComponentType]="ValidationErrorsTip"
+
[tooltipInputData]="getValidationErrorsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasBulletins(item)">
Review Comment:
I don't believe that FlowAnalysisRules support bulletins.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowAnalysisRulesEntity.java:
##########
@@ -16,7 +16,12 @@
*/
package org.apache.nifi.web.api.entity;
+import io.swagger.annotations.ApiModelProperty;
Review Comment:
```suggestion
import io.swagger.v3.oas.annotations.media.Schema;
```
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowAnalysisRulesEntity.java:
##########
@@ -38,4 +44,20 @@ public void setFlowAnalysisRules(Set<FlowAnalysisRuleEntity>
flowAnalysisRules)
this.flowAnalysisRules = flowAnalysisRules;
}
+ /**
+ * @return current time on the server
+ */
+ @XmlJavaTypeAdapter(TimeAdapter.class)
+ @ApiModelProperty(
+ value = "The current time on the system.",
+ dataType = "string"
+ )
Review Comment:
```suggestion
@XmlJavaTypeAdapter(TimeAdapter.class)
@Schema(
description = "The current time on the system.",
type = "string"
)
```
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html:
##########
@@ -0,0 +1,145 @@
+<!--
+ ~ 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="relative h-full border">
+ <div class="flow-analysis-rule-table listing-table absolute inset-0
overflow-y-auto">
+ <table mat-table [dataSource]="dataSource" matSort matSortDisableClear>
+ <!-- More Details Column -->
+ <ng-container matColumnDef="moreDetails">
+ <th mat-header-cell *matHeaderCellDef></th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ <div class="flex items-center gap-x-3">
+ <div class="pointer fa fa-book"
title="Usage"></div>
+ <!-- TODO - handle read only in configure
component? -->
+ <div *ngIf="hasComments(item)">
+ <div
+ class="pointer fa fa-comment"
+ [delayClose]="false"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+
[tooltipInputData]="getCommentsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasErrors(item)">
+ <div
+ class="pointer fa fa-warning has-errors"
+ [delayClose]="false"
+ nifiTooltip
+
[tooltipComponentType]="ValidationErrorsTip"
+
[tooltipInputData]="getValidationErrorsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasBulletins(item)">
+ <div
+ class="pointer fa fa-sticky-note-o"
+ [delayClose]="false"
+ nifiTooltip
+ [tooltipComponentType]="BulletinsTip"
+
[tooltipInputData]="getBulletinsTipData(item)"></div>
+ </div>
+ </div>
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- Name Column -->
+ <ng-container matColumnDef="name">
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item); else
nameNoPermissions">
+ {{ item.component.name }}
+ </ng-container>
+ <ng-template #nameNoPermissions>
+ <div class="unset">{{ item.id }}</div>
+ </ng-template>
+ </td>
+ </ng-container>
+
+ <!-- Type Column -->
+ <ng-container matColumnDef="type">
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ {{ formatType(item) }}
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- Bundle Column -->
+ <ng-container matColumnDef="bundle">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>Bundle</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ {{ formatBundle(item) }}
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- State Column -->
+ <ng-container matColumnDef="state">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>State</th>
+ <td mat-cell *matCellDef="let item">
+ <div class="flex items-center gap-x-2">
+ <div [ngClass]="getStateIcon(item)"></div>
+ <div>{{ formatState(item) }}</div>
+ <div *ngIf="hasActiveThreads(item)">({{
item.status.activeThreadCount }})</div>
+ </div>
+ </td>
+ </ng-container>
+
+ <!-- Actions Column -->
+ <ng-container matColumnDef="actions">
+ <th mat-header-cell *matHeaderCellDef></th>
+ <td mat-cell *matCellDef="let item">
+ <div class="flex items-center gap-x-3">
+ <div
+ class="pointer fa fa-gear"
+ *ngIf="canConfigure(item)"
+ (click)="configureClicked(item, $event)"
+ title="Edit"></div>
+ <!-- TODO - handle read only in configure component?
-->
+ <div
+ class="pointer fa icon icon-enable-false"
+ *ngIf="canDisable(item)"
+ (click)="disableClicked(item, $event)"
+ title="Disable"></div>
+ <div
+ class="pointer fa fa-flash"
+ *ngIf="canEnable(item)"
+ (click)="enabledClicked(item, $event)"
+ title="Enable"></div>
+ <div class="pointer fa fa-exchange"
*ngIf="canChangeVersion(item)" title="Change Version"></div>
+ <div
+ class="pointer fa fa-trash"
+ *ngIf="canDelete(item)"
+ (click)="deleteClicked(item)"
+ title="Delete"></div>
+ <div class="pointer fa fa-tasks"
*ngIf="canViewState(item)" title="View State"></div>
+ </div>
+ <div class="pointer fa fa-key"
*ngIf="canManageAccessPolicies()" title="Access Policies"></div>
Review Comment:
I don't believe that FlowAnalysisRules supports granular access policies.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component.html:
##########
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-<h2 mat-dialog-title>Edit Reporting Task</h2>
+<h2 mat-dialog-title>Configure Reporting Task</h2>
Review Comment:
I think most of the edit dialogs are called `EditXyz`, the titles are `Edit
Xyz`, the form is named `editXyz`, etc. Also, the routes use `edit` and the
action names include `edit`. It's possible that there are some instances of
`Configure` that are present (like in the Context Menu) but if we're looking
to make this more consistent there are many places that will need to be updated.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts:
##########
@@ -0,0 +1,441 @@
+/*
+ * 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 * as FlowAnalysisRuleActions from './flow-analysis-rules.actions';
+import { catchError, from, map, Observable, of, switchMap, take, takeUntil,
tap, withLatestFrom } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { selectFlowAnalysisRuleTypes } from
'../../../../state/extension-types/extension-types.selectors';
+import { YesNoDialog } from
'../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
+import { FlowAnalysisRuleService } from
'../../service/flow-analysis-rule.service';
+import { CreateFlowAnalysisRule } from
'../../ui/flow-analysis-rules/create-flow-analysis-rule/create-flow-analysis-rule.component';
+import { Router } from '@angular/router';
+import { selectSaving } from
'../management-controller-services/management-controller-services.selectors';
+import {
+ NewPropertyDialogRequest,
+ NewPropertyDialogResponse,
+ Property,
+ UpdateControllerServiceRequest
+} from '../../../../state/shared';
+import { EditFlowAnalysisRule } from
'../../ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component';
+import { CreateFlowAnalysisRuleSuccess } from './index';
+import { NewPropertyDialog } from
'../../../../ui/common/new-property-dialog/new-property-dialog.component';
+
+@Injectable()
+export class FlowAnalysisRulesEffects {
+ constructor(
+ private actions$: Actions,
+ private store: Store<NiFiState>,
+ private flowAnalysisRuleService: FlowAnalysisRuleService,
+ private dialog: MatDialog,
+ private router: Router
+ ) {}
+
+ loadFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.loadFlowAnalysisRules),
+ switchMap(() =>
+ from(this.flowAnalysisRuleService.getFlowAnalysisRule()).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.loadFlowAnalysisRulesSuccess({
+ response: {
+ flowAnalysisRules: response.flowAnalysisRules,
+ loadedTimestamp: response.currentTime
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ openNewFlowAnalysisRuleDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.openNewFlowAnalysisRuleDialog),
+ withLatestFrom(this.store.select(selectFlowAnalysisRuleTypes)),
+ tap(([action, flowAnalysisRuleTypes]) => {
+ this.dialog.open(CreateFlowAnalysisRule, {
+ data: {
+ flowAnalysisRuleTypes
+ },
+ panelClass: 'medium-dialog'
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ createFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.createFlowAnalysisRule),
+ map((action) => action.request),
+ switchMap((request) =>
+
from(this.flowAnalysisRuleService.createFlowAnalysisRule(request)).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.createFlowAnalysisRuleSuccess({
+ response: {
+ flowAnalysisRule: response
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ createFlowAnalysisRuleSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.createFlowAnalysisRuleSuccess),
+ map((action) => action.response),
+ tap(() => {
+ this.dialog.closeAll();
+ }),
+ switchMap((response: CreateFlowAnalysisRuleSuccess) =>
+ of(
+ FlowAnalysisRuleActions.selectFlowAnalysisRule({
+ request: {
+ id: response.flowAnalysisRule.id
+ }
+ })
+ )
+ )
+ )
+ );
+
+ promptFlowAnalysisRuleDeletion$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.promptFlowAnalysisRuleDeletion),
+ map((action) => action.request),
+ tap((request) => {
+ const dialogReference = this.dialog.open(YesNoDialog, {
+ data: {
+ title: 'Delete Flow Analysis Rule',
+ message: `Delete reporting task
${request.flowAnalysisRule.component.name}?`
+ },
+ panelClass: 'small-dialog'
+ });
+
+
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
+ this.store.dispatch(
+ FlowAnalysisRuleActions.deleteFlowAnalysisRule({
+ request
+ })
+ );
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ deleteFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.deleteFlowAnalysisRule),
+ map((action) => action.request),
+ switchMap((request) =>
+
from(this.flowAnalysisRuleService.deleteFlowAnalysisRule(request)).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.deleteFlowAnalysisRuleSuccess({
+ response: {
+ flowAnalysisRule: response
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ navigateToEditFlowAnalysisRule$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.navigateToEditFlowAnalysisRule),
+ map((action) => action.id),
+ tap((id) => {
+ this.router.navigate(['/settings', 'flow-analysis-rules',
id, 'edit']);
+ })
+ ),
+ { dispatch: false }
+ );
+
+ openConfigureFlowAnalysisRuleDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+
ofType(FlowAnalysisRuleActions.openConfigureFlowAnalysisRuleDialog),
+ map((action) => action.request),
+ tap((request) => {
+ const taskId: string = request.id;
+
+ const editDialogReference =
this.dialog.open(EditFlowAnalysisRule, {
+ data: {
+ flowAnalysisRule: request.flowAnalysisRule
+ },
+ id: taskId,
+ panelClass: 'large-dialog'
+ });
+
+ editDialogReference.componentInstance.saving$ =
this.store.select(selectSaving);
+
+ editDialogReference.componentInstance.createNewProperty = (
+ existingProperties: string[],
+ allowsSensitive: boolean
+ ): Observable<Property> => {
+ const dialogRequest: NewPropertyDialogRequest = {
existingProperties, allowsSensitive };
+ const newPropertyDialogReference =
this.dialog.open(NewPropertyDialog, {
+ data: dialogRequest,
+ panelClass: 'small-dialog'
+ });
+
+ return
newPropertyDialogReference.componentInstance.newProperty.pipe(
+ take(1),
+ switchMap((dialogResponse:
NewPropertyDialogResponse) => {
+ return this.flowAnalysisRuleService
+ .getPropertyDescriptor(request.id,
dialogResponse.name, dialogResponse.sensitive)
+ .pipe(
+ take(1),
+ map((response) => {
+ newPropertyDialogReference.close();
+
+ return {
+ property: dialogResponse.name,
+ value: null,
+ descriptor:
response.propertyDescriptor
+ };
+ })
+ );
+ })
+ );
+ };
+
+ const goTo = (commands: string[], destination: string):
void => {
Review Comment:
This is currently unused. I think go to Controller Service is missing.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts:
##########
@@ -0,0 +1,261 @@
+/*
+ * 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 { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild }
from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { RouterLink } from '@angular/router';
+import { NgClass, NgIf } from '@angular/common';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { MatSort, MatSortModule } from '@angular/material/sort';
+import { FlowAnalysisRuleEntity } from '../../../state/flow-analysis-rules';
+import { TextTip } from
'../../../../../ui/common/tooltips/text-tip/text-tip.component';
+import { BulletinsTip } from
'../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
+import { ValidationErrorsTip } from
'../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { BulletinsTipInput, TextTipInput, ValidationErrorsTipInput } from
'../../../../../state/shared';
+import { NifiTooltipDirective } from
'../../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { ReportingTaskEntity } from '../../../state/reporting-tasks';
+
+@Component({
+ selector: 'flow-analysis-rule-table',
+ standalone: true,
+ templateUrl: './flow-analysis-rule-table.component.html',
+ imports: [
+ MatButtonModule,
+ MatDialogModule,
+ MatTableModule,
+ MatSortModule,
+ NgIf,
+ NgClass,
+ NifiTooltipDirective,
+ RouterLink
+ ],
+ styleUrls: ['./flow-analysis-rule-table.component.scss',
'../../../../../../assets/styles/listing-table.scss']
+})
+export class FlowAnalysisRuleTable implements AfterViewInit {
+ @Input() set flowAnalysisRules(FlowAnalysisRuleEntities:
FlowAnalysisRuleEntity[]) {
+ this.dataSource = new
MatTableDataSource<FlowAnalysisRuleEntity>(FlowAnalysisRuleEntities);
+ this.dataSource.sort = this.sort;
+ this.dataSource.sortingDataAccessor = (data: FlowAnalysisRuleEntity,
displayColumn: string) => {
+ if (displayColumn == 'name') {
+ return this.formatType(data);
+ } else if (displayColumn == 'type') {
+ return this.formatType(data);
+ } else if (displayColumn == 'bundle') {
+ return this.formatBundle(data);
+ } else if (displayColumn == 'state') {
+ return this.formatState(data);
+ }
+ return '';
+ };
+ }
+ @Input() selectedFlowAnalysisRuleId!: string;
+ @Input() definedByCurrentGroup!: (entity: FlowAnalysisRuleEntity) =>
boolean;
+
+ @Output() selectFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() deleteFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() configureFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() enableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() disableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+
+ protected readonly TextTip = TextTip;
+ protected readonly BulletinsTip = BulletinsTip;
+ protected readonly ValidationErrorsTip = ValidationErrorsTip;
+
+ displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle',
'state', 'actions'];
+ dataSource: MatTableDataSource<FlowAnalysisRuleEntity> = new
MatTableDataSource<FlowAnalysisRuleEntity>();
+
+ @ViewChild(MatSort) sort!: MatSort;
+
+ constructor(private nifiCommon: NiFiCommon) {}
+
+ ngAfterViewInit(): void {
+ this.dataSource.sort = this.sort;
+ }
+
+ canRead(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canRead;
+ }
+
+ canWrite(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canWrite;
+ }
+
+ canOperate(entity: FlowAnalysisRuleEntity): boolean {
+ if (this.canWrite(entity)) {
+ return true;
+ }
+ return !!entity.operatePermissions?.canWrite;
+ }
+
+ hasComments(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isBlank(entity.component.comments);
+ }
+
+ getCommentsTipData(entity: FlowAnalysisRuleEntity): TextTipInput {
+ return {
+ text: entity.component.comments
+ };
+ }
+
+ hasErrors(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.component.validationErrors);
+ }
+
+ getValidationErrorsTipData(entity: FlowAnalysisRuleEntity):
ValidationErrorsTipInput {
+ return {
+ isValidating: entity.status.validationStatus === 'VALIDATING',
+ validationErrors: entity.component.validationErrors
+ };
+ }
+
+ hasBulletins(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.bulletins);
+ }
+
+ getBulletinsTipData(entity: FlowAnalysisRuleEntity): BulletinsTipInput {
+ return {
+ bulletins: entity.bulletins
+ };
+ }
+
+ getStateIcon(entity: FlowAnalysisRuleEntity): string {
+ if (entity.status.validationStatus === 'VALIDATING') {
+ return 'validating fa fa-spin fa-circle-o-notch';
+ } else if (entity.status.validationStatus === 'INVALID') {
+ return 'invalid fa fa-warning';
+ } else {
+ if (entity.status.runStatus === 'DISABLED') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'DISABLING') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'ENABLED') {
+ return 'enabled fa fa-flash';
+ } else if (entity.status.runStatus === 'ENABLING') {
+ return 'enabled fa fa-flash';
+ }
+ }
+ return '';
+ }
+
+ formatState(entity: FlowAnalysisRuleEntity): string {
+ if (entity.status.validationStatus === 'VALIDATING') {
+ return 'Validating';
+ } else if (entity.status.validationStatus === 'INVALID') {
+ return 'Invalid';
+ } else {
+ if (entity.status.runStatus === 'DISABLED') {
+ return 'Disabled';
+ } else if (entity.status.runStatus === 'DISABLING') {
+ return 'Disabling';
+ } else if (entity.status.runStatus === 'ENABLED') {
+ return 'Enabled';
+ } else if (entity.status.runStatus === 'ENABLING') {
+ return 'Enabling';
+ }
+ }
+ return '';
+ }
+
+ formatType(entity: FlowAnalysisRuleEntity): string {
+ return this.nifiCommon.formatType(entity.component);
+ }
+
+ formatBundle(entity: FlowAnalysisRuleEntity): string {
+ return this.nifiCommon.formatBundle(entity.component.bundle);
+ }
+
+ isDisabled(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.status.runStatus === 'DISABLED';
+ }
+
+ isEnabledOrEnabling(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.status.runStatus === 'ENABLED' ||
entity.status.runStatus === 'ENABLING';
+ }
+
+ hasActiveThreads(entity: ReportingTaskEntity): boolean {
+ return entity.status?.activeThreadCount > 0;
+ }
+
+ canConfigure(entity: FlowAnalysisRuleEntity): boolean {
+ return this.canRead(entity) && this.canWrite(entity);
+ }
+
+ configureClicked(entity: FlowAnalysisRuleEntity, event: MouseEvent): void {
+ event.stopPropagation();
+ this.configureFlowAnalysisRule.next(entity);
+ }
+
+ canEnable(entity: FlowAnalysisRuleEntity): boolean {
+ const userAuthorized: boolean = this.canRead(entity) &&
this.canOperate(entity);
+ return userAuthorized && this.isDisabled(entity) &&
entity.status.validationStatus === 'VALID';
+ }
+
+ enabledClicked(entity: FlowAnalysisRuleEntity, event: MouseEvent): void {
+ this.enableFlowAnalysisRule.next(entity);
+ }
+
+ canDisable(entity: FlowAnalysisRuleEntity): boolean {
+ const userAuthorized: boolean = this.canRead(entity) &&
this.canOperate(entity);
+ return userAuthorized && this.isEnabledOrEnabling(entity);
+ }
+
+ disableClicked(entity: FlowAnalysisRuleEntity, event: MouseEvent): void {
+ this.disableFlowAnalysisRule.next(entity);
+ }
+
+ canChangeVersion(entity: FlowAnalysisRuleEntity): boolean {
+ return (
+ this.isDisabled(entity) &&
+ this.canRead(entity) &&
+ this.canWrite(entity) &&
+ entity.component.multipleVersionsAvailable === true
+ );
+ }
+
+ canDelete(entity: FlowAnalysisRuleEntity): boolean {
+ const canWriteParent: boolean = true; // TODO
canWriteFlowAnalysisRuleParent(dataContext)
Review Comment:
This needs to check `canModifyController`. I've implemented this for the
Reporting Tasks table in #8225. Depending on this PR lands first we'll need to
get this updated. Happy to add it to mine if this lands first.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts:
##########
@@ -208,7 +208,7 @@ export class ControllerServiceTable implements
AfterViewInit {
}
canConfigure(entity: ControllerServiceEntity): boolean {
- return this.canRead(entity) && this.canWrite(entity) &&
this.isDisabled(entity);
+ return this.canRead(entity) && this.canWrite(entity);
Review Comment:
We should leave this in until our configuration dialog supports a read-only
mode for viewing configuration when the component is Running or Enabled.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html:
##########
@@ -0,0 +1,145 @@
+<!--
+ ~ 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="relative h-full border">
+ <div class="flow-analysis-rule-table listing-table absolute inset-0
overflow-y-auto">
+ <table mat-table [dataSource]="dataSource" matSort matSortDisableClear>
+ <!-- More Details Column -->
+ <ng-container matColumnDef="moreDetails">
+ <th mat-header-cell *matHeaderCellDef></th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ <div class="flex items-center gap-x-3">
+ <div class="pointer fa fa-book"
title="Usage"></div>
+ <!-- TODO - handle read only in configure
component? -->
+ <div *ngIf="hasComments(item)">
+ <div
+ class="pointer fa fa-comment"
+ [delayClose]="false"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+
[tooltipInputData]="getCommentsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasErrors(item)">
+ <div
+ class="pointer fa fa-warning has-errors"
+ [delayClose]="false"
+ nifiTooltip
+
[tooltipComponentType]="ValidationErrorsTip"
+
[tooltipInputData]="getValidationErrorsTipData(item)"></div>
+ </div>
+ <div *ngIf="hasBulletins(item)">
+ <div
+ class="pointer fa fa-sticky-note-o"
+ [delayClose]="false"
+ nifiTooltip
+ [tooltipComponentType]="BulletinsTip"
+
[tooltipInputData]="getBulletinsTipData(item)"></div>
+ </div>
+ </div>
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- Name Column -->
+ <ng-container matColumnDef="name">
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item); else
nameNoPermissions">
+ {{ item.component.name }}
+ </ng-container>
+ <ng-template #nameNoPermissions>
+ <div class="unset">{{ item.id }}</div>
+ </ng-template>
+ </td>
+ </ng-container>
+
+ <!-- Type Column -->
+ <ng-container matColumnDef="type">
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ {{ formatType(item) }}
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- Bundle Column -->
+ <ng-container matColumnDef="bundle">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>Bundle</th>
+ <td mat-cell *matCellDef="let item">
+ <ng-container *ngIf="canRead(item)">
+ {{ formatBundle(item) }}
+ </ng-container>
+ </td>
+ </ng-container>
+
+ <!-- State Column -->
+ <ng-container matColumnDef="state">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>State</th>
+ <td mat-cell *matCellDef="let item">
+ <div class="flex items-center gap-x-2">
+ <div [ngClass]="getStateIcon(item)"></div>
+ <div>{{ formatState(item) }}</div>
+ <div *ngIf="hasActiveThreads(item)">({{
item.status.activeThreadCount }})</div>
+ </div>
+ </td>
+ </ng-container>
+
+ <!-- Actions Column -->
+ <ng-container matColumnDef="actions">
+ <th mat-header-cell *matHeaderCellDef></th>
+ <td mat-cell *matCellDef="let item">
+ <div class="flex items-center gap-x-3">
+ <div
+ class="pointer fa fa-gear"
+ *ngIf="canConfigure(item)"
+ (click)="configureClicked(item, $event)"
+ title="Edit"></div>
+ <!-- TODO - handle read only in configure component?
-->
+ <div
+ class="pointer fa icon icon-enable-false"
+ *ngIf="canDisable(item)"
+ (click)="disableClicked(item, $event)"
+ title="Disable"></div>
+ <div
+ class="pointer fa fa-flash"
+ *ngIf="canEnable(item)"
+ (click)="enabledClicked(item, $event)"
+ title="Enable"></div>
+ <div class="pointer fa fa-exchange"
*ngIf="canChangeVersion(item)" title="Change Version"></div>
+ <div
+ class="pointer fa fa-trash"
+ *ngIf="canDelete(item)"
+ (click)="deleteClicked(item)"
+ title="Delete"></div>
+ <div class="pointer fa fa-tasks"
*ngIf="canViewState(item)" title="View State"></div>
+ </div>
+ <div class="pointer fa fa-key"
*ngIf="canManageAccessPolicies()" title="Access Policies"></div>
Review Comment:
We do however need to handle State and Change Version but we can handle that
when we introduce those capabilities. We have line items for them in NIFI-12400.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts:
##########
@@ -0,0 +1,441 @@
+/*
+ * 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 * as FlowAnalysisRuleActions from './flow-analysis-rules.actions';
+import { catchError, from, map, Observable, of, switchMap, take, takeUntil,
tap, withLatestFrom } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { selectFlowAnalysisRuleTypes } from
'../../../../state/extension-types/extension-types.selectors';
+import { YesNoDialog } from
'../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
+import { FlowAnalysisRuleService } from
'../../service/flow-analysis-rule.service';
+import { CreateFlowAnalysisRule } from
'../../ui/flow-analysis-rules/create-flow-analysis-rule/create-flow-analysis-rule.component';
+import { Router } from '@angular/router';
+import { selectSaving } from
'../management-controller-services/management-controller-services.selectors';
+import {
+ NewPropertyDialogRequest,
+ NewPropertyDialogResponse,
+ Property,
+ UpdateControllerServiceRequest
+} from '../../../../state/shared';
+import { EditFlowAnalysisRule } from
'../../ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component';
+import { CreateFlowAnalysisRuleSuccess } from './index';
+import { NewPropertyDialog } from
'../../../../ui/common/new-property-dialog/new-property-dialog.component';
+
+@Injectable()
+export class FlowAnalysisRulesEffects {
+ constructor(
+ private actions$: Actions,
+ private store: Store<NiFiState>,
+ private flowAnalysisRuleService: FlowAnalysisRuleService,
+ private dialog: MatDialog,
+ private router: Router
+ ) {}
+
+ loadFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.loadFlowAnalysisRules),
+ switchMap(() =>
+ from(this.flowAnalysisRuleService.getFlowAnalysisRule()).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.loadFlowAnalysisRulesSuccess({
+ response: {
+ flowAnalysisRules: response.flowAnalysisRules,
+ loadedTimestamp: response.currentTime
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ openNewFlowAnalysisRuleDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.openNewFlowAnalysisRuleDialog),
+ withLatestFrom(this.store.select(selectFlowAnalysisRuleTypes)),
+ tap(([action, flowAnalysisRuleTypes]) => {
+ this.dialog.open(CreateFlowAnalysisRule, {
+ data: {
+ flowAnalysisRuleTypes
+ },
+ panelClass: 'medium-dialog'
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ createFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.createFlowAnalysisRule),
+ map((action) => action.request),
+ switchMap((request) =>
+
from(this.flowAnalysisRuleService.createFlowAnalysisRule(request)).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.createFlowAnalysisRuleSuccess({
+ response: {
+ flowAnalysisRule: response
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ createFlowAnalysisRuleSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.createFlowAnalysisRuleSuccess),
+ map((action) => action.response),
+ tap(() => {
+ this.dialog.closeAll();
+ }),
+ switchMap((response: CreateFlowAnalysisRuleSuccess) =>
+ of(
+ FlowAnalysisRuleActions.selectFlowAnalysisRule({
+ request: {
+ id: response.flowAnalysisRule.id
+ }
+ })
+ )
+ )
+ )
+ );
+
+ promptFlowAnalysisRuleDeletion$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.promptFlowAnalysisRuleDeletion),
+ map((action) => action.request),
+ tap((request) => {
+ const dialogReference = this.dialog.open(YesNoDialog, {
+ data: {
+ title: 'Delete Flow Analysis Rule',
+ message: `Delete reporting task
${request.flowAnalysisRule.component.name}?`
+ },
+ panelClass: 'small-dialog'
+ });
+
+
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
+ this.store.dispatch(
+ FlowAnalysisRuleActions.deleteFlowAnalysisRule({
+ request
+ })
+ );
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ deleteFlowAnalysisRule$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.deleteFlowAnalysisRule),
+ map((action) => action.request),
+ switchMap((request) =>
+
from(this.flowAnalysisRuleService.deleteFlowAnalysisRule(request)).pipe(
+ map((response) =>
+ FlowAnalysisRuleActions.deleteFlowAnalysisRuleSuccess({
+ response: {
+ flowAnalysisRule: response
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ FlowAnalysisRuleActions.flowAnalysisRuleApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ navigateToEditFlowAnalysisRule$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(FlowAnalysisRuleActions.navigateToEditFlowAnalysisRule),
+ map((action) => action.id),
+ tap((id) => {
+ this.router.navigate(['/settings', 'flow-analysis-rules',
id, 'edit']);
+ })
+ ),
+ { dispatch: false }
+ );
+
+ openConfigureFlowAnalysisRuleDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+
ofType(FlowAnalysisRuleActions.openConfigureFlowAnalysisRuleDialog),
+ map((action) => action.request),
+ tap((request) => {
+ const taskId: string = request.id;
+
+ const editDialogReference =
this.dialog.open(EditFlowAnalysisRule, {
+ data: {
+ flowAnalysisRule: request.flowAnalysisRule
+ },
+ id: taskId,
+ panelClass: 'large-dialog'
+ });
+
+ editDialogReference.componentInstance.saving$ =
this.store.select(selectSaving);
+
+ editDialogReference.componentInstance.createNewProperty = (
+ existingProperties: string[],
+ allowsSensitive: boolean
+ ): Observable<Property> => {
+ const dialogRequest: NewPropertyDialogRequest = {
existingProperties, allowsSensitive };
+ const newPropertyDialogReference =
this.dialog.open(NewPropertyDialog, {
+ data: dialogRequest,
+ panelClass: 'small-dialog'
+ });
+
+ return
newPropertyDialogReference.componentInstance.newProperty.pipe(
+ take(1),
+ switchMap((dialogResponse:
NewPropertyDialogResponse) => {
+ return this.flowAnalysisRuleService
+ .getPropertyDescriptor(request.id,
dialogResponse.name, dialogResponse.sensitive)
+ .pipe(
+ take(1),
+ map((response) => {
+ newPropertyDialogReference.close();
+
+ return {
+ property: dialogResponse.name,
+ value: null,
+ descriptor:
response.propertyDescriptor
+ };
+ })
+ );
+ })
+ );
+ };
+
+ const goTo = (commands: string[], destination: string):
void => {
+ if
(editDialogReference.componentInstance.editFlowAnalysisRuleForm.dirty) {
+ const saveChangesDialogReference =
this.dialog.open(YesNoDialog, {
+ data: {
+ title: 'Controller Service Configuration',
Review Comment:
This title should reference `Flow Analysis Rule`.
##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts:
##########
@@ -0,0 +1,261 @@
+/*
+ * 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 { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild }
from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { RouterLink } from '@angular/router';
+import { NgClass, NgIf } from '@angular/common';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { MatSort, MatSortModule } from '@angular/material/sort';
+import { FlowAnalysisRuleEntity } from '../../../state/flow-analysis-rules';
+import { TextTip } from
'../../../../../ui/common/tooltips/text-tip/text-tip.component';
+import { BulletinsTip } from
'../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
+import { ValidationErrorsTip } from
'../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { BulletinsTipInput, TextTipInput, ValidationErrorsTipInput } from
'../../../../../state/shared';
+import { NifiTooltipDirective } from
'../../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { ReportingTaskEntity } from '../../../state/reporting-tasks';
+
+@Component({
+ selector: 'flow-analysis-rule-table',
+ standalone: true,
+ templateUrl: './flow-analysis-rule-table.component.html',
+ imports: [
+ MatButtonModule,
+ MatDialogModule,
+ MatTableModule,
+ MatSortModule,
+ NgIf,
+ NgClass,
+ NifiTooltipDirective,
+ RouterLink
+ ],
+ styleUrls: ['./flow-analysis-rule-table.component.scss',
'../../../../../../assets/styles/listing-table.scss']
+})
+export class FlowAnalysisRuleTable implements AfterViewInit {
+ @Input() set flowAnalysisRules(FlowAnalysisRuleEntities:
FlowAnalysisRuleEntity[]) {
+ this.dataSource = new
MatTableDataSource<FlowAnalysisRuleEntity>(FlowAnalysisRuleEntities);
+ this.dataSource.sort = this.sort;
+ this.dataSource.sortingDataAccessor = (data: FlowAnalysisRuleEntity,
displayColumn: string) => {
+ if (displayColumn == 'name') {
+ return this.formatType(data);
+ } else if (displayColumn == 'type') {
+ return this.formatType(data);
+ } else if (displayColumn == 'bundle') {
+ return this.formatBundle(data);
+ } else if (displayColumn == 'state') {
+ return this.formatState(data);
+ }
+ return '';
+ };
+ }
+ @Input() selectedFlowAnalysisRuleId!: string;
+ @Input() definedByCurrentGroup!: (entity: FlowAnalysisRuleEntity) =>
boolean;
+
+ @Output() selectFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() deleteFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() configureFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() enableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() disableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
+
+ protected readonly TextTip = TextTip;
+ protected readonly BulletinsTip = BulletinsTip;
+ protected readonly ValidationErrorsTip = ValidationErrorsTip;
+
+ displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle',
'state', 'actions'];
+ dataSource: MatTableDataSource<FlowAnalysisRuleEntity> = new
MatTableDataSource<FlowAnalysisRuleEntity>();
+
+ @ViewChild(MatSort) sort!: MatSort;
+
+ constructor(private nifiCommon: NiFiCommon) {}
+
+ ngAfterViewInit(): void {
+ this.dataSource.sort = this.sort;
+ }
+
+ canRead(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canRead;
+ }
+
+ canWrite(entity: FlowAnalysisRuleEntity): boolean {
+ return entity.permissions.canWrite;
+ }
+
+ canOperate(entity: FlowAnalysisRuleEntity): boolean {
+ if (this.canWrite(entity)) {
+ return true;
+ }
+ return !!entity.operatePermissions?.canWrite;
+ }
+
+ hasComments(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isBlank(entity.component.comments);
+ }
+
+ getCommentsTipData(entity: FlowAnalysisRuleEntity): TextTipInput {
+ return {
+ text: entity.component.comments
+ };
+ }
+
+ hasErrors(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.component.validationErrors);
+ }
+
+ getValidationErrorsTipData(entity: FlowAnalysisRuleEntity):
ValidationErrorsTipInput {
+ return {
+ isValidating: entity.status.validationStatus === 'VALIDATING',
+ validationErrors: entity.component.validationErrors
+ };
+ }
+
+ hasBulletins(entity: FlowAnalysisRuleEntity): boolean {
+ return !this.nifiCommon.isEmpty(entity.bulletins);
+ }
+
+ getBulletinsTipData(entity: FlowAnalysisRuleEntity): BulletinsTipInput {
+ return {
+ bulletins: entity.bulletins
+ };
+ }
+
+ getStateIcon(entity: FlowAnalysisRuleEntity): string {
+ if (entity.status.validationStatus === 'VALIDATING') {
+ return 'validating fa fa-spin fa-circle-o-notch';
+ } else if (entity.status.validationStatus === 'INVALID') {
+ return 'invalid fa fa-warning';
+ } else {
+ if (entity.status.runStatus === 'DISABLED') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'DISABLING') {
+ return 'disabled icon icon-enable-false';
+ } else if (entity.status.runStatus === 'ENABLED') {
+ return 'enabled fa fa-flash';
+ } else if (entity.status.runStatus === 'ENABLING') {
+ return 'enabled fa fa-flash';
+ }
+ }
+ return '';
+ }
+
+ formatState(entity: FlowAnalysisRuleEntity): string {
+ if (entity.status.validationStatus === 'VALIDATING') {
+ return 'Validating';
+ } else if (entity.status.validationStatus === 'INVALID') {
+ return 'Invalid';
+ } else {
+ if (entity.status.runStatus === 'DISABLED') {
+ return 'Disabled';
+ } else if (entity.status.runStatus === 'DISABLING') {
+ return 'Disabling';
+ } else if (entity.status.runStatus === 'ENABLED') {
+ return 'Enabled';
+ } else if (entity.status.runStatus === 'ENABLING') {
Review Comment:
Compared to the existing NiFi UI, it doesn't appear that FlowAnalysisRules
support `ENABLING` and `DISABLING` transitional states.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]