[NIFI-12774] configure RPG (#8398)

* [NIFI-12774] configure RPG

* address review feedback

* conditionally dispatch selectComponents action

This closes #8398
This commit is contained in:
Scott Aslan 2024-02-14 12:52:01 -05:00 committed by GitHub
parent e03329e01f
commit 22de416ffc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 378 additions and 4 deletions

View File

@ -383,6 +383,11 @@ export const openEditProcessGroupDialog = createAction(
props<{ request: EditComponentDialogRequest }>()
);
export const openEditRemoteProcessGroupDialog = createAction(
`${CANVAS_PREFIX} Open Edit Remote Process Group Dialog`,
props<{ request: EditComponentDialogRequest }>()
);
export const updateComponent = createAction(
`${CANVAS_PREFIX} Update Component`,
props<{ request: UpdateComponentRequest }>()

View File

@ -95,7 +95,7 @@ import { RegistryService } from '../../service/registry.service';
import { ImportFromRegistry } from '../../ui/canvas/items/flow/import-from-registry/import-from-registry.component';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { NoRegistryClientsDialog } from '../../ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component';
import { showOkDialog } from './flow.actions';
import { EditRemoteProcessGroup } from '../../ui/canvas/items/remote-process-group/edit-remote-process-group/edit-remote-process-group.component';
@Injectable()
export class FlowEffects {
@ -348,7 +348,10 @@ export class FlowEffects {
this.flowService.goToRemoteProcessGroup(request);
} else {
this.store.dispatch(
showOkDialog({ title: 'Remote Process Group', message: 'No target URI defined.' })
FlowActions.showOkDialog({
title: 'Remote Process Group',
message: 'No target URI defined.'
})
);
}
})
@ -926,6 +929,8 @@ export class FlowEffects {
return of(FlowActions.openEditConnectionDialog({ request }));
case ComponentType.ProcessGroup:
return of(FlowActions.openEditProcessGroupDialog({ request }));
case ComponentType.RemoteProcessGroup:
return of(FlowActions.openEditRemoteProcessGroupDialog({ request }));
case ComponentType.InputPort:
case ComponentType.OutputPort:
return of(FlowActions.openEditPortDialog({ request }));
@ -1250,6 +1255,57 @@ export class FlowEffects {
{ dispatch: false }
);
openEditRemoteProcessGroupDialog$ = createEffect(
() =>
this.actions$.pipe(
ofType(FlowActions.openEditRemoteProcessGroupDialog),
map((action) => action.request),
tap((request) => {
const editDialogReference = this.dialog.open(EditRemoteProcessGroup, {
data: request,
panelClass: 'large-dialog'
});
editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
editDialogReference.componentInstance.editRemoteProcessGroup
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((payload: any) => {
this.store.dispatch(
FlowActions.updateComponent({
request: {
id: request.entity.id,
uri: request.uri,
type: request.type,
payload
}
})
);
});
editDialogReference.afterClosed().subscribe((response) => {
this.store.dispatch(FlowActions.clearFlowApiError());
if (response != 'ROUTED') {
this.store.dispatch(
FlowActions.selectComponents({
request: {
components: [
{
id: request.entity.id,
componentType: request.type
}
]
}
})
);
}
});
})
),
{ dispatch: false }
);
updateComponent$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.updateComponent),

View File

@ -34,7 +34,7 @@ describe('CreateRemoteProcessGroup', () => {
clientId: 'a6482293-7fe8-43b4-8ab4-ee95b3b27721',
version: 0
},
type: ComponentType.ProcessGroup,
type: ComponentType.RemoteProcessGroup,
position: {
x: -4,
y: -698.5

View File

@ -37,7 +37,6 @@ import { MatIconModule } from '@angular/material/icon';
import { CreateComponentRequest } from '../../../../../state/flow';
@Component({
selector: 'create-process-group',
standalone: true,
imports: [
AsyncPipe,

View File

@ -0,0 +1,109 @@
<!--
~ 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.
-->
<h2 mat-dialog-title>Edit Remote Process Group</h2>
<form class="edit-remote-process-group-form" [formGroup]="editRemoteProcessGroupForm">
<error-banner></error-banner>
<mat-dialog-content>
<div class="flex flex-col mb-6">
<div>Name</div>
<div class="value">{{ request.entity.component.name }}</div>
</div>
<div class="flex flex-col mb-6">
<div>Id</div>
<div class="value">{{ request.entity.component.id }}</div>
</div>
<div class="tab-content py-4 flex gap-x-4">
<div class="w-full">
<mat-form-field>
<mat-label>URLs</mat-label>
<input matInput formControlName="urls" type="text" placeholder="https://remotehost:8443/nifi" />
</mat-form-field>
</div>
</div>
<div class="flex gap-x-4">
<div class="w-full">
<mat-form-field>
<mat-label>Transport Protocol</mat-label>
<mat-select formControlName="transportProtocol">
<mat-option value="RAW"> RAW </mat-option>
<mat-option value="HTTP"> HTTP </mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="w-full">
<mat-form-field>
<mat-label>Local Network Interface</mat-label>
<input matInput formControlName="localNetworkInterface" type="text" />
</mat-form-field>
</div>
</div>
<div class="flex gap-x-4">
<div class="w-full">
<mat-form-field>
<mat-label>HTTP Proxy Server Hostname</mat-label>
<input matInput formControlName="httpProxyServerHostname" type="text" />
</mat-form-field>
</div>
<div class="w-full">
<mat-form-field>
<mat-label>HTTP Proxy Server Port</mat-label>
<input matInput formControlName="httpProxyServerPort" type="text" />
</mat-form-field>
</div>
</div>
<div class="flex gap-x-4">
<div class="w-full">
<mat-form-field>
<mat-label>HTTP Proxy User</mat-label>
<input matInput formControlName="httpProxyUser" type="text" />
</mat-form-field>
</div>
<div class="w-full">
<mat-form-field>
<mat-label>HTTP Proxy Password</mat-label>
<input matInput formControlName="httpProxyPassword" type="text" />
</mat-form-field>
</div>
</div>
<div class="flex gap-x-4">
<div class="w-full">
<mat-form-field>
<mat-label>Communications Timeout</mat-label>
<input matInput formControlName="communicationsTimeout" type="text" />
</mat-form-field>
</div>
<div class="w-full">
<mat-form-field>
<mat-label>Yield Duration</mat-label>
<input matInput formControlName="yieldDuration" type="text" />
</mat-form-field>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
<button
[disabled]="!editRemoteProcessGroupForm.dirty || editRemoteProcessGroupForm.invalid || saving.value"
type="button"
color="primary"
(click)="submitForm()"
mat-raised-button>
<span *nifiSpinner="saving.value">Add</span>
</button>
</mat-dialog-actions>
</form>

View File

@ -0,0 +1,26 @@
/*
* 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 '@angular/material' as mat;
.edit-remote-process-group-form {
@include mat.button-density(-1);
.mat-mdc-form-field {
width: 100%;
}
}

View File

@ -0,0 +1,81 @@
/*
* 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 { EditRemoteProcessGroup } from './edit-remote-process-group.component';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentType } from '../../../../../../../state/shared';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/flow/flow.reducer';
describe('EditRemoteProcessGroup', () => {
let component: EditRemoteProcessGroup;
let fixture: ComponentFixture<EditRemoteProcessGroup>;
const data: any = {
revision: {
clientId: 'a6482293-7fe8-43b4-8ab4-ee95b3b27721',
version: 0
},
type: ComponentType.RemoteProcessGroup,
position: {
x: -4,
y: -698.5
},
entity: {
component: {
activeRemoteInputPortCount: 0,
activeRemoteOutputPortCount: 0,
comments: '',
communicationsTimeout: '30 sec',
flowRefreshed: '02/10/2024 15:20:58 EST',
id: '868228e2-018d-1000-00e2-92a25d9cb363',
inactiveRemoteInputPortCount: 0,
inactiveRemoteOutputPortCount: 0,
inputPortCount: 0,
name: 'NiFi Flow',
outputPortCount: 0,
parentGroupId: '7be4b23a-018d-1000-d059-ca023539b044',
proxyHost: '',
proxyUser: '',
targetSecure: true,
targetUri: 'https://localhost:8443/nifi',
targetUris: 'https://localhost:8443/nifi',
transmitting: false,
transportProtocol: 'HTTP',
yieldDuration: '10 sec'
}
}
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [EditRemoteProcessGroup, BrowserAnimationsModule],
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })]
});
fixture = TestBed.createComponent(EditRemoteProcessGroup);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,98 @@
/*
* 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, EventEmitter, Inject, Input, Output } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatButtonModule } from '@angular/material/button';
import { AsyncPipe, NgIf } from '@angular/common';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
import { Client } from '../../../../../../../service/client.service';
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
import { TextTip } from '../../../../../../../ui/common/tooltips/text-tip/text-tip.component';
import { EditComponentDialogRequest } from '../../../../../state/flow';
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
@Component({
standalone: true,
templateUrl: './edit-remote-process-group.component.html',
imports: [
ReactiveFormsModule,
MatDialogModule,
MatInputModule,
MatCheckboxModule,
MatButtonModule,
NgIf,
MatOptionModule,
MatSelectModule,
AsyncPipe,
NifiSpinnerDirective,
FormsModule,
ErrorBanner
],
styleUrls: ['./edit-remote-process-group.component.scss']
})
export class EditRemoteProcessGroup {
@Input() saving$!: Observable<boolean>;
@Output() editRemoteProcessGroup: EventEmitter<any> = new EventEmitter<any>();
protected readonly TextTip = TextTip;
editRemoteProcessGroupForm: FormGroup;
constructor(
@Inject(MAT_DIALOG_DATA) public request: EditComponentDialogRequest,
private formBuilder: FormBuilder,
private client: Client
) {
this.editRemoteProcessGroupForm = this.formBuilder.group({
urls: new FormControl(request.entity.component.targetUris, Validators.required),
transportProtocol: new FormControl(request.entity.component.transportProtocol, Validators.required),
localNetworkInterface: new FormControl(request.entity.component.localNetworkInterface),
httpProxyServerHostname: new FormControl(request.entity.component.httpProxyServerHostname),
httpProxyServerPort: new FormControl(request.entity.component.httpProxyServerPort),
httpProxyUser: new FormControl(request.entity.component.httpProxyUser),
httpProxyPassword: new FormControl(request.entity.component.httpProxyPassword),
communicationsTimeout: new FormControl(request.entity.component.communicationsTimeout, Validators.required),
yieldDuration: new FormControl(request.entity.component.yieldDuration, Validators.required)
});
}
submitForm() {
const payload: any = {
revision: this.client.getRevision(this.request.entity),
component: {
id: this.request.entity.id,
targetUris: this.editRemoteProcessGroupForm.get('urls')?.value,
transportProtocol: this.editRemoteProcessGroupForm.get('transportProtocol')?.value,
localNetworkInterface: this.editRemoteProcessGroupForm.get('localNetworkInterface')?.value,
proxyHost: this.editRemoteProcessGroupForm.get('httpProxyServerHostname')?.value,
proxyPort: this.editRemoteProcessGroupForm.get('httpProxyServerPort')?.value,
proxyUser: this.editRemoteProcessGroupForm.get('httpProxyUser')?.value,
proxyPassword: this.editRemoteProcessGroupForm.get('httpProxyPassword')?.value,
communicationsTimeout: this.editRemoteProcessGroupForm.get('communicationsTimeout')?.value,
yieldDuration: this.editRemoteProcessGroupForm.get('yieldDuration')?.value
}
};
this.editRemoteProcessGroup.next(payload);
}
}