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) ?? '';
+    }
+}


Reply via email to