NIFI-12948: (#8564)

- Action is optional.
- Removing duplicate check in counter table.
- Ensure flow configuration is loaded in route guard.
- Only show user/group list when there are users or groups.
- Default override policy to copy.
This commit is contained in:
Matt Gilman 2024-03-26 15:21:11 -04:00 committed by GitHub
parent 339a06a8c6
commit 0c01dfe585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 240 additions and 218 deletions

View File

@ -35,7 +35,7 @@ export class OverridePolicyDialog {
constructor(private formBuilder: FormBuilder) {
this.overridePolicyForm = this.formBuilder.group({
override: new FormControl()
override: new FormControl('copy')
});
}

View File

@ -74,16 +74,14 @@
<ng-container matColumnDef="reset">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
@if (canModifyCounters) {
<div class="flex items-center gap-x-3">
@if (canModifyCounters) {
<div
class="pointer fa fa-undo"
title="Reset Counter"
(click)="resetClicked(item, $event)"></div>
}
</div>
}
<div class="flex items-center gap-x-3">
@if (canModifyCounters) {
<div
class="pointer fa fa-undo"
title="Reset Counter"
(click)="resetClicked(item, $event)"></div>
}
</div>
</td>
</ng-container>

View File

@ -33,7 +33,7 @@ export interface ActionEntity {
timestamp: string;
sourceId: string;
canRead: boolean;
action: Action;
action?: Action;
}
export interface Action {

View File

@ -25,164 +25,166 @@
<div class="value">{{ actionEntity.sourceId }}</div>
</div>
@if (actionEntity.action.componentDetails) {
@if (isRemoteProcessGroup(actionEntity)) {
<div>
<div>Uri</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: getRemoteProcessGroupDetails(actionEntity)?.uri }
"></ng-container>
</div>
} @else {
<div>
<div>Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: getExtensionDetails(actionEntity)?.type }
"></ng-container>
</div>
@if (actionEntity.action; as action) {
@if (action.componentDetails) {
@if (isRemoteProcessGroup(action)) {
<div>
<div>Uri</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: getRemoteProcessGroupDetails(action)?.uri }
"></ng-container>
</div>
} @else {
<div>
<div>Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: getExtensionDetails(action)?.type }
"></ng-container>
</div>
}
}
}
@if (actionEntity.action.actionDetails) {
@switch (actionEntity.action.operation) {
@case ('Configure') {
@if (getConfigureActionDetails(actionEntity); as details) {
<div>
<div>Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.name }
"></ng-container>
</div>
<div>
<div>Value</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.value }
"></ng-container>
</div>
<div>
<div>Previous Value</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousValue }
"></ng-container>
</div>
@if (action.actionDetails) {
@switch (action.operation) {
@case ('Configure') {
@if (getConfigureActionDetails(action); as details) {
<div>
<div>Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.name }
"></ng-container>
</div>
<div>
<div>Value</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.value }
"></ng-container>
</div>
<div>
<div>Previous Value</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousValue }
"></ng-container>
</div>
}
}
}
@case ('Connect' || 'Disconnect') {
@if (getConnectActionDetails(actionEntity); as details) {
<div>
<div>Source Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceId }
"></ng-container>
</div>
<div>
<div>Source Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceName }
"></ng-container>
</div>
<div>
<div>Source Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceType }
"></ng-container>
</div>
<div>
<div>Relationship(s)</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.relationship }
"></ng-container>
</div>
<div>
<div>Destination Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationId }
"></ng-container>
</div>
<div>
<div>Destination Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationName }
"></ng-container>
</div>
<div>
<div>Destination Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationType }
"></ng-container>
</div>
@case ('Connect' || 'Disconnect') {
@if (getConnectActionDetails(action); as details) {
<div>
<div>Source Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceId }
"></ng-container>
</div>
<div>
<div>Source Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceName }
"></ng-container>
</div>
<div>
<div>Source Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.sourceType }
"></ng-container>
</div>
<div>
<div>Relationship(s)</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.relationship }
"></ng-container>
</div>
<div>
<div>Destination Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationId }
"></ng-container>
</div>
<div>
<div>Destination Name</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationName }
"></ng-container>
</div>
<div>
<div>Destination Type</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.destinationType }
"></ng-container>
</div>
}
}
}
@case ('Move') {
@if (getMoveActionDetails(actionEntity); as details) {
<div>
<div>Group</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.group }
"></ng-container>
</div>
<div>
<div>Group Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.groupId }
"></ng-container>
</div>
<div>
<div>Previous Group</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousGroup }
"></ng-container>
</div>
<div>
<div>Previous Group Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousGroupId }
"></ng-container>
</div>
@case ('Move') {
@if (getMoveActionDetails(action); as details) {
<div>
<div>Group</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.group }
"></ng-container>
</div>
<div>
<div>Group Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.groupId }
"></ng-container>
</div>
<div>
<div>Previous Group</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousGroup }
"></ng-container>
</div>
<div>
<div>Previous Group Id</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.previousGroupId }
"></ng-container>
</div>
}
}
}
@case ('Purge') {
@if (getPurgeActionDetails(actionEntity); as details) {
<div>
<div>End Date</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.endDate }
"></ng-container>
</div>
@case ('Purge') {
@if (getPurgeActionDetails(action); as details) {
<div>
<div>End Date</div>
<ng-container
*ngTemplateOutlet="
formatValue;
context: { $implicit: details.endDate }
"></ng-container>
</div>
}
}
}
}

View File

@ -19,6 +19,7 @@ import { Component, Inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import {
Action,
ActionEntity,
ConfigureActionDetails,
ConnectionActionDetails,
@ -27,7 +28,6 @@ import {
PurgeActionDetails,
RemoteProcessGroupDetails
} from '../../../state/flow-configuration-history-listing';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { PipesModule } from '../../../../../pipes/pipes.module';
import { MatButtonModule } from '@angular/material/button';
@ -39,54 +39,51 @@ import { MatButtonModule } from '@angular/material/button';
styleUrls: ['./action-details.component.scss']
})
export class ActionDetails {
constructor(
@Inject(MAT_DIALOG_DATA) public actionEntity: ActionEntity,
private nifiCommon: NiFiCommon
) {}
constructor(@Inject(MAT_DIALOG_DATA) public actionEntity: ActionEntity) {}
isRemoteProcessGroup(actionEntity: ActionEntity): boolean {
return actionEntity.action.sourceType === 'RemoteProcessGroup';
isRemoteProcessGroup(action: Action): boolean {
return action.sourceType === 'RemoteProcessGroup';
}
getRemoteProcessGroupDetails(actionEntity: ActionEntity): RemoteProcessGroupDetails | null {
if (!this.isRemoteProcessGroup(actionEntity)) {
getRemoteProcessGroupDetails(action: Action): RemoteProcessGroupDetails | null {
if (!this.isRemoteProcessGroup(action)) {
return null;
}
return actionEntity.action.componentDetails as RemoteProcessGroupDetails;
return action.componentDetails as RemoteProcessGroupDetails;
}
getExtensionDetails(actionEntity: ActionEntity): ExtensionDetails | null {
if (this.isRemoteProcessGroup(actionEntity)) {
getExtensionDetails(action: Action): ExtensionDetails | null {
if (this.isRemoteProcessGroup(action)) {
return null;
}
return actionEntity.action.componentDetails as ExtensionDetails;
return action.componentDetails as ExtensionDetails;
}
getConfigureActionDetails(actionEntity: ActionEntity): ConfigureActionDetails | null {
if (actionEntity.action.operation !== 'Configure') {
getConfigureActionDetails(action: Action): ConfigureActionDetails | null {
if (action.operation !== 'Configure') {
return null;
}
return actionEntity.action.actionDetails as ConfigureActionDetails;
return action.actionDetails as ConfigureActionDetails;
}
getConnectActionDetails(actionEntity: ActionEntity): ConnectionActionDetails | null {
if (!['Connect', 'Disconnect'].includes(actionEntity.action.operation)) {
getConnectActionDetails(action: Action): ConnectionActionDetails | null {
if (!['Connect', 'Disconnect'].includes(action.operation)) {
return null;
}
return actionEntity.action.actionDetails as ConnectionActionDetails;
return action.actionDetails as ConnectionActionDetails;
}
getMoveActionDetails(actionEntity: ActionEntity): MoveActionDetails | null {
if (actionEntity.action.operation !== 'Move') {
getMoveActionDetails(action: Action): MoveActionDetails | null {
if (action.operation !== 'Move') {
return null;
}
return actionEntity.action.actionDetails as MoveActionDetails;
return action.actionDetails as MoveActionDetails;
}
getPurgeActionDetails(actionEntity: ActionEntity): PurgeActionDetails | null {
if (actionEntity.action.operation !== 'Purge') {
getPurgeActionDetails(action: Action): PurgeActionDetails | null {
if (action.operation !== 'Purge') {
return null;
}
return actionEntity.action.actionDetails as PurgeActionDetails;
return action.actionDetails as PurgeActionDetails;
}
}

View File

@ -20,7 +20,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatSortModule, Sort } from '@angular/material/sort';
import { ActionEntity } from '../../../state/flow-configuration-history-listing';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
@Component({
selector: 'flow-configuration-history-table',
@ -52,7 +51,7 @@ export class FlowConfigurationHistoryTable {
displayedColumns: string[] = ['moreDetails', 'timestamp', 'sourceName', 'sourceType', 'operation', 'userIdentity'];
dataSource: MatTableDataSource<ActionEntity> = new MatTableDataSource<ActionEntity>();
constructor(private nifiCommon: NiFiCommon) {}
constructor() {}
sortData(sort: Sort) {
this.sortChanged.next(sort);
@ -63,7 +62,7 @@ export class FlowConfigurationHistoryTable {
}
private format(item: ActionEntity, property: string): string {
if (this.canRead(item) && Object.hasOwn(item.action, property)) {
if (this.canRead(item) && item.action && Object.hasOwn(item.action, property)) {
const value = (item.action as any)[property];
if (!value) {
return 'Empty String Set';

View File

@ -17,10 +17,12 @@
import { CanMatchFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { map } from 'rxjs';
import { catchError, map, of, switchMap, tap } from 'rxjs';
import { Store } from '@ngrx/store';
import { FlowConfiguration, FlowConfigurationState } from '../../state/flow-configuration';
import { selectFlowConfiguration } from '../../state/flow-configuration/flow-configuration.selectors';
import { FlowConfigurationService } from '../flow-configuration.service';
import { loadFlowConfigurationSuccess } from '../../state/flow-configuration/flow-configuration.actions';
export const checkFlowConfiguration = (
flowConfigurationCheck: (flowConfiguration: FlowConfiguration) => boolean
@ -28,15 +30,35 @@ export const checkFlowConfiguration = (
return () => {
const router: Router = inject(Router);
const store: Store<FlowConfigurationState> = inject(Store<FlowConfigurationState>);
const flowConfigurationService: FlowConfigurationService = inject(FlowConfigurationService);
return store.select(selectFlowConfiguration).pipe(
switchMap((flowConfiguration) => {
if (flowConfiguration) {
return of(flowConfiguration);
} else {
return flowConfigurationService.getFlowConfiguration().pipe(
tap((response) =>
store.dispatch(
loadFlowConfigurationSuccess({
response
})
)
)
);
}
}),
map((flowConfiguration) => {
if (flowConfiguration && flowConfigurationCheck(flowConfiguration)) {
if (flowConfigurationCheck(flowConfiguration)) {
return true;
}
// TODO - replace with 409 error page
// TODO - replace with error page
return router.parseUrl('/');
}),
catchError(() => {
// TODO - replace with error page
return of(router.parseUrl('/'));
})
);
};

View File

@ -19,13 +19,13 @@
<form class="edit-tenant-form" [formGroup]="editTenantForm">
<error-banner></error-banner>
<mat-dialog-content>
<div class="mb-6">
<div>
<mat-radio-group formControlName="tenantType" (change)="tenantTypeChanged()">
<mat-radio-button color="primary" [value]="USER">Individual</mat-radio-button>
<mat-radio-button color="primary" [value]="USER_GROUP">Group</mat-radio-button>
</mat-radio-group>
</div>
<div class="mb-2">
<div class="mt-6">
<mat-form-field>
<mat-label>Identity</mat-label>
<input matInput formControlName="identity" type="text" />
@ -35,27 +35,31 @@
</mat-form-field>
</div>
@if (isUser) {
<div>
<mat-label>Member of</mat-label>
<mat-selection-list formControlName="userGroups" class="border">
@for (userGroup of userGroups; track userGroup) {
<mat-list-option togglePosition="before" [value]="userGroup.id"
>{{ userGroup.component.identity }}
</mat-list-option>
}
</mat-selection-list>
</div>
@if (userGroups.length > 0) {
<div class="mt-2">
<mat-label>Member of</mat-label>
<mat-selection-list formControlName="userGroups" class="border">
@for (userGroup of userGroups; track userGroup) {
<mat-list-option togglePosition="before" [value]="userGroup.id"
>{{ userGroup.component.identity }}
</mat-list-option>
}
</mat-selection-list>
</div>
}
} @else {
<div>
<mat-label>Members</mat-label>
<mat-selection-list formControlName="users" class="border">
@for (user of users; track user) {
<mat-list-option togglePosition="before" [value]="user.id"
>{{ user.component.identity }}
</mat-list-option>
}
</mat-selection-list>
</div>
@if (users.length > 0) {
<div class="mt-2">
<mat-label>Members</mat-label>
<mat-selection-list formControlName="users" class="border">
@for (user of users; track user) {
<mat-list-option togglePosition="before" [value]="user.id"
>{{ user.component.identity }}
</mat-list-option>
}
</mat-selection-list>
</div>
}
}
</mat-dialog-content>
@if ({ value: (saving$ | async)! }; as saving) {