This is an automated email from the ASF dual-hosted git repository.
rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new af5c88bd1d [NIFI-13981] inline parameter context creation (#9613)
af5c88bd1d is described below
commit af5c88bd1d45fc314d3107cadb7b198ad81ef7b6
Author: Scott Aslan <[email protected]>
AuthorDate: Fri Jan 24 12:57:30 2025 -0500
[NIFI-13981] inline parameter context creation (#9613)
* [NIFI-13981] inline parameter context creation
* sort parameter contexts
* export pipe and enable test
* only display create inline parameter context button when user has write
permission to parameter context
* restore enter hotkey for create PG
This closes #9613
---
.../pages/flow-designer/service/flow.service.ts | 10 -
.../service/parameter-helper.service.ts | 10 +-
.../controller-services.effects.ts | 8 +-
.../pages/flow-designer/state/flow/flow.effects.ts | 170 +++++--
.../app/pages/flow-designer/state/flow/index.ts | 1 +
.../pages/flow-designer/state/parameter/index.ts | 1 +
.../state/parameter/parameter.actions.ts | 30 ++
.../state/parameter/parameter.effects.ts | 179 +++++++-
.../state/parameter/parameter.reducer.ts | 19 +-
.../state/parameter/parameter.selectors.ts | 7 +
.../create-process-group.component.html | 59 ++-
.../create-process-group.component.spec.ts | 269 +++++------
.../create-process-group.component.ts | 82 +++-
.../edit-process-group.component.html | 39 +-
.../edit-process-group.component.spec.ts | 506 +++++++++++++++------
.../edit-process-group.component.ts | 95 ++--
.../service/parameter-contexts.service.ts | 2 +-
.../parameter-context-listing.actions.ts | 10 +-
.../parameter-context-listing.effects.ts | 20 +-
.../parameter-context-table.component.html | 7 +-
.../apps/nifi/src/app/state/shared/index.ts | 2 +
.../edit-parameter-dialog.component.spec.ts | 1 +
.../edit-parameter-context.component.html | 2 +-
.../edit-parameter-context.component.scss | 0
.../edit-parameter-context.component.spec.ts | 8 +-
.../edit-parameter-context.component.ts | 22 +-
.../common/parameter-context/index.ts} | 30 +-
.../parameter-context-inheritance.component.html | 0
.../parameter-context-inheritance.component.scss | 0
...parameter-context-inheritance.component.spec.ts | 0
.../parameter-context-inheritance.component.ts | 20 +-
.../libs/shared/src/assets/styles/_app.scss | 7 +
.../src/directives/copy/copy.directive.spec.ts | 17 +-
.../main/frontend/libs/shared/src/pipes/index.ts | 1 +
.../frontend/libs/shared/src/pipes/pipes.module.ts | 5 +-
.../shared/src/pipes/sort-by-property.pipe.spec.ts | 132 ++++++
.../{pipes.module.ts => sort-by-property.pipe.ts} | 28 +-
37 files changed, 1308 insertions(+), 491 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow.service.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow.service.ts
index 19fecaf321..153d665bd6 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow.service.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/flow.service.ts
@@ -87,16 +87,6 @@ export class FlowService implements
PropertyDescriptorRetriever {
return
this.httpClient.get(`${FlowService.API}/flow/controller/bulletins`);
}
- getParameterContexts(): Observable<any> {
- return
this.httpClient.get(`${FlowService.API}/flow/parameter-contexts`);
- }
-
- getParameterContext(id: string): Observable<any> {
- return
this.httpClient.get(`${FlowService.API}/parameter-contexts/${id}`, {
- params: { includeInheritedParameters: true }
- });
- }
-
getProcessor(id: string): Observable<any> {
return this.httpClient.get(`${FlowService.API}/processors/${id}`);
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/parameter-helper.service.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/parameter-helper.service.ts
index ffa19782bd..d5ecfb9c0d 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/parameter-helper.service.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/parameter-helper.service.ts
@@ -21,7 +21,6 @@ import { catchError, EMPTY, filter, map, Observable,
switchMap, takeUntil, tap }
import { Store } from '@ngrx/store';
import { HttpErrorResponse } from '@angular/common/http';
import { NiFiState } from '../../../state';
-import { ParameterService } from './parameter.service';
import { Client } from '../../../service/client.service';
import { EditParameterRequest, EditParameterResponse, ParameterContext,
ParameterEntity } from '../../../state/shared';
import { EditParameterDialog } from
'../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
@@ -32,6 +31,7 @@ import * as ParameterActions from
'../state/parameter/parameter.actions';
import { MEDIUM_DIALOG } from '@nifi/shared';
import { ClusterConnectionService } from
'../../../service/cluster-connection.service';
import { ErrorHelper } from '../../../service/error-helper.service';
+import { ParameterContextService } from
'../../parameter-contexts/service/parameter-contexts.service';
export interface ConvertToParameterResponse {
propertyValue: string;
@@ -45,7 +45,7 @@ export class ParameterHelperService {
constructor(
private dialog: MatDialog,
private store: Store<NiFiState>,
- private parameterService: ParameterService,
+ private parameterContextService: ParameterContextService,
private clusterConnectionService: ClusterConnectionService,
private client: Client,
private errorHelper: ErrorHelper
@@ -60,7 +60,7 @@ export class ParameterHelperService {
parameterContextId: string
): (name: string, sensitive: boolean, value: string | null) =>
Observable<ConvertToParameterResponse> {
return (name: string, sensitive: boolean, value: string | null) => {
- return
this.parameterService.getParameterContext(parameterContextId, false).pipe(
+ return
this.parameterContextService.getParameterContext(parameterContextId,
false).pipe(
catchError((errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ErrorActions.snackBarError({ error:
this.errorHelper.getErrorString(errorResponse) })
@@ -80,7 +80,9 @@ export class ParameterHelperService {
sensitive,
description: ''
},
- existingParameters
+ existingParameters,
+ isNewParameterContext: false,
+ isConvert: true
};
const convertToParameterDialogReference =
this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
index af6e0fded6..c97212c58d 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
@@ -51,7 +51,6 @@ import { HttpErrorResponse } from '@angular/common/http';
import { ParameterHelperService } from
'../../service/parameter-helper.service';
import { ExtensionTypesService } from
'../../../../service/extension-types.service';
import { ChangeComponentVersionDialog } from
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
-import { FlowService } from '../../service/flow.service';
import {
resetPropertyVerificationState,
verifyProperties
@@ -64,6 +63,7 @@ import { VerifyPropertiesRequestContext } from
'../../../../state/property-verif
import { BackNavigation } from '../../../../state/navigation';
import { ComponentType, LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG, NiFiCommon,
Storage } from '@nifi/shared';
import { ErrorContextKey } from '../../../../state/error';
+import { ParameterContextService } from
'../../../parameter-contexts/service/parameter-contexts.service';
@Injectable()
export class ControllerServicesEffects {
@@ -73,7 +73,7 @@ export class ControllerServicesEffects {
private storage: Storage,
private client: Client,
private controllerServiceService: ControllerServiceService,
- private flowService: FlowService,
+ private parameterContextService: ParameterContextService,
private errorHelper: ErrorHelper,
private dialog: MatDialog,
private router: Router,
@@ -328,7 +328,9 @@ export class ControllerServicesEffects {
]),
switchMap(([request, parameterContextReference,
processGroupId]) => {
if (parameterContextReference &&
parameterContextReference.permissions.canRead) {
- return
from(this.flowService.getParameterContext(parameterContextReference.id)).pipe(
+ return from(
+
this.parameterContextService.getParameterContext(parameterContextReference.id,
true)
+ ).pipe(
map((parameterContext) => {
return [request, parameterContext,
processGroupId];
}),
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 f126ea1d81..1808f8260b 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
@@ -91,7 +91,7 @@ import {
selectVersionSaving
} from './flow.selectors';
import { ConnectionManager } from
'../../service/manager/connection-manager.service';
-import { MatDialog } from '@angular/material/dialog';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { CreatePort } from
'../../ui/canvas/items/port/create-port/create-port.component';
import { EditPort } from
'../../ui/canvas/items/port/edit-port/edit-port.component';
import {
@@ -176,9 +176,13 @@ import {
import { CopyPasteService } from '../../service/copy-paste.service';
import { selectCopiedContent } from '../../../../state/copy/copy.selectors';
import { CopyRequestContext, CopyResponseContext } from
'../../../../state/copy';
+import * as ParameterActions from '../parameter/parameter.actions';
+import { ParameterContextService } from
'../../../parameter-contexts/service/parameter-contexts.service';
@Injectable()
export class FlowEffects {
+ private createProcessGroupDialogRef: MatDialogRef<CreateProcessGroup, any>
| undefined;
+ private editProcessGroupDialogRef: MatDialogRef<EditProcessGroup, any> |
undefined;
private destroyRef = inject(DestroyRef);
private lastReload: number = 0;
@@ -200,6 +204,7 @@ export class FlowEffects {
private dialog: MatDialog,
private propertyTableHelperService: PropertyTableHelperService,
private parameterHelperService: ParameterHelperService,
+ private parameterContextService: ParameterContextService,
private extensionTypesService: ExtensionTypesService,
private errorHelper: ErrorHelper,
private copyPasteService: CopyPasteService
@@ -324,7 +329,7 @@ export class FlowEffects {
case ComponentType.Processor:
return of(FlowActions.openNewProcessorDialog({ request
}));
case ComponentType.ProcessGroup:
- return
from(this.flowService.getParameterContexts()).pipe(
+ return
from(this.parameterContextService.getParameterContexts()).pipe(
concatLatestFrom(() =>
this.store.select(selectCurrentParameterContext)),
map(([response, parameterContext]) => {
const dialogRequest:
CreateProcessGroupDialogRequest = {
@@ -551,15 +556,20 @@ export class FlowEffects {
ofType(FlowActions.openNewProcessGroupDialog),
map((action) => action.request),
tap((request) => {
- this.dialog
- .open(CreateProcessGroup, {
- ...MEDIUM_DIALOG,
- data: request
- })
- .afterClosed()
- .subscribe(() => {
- this.store.dispatch(FlowActions.setDragging({
dragging: false }));
- });
+ this.createProcessGroupDialogRef =
this.dialog.open(CreateProcessGroup, {
+ ...MEDIUM_DIALOG,
+ data: request
+ });
+
+
this.createProcessGroupDialogRef.componentInstance.parameterContexts =
request.parameterContexts;
+
+
this.createProcessGroupDialogRef.afterClosed().subscribe(() => {
+ if (this.createProcessGroupDialogRef !== undefined) {
+ this.createProcessGroupDialogRef = undefined;
+ }
+
+ this.store.dispatch(FlowActions.setDragging({
dragging: false }));
+ });
})
),
{ dispatch: false }
@@ -617,7 +627,7 @@ export class FlowEffects {
map((action) => action.request),
concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
switchMap(([request]) =>
- from(this.flowService.getParameterContexts()).pipe(
+ from(this.parameterContextService.getParameterContexts()).pipe(
concatLatestFrom(() =>
this.store.select(selectCurrentParameterContext)),
map(([response, parameterContext]) => {
const dialogRequest: GroupComponentsDialogRequest = {
@@ -1273,7 +1283,21 @@ export class FlowEffects {
case ComponentType.Connection:
return of(FlowActions.openEditConnectionDialog({
request }));
case ComponentType.ProcessGroup:
- return of(FlowActions.openEditProcessGroupDialog({
request }));
+ return
from(this.parameterContextService.getParameterContexts()).pipe(
+ map((response) => {
+ const editComponentDialogRequest = {
+ ...request,
+ parameterContexts:
response.parameterContexts
+ };
+
+ return FlowActions.openEditProcessGroupDialog({
+ request: editComponentDialogRequest
+ });
+ }),
+ catchError((errorResponse: HttpErrorResponse) =>
+
of(this.snackBarOrFullScreenError(errorResponse))
+ )
+ );
case ComponentType.RemoteProcessGroup:
return
of(FlowActions.openEditRemoteProcessGroupDialog({ request }));
case ComponentType.InputPort:
@@ -1292,20 +1316,28 @@ export class FlowEffects {
this.actions$.pipe(
ofType(FlowActions.editCurrentProcessGroup),
map((action) => action.request),
- switchMap((request) =>
- from(this.flowService.getProcessGroup(request.id)).pipe(
- map((response) =>
- FlowActions.openEditProcessGroupDialog({
- request: {
- type: ComponentType.ProcessGroup,
- uri: response.uri,
- entity: response
- }
- })
+ switchMap((request) => {
+ return
from(this.parameterContextService.getParameterContexts()).pipe(
+ switchMap((parameterContextResponse) =>
+
from(this.flowService.getProcessGroup(request.id)).pipe(
+ map((response) =>
+ FlowActions.openEditProcessGroupDialog({
+ request: {
+ type: ComponentType.ProcessGroup,
+ uri: response.uri,
+ entity: response,
+ parameterContexts:
parameterContextResponse.parameterContexts
+ }
+ })
+ ),
+ catchError((errorResponse: HttpErrorResponse) =>
+
of(this.snackBarOrFullScreenError(errorResponse))
+ )
+ )
),
catchError((errorResponse: HttpErrorResponse) =>
of(this.snackBarOrFullScreenError(errorResponse)))
- )
- )
+ );
+ })
)
);
@@ -1413,7 +1445,9 @@ export class FlowEffects {
]),
switchMap(([request, parameterContextReference,
processGroupId]) => {
if (parameterContextReference &&
parameterContextReference.permissions.canRead) {
- return
from(this.flowService.getParameterContext(parameterContextReference.id)).pipe(
+ return from(
+
this.parameterContextService.getParameterContext(parameterContextReference.id)
+ ).pipe(
map((parameterContext) => {
return [request, parameterContext,
processGroupId];
}),
@@ -1827,23 +1861,18 @@ export class FlowEffects {
ofType(FlowActions.openEditProcessGroupDialog),
map((action) => action.request),
concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
- switchMap(([request, currentProcessGroupId]) =>
- this.flowService.getParameterContexts().pipe(
- take(1),
- map((response) => [request,
response.parameterContexts, currentProcessGroupId])
- )
- ),
- tap(([request, parameterContexts, currentProcessGroupId]) => {
- const editDialogReference =
this.dialog.open(EditProcessGroup, {
+ tap(([request, currentProcessGroupId]) => {
+ this.editProcessGroupDialogRef =
this.dialog.open(EditProcessGroup, {
...LARGE_DIALOG,
data: request
});
- editDialogReference.componentInstance.saving$ =
this.store.select(selectSaving);
- editDialogReference.componentInstance.parameterContexts =
parameterContexts;
+ this.editProcessGroupDialogRef.componentInstance.saving$ =
this.store.select(selectSaving);
+
this.editProcessGroupDialogRef.componentInstance.parameterContexts =
+ request.parameterContexts || [];
- editDialogReference.componentInstance.editProcessGroup
- .pipe(takeUntil(editDialogReference.afterClosed()))
+
this.editProcessGroupDialogRef.componentInstance.editProcessGroup
+
.pipe(takeUntil(this.editProcessGroupDialogRef.afterClosed()))
.subscribe((payload: any) => {
this.store.dispatch(
FlowActions.updateComponent({
@@ -1858,7 +1887,11 @@ export class FlowEffects {
);
});
- editDialogReference.afterClosed().subscribe(() => {
+ this.editProcessGroupDialogRef.afterClosed().subscribe(()
=> {
+ if (this.editProcessGroupDialogRef !== undefined) {
+ this.editProcessGroupDialogRef = undefined;
+ }
+
if (request.entity.id === currentProcessGroupId) {
this.store.dispatch(
FlowActions.loadProcessGroup({
@@ -1891,6 +1924,65 @@ export class FlowEffects {
{ dispatch: false }
);
+ createParameterContextSuccess$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ParameterActions.createParameterContextSuccess),
+ tap((action) => {
+ if (this.editProcessGroupDialogRef !== undefined) {
+ // clone the current parameter contexts
+ const parameterContexts = [
+
...(this.editProcessGroupDialogRef?.componentInstance.parameterContexts || [])
+ ];
+
+ // add newly created parameter context
+
parameterContexts?.push(action.response.parameterContext);
+
+ // remove all parameter contexts
+
this.editProcessGroupDialogRef.componentInstance.parameterContexts = [];
+
+ // set collection of parameter contexts to include the
newly created parameter context
+
this.editProcessGroupDialogRef.componentInstance.parameterContexts =
parameterContexts || [];
+
+ // auto select the newly created parameter context
+
this.editProcessGroupDialogRef.componentInstance.editProcessGroupForm
+ .get('parameterContext')
+ ?.setValue(action.response.parameterContext.id);
+
+ // mark the form as dirty
+
this.editProcessGroupDialogRef.componentInstance.editProcessGroupForm
+ .get('parameterContext')
+ ?.markAsDirty();
+ } else if (this.createProcessGroupDialogRef !== undefined)
{
+ // clone the current parameter contexts
+ const parameterContexts = [
+
...(this.createProcessGroupDialogRef?.componentInstance.parameterContexts || [])
+ ];
+
+ // add newly created parameter context
+
parameterContexts?.push(action.response.parameterContext);
+
+ // remove all parameter contexts
+
this.createProcessGroupDialogRef.componentInstance.parameterContexts = [];
+
+ // set collection of parameter contexts to include the
newly created parameter context
+
this.createProcessGroupDialogRef.componentInstance.parameterContexts =
parameterContexts || [];
+
+ // auto select the newly created parameter context
+
this.createProcessGroupDialogRef.componentInstance.createProcessGroupForm
+ .get('newProcessGroupParameterContext')
+ ?.setValue(action.response.parameterContext.id);
+
+ // mark the form as dirty
+
this.createProcessGroupDialogRef.componentInstance.createProcessGroupForm
+ .get('newProcessGroupParameterContext')
+ ?.markAsDirty();
+ }
+ })
+ ),
+ { dispatch: false }
+ );
+
navigateToManageRemotePorts$ = createEffect(
() =>
this.actions$.pipe(
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 08615c32ac..be4142ab4c 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
@@ -358,6 +358,7 @@ export interface EditComponentDialogRequest {
uri: string;
entity: any;
history?: ComponentHistory;
+ parameterContexts?: ParameterContextEntity[];
}
export interface EditRemotePortDialogRequest extends
EditComponentDialogRequest {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/index.ts
index c6eeb032b4..ec3550e53d 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/index.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/index.ts
@@ -22,5 +22,6 @@ export const parameterFeatureKey = 'parameter';
export interface ParameterState {
updateRequestEntity: ParameterContextUpdateRequestEntity | null;
saving: boolean;
+ status: 'pending' | 'loading' | 'success';
error: string | null;
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.actions.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.actions.ts
index faaaa89c01..6d48292666 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.actions.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.actions.ts
@@ -17,6 +17,11 @@
import { createAction, props } from '@ngrx/store';
import { PollParameterContextUpdateSuccess, SubmitParameterContextUpdate }
from '../../../../state/shared';
+import {
+ CreateParameterContextRequest,
+ CreateParameterContextSuccess,
+ OpenCreateParameterContextRequest
+} from '../../../../ui/common/parameter-context';
export const parameterApiError = createAction('[Parameter] Parameter Error',
props<{ error: string }>());
@@ -48,3 +53,28 @@ export const stopPollingParameterContextUpdateRequest =
createAction(
export const deleteParameterContextUpdateRequest = createAction('[Parameter]
Delete Parameter Context Update Request');
export const editParameterContextComplete = createAction('[Parameter] Edit
Parameter Context Complete');
+
+export const openNewParameterContextDialog = createAction(
+ '[Parameter Context] Open New Parameter Context Dialog',
+ props<{ request: OpenCreateParameterContextRequest }>()
+);
+
+export const createParameterContext = createAction(
+ '[Parameter Context] Create Parameter Context',
+ props<{ request: CreateParameterContextRequest }>()
+);
+
+export const createParameterContextSuccess = createAction(
+ '[Parameter Context] Create Parameter Context Success',
+ props<{ response: CreateParameterContextSuccess }>()
+);
+
+export const parameterContextSnackbarApiError = createAction(
+ '[Parameter Context] Parameter Context Snackbar Api Error',
+ props<{ error: string }>()
+);
+
+export const parameterContextBannerApiError = createAction(
+ '[Parameter Context] Parameter Context Banner Api Error',
+ props<{ error: string }>()
+);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.effects.ts
index 4967dea96b..18fa722a8a 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.effects.ts
@@ -21,21 +21,45 @@ import { concatLatestFrom } from '@ngrx/operators';
import * as ParameterActions from './parameter.actions';
import { Store } from '@ngrx/store';
import { CanvasState } from '../index';
-import { asyncScheduler, catchError, filter, from, interval, map, of,
switchMap, takeUntil } from 'rxjs';
-import { isDefinedAndNotNull } from '@nifi/shared';
-import { ParameterContextUpdateRequest } from '../../../../state/shared';
-import { selectUpdateRequest } from './parameter.selectors';
+import {
+ asyncScheduler,
+ catchError,
+ filter,
+ from,
+ interval,
+ map,
+ Observable,
+ of,
+ switchMap,
+ take,
+ takeUntil,
+ tap
+} from 'rxjs';
+import { isDefinedAndNotNull, MEDIUM_DIALOG, NiFiCommon, Parameter, Storage,
XL_DIALOG } from '@nifi/shared';
+import { EditParameterRequest, EditParameterResponse,
ParameterContextUpdateRequest } from '../../../../state/shared';
+import { selectSaving, selectUpdateRequest } from './parameter.selectors';
import { ParameterService } from '../../service/parameter.service';
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { EditParameterContext } from
'../../../../ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component';
+import { ParameterContextService } from
'../../../parameter-contexts/service/parameter-contexts.service';
+import { EditParameterDialog } from
'../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { ErrorContextKey } from '../../../../state/error';
@Injectable()
export class ParameterEffects {
+ private parameterContextDialogRef: MatDialogRef<EditParameterContext, any>
| undefined;
+
constructor(
private actions$: Actions,
private store: Store<CanvasState>,
private parameterService: ParameterService,
- private errorHelper: ErrorHelper
+ private parameterContextService: ParameterContextService,
+ private errorHelper: ErrorHelper,
+ private storage: Storage,
+ private dialog: MatDialog
) {}
submitParameterContextUpdateRequest$ = createEffect(() =>
@@ -153,4 +177,149 @@ export class ParameterEffects {
})
)
);
+
+ openNewParameterContextDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ParameterActions.openNewParameterContextDialog),
+ tap((action) => {
+
this.storage.setItem<number>(NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID, 0);
+
+ this.parameterContextDialogRef =
this.dialog.open(EditParameterContext, {
+ ...XL_DIALOG,
+ data: {}
+ });
+
+
this.parameterContextDialogRef.componentInstance.availableParameterContexts$ =
of(
+ action.request.parameterContexts
+ );
+ this.parameterContextDialogRef.componentInstance.saving$ =
this.store.select(selectSaving);
+
+
this.parameterContextDialogRef.componentInstance.createNewParameter = (
+ existingParameters: string[]
+ ): Observable<EditParameterResponse> => {
+ const dialogRequest: EditParameterRequest = {
existingParameters, isNewParameterContext: true };
+ const newParameterDialogReference =
this.dialog.open(EditParameterDialog, {
+ ...MEDIUM_DIALOG,
+ data: dialogRequest
+ });
+
+ newParameterDialogReference.componentInstance.saving$
= of(false);
+
+ return
newParameterDialogReference.componentInstance.editParameter.pipe(
+ take(1),
+ map((dialogResponse: EditParameterResponse) => {
+ newParameterDialogReference.close();
+
+ return {
+ ...dialogResponse
+ };
+ })
+ );
+ };
+
+
this.parameterContextDialogRef.componentInstance.editParameter = (
+ parameter: Parameter
+ ): Observable<EditParameterResponse> => {
+ const dialogRequest: EditParameterRequest = {
+ parameter: {
+ ...parameter
+ },
+ isNewParameterContext: true
+ };
+ const editParameterDialogReference =
this.dialog.open(EditParameterDialog, {
+ ...MEDIUM_DIALOG,
+ data: dialogRequest
+ });
+
+ editParameterDialogReference.componentInstance.saving$
= of(false);
+
+ return
editParameterDialogReference.componentInstance.editParameter.pipe(
+ take(1),
+ map((dialogResponse: EditParameterResponse) => {
+ editParameterDialogReference.close();
+
+ return {
+ ...dialogResponse
+ };
+ })
+ );
+ };
+
+
this.parameterContextDialogRef.componentInstance.addParameterContext
+
.pipe(takeUntil(this.parameterContextDialogRef.afterClosed()))
+ .subscribe((payload: any) => {
+ this.store.dispatch(
+ ParameterActions.createParameterContext({
+ request: { payload }
+ })
+ );
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ createParameterContext$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ParameterActions.createParameterContext),
+ map((action) => action.request),
+ switchMap((request) =>
+
from(this.parameterContextService.createParameterContext(request)).pipe(
+ map((response) =>
+ ParameterActions.createParameterContextSuccess({
+ response: {
+ parameterContext: response
+ }
+ })
+ ),
+ catchError((errorResponse: HttpErrorResponse) => {
+ if
(this.errorHelper.showErrorInContext(errorResponse.status)) {
+ return of(
+
ParameterActions.parameterContextBannerApiError({
+ error:
this.errorHelper.getErrorString(errorResponse)
+ })
+ );
+ } else {
+ this.dialog.closeAll();
+ return
of(this.errorHelper.fullScreenError(errorResponse));
+ }
+ })
+ )
+ )
+ )
+ );
+
+ createParameterContextSuccess$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ParameterActions.createParameterContextSuccess),
+ tap(() => {
+ this.parameterContextDialogRef?.close();
+ })
+ ),
+ { dispatch: false }
+ );
+
+ parameterContextSnackbarApiError$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ParameterActions.parameterContextSnackbarApiError),
+ map((action) => action.error),
+ switchMap((error) => of(ErrorActions.snackBarError({ error })))
+ )
+ );
+
+ parameterContextBannerApiError$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ParameterActions.parameterContextBannerApiError),
+ map((action) => action.error),
+ switchMap((error) =>
+ of(
+ ErrorActions.addBannerError({
+ errorContext: { errors: [error], context:
ErrorContextKey.PARAMETER_CONTEXTS }
+ })
+ )
+ )
+ )
+ );
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.reducer.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.reducer.ts
index 50e36b9810..879079d2f7 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.reducer.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.reducer.ts
@@ -18,8 +18,12 @@
import { createReducer, on } from '@ngrx/store';
import { ParameterState } from './index';
import {
+ createParameterContext,
+ createParameterContextSuccess,
editParameterContextComplete,
parameterApiError,
+ parameterContextBannerApiError,
+ parameterContextSnackbarApiError,
pollParameterContextUpdateRequestSuccess,
submitParameterContextUpdateRequest,
submitParameterContextUpdateRequestSuccess
@@ -28,11 +32,24 @@ import {
export const initialState: ParameterState = {
updateRequestEntity: null,
saving: false,
- error: null
+ error: null,
+ status: 'pending'
};
export const parameterReducer = createReducer(
initialState,
+ on(parameterContextSnackbarApiError, parameterContextBannerApiError,
(state) => ({
+ ...state,
+ saving: false
+ })),
+ on(createParameterContext, (state) => ({
+ ...state,
+ saving: true
+ })),
+ on(createParameterContextSuccess, (state) => ({
+ ...state,
+ saving: false
+ })),
on(submitParameterContextUpdateRequest, (state) => ({
...state,
saving: true
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.selectors.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.selectors.ts
index bec6b20708..276765019c 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.selectors.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/parameter/parameter.selectors.ts
@@ -30,3 +30,10 @@ export const selectUpdateRequest = createSelector(
);
export const selectParameterSaving = createSelector(selectParameterState,
(state: ParameterState) => state.saving);
+
+export const selectSaving = createSelector(selectParameterState, (state:
ParameterState) => state.saving);
+
+export const selectParameterContextStatus = createSelector(
+ selectParameterState,
+ (state: ParameterState) => state.status
+);
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.html
index 6f23e04491..f19cc4b030 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.html
@@ -32,25 +32,50 @@
</button>
</mat-form-field>
@if (!flowDefinition) {
- <mat-form-field>
- <mat-label>Parameter Context</mat-label>
- <mat-select formControlName="newProcessGroupParameterContext">
- @for (option of parameterContextsOptions; track option) {
- @if (option.description) {
- <mat-option
- [value]="option.value"
- nifiTooltip
- [tooltipComponentType]="TextTip"
- [tooltipInputData]="option.description"
- [delayClose]="false"
- >{{ option.text }}</mat-option
- >
- } @else {
- <mat-option [value]="option.value">{{ option.text
}}</mat-option>
+ <div class="flex flew-row w-full justify-start items-start">
+ <mat-form-field>
+ <mat-label>Parameter Context</mat-label>
+ <mat-select
formControlName="newProcessGroupParameterContext">
+ <mat-option
+ [value]="null"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+ [tooltipInputData]="'No parameter context'"
+ [delayClose]="false"
+ >No parameter context
+ </mat-option>
+ @for (option of parameterContextsOptions |
sortObjectByProperty: 'text'; track option.value) {
+ @if (option.description) {
+ <mat-option
+ [value]="option.value"
+ nifiTooltip
+ [disabled]="option.disabled"
+ [tooltipComponentType]="TextTip"
+ [tooltipInputData]="option.description"
+ [delayClose]="false"
+ >{{ option.text }}</mat-option
+ >
+ } @else {
+ <mat-option [value]="option.value"
[disabled]="option.disabled">{{
+ option.text
+ }}</mat-option>
+ }
}
+ </mat-select>
+ </mat-form-field>
+ @if (currentUser$ | async; as currentUser) {
+ @if (currentUser.parameterContextPermissions.canWrite) {
+ <button
+ mat-icon-button
+ type="button"
+ class="primary-icon-button mt-1 ml-1"
+ (click)="openNewParameterContextDialog()"
+ title="Create parameter context">
+ <i class="fa fa-plus"></i>
+ </button>
}
- </mat-select>
- </mat-form-field>
+ }
+ </div>
}
@if (flowDefinition) {
<div>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts
index 3e9529e24b..a4ce0a4f4b 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts
@@ -24,150 +24,153 @@ import { ComponentType } from '@nifi/shared';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/flow/flow.reducer';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { CurrentUser } from '../../../../../../../state/current-user';
+import { of } from 'rxjs';
+import { By } from '@angular/platform-browser';
-describe('CreateProcessGroup', () => {
- let component: CreateProcessGroup;
- let fixture: ComponentFixture<CreateProcessGroup>;
-
- const data: CreateProcessGroupDialogRequest = {
- request: {
- revision: {
- clientId: 'a6482293-7fe8-43b4-8ab4-ee95b3b27721',
- version: 0
- },
- type: ComponentType.ProcessGroup,
- position: {
- x: -4,
- y: -698.5
- }
+const noPermissionsParameterContextId = '95d509b9-018b-1000-daff-b7957ea7935e';
+const parameterContextId = '95d509b9-018b-1000-daff-b7957ea7934f';
+const parameterContexts = [
+ {
+ revision: {
+ version: 0
+ },
+ id: parameterContextId,
+ uri: '',
+ permissions: {
+ canRead: true,
+ canWrite: true
+ },
+ component: {
+ name: 'params 2',
+ description: '',
+ parameters: [],
+ boundProcessGroups: [],
+ inheritedParameterContexts: [],
+ id: parameterContextId
+ }
+ },
+ {
+ revision: {
+ version: 0
},
- parameterContexts: [
- {
+ id: noPermissionsParameterContextId,
+ uri: '',
+ permissions: {
+ canRead: false,
+ canWrite: false
+ }
+ }
+];
+
+describe('CreateProcessGroup', () => {
+ describe('user has permission to current parameter context', () => {
+ let component: CreateProcessGroup;
+ let fixture: ComponentFixture<CreateProcessGroup>;
+
+ const data: CreateProcessGroupDialogRequest = {
+ request: {
revision: {
- version: 1
- },
- id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
- uri: '',
- permissions: {
- canRead: true,
- canWrite: true
+ clientId: 'a6482293-7fe8-43b4-8ab4-ee95b3b27721',
+ version: 0
},
- component: {
- name: 'params 1',
- description: '',
- parameters: [
- {
- canWrite: true,
- parameter: {
- name: 'one',
- description: 'Description for one.',
- sensitive: false,
- value: 'value',
- provided: false,
- referencingComponents: [],
- parameterContext: {
- id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
- permissions: {
- canRead: true,
- canWrite: true
- },
- component: {
- id:
'95d4f3d2-018b-1000-b7c7-b830c49a8026',
- name: 'params 1'
- }
- },
- inherited: false
- }
- },
- {
- canWrite: true,
- parameter: {
- name: 'two',
- description: 'Description for two.',
- sensitive: false,
- value: 'value',
- provided: false,
- referencingComponents: [],
- parameterContext: {
- id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
- permissions: {
- canRead: true,
- canWrite: true
- },
- component: {
- id:
'95d4f3d2-018b-1000-b7c7-b830c49a8026',
- name: 'params 1'
- }
- },
- inherited: false
- }
- },
- {
- canWrite: true,
- parameter: {
- name: 'Group ID',
- description: '',
- sensitive: false,
- value: 'asdf',
- provided: false,
- referencingComponents: [],
- parameterContext: {
- id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
- permissions: {
- canRead: true,
- canWrite: true
- },
- component: {
- id:
'95d4f3d2-018b-1000-b7c7-b830c49a8026',
- name: 'params 1'
- }
- },
- inherited: false
- }
- }
- ],
- boundProcessGroups: [],
- inheritedParameterContexts: [],
- id: '95d4f3d2-018b-1000-b7c7-b830c49a8026'
+ type: ComponentType.ProcessGroup,
+ position: {
+ x: -4,
+ y: -698.5
}
},
- {
+ currentParameterContextId: parameterContextId,
+ parameterContexts
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CreateProcessGroup, NoopAnimationsModule],
+ providers: [
+ { provide: MAT_DIALOG_DATA, useValue: data },
+ provideMockStore({ initialState }),
+ { provide: MatDialogRef, useValue: null }
+ ]
+ });
+ fixture = TestBed.createComponent(CreateProcessGroup);
+ component = fixture.componentInstance;
+ component.parameterContexts = [...parameterContexts];
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('verify current parameter context selected', () => {
+ fixture.detectChanges();
+
expect(component.createProcessGroupForm.get('newProcessGroupParameterContext')?.value).toEqual(
+ parameterContextId
+ );
+ });
+
+ it('verify setting new parameter contexts does not append', () => {
+ component.parameterContexts = parameterContexts;
+ fixture.detectChanges();
+ expect(component.parameterContextsOptions.length).toEqual(2);
+ });
+ });
+
+ describe('user does NOT have permission to current parameter context', ()
=> {
+ let component: CreateProcessGroup;
+ let fixture: ComponentFixture<CreateProcessGroup>;
+
+ const data: CreateProcessGroupDialogRequest = {
+ request: {
revision: {
+ clientId: 'a6482293-7fe8-43b4-8ab4-ee95b3b27721',
version: 0
},
- id: '95d509b9-018b-1000-daff-b7957ea7934f',
- uri: '',
- permissions: {
- canRead: true,
- canWrite: true
- },
- component: {
- name: 'params 2',
- description: '',
- parameters: [],
- boundProcessGroups: [],
- inheritedParameterContexts: [],
- id: '95d509b9-018b-1000-daff-b7957ea7934f'
+ type: ComponentType.ProcessGroup,
+ position: {
+ x: -4,
+ y: -698.5
}
- }
- ]
- };
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [CreateProcessGroup, NoopAnimationsModule],
- providers: [
- { provide: MAT_DIALOG_DATA, useValue: data },
- provideMockStore({ initialState }),
- { provide: MatDialogRef, useValue: null }
- ]
+ },
+ currentParameterContextId: noPermissionsParameterContextId,
+ parameterContexts
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [CreateProcessGroup, NoopAnimationsModule],
+ providers: [
+ { provide: MAT_DIALOG_DATA, useValue: data },
+ provideMockStore({ initialState }),
+ { provide: MatDialogRef, useValue: null }
+ ]
+ });
+ fixture = TestBed.createComponent(CreateProcessGroup);
+ component = fixture.componentInstance;
+ component.parameterContexts = [...parameterContexts];
+ fixture.detectChanges();
});
- fixture = TestBed.createComponent(CreateProcessGroup);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
- it('should create', () => {
- expect(component).toBeTruthy();
+ it('verify no parameter context selected', () => {
+
expect(component.createProcessGroupForm.get('newProcessGroupParameterContext')?.value).toEqual(null);
+ });
+
+ it('should not display the create parameter context button when
currentUser.parameterContextPermissions.canWrite is false', () => {
+ // Mock the currentUser observable to return a user with canWrite
set to false
+ component.currentUser$ = of({
+ parameterContextPermissions: {
+ canWrite: false
+ }
+ } as unknown as CurrentUser);
+
+ fixture.detectChanges();
+
+ // Query for the button element
+ const buttonElement =
fixture.debugElement.query(By.css('button[title="Create parameter context"]'));
+
+ // Assert that the button is not present in the DOM
+ expect(buttonElement).toBeNull();
+ });
});
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.ts
index bb901109a0..6f3d20aa7d 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
+import { Component, ElementRef, Inject, Input, ViewChild } from
'@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { CreateProcessGroupDialogRequest } from '../../../../../state/flow';
import { Store } from '@ngrx/store';
@@ -31,10 +31,19 @@ import { MatSelectModule } from '@angular/material/select';
import { NifiSpinnerDirective } from
'../../../../../../../ui/common/spinner/nifi-spinner.directive';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators
} from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
-import { NiFiCommon, TextTip, NifiTooltipDirective } from '@nifi/shared';
-import { CloseOnEscapeDialog, SelectOption } from '@nifi/shared';
import { ErrorContextKey } from '../../../../../../../state/error';
import { ContextErrorBanner } from
'../../../../../../../ui/common/context-error-banner/context-error-banner.component';
+import { openNewParameterContextDialog } from
'../../../../../state/parameter/parameter.actions';
+import {
+ CloseOnEscapeDialog,
+ NiFiCommon,
+ NifiTooltipDirective,
+ PipesModule,
+ SelectOption,
+ TextTip
+} from '@nifi/shared';
+import { ParameterContextEntity } from '../../../../../../../state/shared';
+import { selectCurrentUser } from
'../../../../../../../state/current-user/current-user.selectors';
@Component({
selector: 'create-process-group',
@@ -51,15 +60,57 @@ import { ContextErrorBanner } from
'../../../../../../../ui/common/context-error
MatSelectModule,
NifiTooltipDirective,
MatIconModule,
- ContextErrorBanner
+ ContextErrorBanner,
+ PipesModule
],
templateUrl: './create-process-group.component.html',
styleUrls: ['./create-process-group.component.scss']
})
export class CreateProcessGroup extends CloseOnEscapeDialog {
+ @Input() set parameterContexts(parameterContexts:
ParameterContextEntity[]) {
+ this.parameterContextsOptions = [];
+ this._parameterContexts = parameterContexts;
+ let currentParameterContextIdEnabled: boolean = false;
+
+ if (parameterContexts.length === 0) {
+ this.parameterContextsOptions = [];
+ } else {
+ parameterContexts.forEach((parameterContext) => {
+ if (parameterContext.permissions.canRead &&
parameterContext.component) {
+ this.parameterContextsOptions.push({
+ text: parameterContext.component.name,
+ value: parameterContext.id,
+ description: parameterContext.component.description
+ });
+
+ if (this.dialogRequest.currentParameterContextId ===
parameterContext.id) {
+ currentParameterContextIdEnabled = true;
+ }
+ } else {
+ this.parameterContextsOptions.push({
+ text: parameterContext.id,
+ value: parameterContext.id,
+ disabled: true
+ });
+ }
+ });
+ }
+
+ if (currentParameterContextIdEnabled) {
+ this.createProcessGroupForm
+ .get('newProcessGroupParameterContext')
+ ?.setValue(this.dialogRequest.currentParameterContextId);
+ }
+ }
+
+ get parameterContexts() {
+ return this._parameterContexts;
+ }
+
saving$ = this.store.select(selectSaving);
protected readonly TextTip = TextTip;
+ private _parameterContexts: ParameterContextEntity[] = [];
@ViewChild('flowUploadControl') flowUploadControl!: ElementRef;
@@ -68,6 +119,7 @@ export class CreateProcessGroup extends CloseOnEscapeDialog {
flowNameAttached: string | null = null;
flowDefinition: File | null = null;
+ currentUser$ = this.store.select(selectCurrentUser);
constructor(
@Inject(MAT_DIALOG_DATA) private dialogRequest:
CreateProcessGroupDialogRequest,
@@ -76,24 +128,10 @@ export class CreateProcessGroup extends
CloseOnEscapeDialog {
private nifiCommon: NiFiCommon
) {
super();
- this.parameterContextsOptions.push({
- text: 'No parameter context',
- value: null
- });
-
- dialogRequest.parameterContexts.forEach((parameterContext) => {
- if (parameterContext.permissions.canRead &&
parameterContext.component) {
- this.parameterContextsOptions.push({
- text: parameterContext.component.name,
- value: parameterContext.id,
- description: parameterContext.component.description
- });
- }
- });
this.createProcessGroupForm = this.formBuilder.group({
newProcessGroupName: new FormControl('', Validators.required),
- newProcessGroupParameterContext: new
FormControl(dialogRequest.currentParameterContextId)
+ newProcessGroupParameterContext: new FormControl(null)
});
}
@@ -143,5 +181,11 @@ export class CreateProcessGroup extends
CloseOnEscapeDialog {
}
}
+ openNewParameterContextDialog(): void {
+ this.store.dispatch(
+ openNewParameterContextDialog({ request: { parameterContexts:
this.dialogRequest.parameterContexts } })
+ );
+ }
+
protected readonly ErrorContextKey = ErrorContextKey;
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.html
index 416fe4d03a..5e838ab56a 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.html
@@ -29,11 +29,23 @@
<input matInput formControlName="name"
type="text" [readonly]="readonly" />
</mat-form-field>
</div>
- <div>
+ <div class="flex flew-row w-full justify-start
items-start">
<mat-form-field>
<mat-label>Parameter Context</mat-label>
<mat-select formControlName="parameterContext">
- @for (option of parameterContextsOptions;
track option.value) {
+ <mat-option
+ [value]="null"
+ [disabled]="readonly"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+ [tooltipInputData]="'No parameter
context'"
+ [delayClose]="false"
+ >No parameter context
+ </mat-option>
+ @for (
+ option of parameterContextsOptions |
sortObjectByProperty: 'text';
+ track option.value
+ ) {
@if (option.description) {
<mat-option
[value]="option.value"
@@ -42,18 +54,33 @@
[tooltipComponentType]="TextTip"
[tooltipInputData]="option.description"
[delayClose]="false"
- >{{ option.text }}</mat-option
- >
+ >{{ option.text }}
+ </mat-option>
} @else {
<mat-option
[value]="option.value"
[disabled]="readonly ||
option.disabled"
- >{{ option.text }}</mat-option
- >
+ nifiTooltip
+
[tooltipComponentType]="TextTip"
+
[tooltipInputData]="option.text"
+ [delayClose]="false"
+ >{{ option.text }}
+ </mat-option>
}
}
</mat-select>
</mat-form-field>
+ @if (currentUser$ | async; as currentUser) {
+ @if
(currentUser.parameterContextPermissions.canWrite) {
+ <button
+ mat-icon-button
+ class="primary-icon-button mt-1 ml-1"
+
(click)="openNewParameterContextDialog()"
+ title="Create parameter context">
+ <i class="fa fa-plus"></i>
+ </button>
+ }
+ }
</div>
<div class="-mt-4 mb-4">
<mat-checkbox
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.spec.ts
index 09a8bf8487..b427dec048 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.spec.ts
@@ -23,52 +23,108 @@ import { NoopAnimationsModule } from
'@angular/platform-browser/animations';
import { ClusterConnectionService } from
'../../../../../../../service/cluster-connection.service';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/flow/flow.reducer';
+import { CurrentUser } from '../../../../../../../state/current-user';
+import { of } from 'rxjs';
+import { By } from '@angular/platform-browser';
+
+const noPermissionsParameterContextId = '95d509b9-018b-1000-daff-b7957ea7935e';
+const selectedParameterContextId = '95d509b9-018b-1000-daff-b7957ea7934f';
+const parameterContexts = [
+ {
+ revision: {
+ version: 0
+ },
+ id: selectedParameterContextId,
+ uri: '',
+ permissions: {
+ canRead: true,
+ canWrite: true
+ },
+ component: {
+ name: 'params 2',
+ description: '',
+ parameters: [],
+ boundProcessGroups: [],
+ inheritedParameterContexts: [],
+ id: '95d509b9-018b-1000-daff-b7957ea7934f'
+ }
+ },
+ {
+ revision: {
+ version: 0
+ },
+ id: noPermissionsParameterContextId,
+ uri: '',
+ permissions: {
+ canRead: false,
+ canWrite: false
+ }
+ }
+];
describe('EditProcessGroup', () => {
let component: EditProcessGroup;
let fixture: ComponentFixture<EditProcessGroup>;
- const noPermissionsParameterContextId =
'95d509b9-018b-1000-daff-b7957ea7935e';
- const selectedParameterContextId = '95d509b9-018b-1000-daff-b7957ea7934f';
- const data: any = {
- type: 'ProcessGroup',
- uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
- entity: {
- revision: {
- clientId: 'de5d3be3-05be-4ba5-bc42-729e7a4b00c4',
- version: 14
- },
- id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ describe('user has permission to current parameter context', () => {
+ const data: any = {
+ type: 'ProcessGroup',
uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
- position: {
- x: 446,
- y: 151
- },
- permissions: {
- canRead: true,
- canWrite: true
- },
- bulletins: [],
- component: {
+ entity: {
+ revision: {
+ clientId: 'de5d3be3-05be-4ba5-bc42-729e7a4b00c4',
+ version: 14
+ },
id: '162380af-018c-1000-a7eb-f5d06f77168b',
- parentGroupId: '1621f9d1-018c-1000-cb13-7eab94ffe23c',
+ uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
position: {
x: 446,
y: 151
},
- name: 'pg2',
- comments: '',
- flowfileConcurrency: 'UNBOUNDED',
- flowfileOutboundPolicy: 'BATCH_OUTPUT',
- defaultFlowFileExpiration: '0 sec',
- defaultBackPressureObjectThreshold: 10000,
- defaultBackPressureDataSizeThreshold: '1 GB',
- parameterContext: {
- id: selectedParameterContextId
+ permissions: {
+ canRead: true,
+ canWrite: true
+ },
+ bulletins: [],
+ component: {
+ id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ parentGroupId: '1621f9d1-018c-1000-cb13-7eab94ffe23c',
+ position: {
+ x: 446,
+ y: 151
+ },
+ name: 'pg2',
+ comments: '',
+ flowfileConcurrency: 'UNBOUNDED',
+ flowfileOutboundPolicy: 'BATCH_OUTPUT',
+ defaultFlowFileExpiration: '0 sec',
+ defaultBackPressureObjectThreshold: 10000,
+ defaultBackPressureDataSizeThreshold: '1 GB',
+ parameterContext: {
+ id: selectedParameterContextId
+ },
+ executionEngine: 'INHERITED',
+ maxConcurrentTasks: 1,
+ statelessFlowTimeout: '1 min',
+ runningCount: 0,
+ stoppedCount: 0,
+ invalidCount: 0,
+ disabledCount: 0,
+ activeRemotePortCount: 0,
+ inactiveRemotePortCount: 0,
+ upToDateCount: 0,
+ locallyModifiedCount: 0,
+ staleCount: 0,
+ locallyModifiedAndStaleCount: 0,
+ syncFailureCount: 0,
+ localInputPortCount: 0,
+ localOutputPortCount: 0,
+ publicInputPortCount: 0,
+ publicOutputPortCount: 0,
+ statelessGroupScheduledState: 'STOPPED',
+ inputPortCount: 0,
+ outputPortCount: 0
},
- executionEngine: 'INHERITED',
- maxConcurrentTasks: 1,
- statelessFlowTimeout: '1 min',
runningCount: 0,
stoppedCount: 0,
invalidCount: 0,
@@ -84,154 +140,309 @@ describe('EditProcessGroup', () => {
localOutputPortCount: 0,
publicInputPortCount: 0,
publicOutputPortCount: 0,
- statelessGroupScheduledState: 'STOPPED',
inputPortCount: 0,
outputPortCount: 0
- },
- runningCount: 0,
- stoppedCount: 0,
- invalidCount: 0,
- disabledCount: 0,
- activeRemotePortCount: 0,
- inactiveRemotePortCount: 0,
- upToDateCount: 0,
- locallyModifiedCount: 0,
- staleCount: 0,
- locallyModifiedAndStaleCount: 0,
- syncFailureCount: 0,
- localInputPortCount: 0,
- localOutputPortCount: 0,
- publicInputPortCount: 0,
- publicOutputPortCount: 0,
- inputPortCount: 0,
- outputPortCount: 0
- }
- };
- const parameterContexts = [
- {
- revision: {
- version: 0
- },
- id: selectedParameterContextId,
- uri: '',
- permissions: {
- canRead: true,
- canWrite: true
- },
- component: {
- name: 'params 2',
- description: '',
- parameters: [],
- boundProcessGroups: [],
- inheritedParameterContexts: [],
- id: '95d509b9-018b-1000-daff-b7957ea7934f'
- }
- },
- {
- revision: {
- version: 0
- },
- id: noPermissionsParameterContextId,
- uri: '',
- permissions: {
- canRead: false,
- canWrite: false
}
- }
- ];
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [EditProcessGroup, NoopAnimationsModule],
- providers: [
- { provide: MAT_DIALOG_DATA, useValue: data },
- provideMockStore({ initialState }),
- {
- provide: ClusterConnectionService,
- useValue: {
- isDisconnectionAcknowledged: jest.fn()
- }
- },
- { provide: MatDialogRef, useValue: null }
- ]
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [EditProcessGroup, NoopAnimationsModule],
+ providers: [
+ { provide: MAT_DIALOG_DATA, useValue: data },
+ provideMockStore({ initialState }),
+ {
+ provide: ClusterConnectionService,
+ useValue: {
+ isDisconnectionAcknowledged: jest.fn()
+ }
+ },
+ { provide: MatDialogRef, useValue: null }
+ ]
+ });
+ fixture = TestBed.createComponent(EditProcessGroup);
+ component = fixture.componentInstance;
+ component.parameterContexts = parameterContexts;
+
+ fixture.detectChanges();
});
- fixture = TestBed.createComponent(EditProcessGroup);
- component = fixture.componentInstance;
- component.parameterContexts = parameterContexts;
- fixture.detectChanges();
- });
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
- it('should create', () => {
- expect(component).toBeTruthy();
- });
+ it('verify parameter context value initialized', () => {
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(selectedParameterContextId);
+ });
+
+ it('verify no parameter context value selected', () => {
+
component.request.entity.component.parameterContext.parameterContextId = null;
+ fixture.detectChanges();
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(selectedParameterContextId);
+ });
+
+ it('verify parameter context value', () => {
+ expect(component.parameterContextsOptions.length).toEqual(2);
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(selectedParameterContextId);
+ });
+
+ it('should not display the create parameter context button when
currentUser.parameterContextPermissions.canWrite is false', () => {
+ // Mock the currentUser observable to return a user with canWrite
set to false
+ component.currentUser$ = of({
+ parameterContextPermissions: {
+ canWrite: false
+ }
+ } as unknown as CurrentUser);
- it('verify parameter context value', () => {
- expect(component.parameterContextsOptions.length).toEqual(3);
-
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(selectedParameterContextId);
+ fixture.detectChanges();
+
+ // Query for the button element
+ const buttonElement =
fixture.debugElement.query(By.css('button[title="Create parameter context"]'));
+
+ // Assert that the button is not present in the DOM
+ expect(buttonElement).toBeNull();
+ });
});
describe('no permissions to available parameter context', () => {
it('verify selected parameter context with permissions', () => {
- expect(component.parameterContextsOptions.length).toEqual(3);
+ expect(component.parameterContextsOptions.length).toEqual(2);
component.parameterContextsOptions.forEach((parameterContextsOption) => {
- if (parameterContextsOption.value ===
noPermissionsParameterContextId) {
+ if (
+ parameterContextsOption.value ===
noPermissionsParameterContextId &&
+ parameterContextsOption.text ===
noPermissionsParameterContextId
+ ) {
expect(parameterContextsOption.disabled).toBeTruthy();
} else if (parameterContextsOption.value ===
selectedParameterContextId) {
expect(parameterContextsOption.disabled).toBeFalsy();
}
});
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(selectedParameterContextId);
});
+ });
- it('verify selected parameter context with no permissions', () => {
- // change the selected parameter context to one with no permissions
- component.request = {
- ...data,
- entity: {
- ...data.entity,
- component: {
- ...data.entity.component,
- parameterContext: {
- id: noPermissionsParameterContextId
- }
- }
- }
- };
+ describe('user does NOT have permission to current parameter context', ()
=> {
+ const data: any = {
+ type: 'ProcessGroup',
+ uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
+ entity: {
+ revision: {
+ clientId: 'de5d3be3-05be-4ba5-bc42-729e7a4b00c4',
+ version: 14
+ },
+ id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
+ position: {
+ x: 446,
+ y: 151
+ },
+ permissions: {
+ canRead: true,
+ canWrite: true
+ },
+ bulletins: [],
+ component: {
+ id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ parentGroupId: '1621f9d1-018c-1000-cb13-7eab94ffe23c',
+ position: {
+ x: 446,
+ y: 151
+ },
+ name: 'pg2',
+ comments: '',
+ flowfileConcurrency: 'UNBOUNDED',
+ flowfileOutboundPolicy: 'BATCH_OUTPUT',
+ defaultFlowFileExpiration: '0 sec',
+ defaultBackPressureObjectThreshold: 10000,
+ defaultBackPressureDataSizeThreshold: '1 GB',
+ parameterContext: {
+ id: noPermissionsParameterContextId
+ },
+ executionEngine: 'INHERITED',
+ maxConcurrentTasks: 1,
+ statelessFlowTimeout: '1 min',
+ runningCount: 0,
+ stoppedCount: 0,
+ invalidCount: 0,
+ disabledCount: 0,
+ activeRemotePortCount: 0,
+ inactiveRemotePortCount: 0,
+ upToDateCount: 0,
+ locallyModifiedCount: 0,
+ staleCount: 0,
+ locallyModifiedAndStaleCount: 0,
+ syncFailureCount: 0,
+ localInputPortCount: 0,
+ localOutputPortCount: 0,
+ publicInputPortCount: 0,
+ publicOutputPortCount: 0,
+ statelessGroupScheduledState: 'STOPPED',
+ inputPortCount: 0,
+ outputPortCount: 0
+ },
+ runningCount: 0,
+ stoppedCount: 0,
+ invalidCount: 0,
+ disabledCount: 0,
+ activeRemotePortCount: 0,
+ inactiveRemotePortCount: 0,
+ upToDateCount: 0,
+ locallyModifiedCount: 0,
+ staleCount: 0,
+ locallyModifiedAndStaleCount: 0,
+ syncFailureCount: 0,
+ localInputPortCount: 0,
+ localOutputPortCount: 0,
+ publicInputPortCount: 0,
+ publicOutputPortCount: 0,
+ inputPortCount: 0,
+ outputPortCount: 0
+ }
+ };
- // reset the parameter contexts to rebuild the options
- component.parameterContexts = [...parameterContexts];
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [EditProcessGroup, NoopAnimationsModule],
+ providers: [
+ { provide: MAT_DIALOG_DATA, useValue: data },
+ provideMockStore({ initialState }),
+ {
+ provide: ClusterConnectionService,
+ useValue: {
+ isDisconnectionAcknowledged: jest.fn()
+ }
+ },
+ { provide: MatDialogRef, useValue: null }
+ ]
+ });
+ fixture = TestBed.createComponent(EditProcessGroup);
+ component = fixture.componentInstance;
+ component.parameterContexts = parameterContexts;
fixture.detectChanges();
+ });
- expect(component.parameterContextsOptions.length).toEqual(3);
+ it('verify selected parameter context with no permissions', () => {
+ expect(component.parameterContextsOptions.length).toEqual(2);
component.parameterContextsOptions.forEach((parameterContextsOption) => {
- if (parameterContextsOption.value ===
noPermissionsParameterContextId) {
+ if (
+ parameterContextsOption.value ===
noPermissionsParameterContextId &&
+ parameterContextsOption.text ===
noPermissionsParameterContextId
+ ) {
expect(parameterContextsOption.disabled).toBeFalsy();
} else if (parameterContextsOption.value ===
selectedParameterContextId) {
expect(parameterContextsOption.disabled).toBeFalsy();
}
});
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(
+ noPermissionsParameterContextId
+ );
});
+ });
- it('verify no selected parameter context', () => {
- // clear the selected parameter context
- component.request = {
- ...data,
- entity: {
- ...data.entity,
- component: {
- ...data.entity.component,
- parameterContext: undefined
- }
- }
- };
+ describe('when no current parameter context is set', () => {
+ const data: any = {
+ type: 'ProcessGroup',
+ uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
+ entity: {
+ revision: {
+ clientId: 'de5d3be3-05be-4ba5-bc42-729e7a4b00c4',
+ version: 14
+ },
+ id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ uri:
'https://localhost:4200/nifi-api/process-groups/162380af-018c-1000-a7eb-f5d06f77168b',
+ position: {
+ x: 446,
+ y: 151
+ },
+ permissions: {
+ canRead: true,
+ canWrite: true
+ },
+ bulletins: [],
+ component: {
+ id: '162380af-018c-1000-a7eb-f5d06f77168b',
+ parentGroupId: '1621f9d1-018c-1000-cb13-7eab94ffe23c',
+ position: {
+ x: 446,
+ y: 151
+ },
+ name: 'pg2',
+ comments: '',
+ flowfileConcurrency: 'UNBOUNDED',
+ flowfileOutboundPolicy: 'BATCH_OUTPUT',
+ defaultFlowFileExpiration: '0 sec',
+ defaultBackPressureObjectThreshold: 10000,
+ defaultBackPressureDataSizeThreshold: '1 GB',
+ parameterContext: {
+ id: undefined
+ },
+ executionEngine: 'INHERITED',
+ maxConcurrentTasks: 1,
+ statelessFlowTimeout: '1 min',
+ runningCount: 0,
+ stoppedCount: 0,
+ invalidCount: 0,
+ disabledCount: 0,
+ activeRemotePortCount: 0,
+ inactiveRemotePortCount: 0,
+ upToDateCount: 0,
+ locallyModifiedCount: 0,
+ staleCount: 0,
+ locallyModifiedAndStaleCount: 0,
+ syncFailureCount: 0,
+ localInputPortCount: 0,
+ localOutputPortCount: 0,
+ publicInputPortCount: 0,
+ publicOutputPortCount: 0,
+ statelessGroupScheduledState: 'STOPPED',
+ inputPortCount: 0,
+ outputPortCount: 0
+ },
+ runningCount: 0,
+ stoppedCount: 0,
+ invalidCount: 0,
+ disabledCount: 0,
+ activeRemotePortCount: 0,
+ inactiveRemotePortCount: 0,
+ upToDateCount: 0,
+ locallyModifiedCount: 0,
+ staleCount: 0,
+ locallyModifiedAndStaleCount: 0,
+ syncFailureCount: 0,
+ localInputPortCount: 0,
+ localOutputPortCount: 0,
+ publicInputPortCount: 0,
+ publicOutputPortCount: 0,
+ inputPortCount: 0,
+ outputPortCount: 0
+ }
+ };
- // reset the parameter contexts to rebuild the options
- component.parameterContexts = [...parameterContexts];
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [EditProcessGroup, NoopAnimationsModule],
+ providers: [
+ { provide: MAT_DIALOG_DATA, useValue: data },
+ provideMockStore({ initialState }),
+ {
+ provide: ClusterConnectionService,
+ useValue: {
+ isDisconnectionAcknowledged: jest.fn()
+ }
+ },
+ { provide: MatDialogRef, useValue: null }
+ ]
+ });
+ fixture = TestBed.createComponent(EditProcessGroup);
+ component = fixture.componentInstance;
+ component.parameterContexts = parameterContexts;
fixture.detectChanges();
+ });
- expect(component.parameterContextsOptions.length).toEqual(3);
+ it('verify no selected parameter context', () => {
+ expect(component.parameterContextsOptions.length).toEqual(2);
component.parameterContextsOptions.forEach((parameterContextsOption) => {
if (parameterContextsOption.value ===
noPermissionsParameterContextId) {
expect(parameterContextsOption.disabled).toBeTruthy();
@@ -239,6 +450,7 @@ describe('EditProcessGroup', () => {
expect(parameterContextsOption.disabled).toBeFalsy();
}
});
+
expect(component.editProcessGroupForm.get('parameterContext')?.value).toEqual(undefined);
});
});
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
index 254067de83..416aff7a4f 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
@@ -26,15 +26,19 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
-import { ParameterContextEntity } from '../../../../../../../state/shared';
import { Client } from '../../../../../../../service/client.service';
import { NifiSpinnerDirective } from
'../../../../../../../ui/common/spinner/nifi-spinner.directive';
-import { NifiTooltipDirective, SelectOption, TextTip } from '@nifi/shared';
import { EditComponentDialogRequest } from '../../../../../state/flow';
import { ClusterConnectionService } from
'../../../../../../../service/cluster-connection.service';
import { TabbedDialog } from
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
import { ErrorContextKey } from '../../../../../../../state/error';
import { ContextErrorBanner } from
'../../../../../../../ui/common/context-error-banner/context-error-banner.component';
+import { openNewParameterContextDialog } from
'../../../../../state/parameter/parameter.actions';
+import { Store } from '@ngrx/store';
+import { CanvasState } from '../../../../../state';
+import { ParameterContextEntity } from '../../../../../../../state/shared';
+import { NifiTooltipDirective, PipesModule, SelectOption, TextTip } from
'@nifi/shared';
+import { selectCurrentUser } from
'../../../../../../../state/current-user/current-user.selectors';
@Component({
selector: 'edit-process-group',
@@ -53,46 +57,56 @@ import { ContextErrorBanner } from
'../../../../../../../ui/common/context-error
NifiSpinnerDirective,
NifiTooltipDirective,
FormsModule,
- ContextErrorBanner
+ ContextErrorBanner,
+ PipesModule
],
styleUrls: ['./edit-process-group.component.scss']
})
export class EditProcessGroup extends TabbedDialog {
@Input() set parameterContexts(parameterContexts:
ParameterContextEntity[]) {
- this.initializeParameterContextOptions();
+ if (parameterContexts !== undefined) {
+ this.parameterContextsOptions = [];
+ this._parameterContexts = parameterContexts;
- parameterContexts.forEach((parameterContext) => {
- if (parameterContext.permissions.canRead &&
parameterContext.component) {
- this.parameterContextsOptions.push({
- text: parameterContext.component.name,
- value: parameterContext.id,
- description: parameterContext.component.description
- });
+ if (parameterContexts.length === 0) {
+ this.parameterContextsOptions = [];
} else {
- let disabled: boolean;
- if (this.request.entity.component.parameterContext) {
- disabled =
this.request.entity.component.parameterContext.id !== parameterContext.id;
- } else {
- disabled = true;
- }
+ parameterContexts.forEach((parameterContext) => {
+ if (parameterContext.permissions.canRead &&
parameterContext.component) {
+ this.parameterContextsOptions.push({
+ text: parameterContext.component.name,
+ value: parameterContext.id,
+ description: parameterContext.component.description
+ });
+ } else {
+ let disabled: boolean;
+ if (this.request.entity.component.parameterContext) {
+ disabled =
this.request.entity.component.parameterContext.id !== parameterContext.id;
+ } else {
+ disabled = true;
+ }
- this.parameterContextsOptions.push({
- text: parameterContext.id,
- value: parameterContext.id,
- disabled
+ this.parameterContextsOptions.push({
+ text: parameterContext.id,
+ value: parameterContext.id,
+ disabled
+ });
+ }
});
}
- });
- if (this.request.entity.component.parameterContext) {
- this.editProcessGroupForm.addControl(
- 'parameterContext',
- new
FormControl(this.request.entity.component.parameterContext.id)
- );
- } else {
- this.editProcessGroupForm.addControl('parameterContext', new
FormControl(null));
+ if (this.request.entity.component.parameterContext) {
+ this.editProcessGroupForm
+ .get('parameterContext')
+
?.setValue(this.request.entity.component.parameterContext.id);
+ }
}
}
+
+ get parameterContexts() {
+ return this._parameterContexts;
+ }
+
@Input() saving$!: Observable<boolean>;
@Output() editProcessGroup: EventEmitter<any> = new EventEmitter<any>();
@@ -100,10 +114,12 @@ export class EditProcessGroup extends TabbedDialog {
protected readonly STATELESS: string = 'STATELESS';
private initialMaxConcurrentTasks: number;
private initialStatelessFlowTimeout: string;
+ private _parameterContexts: ParameterContextEntity[] = [];
editProcessGroupForm: FormGroup;
readonly: boolean;
parameterContextsOptions: SelectOption[] = [];
+ currentUser$ = this.store.select(selectCurrentUser);
executionEngineOptions: SelectOption[] = [
{
@@ -170,14 +186,13 @@ export class EditProcessGroup extends TabbedDialog {
@Inject(MAT_DIALOG_DATA) public request: EditComponentDialogRequest,
private formBuilder: FormBuilder,
private client: Client,
- private clusterConnectionService: ClusterConnectionService
+ private clusterConnectionService: ClusterConnectionService,
+ private store: Store<CanvasState>
) {
super('edit-process-group-selected-index');
this.readonly = !request.entity.permissions.canWrite;
- this.initializeParameterContextOptions();
-
this.editProcessGroupForm = this.formBuilder.group({
name: new FormControl(request.entity.component.name,
Validators.required),
applyParameterContextRecursively: new FormControl({ value: false,
disabled: this.readonly }),
@@ -200,7 +215,8 @@ export class EditProcessGroup extends TabbedDialog {
Validators.required
),
logFileSuffix: new
FormControl(request.entity.component.logFileSuffix),
- comments: new FormControl(request.entity.component.comments)
+ comments: new FormControl(request.entity.component.comments),
+ parameterContext: new FormControl(null)
});
this.initialMaxConcurrentTasks =
request.entity.component.maxConcurrentTasks;
@@ -209,15 +225,6 @@ export class EditProcessGroup extends TabbedDialog {
this.executionEngineChanged(request.entity.component.executionEngine);
}
- private initializeParameterContextOptions(): void {
- this.parameterContextsOptions = [
- {
- text: 'No parameter context',
- value: null
- }
- ];
- }
-
executionEngineChanged(value: string): void {
if (value == this.STATELESS) {
this.editProcessGroupForm.addControl(
@@ -272,6 +279,10 @@ export class EditProcessGroup extends TabbedDialog {
this.editProcessGroup.next(payload);
}
+ openNewParameterContextDialog(): void {
+ this.store.dispatch(openNewParameterContextDialog({ request: {
parameterContexts: this._parameterContexts } }));
+ }
+
override isDirty(): boolean {
return this.editProcessGroupForm.dirty;
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
index d130baee78..a52cd08a10 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
@@ -50,7 +50,7 @@ export class ParameterContextService {
);
}
- getParameterContext(id: string, includeInheritedParameters: boolean):
Observable<any> {
+ getParameterContext(id: string, includeInheritedParameters: boolean =
true): Observable<any> {
return
this.httpClient.get(`${ParameterContextService.API}/parameter-contexts/${id}`, {
params: {
includeInheritedParameters
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
index f39b99e196..ec70ea82b6 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
@@ -16,17 +16,15 @@
*/
import { createAction, props } from '@ngrx/store';
+import { LoadParameterContextsResponse, SelectParameterContextRequest,
GetEffectiveParameterContext } from './index';
+import { PollParameterContextUpdateSuccess, SubmitParameterContextUpdate }
from '../../../../state/shared';
import {
CreateParameterContextRequest,
CreateParameterContextSuccess,
DeleteParameterContextRequest,
DeleteParameterContextSuccess,
- EditParameterContextRequest,
- LoadParameterContextsResponse,
- SelectParameterContextRequest,
- GetEffectiveParameterContext
-} from './index';
-import { PollParameterContextUpdateSuccess, SubmitParameterContextUpdate }
from '../../../../state/shared';
+ EditParameterContextRequest
+} from '../../../../ui/common/parameter-context';
export const loadParameterContexts = createAction('[Parameter Context Listing]
Load Parameter Contexts');
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
index ea5c5d506f..bd0bc4c71b 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
@@ -40,7 +40,6 @@ import { NiFiState } from '../../../../state';
import { Router } from '@angular/router';
import { ParameterContextService } from
'../../service/parameter-contexts.service';
import { Parameter, YesNoDialog } from '@nifi/shared';
-import { EditParameterContext } from
'../../ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component';
import {
selectParameterContexts,
selectParameterContextStatus,
@@ -55,6 +54,7 @@ import { HttpErrorResponse } from '@angular/common/http';
import { BackNavigation } from '../../../../state/navigation';
import { isDefinedAndNotNull, MEDIUM_DIALOG, SMALL_DIALOG, XL_DIALOG,
NiFiCommon, Storage } from '@nifi/shared';
import { ErrorContextKey } from '../../../../state/error';
+import { EditParameterContext } from
'../../../../ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component';
@Injectable()
export class ParameterContextListingEffects {
@@ -109,7 +109,7 @@ export class ParameterContextListingEffects {
dialogReference.componentInstance.createNewParameter = (
existingParameters: string[]
): Observable<EditParameterResponse> => {
- const dialogRequest: EditParameterRequest = {
existingParameters };
+ const dialogRequest: EditParameterRequest = {
existingParameters, isNewParameterContext: true };
const newParameterDialogReference =
this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
data: dialogRequest
@@ -135,7 +135,8 @@ export class ParameterContextListingEffects {
const dialogRequest: EditParameterRequest = {
parameter: {
...parameter
- }
+ },
+ isNewParameterContext: true
};
const editParameterDialogReference =
this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
@@ -254,7 +255,7 @@ export class ParameterContextListingEffects {
{ dispatch: false }
);
- navigateToEditService$ = createEffect(
+ navigateToEditParameterContext$ = createEffect(
() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.navigateToEditParameterContext),
@@ -311,6 +312,7 @@ export class ParameterContextListingEffects {
});
editDialogReference.componentInstance.updateRequest =
this.store.select(selectUpdateRequest);
+
editDialogReference.componentInstance.availableParameterContexts$ = this.store
.select(selectParameterContexts)
.pipe(
@@ -321,7 +323,10 @@ export class ParameterContextListingEffects {
editDialogReference.componentInstance.createNewParameter =
(
existingParameters: string[]
): Observable<EditParameterResponse> => {
- const dialogRequest: EditParameterRequest = {
existingParameters };
+ const dialogRequest: EditParameterRequest = {
+ existingParameters,
+ isNewParameterContext: false
+ };
const newParameterDialogReference =
this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
data: dialogRequest
@@ -333,7 +338,6 @@ export class ParameterContextListingEffects {
take(1),
map((dialogResponse: EditParameterResponse) => {
newParameterDialogReference.close();
-
return {
...dialogResponse
};
@@ -347,7 +351,8 @@ export class ParameterContextListingEffects {
const dialogRequest: EditParameterRequest = {
parameter: {
...parameter
- }
+ },
+ isNewParameterContext: false
};
const editParameterDialogReference =
this.dialog.open(EditParameterDialog, {
...MEDIUM_DIALOG,
@@ -360,7 +365,6 @@ export class ParameterContextListingEffects {
take(1),
map((dialogResponse: EditParameterResponse) => {
editParameterDialogReference.close();
-
return {
...dialogResponse
};
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html
index dfee986e38..44dce74ab8 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html
@@ -31,7 +31,8 @@
<td mat-cell *matCellDef="let item">
<div
[ngClass]="{ unset: !canRead(item), 'neutral-color':
!canRead(item) }"
- class="overflow-hidden overflow-ellipsis
whitespace-nowrap">
+ class="truncate"
+ [title]="formatName(item)">
{{ formatName(item) }}
</div>
</td>
@@ -40,7 +41,7 @@
<!-- Provider Column -->
<ng-container matColumnDef="provider">
<th mat-header-cell *matHeaderCellDef
mat-sort-header>Provider</th>
- <td mat-cell *matCellDef="let item">
+ <td mat-cell *matCellDef="let item"
[title]="formatProvider(item)">
{{ formatProvider(item) }}
</td>
</ng-container>
@@ -48,7 +49,7 @@
<!-- Description Column -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef
mat-sort-header>Description</th>
- <td mat-cell *matCellDef="let item">
+ <td mat-cell *matCellDef="let item"
[title]="formatDescription(item)">
{{ formatDescription(item) }}
</td>
</ng-container>
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
index 1143a4d062..6033e4b1f2 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/shared/index.ts
@@ -49,6 +49,8 @@ export interface NewPropertyDialogResponse {
export interface EditParameterRequest {
existingParameters?: string[];
parameter?: Parameter;
+ isNewParameterContext: boolean;
+ isConvert?: boolean;
}
export interface EditParameterResponse {
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/edit-parameter-dialog/edit-parameter-dialog.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/edit-parameter-dialog/edit-parameter-dialog.component.spec.ts
index c8a85340bb..d179105fbd 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/edit-parameter-dialog/edit-parameter-dialog.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/edit-parameter-dialog/edit-parameter-dialog.component.spec.ts
@@ -27,6 +27,7 @@ describe('EditParameterDialog', () => {
let fixture: ComponentFixture<EditParameterDialog>;
const data: EditParameterRequest = {
+ isNewParameterContext: false,
parameter: {
name: 'one',
description: 'Description for one.',
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html
similarity index 99%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html
index 26cd7a97af..bbb60b6337 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.html
@@ -89,7 +89,7 @@
<div class="dialog-tab-content flex gap-x-4">
<div class="w-full">
@if (!isNew) {
- <div class="flex flex-col mb-5">
+ <div class="flex w-full flex-col mb-5">
<div>Id</div>
@if (request.parameterContext) {
<div
[copy]="request.parameterContext.id" class="tertiary-color font-medium">
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.scss
similarity index 100%
copy from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.scss
copy to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.scss
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.spec.ts
similarity index 96%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.spec.ts
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.spec.ts
index 1b246dd019..a462c677d0 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.spec.ts
@@ -18,16 +18,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditParameterContext } from './edit-parameter-context.component';
-import { EditParameterContextRequest } from
'../../../state/parameter-context-listing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideMockStore } from '@ngrx/store/testing';
-import { initialState } from
'../../../state/parameter-context-listing/parameter-context-listing.reducer';
-import { ClusterConnectionService } from
'../../../../../service/cluster-connection.service';
-import { ParameterContextEntity } from '../../../../../state/shared';
+import { initialState } from
'../../../../pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.reducer';
+import { ClusterConnectionService } from
'../../../../service/cluster-connection.service';
+import { ParameterContextEntity } from '../../../../state/shared';
import 'codemirror/addon/hint/show-hint';
+import { EditParameterContextRequest } from '../index';
describe('EditParameterContext', () => {
let component: EditParameterContext;
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
similarity index 89%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
index 3da070af09..05fa492f52 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
@@ -26,10 +26,10 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
-import { EditParameterContextRequest } from
'../../../state/parameter-context-listing';
-import { NifiSpinnerDirective } from
'../../../../../ui/common/spinner/nifi-spinner.directive';
-import { Client } from '../../../../../service/client.service';
-import { ParameterTable } from '../parameter-table/parameter-table.component';
+import { EditParameterContextRequest } from
'../../../../pages/parameter-contexts/state/parameter-context-listing';
+import { NifiSpinnerDirective } from '../../spinner/nifi-spinner.directive';
+import { Client } from '../../../../service/client.service';
+import { ParameterTable } from
'../../../../pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component';
import {
EditParameterResponse,
ParameterContext,
@@ -37,16 +37,16 @@ import {
ParameterContextUpdateRequestEntity,
ParameterEntity,
ParameterProviderConfiguration
-} from '../../../../../state/shared';
-import { ProcessGroupReferences } from
'../process-group-references/process-group-references.component';
+} from '../../../../state/shared';
+import { ProcessGroupReferences } from
'../../../../pages/parameter-contexts/ui/parameter-context-listing/process-group-references/process-group-references.component';
import { ParameterContextInheritance } from
'../parameter-context-inheritance/parameter-context-inheritance.component';
-import { ParameterReferences } from
'../../../../../ui/common/parameter-references/parameter-references.component';
+import { ParameterReferences } from
'../../parameter-references/parameter-references.component';
import { RouterLink } from '@angular/router';
-import { ClusterConnectionService } from
'../../../../../service/cluster-connection.service';
-import { TabbedDialog } from
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { ClusterConnectionService } from
'../../../../service/cluster-connection.service';
+import { TabbedDialog } from '../../tabbed-dialog/tabbed-dialog.component';
import { NiFiCommon, TextTip, NifiTooltipDirective, CopyDirective, Parameter }
from '@nifi/shared';
-import { ErrorContextKey } from '../../../../../state/error';
-import { ContextErrorBanner } from
'../../../../../ui/common/context-error-banner/context-error-banner.component';
+import { ErrorContextKey } from '../../../../state/error';
+import { ContextErrorBanner } from
'../../context-error-banner/context-error-banner.component';
@Component({
selector: 'edit-parameter-context',
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/index.ts
similarity index 56%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.scss
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/index.ts
index 364838b29f..4d4847b779 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.scss
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/index.ts
@@ -15,16 +15,28 @@
* limitations under the License.
*/
-@use '@angular/material' as mat;
+import { ParameterContextEntity } from '../../../state/shared';
-.parameter-context-edit-form {
- @include mat.button-density(-1);
+export interface EditParameterContextRequest {
+ parameterContext?: ParameterContextEntity;
+}
+
+export interface CreateParameterContextRequest {
+ payload: any;
+}
+
+export interface CreateParameterContextSuccess {
+ parameterContext: ParameterContextEntity;
+}
+
+export interface OpenCreateParameterContextRequest {
+ parameterContexts: ParameterContextEntity[];
+}
- .mat-mdc-form-field {
- width: 100%;
- }
+export interface DeleteParameterContextRequest {
+ parameterContext: ParameterContextEntity;
+}
- mat-dialog-actions {
- margin-top: auto;
- }
+export interface DeleteParameterContextSuccess {
+ parameterContext: ParameterContextEntity;
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.html
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.html
similarity index 100%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.html
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.html
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.scss
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.scss
similarity index 100%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.scss
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.scss
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.spec.ts
similarity index 100%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.spec.ts
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.spec.ts
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.ts
similarity index 89%
rename from
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts
rename to
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.ts
index 3bd3f195a5..14e9aae6a4 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/parameter-context-inheritance/parameter-context-inheritance.component.ts
@@ -21,8 +21,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTableModule } from '@angular/material/table';
import { NgTemplateOutlet } from '@angular/common';
-import { ParameterContextEntity } from '../../../../../state/shared';
-import { NifiTooltipDirective, NiFiCommon, TextTip,
ParameterContextReferenceEntity } from '@nifi/shared';
+import { ParameterContextEntity } from '../../../../state/shared';
import {
DragDropModule,
CdkDrag,
@@ -31,6 +30,8 @@ import {
moveItemInArray,
transferArrayItem
} from '@angular/cdk/drag-drop';
+import { NiFiCommon, NifiTooltipDirective, ParameterContextReferenceEntity,
PipesModule, TextTip } from '@nifi/shared';
+import { SortObjectByPropertyPipe } from
'../../../../../../../../libs/shared/src/pipes/sort-by-property.pipe';
@Component({
selector: 'parameter-context-inheritance',
@@ -44,14 +45,16 @@ import {
NgTemplateOutlet,
NifiTooltipDirective,
CdkDropList,
- CdkDrag
+ CdkDrag,
+ PipesModule
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ParameterContextInheritance),
multi: true
- }
+ },
+ SortObjectByPropertyPipe
],
styleUrls: ['./parameter-context-inheritance.component.scss']
})
@@ -75,7 +78,10 @@ export class ParameterContextInheritance implements
ControlValueAccessor {
inheritedParameterContexts!: ParameterContextReferenceEntity[];
- constructor(private nifiCommon: NiFiCommon) {}
+ constructor(
+ private nifiCommon: NiFiCommon,
+ private sortObjectByPropertyPipe: SortObjectByPropertyPipe
+ ) {}
private processParameterContexts(): void {
this.availableParameterContexts = [];
@@ -92,6 +98,8 @@ export class ParameterContextInheritance implements
ControlValueAccessor {
this.availableParameterContexts.push(parameterContext);
}
});
+
+
this.sortObjectByPropertyPipe.transform(this.availableParameterContexts,
'component.name');
}
}
@@ -168,6 +176,8 @@ export class ParameterContextInheritance implements
ControlValueAccessor {
// emit the changes
this.onChange(this.serializeInheritedParameterContexts());
+
+
this.sortObjectByPropertyPipe.transform(this.availableParameterContexts,
'component.name');
}
private serializeInheritedParameterContexts():
ParameterContextReferenceEntity[] {
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_app.scss
b/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_app.scss
index 89beaac405..052820d4df 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_app.scss
+++ b/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_app.scss
@@ -318,6 +318,13 @@
min-height: 2.25rem;
}
+ // mat-select mat-option ellipsis
+ .mat-mdc-option .mdc-list-item__primary-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap !important;
+ }
+
// markdown styles
.mat-typography.text-base markdown {
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/directives/copy/copy.directive.spec.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/directives/copy/copy.directive.spec.ts
index 78f1a09b7e..e299d85ef8 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/directives/copy/copy.directive.spec.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/directives/copy/copy.directive.spec.ts
@@ -67,13 +67,18 @@ describe('CopyDirective', () => {
expect(copyButton).toBeNull();
});
- xit('should copy text to clipboard and change button appearance on click',
async () => {
- // Mock the clipboard's writeText method
- Object.assign(navigator, {
- clipboard: {
- writeText: jest.fn().mockResolvedValue(undefined)
- }
+ it('should copy text to clipboard and change button appearance on click',
async () => {
+ // Mock the clipboard object with a writable property
+ const mockClipboard = {
+ writeText: jest.fn().mockResolvedValue(undefined)
+ };
+
+ // Use Object.defineProperty to define a writable property
+ Object.defineProperty(navigator, 'clipboard', {
+ value: mockClipboard,
+ writable: true
});
+
directiveDebugEl.triggerEventHandler('mouseenter', null);
fixture.detectChanges();
const copyButton =
directiveDebugEl.nativeElement.querySelector('.copy-button');
diff --git a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/index.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/index.ts
index e3eea9b9b1..5193ab0b57 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/index.ts
+++ b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/index.ts
@@ -18,4 +18,5 @@
export * from './component-type-name.pipe';
export * from './join.pipe';
export * from './sort.pipe';
+export * from './sort-by-property.pipe';
export * from './pipes.module';
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
index cbbe94c2fa..e957b3ff13 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
+++ b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
@@ -17,12 +17,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
+import { SortObjectByPropertyPipe } from './sort-by-property.pipe';
import { JoinPipe } from './join.pipe';
import { SortPipe } from './sort.pipe';
@NgModule({
- declarations: [SortPipe, JoinPipe],
- exports: [SortPipe, JoinPipe],
+ declarations: [SortPipe, SortObjectByPropertyPipe, JoinPipe],
+ exports: [SortPipe, SortObjectByPropertyPipe, JoinPipe],
imports: [CommonModule]
})
export class PipesModule {}
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.spec.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.spec.ts
new file mode 100644
index 0000000000..f5e34ddb7e
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.spec.ts
@@ -0,0 +1,132 @@
+/*
+ * 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 { SortableBy, SortObjectByPropertyPipe } from './sort-by-property.pipe';
+
+describe('SortObjectByPipe', () => {
+ let pipe: SortObjectByPropertyPipe;
+
+ beforeEach(() => {
+ pipe = new SortObjectByPropertyPipe();
+ });
+
+ it('should sort objects alphabetically by name', () => {
+ const items: SortableBy[] = [
+ { name: 'Banana', value: 'Fruit' },
+ { name: 'Apple', value: 'Fruit' },
+ { name: 'Carrot', value: 'Vegetable' }
+ ];
+ const result = pipe.transform(items);
+ expect(result).toEqual([
+ { name: 'Apple', value: 'Fruit' },
+ { name: 'Banana', value: 'Fruit' },
+ { name: 'Carrot', value: 'Vegetable' }
+ ]);
+ });
+
+ it('should handle case-insensitive sorting', () => {
+ const items: SortableBy[] = [{ name: 'banana' }, { name: 'Apple' }, {
name: 'carrot' }];
+ const result = pipe.transform(items);
+ expect(result).toEqual([{ name: 'Apple' }, { name: 'banana' }, { name:
'carrot' }]);
+ });
+
+ it('should return an empty array if input is empty', () => {
+ const items: SortableBy[] = [];
+ const result = pipe.transform(items);
+ expect(result).toEqual([]);
+ });
+
+ it('should handle a single-item array', () => {
+ const items: SortableBy[] = [{ name: 'Apple' }];
+ const result = pipe.transform(items);
+ expect(result).toEqual([{ name: 'Apple' }]);
+ });
+
+ it('should handle null or undefined value properties', () => {
+ const items: any[] = [{ name: 'Apple' }, { name: undefined }, { name:
null }];
+ const result = pipe.transform(items);
+ expect(result).toEqual([{ name: undefined }, { name: null }, { name:
'Apple' }]);
+ });
+
+ it('should handle an array of objects with only missing name properties',
() => {
+ const items: SortableBy[] = [{ color: 'Blue' }, { color: 'Red' }, {
color: 'Green' }];
+ const result = pipe.transform(items);
+ expect(result).toEqual([{ color: 'Blue' }, { color: 'Red' }, { color:
'Green' }]);
+ });
+
+ it('should sort objects by nested properties', () => {
+ const items = [
+ {
+ id: '12345',
+ component: {
+ name: 'bbb'
+ }
+ },
+ {
+ id: '98765',
+ component: {
+ name: 'aaa'
+ }
+ }
+ ];
+
+ const sortedItems = pipe.transform(items, 'component.name');
+
+ expect(sortedItems).toEqual([
+ {
+ id: '98765',
+ component: {
+ name: 'aaa'
+ }
+ },
+ {
+ id: '12345',
+ component: {
+ name: 'bbb'
+ }
+ }
+ ]);
+ });
+
+ it('should handle missing nested properties gracefully', () => {
+ const items = [
+ {
+ id: '12345',
+ component: {
+ name: 'bbb'
+ }
+ },
+ {
+ id: '98765'
+ }
+ ];
+
+ const sortedItems = pipe.transform(items, 'component.name');
+
+ expect(sortedItems).toEqual([
+ {
+ id: '98765'
+ },
+ {
+ id: '12345',
+ component: {
+ name: 'bbb'
+ }
+ }
+ ]);
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.ts
similarity index 51%
copy from nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
copy to
nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.ts
index cbbe94c2fa..b0fc2be2a6 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/pipes.module.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/sort-by-property.pipe.ts
@@ -15,14 +15,24 @@
* limitations under the License.
*/
-import { NgModule } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { JoinPipe } from './join.pipe';
-import { SortPipe } from './sort.pipe';
+import { Pipe, PipeTransform } from '@angular/core';
-@NgModule({
- declarations: [SortPipe, JoinPipe],
- exports: [SortPipe, JoinPipe],
- imports: [CommonModule]
+export interface SortableBy {
+ [key: string]: any; // Allows for flexibility with additional properties
+}
+
+@Pipe({
+ name: 'sortObjectByProperty',
+ pure: true // Set to true to ensure the pipe is only recalculated when
inputs change
})
-export class PipesModule {}
+export class SortObjectByPropertyPipe implements PipeTransform {
+ transform(items: SortableBy[], property: string = 'name'): any[] {
+ return items.sort((a, b) =>
+ this.getPropertyValue(a,
property).localeCompare(this.getPropertyValue(b, property))
+ );
+ }
+
+ private getPropertyValue(item: SortableBy, propertyPath: string): any {
+ return propertyPath.split('.').reduce((obj, key) => (obj ? obj[key] :
''), item) ?? '';
+ }
+}