This is an automated email from the ASF dual-hosted git repository.

mcgilman 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 6679680852 [NIFI-13470] Jolt codemirror linting (#9068)
6679680852 is described below

commit 6679680852308eeba54ccea6dcf08d02ff777207
Author: Scott Aslan <[email protected]>
AuthorDate: Thu Jul 25 14:17:25 2024 -0400

    [NIFI-13470] Jolt codemirror linting (#9068)
    
    * [NIFI-13470] Jolt codemirror linting
    
    * fix nifi-jolt-transform-ui:build:production
    
    * remove file and system from deps
    
    * rebase
    
    * remove assert
    
    * protect against undefined codeEditor.codeMirror
    
    * force resolve underscore 1.13.6
    
    * refactor to have processor details state, validation state, property 
state, and transform state separated in the store
    
    * more clean up of state
    
    * move Effects injection into jolt-transform-json-ui.module
    
    * use effects .forFeature()
    
    This closes #9068
---
 .../apps/nifi-jolt-transform-ui/project.json       |   2 +-
 .../nifi-jolt-transform-ui/src/app/app.module.ts   |   6 +-
 .../feature/jolt-transform-json-ui.component.html  |  70 ++++++----
 .../jolt-transform-json-ui.component.spec.ts       |   2 +-
 .../feature/jolt-transform-json-ui.component.ts    | 152 +++++++++++++++++----
 .../feature/jolt-transform-json-ui.module.ts       |  12 +-
 .../service/jolt-transform-json-ui.service.ts      |   3 +-
 .../pages/jolt-transform-json-ui/state/index.ts    |  31 ++++-
 .../index.ts}                                      |  33 ++---
 ...olt-transform-json-processor-details.actions.ts |  41 ++++++
 ...olt-transform-json-processor-details.effects.ts |  56 ++++++++
 ...olt-transform-json-processor-details.reducer.ts |  44 ++++++
 ...-transform-json-processor-details.selectors.ts} |  27 ++--
 .../state/jolt-transform-json-property/index.ts}   |  30 ++--
 .../jolt-transform-json-property.actions.ts        |  45 ++++++
 .../jolt-transform-json-property.effects.ts        |  63 +++++++++
 .../jolt-transform-json-property.reducer.ts        |  59 ++++++++
 .../jolt-transform-json-property.selectors.ts}     |  31 +++--
 .../state/jolt-transform-json-transform/index.ts}  |  30 ++--
 .../jolt-transform-json-transform.actions.ts       |  41 ++++++
 .../jolt-transform-json-transform.effects.ts       |  56 ++++++++
 .../jolt-transform-json-transform.reducer.ts       |  53 +++++++
 .../jolt-transform-json-transform.selectors.ts}    |  31 +++--
 .../jolt-transform-json-ui.actions.ts              | 101 --------------
 .../jolt-transform-json-ui.effects.ts              | 133 ------------------
 .../jolt-transform-json-ui.reducer.ts              | 121 ----------------
 .../index.ts                                       |  40 +-----
 .../jolt-transform-json-validate.actions.ts        |  45 ++++++
 .../jolt-transform-json-validate.effects.ts        |  63 +++++++++
 .../jolt-transform-json-validate.reducer.ts        |  59 ++++++++
 .../jolt-transform-json-validate.selectors.ts}     |  31 +++--
 .../apps/nifi-jolt-transform-ui/src/main.ts        |  14 +-
 .../apps/nifi-jolt-transform-ui/src/styles.scss    |   2 -
 .../libs/shared/src/assets/styles/_app.scss        |  13 --
 .../src/assets/styles/_codemirror-theme.scss       |   9 ++
 nifi-frontend/src/main/frontend/package-lock.json  | 141 +++++++++++++++----
 nifi-frontend/src/main/frontend/package.json       |  14 +-
 37 files changed, 1089 insertions(+), 615 deletions(-)

diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/project.json 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/project.json
index 55d86718e1..c1e9401a57 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/project.json
+++ b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/project.json
@@ -32,7 +32,7 @@
                     "includePaths": [""]
                 },
                 "scripts": [],
-                "allowedCommonJsDependencies": ["codemirror"],
+                "allowedCommonJsDependencies": ["codemirror", "jsonlint", 
"js-beautify"],
                 "preserveSymlinks": true
             },
             "configurations": {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/app.module.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/app.module.ts
index 273989ef76..2906667eca 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/app.module.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/app.module.ts
@@ -30,9 +30,8 @@ import { environment } from '../environments/environment';
 import { provideHttpClient, withInterceptors, withXsrfConfiguration } from 
'@angular/common/http';
 import { NavigationActionTiming, RouterState, StoreRouterConnectingModule } 
from '@ngrx/router-store';
 import { rootReducers } from './state';
-import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
 import { EffectsModule } from '@ngrx/effects';
-import { JoltTransformJsonUiEffects } from 
'./pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.effects';
+import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
 
 const entry = localStorage.getItem('disable-animations');
 let disableAnimations: string = entry !== null ? JSON.parse(entry).item : '';
@@ -54,7 +53,8 @@ if (disableAnimations !== 'true' && disableAnimations !== 
'false') {
             routerState: RouterState.Minimal,
             navigationActionTiming: NavigationActionTiming.PostActivation
         }),
-        EffectsModule.forRoot(JoltTransformJsonUiEffects),
+
+        EffectsModule.forRoot(),
         StoreDevtoolsModule.instrument({
             maxAge: 25,
             logOnly: environment.production,
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.html
index b41da2d267..ecad525303 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.html
@@ -83,7 +83,9 @@
                                         <button
                                             mat-icon-button
                                             color="primary"
-                                            title="Format Jolt Specification"
+                                            nifiTooltip
+                                            [tooltipComponentType]="TextTip"
+                                            
[tooltipInputData]="getFormatTooltip()"
                                             (click)="formatSpecification()">
                                             <i class="fa fa-align-left"></i>
                                         </button>
@@ -91,10 +93,11 @@
                                     <ngx-codemirror
                                         formControlName="specification"
                                         [options]="getJoltSpecOptions()"
-                                        
(mousedown)="preventDrag($event)"></ngx-codemirror>
+                                        (mousedown)="preventDrag($event)"
+                                        
(codeMirrorLoaded)="initSpecEditor($event)"></ngx-codemirror>
                                 </div>
                                 <div class="w-full overflow-ellipsis 
overflow-hidden whitespace-nowrap">
-                                    @if (joltState().validatingJoltSpec) {
+                                    @if (joltState.validate().saving) {
                                         <i
                                             class="fa fa-refresh fa-spin mr-2"
                                             nifiTooltip
@@ -102,7 +105,7 @@
                                             
tooltipInputData="Validating..."></i
                                         ><mat-hint>Validating...</mat-hint>
                                     } @else {
-                                        @if 
(joltState().validationResponse?.valid) {
+                                        @if 
(joltState.validate().validationResponse?.valid) {
                                             <i
                                                 class="fa fa-check 
success-color mr-2"
                                                 nifiTooltip
@@ -110,25 +113,31 @@
                                                 tooltipInputData="The 
specification is valid."></i
                                             ><mat-hint>The specification is 
valid.</mat-hint>
                                         } @else if (
-                                            
joltState().validationResponse?.valid === false &&
-                                            
joltState().validationResponse?.message !== null
+                                            
joltState.validate().validationResponse?.valid === false &&
+                                            
joltState.validate().validationResponse?.message !== null
                                         ) {
                                             <i
                                                 class="fa fa-warning 
caution-color mr-2"
                                                 nifiTooltip
                                                 
[tooltipComponentType]="TextTip"
-                                                
[tooltipInputData]="joltState().validationResponse?.message"></i
-                                            ><mat-hint>{{ 
joltState().validationResponse?.message }}</mat-hint>
-                                        } @else if 
(joltState().validationFailureResponse) {
+                                                [tooltipInputData]="
+                                                    
joltState.validate().validationResponse?.message
+                                                "></i
+                                            ><mat-hint>{{ 
joltState.validate().validationResponse?.message }}</mat-hint>
+                                        } @else if 
(joltState.validate().validationFailureResponse) {
                                             <i
                                                 class="fa fa-warning 
caution-color mr-2"
                                                 nifiTooltip
                                                 
[tooltipComponentType]="TextTip"
-                                                
[tooltipInputData]="joltState().validationFailureResponse?.message"></i
-                                            ><mat-hint>{{ 
joltState().validationFailureResponse?.message }}</mat-hint>
+                                                [tooltipInputData]="
+                                                    
joltState.validate().validationFailureResponse?.message
+                                                "></i
+                                            ><mat-hint>{{
+                                                
joltState.validate().validationFailureResponse?.message
+                                            }}</mat-hint>
                                         }
                                     }
-                                    @if (joltState().savingProperties) {
+                                    @if (joltState.properties().saving) {
                                         <i
                                             class="fa fa-refresh fa-spin 
text-shadow mr-2"
                                             nifiTooltip
@@ -136,31 +145,31 @@
                                             tooltipInputData="Saving 
Properties..."></i
                                         ><mat-hint>Saving...</mat-hint>
                                     } @else {
-                                        @if 
(joltState().savePropertiesResponse) {
+                                        @if 
(joltState.properties().savePropertiesResponse) {
                                             <i
                                                 class="fa fa-check 
success-color text-shadow mr-2"
                                                 nifiTooltip
                                                 
[tooltipComponentType]="TextTip"
                                                 tooltipInputData="Properties 
successfully saved!"></i
                                             ><mat-hint>Properties successfully 
saved!</mat-hint>
-                                        } @else if 
(joltState().savePropertiesFailureResponse) {
+                                        } @else if 
(joltState.properties().savePropertiesFailureResponse) {
                                             <i
                                                 class="fa fa-warning 
caution-color text-shadow mr-2"
                                                 nifiTooltip
                                                 
[tooltipComponentType]="TextTip"
                                                 [tooltipInputData]="
-                                                    
joltState().savePropertiesFailureResponse?.message
+                                                    
joltState.properties().savePropertiesFailureResponse?.message
                                                 "></i
                                             ><mat-hint>{{
-                                                
joltState().savePropertiesFailureResponse?.message
+                                                
joltState.properties().savePropertiesFailureResponse?.message
                                             }}</mat-hint>
                                         }
                                     }
                                     @if (
-                                        joltState().savePropertiesResponse === 
null &&
-                                        
joltState().savePropertiesFailureResponse === null &&
-                                        joltState().validationResponse === 
null &&
-                                        joltState().validationFailureResponse 
=== null
+                                        
joltState.properties().savePropertiesResponse === null &&
+                                        
joltState.properties().savePropertiesFailureResponse === null &&
+                                        
joltState.validate().validationResponse === null &&
+                                        
joltState.validate().validationFailureResponse === null
                                     ) {
                                         <i
                                             class="fa fa-info-circle 
primary-color mr-2"
@@ -233,7 +242,9 @@
                                                 <button
                                                     mat-icon-button
                                                     color="primary"
-                                                    title="Format flow file 
content"
+                                                    nifiTooltip
+                                                    
[tooltipComponentType]="TextTip"
+                                                    
[tooltipInputData]="getFormatTooltip()"
                                                     (click)="formatInput()">
                                                     <i class="fa 
fa-align-left"></i>
                                                 </button>
@@ -241,7 +252,8 @@
                                             <ngx-codemirror
                                                 formControlName="input"
                                                 
[options]="getExampleDataOptions()"
-                                                
(mousedown)="preventDrag($event)"></ngx-codemirror>
+                                                
(mousedown)="preventDrag($event)"
+                                                
(codeMirrorLoaded)="initInputEditor($event)"></ngx-codemirror>
                                         </div>
                                     </mat-expansion-panel>
                                     <mat-expansion-panel [expanded]="step === 
1" (opened)="setStep(1)" hideToggle>
@@ -281,7 +293,7 @@
                                 </mat-accordion>
                                 <div class="flex flex-1 mt-2 justify-start">
                                     <div class="w-full overflow-ellipsis 
overflow-hidden whitespace-nowrap">
-                                        @if (joltState().transformingJoltSpec) 
{
+                                        @if (joltState.transform().saving) {
                                             <i
                                                 class="fa fa-refresh fa-spin 
mr-2"
                                                 nifiTooltip
@@ -289,23 +301,23 @@
                                                 
tooltipInputData="Transforming..."></i
                                             
><mat-hint>Transforming...</mat-hint>
                                         } @else {
-                                            @if 
(joltState().transformationResponse) {
+                                            @if 
(joltState.transform().transformationResponse) {
                                                 <i
                                                     class="fa fa-check 
success-color mr-2"
                                                     nifiTooltip
                                                     
[tooltipComponentType]="TextTip"
                                                     tooltipInputData="The 
transform was successful."></i
                                                 ><mat-hint>The transform was 
successful.</mat-hint>
-                                            } @else if 
(joltState().transformationFailureResponse) {
+                                            } @else if 
(joltState.transform().transformationFailureResponse) {
                                                 <i
                                                     class="fa fa-warning 
caution-color mr-2"
                                                     nifiTooltip
                                                     
[tooltipComponentType]="TextTip"
                                                     [tooltipInputData]="
-                                                        
joltState().transformationFailureResponse?.message
+                                                        
joltState.transform().transformationFailureResponse?.message
                                                     "></i
                                                 ><mat-hint>{{
-                                                    
joltState().transformationFailureResponse?.message
+                                                    
joltState.transform().transformationFailureResponse?.message
                                                 }}</mat-hint>
                                             } @else {
                                                 <i
@@ -355,8 +367,8 @@
                                 <div class="output-editor mt-4 mb-4 flex-1">
                                     <ngx-codemirror
                                         [ngModel]="
-                                            joltState().transformationResponse
-                                                ? 
(joltState().transformationResponse | json)
+                                            
joltState.transform().transformationResponse
+                                                ? 
(joltState.transform().transformationResponse | json)
                                                 : ''
                                         "
                                         [ngModelOptions]="{ standalone: true }"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.spec.ts
index dd1fb92026..29d0a23e82 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.spec.ts
@@ -18,7 +18,7 @@
 import { JoltTransformJsonUi } from './jolt-transform-json-ui.component';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { provideMockStore } from '@ngrx/store/testing';
-import { initialState } from 
'../state/jolt-transform-json-ui/jolt-transform-json-ui.reducer';
+import { initialState } from 
'../state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.reducer';
 import { joltTransformJsonUiFeatureKey } from '../state';
 
 describe('jolt-transform-json-ui', () => {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts
index 9a224c684b..895cf95fe3 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts
@@ -16,7 +16,7 @@
  */
 
 import { js_beautify } from 'js-beautify';
-import { Component, OnDestroy } from '@angular/core';
+import { Component, OnDestroy, Renderer2 } from '@angular/core';
 import { Store } from '@ngrx/store';
 import { NiFiJoltTransformJsonUiState } from '../../../state';
 import { TextTip, isDefinedAndNotNull, MapTableHelperService, MapTableEntry } 
from '@nifi/shared';
@@ -24,23 +24,38 @@ import {
     selectClientIdFromRoute,
     selectDisconnectedNodeAcknowledgedFromRoute,
     selectEditableFromRoute,
-    selectJoltTransformJsonUiState,
+    selectJoltTransformJsonProcessorDetailsState,
     selectProcessorDetails,
     selectProcessorIdFromRoute,
     selectRevisionFromRoute
-} from '../state/jolt-transform-json-ui/jolt-transform-json-ui.selectors';
+} from 
'../state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.selectors';
 import { Observable, tap } from 'rxjs';
 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 import {
     loadProcessorDetails,
-    resetJoltTransformJsonUiState,
+    resetJoltTransformJsonProcessorDetailsState
+} from 
'../state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.actions';
+import { FormBuilder, FormControl, FormGroup, Validators } from 
'@angular/forms';
+import { Editor, EditorChange, EditorFromTextArea } from 'codemirror';
+import { CodemirrorComponent } from 
'@ctrl/ngx-codemirror/codemirror.component';
+import {
+    resetJoltTransformJsonTransformState,
+    transformJoltSpec
+} from 
'../state/jolt-transform-json-transform/jolt-transform-json-transform.actions';
+import {
+    resetJoltTransformJsonValidateState,
     resetValidateJoltSpecState,
-    saveProperties,
-    transformJoltSpec,
     validateJoltSpec
-} from '../state/jolt-transform-json-ui/jolt-transform-json-ui.actions';
-import { SavePropertiesRequest, ValidateJoltSpecRequest } from 
'../state/jolt-transform-json-ui';
-import { FormBuilder, FormControl, FormGroup, Validators } from 
'@angular/forms';
+} from 
'../state/jolt-transform-json-validate/jolt-transform-json-validate.actions';
+import {
+    resetJoltTransformJsonPropertyState,
+    saveProperties
+} from 
'../state/jolt-transform-json-property/jolt-transform-json-property.actions';
+import { SavePropertiesRequest } from '../state/jolt-transform-json-property';
+import { ValidateJoltSpecRequest } from 
'../state/jolt-transform-json-validate';
+import { selectJoltTransformJsonPropertyState } from 
'../state/jolt-transform-json-property/jolt-transform-json-property.selectors';
+import { selectJoltTransformJsonValidateState } from 
'../state/jolt-transform-json-validate/jolt-transform-json-validate.selectors';
+import { selectJoltTransformJsonTransformState } from 
'../state/jolt-transform-json-transform/jolt-transform-json-transform.selectors';
 
 const JS_BEAUTIFY_OPTIONS = {
     indent_size: 2,
@@ -62,7 +77,12 @@ export class JoltTransformJsonUi implements OnDestroy {
 
     editJoltTransformJSONProcessorForm: FormGroup;
     step = 0;
-    joltState = this.store.selectSignal(selectJoltTransformJsonUiState);
+    joltState = {
+        processorDetails: 
this.store.selectSignal(selectJoltTransformJsonProcessorDetailsState),
+        properties: 
this.store.selectSignal(selectJoltTransformJsonPropertyState),
+        validate: 
this.store.selectSignal(selectJoltTransformJsonValidateState),
+        transform: 
this.store.selectSignal(selectJoltTransformJsonTransformState)
+    };
     processorDetails$ = this.store.select(selectProcessorDetails);
     editable: boolean = false;
     createNew: (existingEntries: string[]) => Observable<MapTableEntry> =
@@ -71,7 +91,8 @@ export class JoltTransformJsonUi implements OnDestroy {
     constructor(
         private formBuilder: FormBuilder,
         private store: Store<NiFiJoltTransformJsonUiState>,
-        private mapTableHelperService: MapTableHelperService
+        private mapTableHelperService: MapTableHelperService,
+        private renderer: Renderer2
     ) {
         // Select the processor id from the query params and GET processor 
details
         this.store
@@ -123,26 +144,20 @@ export class JoltTransformJsonUi implements OnDestroy {
 
         // build the form
         this.editJoltTransformJSONProcessorForm = this.formBuilder.group({
-            input: new FormControl('', Validators.required),
+            input: new FormControl(''),
             specification: new FormControl('', Validators.required),
             transform: new FormControl('', Validators.required),
             customClass: new FormControl(''),
             expressionLanguageAttributes: new FormControl([]),
             modules: new FormControl('')
         });
-
-        // listen to value changes
-        
this.editJoltTransformJSONProcessorForm.controls['specification'].valueChanges.subscribe(()
 => {
-            this.store.dispatch(resetValidateJoltSpecState());
-        });
-
-        
this.editJoltTransformJSONProcessorForm.controls['transform'].valueChanges.subscribe(()
 => {
-            this.store.dispatch(resetValidateJoltSpecState());
-        });
     }
 
     ngOnDestroy(): void {
-        this.store.dispatch(resetJoltTransformJsonUiState());
+        this.store.dispatch(resetJoltTransformJsonTransformState());
+        this.store.dispatch(resetJoltTransformJsonValidateState());
+        this.store.dispatch(resetJoltTransformJsonProcessorDetailsState());
+        this.store.dispatch(resetJoltTransformJsonPropertyState());
     }
 
     getJoltSpecOptions(): any {
@@ -166,7 +181,7 @@ export class JoltTransformJsonUi implements OnDestroy {
             lineNumbers: true,
             gutters: ['CodeMirror-lint-markers'],
             mode: 'application/json',
-            lint: true,
+            lint: false,
             extraKeys: {
                 'Shift-Ctrl-F': () => {
                     this.formatInput();
@@ -179,7 +194,6 @@ export class JoltTransformJsonUi implements OnDestroy {
         return {
             theme: 'nifi',
             lineNumbers: true,
-            gutters: ['CodeMirror-lint-markers'],
             mode: 'application/json',
             lint: false,
             readOnly: true
@@ -326,4 +340,94 @@ export class JoltTransformJsonUi implements OnDestroy {
         }
         return result;
     }
+
+    initSpecEditor(codeEditor: CodemirrorComponent): void {
+        if (codeEditor.codeMirror) {
+            codeEditor.codeMirror.on('change', (cm: Editor, changeObj: 
EditorChange) => {
+                const transform = 
this.editJoltTransformJSONProcessorForm.get('transform')?.value;
+
+                if (!(transform == 'jolt-transform-sort' && 
changeObj.text.toString() == '')) {
+                    this.clearMessages();
+                }
+            });
+
+            // listen to value changes
+            
this.editJoltTransformJSONProcessorForm.controls['specification'].valueChanges.subscribe(()
 => {
+                this.toggleSpecEditorEnabled(codeEditor.codeMirror);
+            });
+
+            
this.editJoltTransformJSONProcessorForm.controls['transform'].valueChanges.subscribe(()
 => {
+                this.toggleSpecEditorEnabled(codeEditor.codeMirror);
+            });
+        }
+    }
+
+    initInputEditor(codeEditor: any): void {
+        codeEditor.codeMirror.on('change', () => {
+            this.clearMessages();
+        });
+
+        
this.editJoltTransformJSONProcessorForm.controls['input'].valueChanges.subscribe(()
 => {
+            codeEditor.codeMirror.setOption('lint', true);
+        });
+    }
+
+    private clearMessages() {
+        this.store.dispatch(resetValidateJoltSpecState());
+    }
+
+    private toggleSpecEditorEnabled(specEditor: EditorFromTextArea | 
undefined) {
+        if (specEditor) {
+            const transform = 
this.editJoltTransformJSONProcessorForm.get('transform')?.value;
+            const display: HTMLElement = specEditor.getWrapperElement();
+
+            if (transform == 'jolt-transform-sort') {
+                specEditor.setOption('readOnly', 'nocursor');
+                this.renderer.addClass(display, 'disabled');
+                this.toggleDisplayEditorErrors(specEditor, true);
+            } else {
+                specEditor.setOption('readOnly', false);
+                this.renderer.removeClass(display, 'disabled');
+                this.toggleDisplayEditorErrors(specEditor);
+            }
+
+            this.clearMessages();
+        }
+    }
+
+    private toggleDisplayEditorErrors(specEditor: EditorFromTextArea | 
undefined, hideErrors: boolean = false) {
+        if (specEditor) {
+            const display: HTMLElement = specEditor.getWrapperElement();
+            const errors: Element[] = 
Array.from(display.getElementsByClassName('CodeMirror-lint-marker-error'));
+
+            if (hideErrors) {
+                errors.forEach((error: Element) => {
+                    this.renderer.addClass(error, 'hidden');
+                });
+
+                const markErrors: Element[] = 
Array.from(display.getElementsByClassName('CodeMirror-lint-mark-error'));
+                markErrors.forEach((markError: Element) => {
+                    this.renderer.addClass(markError, 
'CodeMirror-lint-mark-error-hide');
+                    this.renderer.removeClass(markError, 
'CodeMirror-lint-mark-error');
+                });
+            } else {
+                errors.forEach((error: Element) => {
+                    this.renderer.removeClass(error, 'hidden');
+                });
+
+                const markErrors: Element[] = Array.from(
+                    
display.getElementsByClassName('CodeMirror-lint-mark-error-hide')
+                );
+                markErrors.forEach((markError: Element) => {
+                    this.renderer.addClass(markError, 
'CodeMirror-lint-mark-error');
+                    this.renderer.removeClass(markError, 
'CodeMirror-lint-mark-error-hide');
+                });
+            }
+        }
+    }
+
+    getFormatTooltip() {
+        const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
+        return 'Format JSON:    ⇧' + (isMac ? '⌘' : '⌃') + 'F';
+    }
 }
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.module.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.module.ts
index 425be758ab..19fbaa2160 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.module.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.module.ts
@@ -23,7 +23,7 @@ import { StoreModule } from '@ngrx/store';
 import { joltTransformJsonUiFeatureKey, reducers } from '../state';
 import { EffectsModule } from '@ngrx/effects';
 import { MatDialogModule } from '@angular/material/dialog';
-import { JoltTransformJsonUiEffects } from 
'../state/jolt-transform-json-ui/jolt-transform-json-ui.effects';
+import { JoltTransformJsonProcessorDetailsEffects } from 
'../state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.effects';
 import { CodemirrorModule } from '@ctrl/ngx-codemirror';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { MatCardModule } from '@angular/material/card';
@@ -36,6 +36,9 @@ import { MatSelect } from '@angular/material/select';
 import { NifiTooltipDirective, ComponentContext, MapTable } from 
'@nifi/shared';
 import { MatInput } from '@angular/material/input';
 import { MatExpansionModule } from '@angular/material/expansion';
+import { JoltTransformJsonTransformEffects } from 
'../state/jolt-transform-json-transform/jolt-transform-json-transform.effects';
+import { JoltTransformJsonValidateEffects } from 
'../state/jolt-transform-json-validate/jolt-transform-json-validate.effects';
+import { JoltTransformJsonPropertyEffects } from 
'../state/jolt-transform-json-property/jolt-transform-json-property.effects';
 
 @NgModule({
     declarations: [JoltTransformJsonUi],
@@ -44,7 +47,12 @@ import { MatExpansionModule } from 
'@angular/material/expansion';
         CommonModule,
         JoltTransformJsonUiRoutingModule,
         StoreModule.forFeature(joltTransformJsonUiFeatureKey, reducers),
-        EffectsModule.forFeature(JoltTransformJsonUiEffects),
+        EffectsModule.forFeature(
+            JoltTransformJsonProcessorDetailsEffects,
+            JoltTransformJsonTransformEffects,
+            JoltTransformJsonValidateEffects,
+            JoltTransformJsonPropertyEffects
+        ),
         MatDialogModule,
         CodemirrorModule,
         ReactiveFormsModule,
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/service/jolt-transform-json-ui.service.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/service/jolt-transform-json-ui.service.ts
index 47d20b0223..e5b22d5b71 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/service/jolt-transform-json-ui.service.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/service/jolt-transform-json-ui.service.ts
@@ -18,7 +18,8 @@
 import { Injectable } from '@angular/core';
 import { HttpClient, HttpParams } from '@angular/common/http';
 import { Observable } from 'rxjs';
-import { SavePropertiesRequest, ValidateJoltSpecRequest } from 
'../state/jolt-transform-json-ui';
+import { SavePropertiesRequest } from '../state/jolt-transform-json-property';
+import { ValidateJoltSpecRequest } from 
'../state/jolt-transform-json-validate';
 
 @Injectable({ providedIn: 'root' })
 export class JoltTransformJsonUiService {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/index.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/index.ts
index da94c82892..8fc08f4685 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/index.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/index.ts
@@ -16,19 +16,36 @@
  */
 
 import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
-import { joltTransformJsonUiReducer } from 
'./jolt-transform-json-ui/jolt-transform-json-ui.reducer';
-import { JoltTransformJsonUiState } from './jolt-transform-json-ui';
+import { JoltTransformJsonPropertyState } from 
'./jolt-transform-json-property';
+import { joltTransformJsonPropertyReducer } from 
'./jolt-transform-json-property/jolt-transform-json-property.reducer';
+import { JoltTransformJsonTransformState } from 
'./jolt-transform-json-transform';
+import { joltTransformJsonTransformReducer } from 
'./jolt-transform-json-transform/jolt-transform-json-transform.reducer';
+import { JoltTransformJsonProcessorDetailsState } from 
'./jolt-transform-json-processor-details';
+import { joltTransformJsonProcessorDetailsReducer } from 
'./jolt-transform-json-processor-details/jolt-transform-json-processor-details.reducer';
+import { joltTransformJsonValidateReducer } from 
'./jolt-transform-json-validate/jolt-transform-json-validate.reducer';
+import { JoltTransformJsonValidateState } from 
'./jolt-transform-json-validate';
 
 export const joltTransformJsonUiFeatureKey = 'joltTransformJsonUi';
+export const joltTransformJsonProcessorDetailsFeatureKey = 
'joltTransformJsonProcessorDetails';
+export const joltTransformJsonPropertyFeatureKey = 'joltTransformJsonProperty';
+export const joltTransformJsonTransformFeatureKey = 
'joltTransformJsonTransform';
+export const joltTransformJsonValidateFeatureKey = 'joltTransformJsonValidate';
 
-export interface JoltTransformState {
-    [joltTransformJsonUiFeatureKey]: JoltTransformJsonUiState;
+export interface JoltTransformJsonUiState {
+    [joltTransformJsonProcessorDetailsFeatureKey]: 
JoltTransformJsonProcessorDetailsState;
+    [joltTransformJsonPropertyFeatureKey]: JoltTransformJsonPropertyState;
+    [joltTransformJsonTransformFeatureKey]: JoltTransformJsonTransformState;
+    [joltTransformJsonValidateFeatureKey]: JoltTransformJsonValidateState;
 }
 
-export function reducers(state: JoltTransformState | undefined, action: 
Action) {
+export function reducers(state: any, action: Action) {
     return combineReducers({
-        [joltTransformJsonUiFeatureKey]: joltTransformJsonUiReducer
+        [joltTransformJsonProcessorDetailsFeatureKey]: 
joltTransformJsonProcessorDetailsReducer,
+        [joltTransformJsonPropertyFeatureKey]: 
joltTransformJsonPropertyReducer,
+        [joltTransformJsonTransformFeatureKey]: 
joltTransformJsonTransformReducer,
+        [joltTransformJsonValidateFeatureKey]: joltTransformJsonValidateReducer
     })(state, action);
 }
 
-export const selectJoltTransformState = 
createFeatureSelector<JoltTransformState>(joltTransformJsonUiFeatureKey);
+export const selectJoltTransformJsonUiState =
+    
createFeatureSelector<JoltTransformJsonUiState>(joltTransformJsonUiFeatureKey);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/index.ts
similarity index 54%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/index.ts
index 2d0bdf4ada..85c91f3bad 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/index.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,20 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+export interface JoltTransformJsonProcessorDetailsState {
+    saving: boolean;
+    processorDetails: ProcessorDetails | null;
+}
 
-import { AppModule } from './app/app.module';
-
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
-
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export interface ProcessorDetails {
+    id: string;
+    descriptors: {
+        [key: string]: any;
+    };
+    properties: any;
+    name: string;
+    state: string;
+    type: string;
+    validationErrors: [] | null;
+    annotationData: [] | null;
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.actions.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.actions.ts
new file mode 100644
index 0000000000..1ddc1ab425
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.actions.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { ProcessorDetails } from './index';
+import { HttpErrorResponse } from '@angular/common/http';
+
+const JOLT_TRANSFORM_JSON_PROCESSOR_DETAILS_PREFIX = '[Jolt Transform Json 
Processor Details]';
+
+export const resetJoltTransformJsonProcessorDetailsState = createAction(
+    `${JOLT_TRANSFORM_JSON_PROCESSOR_DETAILS_PREFIX} Reset Jolt Transform Json 
Processor Details State`
+);
+
+export const loadProcessorDetails = createAction(
+    `${JOLT_TRANSFORM_JSON_PROCESSOR_DETAILS_PREFIX} Load Processor Details`,
+    props<{ processorId: string }>()
+);
+
+export const loadProcessorDetailsSuccess = createAction(
+    `${JOLT_TRANSFORM_JSON_PROCESSOR_DETAILS_PREFIX} Load Processor Details 
Success`,
+    props<{ response: ProcessorDetails }>()
+);
+
+export const loadProcessorDetailsFailure = createAction(
+    `${JOLT_TRANSFORM_JSON_PROCESSOR_DETAILS_PREFIX} Load Processor Details 
Failure`,
+    props<{ response: HttpErrorResponse }>()
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.effects.ts
new file mode 100644
index 0000000000..fbd12b2281
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.effects.ts
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as JoltTransformJsonUiActions from 
'./jolt-transform-json-processor-details.actions';
+import { JoltTransformJsonUiService } from 
'../../service/jolt-transform-json-ui.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { catchError, from, map, of, switchMap } from 'rxjs';
+import { loadProcessorDetailsFailure } from 
'./jolt-transform-json-processor-details.actions';
+import { ProcessorDetails } from './index';
+
+@Injectable()
+export class JoltTransformJsonProcessorDetailsEffects {
+    constructor(
+        private actions$: Actions,
+        private joltTransformJsonUiService: JoltTransformJsonUiService
+    ) {}
+
+    loadProcessorDetails$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(JoltTransformJsonUiActions.loadProcessorDetails),
+            map((action) => action.processorId),
+            switchMap((processorId) =>
+                
from(this.joltTransformJsonUiService.getProcessorDetails(processorId)).pipe(
+                    map((response: ProcessorDetails) =>
+                        
JoltTransformJsonUiActions.loadProcessorDetailsSuccess({
+                            response: response
+                        })
+                    ),
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        return of(
+                            loadProcessorDetailsFailure({
+                                response: errorResponse
+                            })
+                        );
+                    })
+                )
+            )
+        )
+    );
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.reducer.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.reducer.ts
new file mode 100644
index 0000000000..8165d928bc
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.reducer.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { JoltTransformJsonProcessorDetailsState } from './index';
+import { createReducer, on } from '@ngrx/store';
+import {
+    loadProcessorDetailsFailure,
+    loadProcessorDetailsSuccess,
+    resetJoltTransformJsonProcessorDetailsState
+} from './jolt-transform-json-processor-details.actions';
+
+export const initialState: JoltTransformJsonProcessorDetailsState = {
+    saving: false,
+    processorDetails: null
+};
+
+export const joltTransformJsonProcessorDetailsReducer = createReducer(
+    initialState,
+    on(resetJoltTransformJsonProcessorDetailsState, () => ({
+        ...initialState
+    })),
+    on(loadProcessorDetailsSuccess, (state, { response }) => ({
+        ...state,
+        processorDetails: response
+    })),
+    on(loadProcessorDetailsFailure, (state) => ({
+        ...state,
+        processorDetails: null
+    }))
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.selectors.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.selectors.ts
similarity index 75%
rename from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.selectors.ts
rename to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.selectors.ts
index 28b72cf0b1..63e8a4a58f 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.selectors.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-processor-details/jolt-transform-json-processor-details.selectors.ts
@@ -16,28 +16,27 @@
  */
 
 import { createSelector } from '@ngrx/store';
-import { selectJoltTransformState, JoltTransformState, 
joltTransformJsonUiFeatureKey } from '../index';
-import { JoltTransformJsonUiState } from './index';
 import { selectCurrentRoute } from '@nifi/shared';
+import {
+    joltTransformJsonProcessorDetailsFeatureKey,
+    JoltTransformJsonUiState,
+    selectJoltTransformJsonUiState
+} from '../index';
+import { JoltTransformJsonProcessorDetailsState } from './index';
 
-export const selectJoltTransformJsonUiState = createSelector(
-    selectJoltTransformState,
-    (state: JoltTransformState) => state[joltTransformJsonUiFeatureKey]
+export const selectJoltTransformJsonProcessorDetailsState = createSelector(
+    selectJoltTransformJsonUiState,
+    (state: JoltTransformJsonUiState) => 
state[joltTransformJsonProcessorDetailsFeatureKey]
 );
 
 export const selectProcessorDetails = createSelector(
-    selectJoltTransformJsonUiState,
-    (state: JoltTransformJsonUiState) => state.processorDetails
+    selectJoltTransformJsonProcessorDetailsState,
+    (state: JoltTransformJsonProcessorDetailsState) => state.processorDetails
 );
 
 export const selectSaving = createSelector(
-    selectJoltTransformJsonUiState,
-    (state: JoltTransformJsonUiState) => state.saving
-);
-
-export const selectStatus = createSelector(
-    selectJoltTransformJsonUiState,
-    (state: JoltTransformJsonUiState) => state.status
+    selectJoltTransformJsonProcessorDetailsState,
+    (state: JoltTransformJsonProcessorDetailsState) => state.saving
 );
 
 export const selectProcessorIdFromRoute = createSelector(selectCurrentRoute, 
(route) => {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/index.ts
similarity index 55%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/index.ts
index 2d0bdf4ada..16a8de9902 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/index.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,21 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { HttpErrorResponse } from '@angular/common/http';
 
-import { AppModule } from './app/app.module';
+export interface JoltTransformJsonPropertyState {
+    saving: boolean;
+    savePropertiesResponse?: SavePropertiesSuccess | null;
+    savePropertiesFailureResponse?: HttpErrorResponse | null;
+}
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
+export interface SavePropertiesRequest {
+    'Jolt Specification': string;
+    'Jolt Transform': string;
+    processorId: string;
+    revision: string;
+    clientId: string;
+    disconnectedNodeAcknowledged: boolean;
+}
 
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export interface SavePropertiesSuccess {}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.actions.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.actions.ts
new file mode 100644
index 0000000000..6b8f46b357
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.actions.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { SavePropertiesRequest, SavePropertiesSuccess } from './index';
+import { HttpErrorResponse } from '@angular/common/http';
+
+const JOLT_TRANSFORM_JSON_PROPERTY_PREFIX = '[Jolt Transform Json Properties]';
+
+export const resetJoltTransformJsonPropertyState = createAction(
+    `${JOLT_TRANSFORM_JSON_PROPERTY_PREFIX} Reset Jolt Transform Json Property 
State`
+);
+
+export const saveProperties = createAction(
+    `${JOLT_TRANSFORM_JSON_PROPERTY_PREFIX} Save Properties`,
+    props<{ request: SavePropertiesRequest }>()
+);
+
+export const savePropertiesSuccess = createAction(
+    `${JOLT_TRANSFORM_JSON_PROPERTY_PREFIX} Save Properties Success`,
+    props<{ response: SavePropertiesSuccess }>()
+);
+
+export const savePropertiesFailure = createAction(
+    `${JOLT_TRANSFORM_JSON_PROPERTY_PREFIX} Save Properties Failure`,
+    props<{ response: HttpErrorResponse }>()
+);
+
+export const resetSavePropertiesState = createAction(
+    `${JOLT_TRANSFORM_JSON_PROPERTY_PREFIX} Reset Validate Transform and Spec 
Ui State`
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.effects.ts
new file mode 100644
index 0000000000..1847ea8785
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.effects.ts
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as JoltTransformJsonUiActions from 
'./jolt-transform-json-property.actions';
+import { JoltTransformJsonUiService } from 
'../../service/jolt-transform-json-ui.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { catchError, from, map, of, switchMap, tap } from 'rxjs';
+import { savePropertiesFailure } from './jolt-transform-json-property.actions';
+import { SavePropertiesSuccess } from './index';
+import { Store } from '@ngrx/store';
+import { resetJoltTransformJsonValidateState } from 
'../jolt-transform-json-validate/jolt-transform-json-validate.actions';
+import { NiFiJoltTransformJsonUiState } from '../../../../state';
+
+@Injectable()
+export class JoltTransformJsonPropertyEffects {
+    constructor(
+        private actions$: Actions,
+        private store: Store<NiFiJoltTransformJsonUiState>,
+        private joltTransformJsonUiService: JoltTransformJsonUiService
+    ) {}
+
+    saveProperties$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(JoltTransformJsonUiActions.saveProperties),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.joltTransformJsonUiService.saveProperties(request)).pipe(
+                    tap(() => {
+                        
this.store.dispatch(resetJoltTransformJsonValidateState());
+                    }),
+                    map((response: SavePropertiesSuccess) =>
+                        JoltTransformJsonUiActions.savePropertiesSuccess({
+                            response: response
+                        })
+                    ),
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        return of(
+                            savePropertiesFailure({
+                                response: errorResponse
+                            })
+                        );
+                    })
+                )
+            )
+        )
+    );
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.reducer.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.reducer.ts
new file mode 100644
index 0000000000..3b79a2789f
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.reducer.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { JoltTransformJsonPropertyState } from './index';
+import { createReducer, on } from '@ngrx/store';
+import {
+    resetJoltTransformJsonPropertyState,
+    resetSavePropertiesState,
+    saveProperties,
+    savePropertiesFailure,
+    savePropertiesSuccess
+} from './jolt-transform-json-property.actions';
+
+export const initialState: JoltTransformJsonPropertyState = {
+    saving: false,
+    savePropertiesResponse: null,
+    savePropertiesFailureResponse: null
+};
+
+export const joltTransformJsonPropertyReducer = createReducer(
+    initialState,
+    on(resetJoltTransformJsonPropertyState, () => ({
+        ...initialState
+    })),
+    on(saveProperties, (state) => ({
+        ...state,
+        saving: true
+    })),
+    on(savePropertiesSuccess, (state, { response }) => ({
+        ...state,
+        saving: false,
+        savePropertiesResponse: response
+    })),
+    on(savePropertiesFailure, (state, { response }) => ({
+        ...state,
+        saving: false,
+        savePropertiesFailureResponse: response
+    })),
+    on(resetSavePropertiesState, (state) => ({
+        ...state,
+        saving: false,
+        savePropertiesResponse: null,
+        savePropertiesFailureResponse: null
+    }))
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.selectors.ts
similarity index 54%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.selectors.ts
index 2d0bdf4ada..218cea6db8 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-property/jolt-transform-json-property.selectors.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,20 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { createSelector } from '@ngrx/store';
+import {
+    selectJoltTransformJsonUiState,
+    JoltTransformJsonUiState,
+    joltTransformJsonPropertyFeatureKey
+} from '../index';
+import { JoltTransformJsonPropertyState } from './index';
 
-import { AppModule } from './app/app.module';
+export const selectJoltTransformJsonPropertyState = createSelector(
+    selectJoltTransformJsonUiState,
+    (state: JoltTransformJsonUiState) => 
state[joltTransformJsonPropertyFeatureKey]
+);
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
-
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export const selectSaving = createSelector(
+    selectJoltTransformJsonPropertyState,
+    (state: JoltTransformJsonPropertyState) => state.saving
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/index.ts
similarity index 55%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/index.ts
index 2d0bdf4ada..e3e2ea3865 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/index.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,21 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { HttpErrorResponse } from '@angular/common/http';
 
-import { AppModule } from './app/app.module';
+export interface JoltTransformJsonTransformState {
+    saving: boolean;
+    transformationResponse?: TransformJoltSpecSuccess | null;
+    transformationFailureResponse?: HttpErrorResponse | null;
+}
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
+export interface TransformJoltSpecSuccess {}
 
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export interface TransformJoltSpecRequest {
+    customClass: string;
+    expressionLanguageAttributes: any;
+    input: string;
+    modules: string;
+    specification: string;
+    transform: string;
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.actions.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.actions.ts
new file mode 100644
index 0000000000..bb1abeda36
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.actions.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { TransformJoltSpecRequest, TransformJoltSpecSuccess } from './index';
+import { HttpErrorResponse } from '@angular/common/http';
+
+const JOLT_TRANSFORM_JSON_TRANSFORM_PREFIX = '[Jolt Transform Json Transform]';
+
+export const resetJoltTransformJsonTransformState = createAction(
+    `${JOLT_TRANSFORM_JSON_TRANSFORM_PREFIX} Reset Jolt Transform Json 
Transform State`
+);
+
+export const transformJoltSpec = createAction(
+    `${JOLT_TRANSFORM_JSON_TRANSFORM_PREFIX} Transform Jolt Specification`,
+    props<{ request: TransformJoltSpecRequest }>()
+);
+
+export const transformJoltSpecSuccess = createAction(
+    `${JOLT_TRANSFORM_JSON_TRANSFORM_PREFIX} Transform Jolt Specification 
Success`,
+    props<{ response: TransformJoltSpecSuccess }>()
+);
+
+export const transformJoltSpecFailure = createAction(
+    `${JOLT_TRANSFORM_JSON_TRANSFORM_PREFIX} Transform Jolt Specification 
Failure`,
+    props<{ response: HttpErrorResponse }>()
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.effects.ts
new file mode 100644
index 0000000000..04682c677f
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.effects.ts
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as JoltTransformJsonUiActions from 
'./jolt-transform-json-transform.actions';
+import { JoltTransformJsonUiService } from 
'../../service/jolt-transform-json-ui.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { catchError, from, map, of, switchMap } from 'rxjs';
+import { transformJoltSpecFailure } from 
'./jolt-transform-json-transform.actions';
+import { TransformJoltSpecRequest } from './index';
+
+@Injectable()
+export class JoltTransformJsonTransformEffects {
+    constructor(
+        private actions$: Actions,
+        private joltTransformJsonUiService: JoltTransformJsonUiService
+    ) {}
+
+    transformJoltSpec$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(JoltTransformJsonUiActions.transformJoltSpec),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.joltTransformJsonUiService.transformJoltSpec(request)).pipe(
+                    map((response: TransformJoltSpecRequest) =>
+                        JoltTransformJsonUiActions.transformJoltSpecSuccess({
+                            response: response
+                        })
+                    ),
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        return of(
+                            transformJoltSpecFailure({
+                                response: errorResponse
+                            })
+                        );
+                    })
+                )
+            )
+        )
+    );
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.reducer.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.reducer.ts
new file mode 100644
index 0000000000..463b140dcc
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.reducer.ts
@@ -0,0 +1,53 @@
+/*
+ * 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 { JoltTransformJsonTransformState } from './index';
+import { createReducer, on } from '@ngrx/store';
+import {
+    resetJoltTransformJsonTransformState,
+    transformJoltSpec,
+    transformJoltSpecFailure,
+    transformJoltSpecSuccess
+} from './jolt-transform-json-transform.actions';
+
+export const initialState: JoltTransformJsonTransformState = {
+    saving: false,
+    transformationResponse: null,
+    transformationFailureResponse: null
+};
+
+export const joltTransformJsonTransformReducer = createReducer(
+    initialState,
+    on(resetJoltTransformJsonTransformState, () => ({
+        ...initialState
+    })),
+    on(transformJoltSpec, (state) => ({
+        ...state,
+        saving: true
+    })),
+    on(transformJoltSpecSuccess, (state, { response }) => ({
+        ...state,
+        saving: false,
+        transformationResponse: response
+    })),
+    on(transformJoltSpecFailure, (state, { response }) => ({
+        ...state,
+        saving: false,
+        transformationResponse: null,
+        transformationFailureResponse: response
+    }))
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.selectors.ts
similarity index 54%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.selectors.ts
index 2d0bdf4ada..fd42383f5a 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-transform/jolt-transform-json-transform.selectors.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,20 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { createSelector } from '@ngrx/store';
+import {
+    joltTransformJsonTransformFeatureKey,
+    JoltTransformJsonUiState,
+    selectJoltTransformJsonUiState
+} from '../index';
+import { JoltTransformJsonTransformState } from './index';
 
-import { AppModule } from './app/app.module';
+export const selectJoltTransformJsonTransformState = createSelector(
+    selectJoltTransformJsonUiState,
+    (state: JoltTransformJsonUiState) => 
state[joltTransformJsonTransformFeatureKey]
+);
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
-
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export const selectSaving = createSelector(
+    selectJoltTransformJsonTransformState,
+    (state: JoltTransformJsonTransformState) => state.saving
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.actions.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.actions.ts
deleted file mode 100644
index 802d1b0a75..0000000000
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.actions.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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 { createAction, props } from '@ngrx/store';
-import {
-    ProcessorDetails,
-    SavePropertiesRequest,
-    SavePropertiesSuccess,
-    TransformJoltSpecSuccess,
-    ValidateJoltSpecRequest,
-    ValidateJoltSpecSuccess
-} from './index';
-import { HttpErrorResponse } from '@angular/common/http';
-
-const JOLT_TRANSFORM_JSON_UI_PREFIX = '[Jolt Transform Json Ui]';
-
-export const resetJoltTransformJsonUiState = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Reset Jolt Transform Json Ui State`
-);
-
-export const transformJoltSpec = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Transform Jolt Specification`,
-    props<{ request: ValidateJoltSpecRequest }>()
-);
-
-export const transformJoltSpecSuccess = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Transform Jolt Specification Success`,
-    props<{ response: TransformJoltSpecSuccess }>()
-);
-
-export const transformJoltSpecFailure = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Transform Jolt Specification Failure`,
-    props<{ response: HttpErrorResponse }>()
-);
-
-export const validateJoltSpec = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Validate Jolt Specification`,
-    props<{ request: ValidateJoltSpecRequest }>()
-);
-
-export const validateJoltSpecSuccess = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Validate Jolt Specification Success`,
-    props<{ response: ValidateJoltSpecSuccess }>()
-);
-
-export const validateJoltSpecFailure = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Validate Jolt Specification Failure`,
-    props<{ response: HttpErrorResponse }>()
-);
-
-export const resetValidateJoltSpecState = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Reset Validate Transform and Spec Ui 
State`
-);
-
-export const saveProperties = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Save Properties`,
-    props<{ request: SavePropertiesRequest }>()
-);
-
-export const savePropertiesSuccess = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Save Properties Success`,
-    props<{ response: SavePropertiesSuccess }>()
-);
-
-export const savePropertiesFailure = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Save Properties Failure`,
-    props<{ response: HttpErrorResponse }>()
-);
-
-export const resetSavePropertiesState = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Reset Validate Transform and Spec Ui 
State`
-);
-
-export const loadProcessorDetails = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Load Processor Details`,
-    props<{ processorId: string }>()
-);
-
-export const loadProcessorDetailsSuccess = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Load Processor Details Success`,
-    props<{ response: ProcessorDetails }>()
-);
-
-export const loadProcessorDetailsFailure = createAction(
-    `${JOLT_TRANSFORM_JSON_UI_PREFIX} Load Processor Details Failure`,
-    props<{ response: HttpErrorResponse }>()
-);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.effects.ts
deleted file mode 100644
index 25b9a2734c..0000000000
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.effects.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { Injectable } from '@angular/core';
-import { Actions, createEffect, ofType } from '@ngrx/effects';
-import { NiFiJoltTransformJsonUiState } from '../../../../state';
-import { Store } from '@ngrx/store';
-import * as JoltTransformJsonUiActions from './jolt-transform-json-ui.actions';
-import { JoltTransformJsonUiService } from 
'../../service/jolt-transform-json-ui.service';
-import { HttpErrorResponse } from '@angular/common/http';
-import { catchError, from, map, of, switchMap } from 'rxjs';
-import {
-    loadProcessorDetailsFailure,
-    savePropertiesFailure,
-    transformJoltSpecFailure,
-    validateJoltSpecFailure
-} from './jolt-transform-json-ui.actions';
-import { ProcessorDetails, SavePropertiesSuccess, ValidateJoltSpecSuccess } 
from './index';
-
-@Injectable()
-export class JoltTransformJsonUiEffects {
-    constructor(
-        private actions$: Actions,
-        private store: Store<NiFiJoltTransformJsonUiState>,
-        private joltTransformJsonUiService: JoltTransformJsonUiService
-    ) {}
-
-    loadProcessorDetails$ = createEffect(() =>
-        this.actions$.pipe(
-            ofType(JoltTransformJsonUiActions.loadProcessorDetails),
-            map((action) => action.processorId),
-            switchMap((processorId) =>
-                
from(this.joltTransformJsonUiService.getProcessorDetails(processorId)).pipe(
-                    map((response: ProcessorDetails) =>
-                        
JoltTransformJsonUiActions.loadProcessorDetailsSuccess({
-                            response: response
-                        })
-                    ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        return of(
-                            loadProcessorDetailsFailure({
-                                response: errorResponse
-                            })
-                        );
-                    })
-                )
-            )
-        )
-    );
-
-    saveProperties$ = createEffect(() =>
-        this.actions$.pipe(
-            ofType(JoltTransformJsonUiActions.saveProperties),
-            map((action) => action.request),
-            switchMap((request) =>
-                
from(this.joltTransformJsonUiService.saveProperties(request)).pipe(
-                    map((response: SavePropertiesSuccess) =>
-                        JoltTransformJsonUiActions.savePropertiesSuccess({
-                            response: response
-                        })
-                    ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        return of(
-                            savePropertiesFailure({
-                                response: errorResponse
-                            })
-                        );
-                    })
-                )
-            )
-        )
-    );
-
-    validateJoltSpec$ = createEffect(() =>
-        this.actions$.pipe(
-            ofType(JoltTransformJsonUiActions.validateJoltSpec),
-            map((action) => action.request),
-            switchMap((request) =>
-                
from(this.joltTransformJsonUiService.validateJoltSpec(request)).pipe(
-                    map((response: ValidateJoltSpecSuccess) =>
-                        JoltTransformJsonUiActions.validateJoltSpecSuccess({
-                            response: response
-                        })
-                    ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        return of(
-                            validateJoltSpecFailure({
-                                response: errorResponse
-                            })
-                        );
-                    })
-                )
-            )
-        )
-    );
-
-    transformJoltSpec$ = createEffect(() =>
-        this.actions$.pipe(
-            ofType(JoltTransformJsonUiActions.transformJoltSpec),
-            map((action) => action.request),
-            switchMap((request) =>
-                
from(this.joltTransformJsonUiService.transformJoltSpec(request)).pipe(
-                    map((response: ValidateJoltSpecSuccess) =>
-                        JoltTransformJsonUiActions.transformJoltSpecSuccess({
-                            response: response
-                        })
-                    ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        return of(
-                            transformJoltSpecFailure({
-                                response: errorResponse
-                            })
-                        );
-                    })
-                )
-            )
-        )
-    );
-}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.reducer.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.reducer.ts
deleted file mode 100644
index 42739851d7..0000000000
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/jolt-transform-json-ui.reducer.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 { JoltTransformJsonUiState } from './index';
-import { createReducer, on } from '@ngrx/store';
-import {
-    loadProcessorDetailsFailure,
-    loadProcessorDetailsSuccess,
-    resetJoltTransformJsonUiState,
-    resetSavePropertiesState,
-    resetValidateJoltSpecState,
-    saveProperties,
-    savePropertiesFailure,
-    savePropertiesSuccess,
-    transformJoltSpec,
-    transformJoltSpecFailure,
-    transformJoltSpecSuccess,
-    validateJoltSpec,
-    validateJoltSpecFailure,
-    validateJoltSpecSuccess
-} from './jolt-transform-json-ui.actions';
-
-export const initialState: JoltTransformJsonUiState = {
-    saving: false,
-    loadedTimestamp: '',
-    status: 'pending',
-    processorDetails: null,
-    transformingJoltSpec: false,
-    validatingJoltSpec: false,
-    savingProperties: false
-};
-
-export const joltTransformJsonUiReducer = createReducer(
-    initialState,
-    on(resetJoltTransformJsonUiState, () => ({
-        ...initialState
-    })),
-    on(loadProcessorDetailsSuccess, (state, { response }) => ({
-        ...state,
-        processorDetails: response
-    })),
-    on(loadProcessorDetailsFailure, (state) => ({
-        ...state,
-        processorDetails: null
-    })),
-    on(saveProperties, (state) => ({
-        ...state,
-        savingProperties: true,
-        validatingJoltSpec: null,
-        validationResponse: null,
-        validationFailureResponse: null
-    })),
-    on(savePropertiesSuccess, (state, { response }) => ({
-        ...state,
-        savingProperties: false,
-        savePropertiesResponse: response
-    })),
-    on(savePropertiesFailure, (state, { response }) => ({
-        ...state,
-        savingProperties: false,
-        savePropertiesFailureResponse: response
-    })),
-    on(resetSavePropertiesState, (state) => ({
-        ...state,
-        savingProperties: false,
-        savePropertiesResponse: null,
-        savePropertiesFailureResponse: null
-    })),
-    on(validateJoltSpec, (state) => ({
-        ...state,
-        validatingJoltSpec: true
-    })),
-    on(validateJoltSpecSuccess, (state, { response }) => ({
-        ...state,
-        validatingJoltSpec: false,
-        validationResponse: response,
-        savingProperties: false,
-        savePropertiesResponse: null,
-        savePropertiesFailureResponse: null
-    })),
-    on(validateJoltSpecFailure, (state, { response }) => ({
-        ...state,
-        validatingJoltSpec: false,
-        validationFailureResponse: response
-    })),
-    on(resetValidateJoltSpecState, (state) => ({
-        ...state,
-        validatingJoltSpec: null,
-        validationResponse: null,
-        validationFailureResponse: null
-    })),
-    on(transformJoltSpec, (state) => ({
-        ...state,
-        transformingJoltSpec: true
-    })),
-    on(transformJoltSpecSuccess, (state, { response }) => ({
-        ...state,
-        transformingJoltSpec: false,
-        transformationResponse: response
-    })),
-    on(transformJoltSpecFailure, (state, { response }) => ({
-        ...state,
-        transformingJoltSpec: false,
-        transformationResponse: null,
-        transformationFailureResponse: response
-    }))
-);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/index.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/index.ts
similarity index 53%
rename from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/index.ts
rename to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/index.ts
index c67776e26d..5084c00dcd 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-ui/index.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/index.ts
@@ -17,20 +17,10 @@
 
 import { HttpErrorResponse } from '@angular/common/http';
 
-export interface JoltTransformJsonUiState {
-    saving: boolean;
-    transformingJoltSpec: boolean;
-    validatingJoltSpec: boolean | null;
-    savingProperties: boolean;
-    loadedTimestamp: string;
-    status: 'pending' | 'loading' | 'success';
-    savePropertiesResponse?: SavePropertiesSuccess | null;
-    savePropertiesFailureResponse?: HttpErrorResponse | null;
+export interface JoltTransformJsonValidateState {
+    saving: boolean | null;
     validationResponse?: ValidateJoltSpecSuccess | null;
     validationFailureResponse?: HttpErrorResponse | null;
-    transformationResponse?: TransformJoltSpecSuccess | null;
-    transformationFailureResponse?: HttpErrorResponse | null;
-    processorDetails: ProcessorDetails | null;
 }
 
 export interface ValidateJoltSpecRequest {
@@ -42,33 +32,7 @@ export interface ValidateJoltSpecRequest {
     transform: string;
 }
 
-export interface SavePropertiesRequest {
-    'Jolt Specification': string;
-    'Jolt Transform': string;
-    processorId: string;
-    revision: string;
-    clientId: string;
-    disconnectedNodeAcknowledged: boolean;
-}
-
-export interface SavePropertiesSuccess {}
-
 export interface ValidateJoltSpecSuccess {
     valid: boolean;
     message: string | null;
 }
-
-export interface TransformJoltSpecSuccess {}
-
-export interface ProcessorDetails {
-    id: string;
-    descriptors: {
-        [key: string]: any;
-    };
-    properties: any;
-    name: string;
-    state: string;
-    type: string;
-    validationErrors: [] | null;
-    annotationData: [] | null;
-}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.actions.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.actions.ts
new file mode 100644
index 0000000000..9a400aa93a
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.actions.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { ValidateJoltSpecRequest, ValidateJoltSpecSuccess } from './index';
+import { HttpErrorResponse } from '@angular/common/http';
+
+const JOLT_TRANSFORM_JSON_VALIDATE_PREFIX = '[Jolt Transform Json Validate]';
+
+export const resetJoltTransformJsonValidateState = createAction(
+    `${JOLT_TRANSFORM_JSON_VALIDATE_PREFIX} Reset Jolt Transform Json Validate 
State`
+);
+
+export const validateJoltSpec = createAction(
+    `${JOLT_TRANSFORM_JSON_VALIDATE_PREFIX} Validate Jolt Specification`,
+    props<{ request: ValidateJoltSpecRequest }>()
+);
+
+export const validateJoltSpecSuccess = createAction(
+    `${JOLT_TRANSFORM_JSON_VALIDATE_PREFIX} Validate Jolt Specification 
Success`,
+    props<{ response: ValidateJoltSpecSuccess }>()
+);
+
+export const validateJoltSpecFailure = createAction(
+    `${JOLT_TRANSFORM_JSON_VALIDATE_PREFIX} Validate Jolt Specification 
Failure`,
+    props<{ response: HttpErrorResponse }>()
+);
+
+export const resetValidateJoltSpecState = createAction(
+    `${JOLT_TRANSFORM_JSON_VALIDATE_PREFIX} Reset Validate Transform and Spec 
Ui State`
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.effects.ts
new file mode 100644
index 0000000000..635637c1fe
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.effects.ts
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as JoltTransformJsonUiActions from 
'./jolt-transform-json-validate.actions';
+import { JoltTransformJsonUiService } from 
'../../service/jolt-transform-json-ui.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { catchError, from, map, of, switchMap, tap } from 'rxjs';
+import { validateJoltSpecFailure } from 
'./jolt-transform-json-validate.actions';
+import { ValidateJoltSpecSuccess } from './index';
+import { Store } from '@ngrx/store';
+import { NiFiJoltTransformJsonUiState } from '../../../../state';
+import { resetJoltTransformJsonPropertyState } from 
'../jolt-transform-json-property/jolt-transform-json-property.actions';
+
+@Injectable()
+export class JoltTransformJsonValidateEffects {
+    constructor(
+        private actions$: Actions,
+        private store: Store<NiFiJoltTransformJsonUiState>,
+        private joltTransformJsonUiService: JoltTransformJsonUiService
+    ) {}
+
+    validateJoltSpec$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(JoltTransformJsonUiActions.validateJoltSpec),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.joltTransformJsonUiService.validateJoltSpec(request)).pipe(
+                    tap(() => {
+                        
this.store.dispatch(resetJoltTransformJsonPropertyState());
+                    }),
+                    map((response: ValidateJoltSpecSuccess) =>
+                        JoltTransformJsonUiActions.validateJoltSpecSuccess({
+                            response: response
+                        })
+                    ),
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        return of(
+                            validateJoltSpecFailure({
+                                response: errorResponse
+                            })
+                        );
+                    })
+                )
+            )
+        )
+    );
+}
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.reducer.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.reducer.ts
new file mode 100644
index 0000000000..f6c4ccbb04
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.reducer.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { JoltTransformJsonValidateState } from './index';
+import { createReducer, on } from '@ngrx/store';
+import {
+    resetJoltTransformJsonValidateState,
+    resetValidateJoltSpecState,
+    validateJoltSpec,
+    validateJoltSpecFailure,
+    validateJoltSpecSuccess
+} from './jolt-transform-json-validate.actions';
+
+export const initialState: JoltTransformJsonValidateState = {
+    saving: false,
+    validationResponse: null,
+    validationFailureResponse: null
+};
+
+export const joltTransformJsonValidateReducer = createReducer(
+    initialState,
+    on(resetJoltTransformJsonValidateState, () => ({
+        ...initialState
+    })),
+    on(validateJoltSpec, (state) => ({
+        ...state,
+        saving: true
+    })),
+    on(validateJoltSpecSuccess, (state, { response }) => ({
+        ...state,
+        saving: false,
+        validationResponse: response
+    })),
+    on(validateJoltSpecFailure, (state, { response }) => ({
+        ...state,
+        saving: false,
+        validationFailureResponse: response
+    })),
+    on(resetValidateJoltSpecState, (state) => ({
+        ...state,
+        saving: null,
+        validationResponse: null,
+        validationFailureResponse: null
+    }))
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.selectors.ts
similarity index 54%
copy from 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
copy to 
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.selectors.ts
index 2d0bdf4ada..8363427535 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/state/jolt-transform-json-validate/jolt-transform-json-validate.selectors.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,19 +15,20 @@
  * limitations under the License.
  */
 
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { createSelector } from '@ngrx/store';
+import {
+    JoltTransformJsonUiState,
+    joltTransformJsonValidateFeatureKey,
+    selectJoltTransformJsonUiState
+} from '../index';
+import { JoltTransformJsonValidateState } from './index';
 
-import { AppModule } from './app/app.module';
+export const selectJoltTransformJsonValidateState = createSelector(
+    selectJoltTransformJsonUiState,
+    (state: JoltTransformJsonUiState) => 
state[joltTransformJsonValidateFeatureKey]
+);
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
-
-platformBrowserDynamic()
-    .bootstrapModule(AppModule)
-    .catch((err) => console.error(err));
+export const selectSaving = createSelector(
+    selectJoltTransformJsonValidateState,
+    (state: JoltTransformJsonValidateState) => state.saving
+);
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
index 2d0bdf4ada..e8af0f5485 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/main.ts
@@ -19,14 +19,12 @@ import { platformBrowserDynamic } from 
'@angular/platform-browser-dynamic';
 
 import { AppModule } from './app/app.module';
 
-import 'codemirror/addon/edit/matchbrackets';
-import 'codemirror/addon/fold/brace-fold';
-import 'codemirror/addon/fold/comment-fold';
-import 'codemirror/addon/fold/foldcode';
-import 'codemirror/addon/fold/foldgutter';
-import 'codemirror/addon/fold/markdown-fold';
-import 'codemirror/addon/fold/xml-fold';
-import 'codemirror/addon/hint/show-hint';
+import 'codemirror/mode/javascript/javascript.js';
+import 'codemirror/addon/lint/lint';
+import 'codemirror/addon/lint/json-lint';
+import * as jsonlint from 'jsonlint';
+
+(window as any).jsonlint = jsonlint;
 
 platformBrowserDynamic()
     .bootstrapModule(AppModule)
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/styles.scss 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/styles.scss
index 6bbd704dfc..62c5dd3187 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/styles.scss
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/styles.scss
@@ -27,8 +27,6 @@
 // Plus imports for other components in your app.
 @use 'libs/shared/src/assets/fonts/flowfont/flowfont.css';
 @use 'codemirror/lib/codemirror.css';
-@use 'codemirror/addon/fold/foldgutter.css';
-@use 'codemirror/addon/hint/show-hint.css';
 @use 'codemirror/addon/lint/lint.css';
 
 @import 'font-awesome';
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 730d9bdbc8..5e6c914bde 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
@@ -113,15 +113,6 @@
 
     /* overriding 3rd party styles */
 
-    /* Codemirror begin */
-
-    .CodeMirror-hints {
-        z-index: 1000 !important;
-        overflow-y: scroll !important;
-    }
-
-    /* Codemirror end */
-
     /* material typography begin */
 
     .mat-h2,
@@ -152,10 +143,6 @@
         margin-right: 0 !important;
     }
 
-    .mat-expansion-panel-body {
-        overflow-y: auto;
-    }
-
     .mat-content.mat-content-hide-toggle {
         margin-right: 0 !important;
     }
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_codemirror-theme.scss
 
b/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_codemirror-theme.scss
index b7e4b235b9..031a4913a2 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_codemirror-theme.scss
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_codemirror-theme.scss
@@ -71,6 +71,10 @@
             $supplemental-theme-surface-palette-lighter
         );
         border: 1px solid var(--mdc-outlined-text-field-label-text-color);
+
+        &.disabled {
+            opacity: 0.4;
+        }
     }
 
     .cm-s-nifi .CodeMirror-gutters {
@@ -250,4 +254,9 @@
     .cm-s-nifi .CodeMirror-activeline-background {
         background: if($is-dark, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.5));
     }
+
+    .cm-s-nifi .CodeMirror-hints {
+        z-index: 1000 !important;
+        overflow-y: scroll !important;
+    }
 }
diff --git a/nifi-frontend/src/main/frontend/package-lock.json 
b/nifi-frontend/src/main/frontend/package-lock.json
index f6cece37d8..aee6bd6acf 100644
--- a/nifi-frontend/src/main/frontend/package-lock.json
+++ b/nifi-frontend/src/main/frontend/package-lock.json
@@ -7,6 +7,7 @@
         "": {
             "name": "nifi-frontend",
             "version": "0.0.0",
+            "hasInstallScript": true,
             "dependencies": {
                 "@angular/animations": "18.0.7",
                 "@angular/cdk": "18.0.6",
@@ -32,6 +33,7 @@
                 "humanize-duration": "^3.31.0",
                 "immer": "10.0.3",
                 "js-beautify": "^1.15.1",
+                "jsonlint": "^1.6.3",
                 "ngx-skeleton-loader": "^8.1.0",
                 "rxjs": "~7.8.1",
                 "tslib": "^2.6.2",
@@ -60,6 +62,7 @@
                 "@types/d3": "^7.4.3",
                 "@types/humanize-duration": "^3.27.3",
                 "@types/jest": "^29.5.12",
+                "@types/jsonlint": "^1.6.3",
                 "@types/webfontloader": "^1.6.38",
                 "@typescript-eslint/eslint-plugin": "6.21.0",
                 "@typescript-eslint/parser": "6.21.0",
@@ -9357,6 +9360,12 @@
             "integrity": 
"sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
             "dev": true
         },
+        "node_modules/@types/jsonlint": {
+            "version": "1.6.3",
+            "resolved": 
"https://registry.npmjs.org/@types/jsonlint/-/jsonlint-1.6.3.tgz";,
+            "integrity": 
"sha512-9BlMijV0OxhBaVQADQnQB5yJQtH65T+OikLI9Bz6vdCSxkA6WNPsec4r3NNI4lhbRP1hNYeLQrYN6gpAZmJ6+A==",
+            "dev": true
+        },
         "node_modules/@types/mime": {
             "version": "1.3.5",
             "resolved": 
"https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz";,
@@ -9819,15 +9828,15 @@
             }
         },
         "node_modules/@typescript-eslint/utils": {
-            "version": "8.0.0-alpha.44",
-            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.44.tgz";,
-            "integrity": 
"sha512-gOSA4Yo1jufcOuV68yX3hzpwzufd/Ru6KYL04od1T1c5tt6cvN3i5D5Tc3BBJ3xYFE7ge821mJbUJMTc+BMaWg==",
+            "version": "8.0.0-alpha.45",
+            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.45.tgz";,
+            "integrity": 
"sha512-5YVHji5bovAKsDdT3mV7vjDEUhPJvmsh7LXY+/ixHyZJDE52TmsobBGSEBmijeqYWfz2vuNJyyvFGJTo70UikA==",
             "dev": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.4.0",
-                "@typescript-eslint/scope-manager": "8.0.0-alpha.44",
-                "@typescript-eslint/types": "8.0.0-alpha.44",
-                "@typescript-eslint/typescript-estree": "8.0.0-alpha.44"
+                "@typescript-eslint/scope-manager": "8.0.0-alpha.45",
+                "@typescript-eslint/types": "8.0.0-alpha.45",
+                "@typescript-eslint/typescript-estree": "8.0.0-alpha.45"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9841,13 +9850,13 @@
             }
         },
         
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager":
 {
-            "version": "8.0.0-alpha.44",
-            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.44.tgz";,
-            "integrity": 
"sha512-0w0pDILwfwRXSz9lQBXnJmeGaIbSBgl4vAw/lB2kCnOKYl2SXCVbdNOHPwxWigvQ08QVpuaKy+wEjbFKr9Xwfg==",
+            "version": "8.0.0-alpha.45",
+            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.45.tgz";,
+            "integrity": 
"sha512-zmfZYLH6Oaq1drf99idktn1/m4SZvBXFUKdl8B2A1SrBc6E57wtRW9OwFBnROgM4gHeG1wb89DLhQ/UeqcUmMQ==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.0.0-alpha.44",
-                "@typescript-eslint/visitor-keys": "8.0.0-alpha.44"
+                "@typescript-eslint/types": "8.0.0-alpha.45",
+                "@typescript-eslint/visitor-keys": "8.0.0-alpha.45"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9858,9 +9867,9 @@
             }
         },
         
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
-            "version": "8.0.0-alpha.44",
-            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.44.tgz";,
-            "integrity": 
"sha512-FNBBUTJBNbIaTJhhBbSNxKv+qS8lrwwnpBg36APp5fhDRu8K/YFQZP/VEa19nKBz+8+QUK7R6wV9DHYjj56S7w==",
+            "version": "8.0.0-alpha.45",
+            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.45.tgz";,
+            "integrity": 
"sha512-yjTlmcSnkFV8IoqE0vinmWo+fl7TjkaGyGX/g9gKN/b2IO8g+AimB7BhilmlBqvZupvo2AfiHqcnZEVhQAXI8w==",
             "dev": true,
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9871,13 +9880,13 @@
             }
         },
         
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree":
 {
-            "version": "8.0.0-alpha.44",
-            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.44.tgz";,
-            "integrity": 
"sha512-IyLELYPMFaleWpEVrcYhSfgFXFx4/505P4/vi9Dfp6s6T2xapyAdti6WL9iZbnXk72SL5M0wMp3V73nHn8ce1A==",
+            "version": "8.0.0-alpha.45",
+            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.45.tgz";,
+            "integrity": 
"sha512-FcvtdTxahvP+qlZ1XXF+m0GVqomklKtkG6cIHLdBvTOHgIBILtahU7yyRE5rOHDdGoAFu8AzItJI12rtf9TRyA==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.0.0-alpha.44",
-                "@typescript-eslint/visitor-keys": "8.0.0-alpha.44",
+                "@typescript-eslint/types": "8.0.0-alpha.45",
+                "@typescript-eslint/visitor-keys": "8.0.0-alpha.45",
                 "debug": "^4.3.4",
                 "globby": "^11.1.0",
                 "is-glob": "^4.0.3",
@@ -9899,12 +9908,12 @@
             }
         },
         
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys":
 {
-            "version": "8.0.0-alpha.44",
-            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.44.tgz";,
-            "integrity": 
"sha512-geWzLM8S6vYGdhA01mWJyGh2V/7VRzAmsD6ZKuc/rLkeJhYjvkMY0g0uMDw/7wmNLeRrpjHnL8HJklrpAlrb9g==",
+            "version": "8.0.0-alpha.45",
+            "resolved": 
"https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.45.tgz";,
+            "integrity": 
"sha512-SZmtknee9MzeT41tCpvh5vUyji0Zr4OyfERJqDmfg5YZwkm3BRdTeexrBKK9C8da97ULR1SUSeLUTqttC77vJw==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.0.0-alpha.44",
+                "@typescript-eslint/types": "8.0.0-alpha.45",
                 "eslint-visitor-keys": "^3.4.3"
             },
             "engines": {
@@ -15012,6 +15021,14 @@
             "integrity": 
"sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
             "dev": true
         },
+        "node_modules/has-color": {
+            "version": "0.1.7",
+            "resolved": 
"https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz";,
+            "integrity": 
"sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/has-flag": {
             "version": "3.0.0",
             "resolved": 
"https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz";,
@@ -18136,6 +18153,21 @@
                 "graceful-fs": "^4.1.6"
             }
         },
+        "node_modules/jsonlint": {
+            "version": "1.6.3",
+            "resolved": 
"https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz";,
+            "integrity": 
"sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==",
+            "dependencies": {
+                "JSV": "^4.0.x",
+                "nomnom": "^1.5.x"
+            },
+            "bin": {
+                "jsonlint": "lib/cli.js"
+            },
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
         "node_modules/jsonparse": {
             "version": "1.3.1",
             "resolved": 
"https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz";,
@@ -18145,6 +18177,14 @@
                 "node >= 0.2.0"
             ]
         },
+        "node_modules/JSV": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz";,
+            "integrity": 
"sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==",
+            "engines": {
+                "node": "*"
+            }
+        },
         "node_modules/karma-source-map-support": {
             "version": "1.4.0",
             "resolved": 
"https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz";,
@@ -19769,6 +19809,48 @@
             "integrity": 
"sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
             "dev": true
         },
+        "node_modules/nomnom": {
+            "version": "1.8.1",
+            "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz";,
+            "integrity": 
"sha512-5s0JxqhDx9/rksG2BTMVN1enjWSvPidpoSgViZU4ZXULyTe+7jxcCRLB6f42Z0l1xYJpleCBtSyY6Lwg3uu5CQ==",
+            "deprecated": "Package no longer supported. Contact 
[email protected] for more info.",
+            "dependencies": {
+                "chalk": "~0.4.0",
+                "underscore": "1.13.6"
+            }
+        },
+        "node_modules/nomnom/node_modules/ansi-styles": {
+            "version": "1.0.0",
+            "resolved": 
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz";,
+            "integrity": 
"sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==",
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/nomnom/node_modules/chalk": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz";,
+            "integrity": 
"sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ==",
+            "dependencies": {
+                "ansi-styles": "~1.0.0",
+                "has-color": "~0.1.0",
+                "strip-ansi": "~0.1.0"
+            },
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/nomnom/node_modules/strip-ansi": {
+            "version": "0.1.1",
+            "resolved": 
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz";,
+            "integrity": 
"sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==",
+            "bin": {
+                "strip-ansi": "cli.js"
+            },
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
         "node_modules/nopt": {
             "version": "7.2.1",
             "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz";,
@@ -23627,13 +23709,13 @@
             "dev": true
         },
         "node_modules/ts-jest": {
-            "version": "29.2.2",
-            "resolved": 
"https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz";,
-            "integrity": 
"sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==",
+            "version": "29.2.3",
+            "resolved": 
"https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.3.tgz";,
+            "integrity": 
"sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ==",
             "dev": true,
             "dependencies": {
                 "bs-logger": "0.x",
-                "ejs": "^3.0.0",
+                "ejs": "^3.1.10",
                 "fast-json-stable-stringify": "2.x",
                 "jest-util": "^29.0.0",
                 "json5": "^2.2.3",
@@ -24004,6 +24086,11 @@
                 "node": ">=14.17"
             }
         },
+        "node_modules/underscore": {
+            "version": "1.13.6",
+            "resolved": 
"https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz";,
+            "integrity": 
"sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
+        },
         "node_modules/undici": {
             "version": "6.18.0",
             "resolved": 
"https://registry.npmjs.org/undici/-/undici-6.18.0.tgz";,
diff --git a/nifi-frontend/src/main/frontend/package.json 
b/nifi-frontend/src/main/frontend/package.json
index f4f543cde7..cba2e7082a 100644
--- a/nifi-frontend/src/main/frontend/package.json
+++ b/nifi-frontend/src/main/frontend/package.json
@@ -10,7 +10,8 @@
         "test:dev": "nx test --watch=true",
         "prettier": "prettier --config .prettierrc . --check",
         "prettier-format": "prettier --config .prettierrc . --write",
-        "ci": "npm ci --ignore-scripts"
+        "ci": "npm ci --ignore-scripts",
+        "preinstall": "npx force-resolutions"
     },
     "private": true,
     "dependencies": {
@@ -38,6 +39,7 @@
         "humanize-duration": "^3.31.0",
         "immer": "10.0.3",
         "js-beautify": "^1.15.1",
+        "jsonlint": "^1.6.3",
         "ngx-skeleton-loader": "^8.1.0",
         "rxjs": "~7.8.1",
         "tslib": "^2.6.2",
@@ -66,6 +68,7 @@
         "@types/d3": "^7.4.3",
         "@types/humanize-duration": "^3.27.3",
         "@types/jest": "^29.5.12",
+        "@types/jsonlint": "^1.6.3",
         "@types/webfontloader": "^1.6.38",
         "@typescript-eslint/eslint-plugin": "6.21.0",
         "@typescript-eslint/parser": "6.21.0",
@@ -88,5 +91,14 @@
     },
     "engines": {
         "node": ">=22.0.0"
+    },
+    "browser": {
+        "fs": false,
+        "path": false,
+        "file": false,
+        "system": false
+    },
+    "resolutions": {
+        "underscore": "1.13.6"
     }
 }

Reply via email to