This is an automated email from the ASF dual-hosted git repository.
rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new ecb87149fe NIFI-12611: (#8296)
ecb87149fe is described below
commit ecb87149fe6baea4a6707d98d491482461bcb8eb
Author: Matt Gilman <[email protected]>
AuthorDate: Thu Jan 25 14:53:17 2024 -0500
NIFI-12611: (#8296)
- Component State.
This closes #8296
---
.../src/main/nifi/src/app/app.module.ts | 4 +-
.../service/canvas-context-menu.service.ts | 17 ++-
.../flow-designer/service/canvas-utils.service.ts | 22 +++
.../controller-services.component.html | 1 +
.../controller-services.component.spec.ts | 10 +-
.../controller-services.component.ts | 13 ++
.../flow-analysis-rule-table.component.html | 6 +-
.../flow-analysis-rule-table.component.ts | 6 +
.../flow-analysis-rules.component.html | 1 +
.../flow-analysis-rules.component.ts | 14 ++
.../management-controller-services.component.html | 1 +
.../management-controller-services.component.ts | 13 ++
.../reporting-task-table.component.html | 6 +-
.../reporting-task-table.component.ts | 5 +
.../reporting-tasks/reporting-tasks.component.html | 1 +
.../reporting-tasks/reporting-tasks.component.ts | 14 ++
.../src/app/service/component-state.service.ts | 51 +++++++
.../component-state/component-state.actions.ts | 49 ++++++
.../component-state/component-state.effects.ts | 139 +++++++++++++++++
.../component-state.reducer.ts} | 53 +++----
.../component-state/component-state.selectors.ts | 38 +++++
.../nifi/src/app/state/component-state/index.ts | 65 ++++++++
.../src/main/nifi/src/app/state/index.ts | 6 +-
.../state/status-history/status-history.reducer.ts | 1 -
.../component-state/component-state.component.html | 86 +++++++++++
.../component-state/component-state.component.scss | 32 ++++
.../component-state.component.spec.ts} | 23 ++-
.../component-state/component-state.component.ts | 169 +++++++++++++++++++++
.../controller-service-table.component.html | 6 +-
.../controller-service-table.component.ts | 6 +
.../extension-creation.component.html | 1 -
31 files changed, 803 insertions(+), 56 deletions(-)
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
index 4fde32890c..749a8dda7a 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
@@ -41,6 +41,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { ControllerServiceStateEffects } from
'./state/contoller-service-state/controller-service-state.effects';
import { SystemDiagnosticsEffects } from
'./state/system-diagnostics/system-diagnostics.effects';
import { FlowConfigurationEffects } from
'./state/flow-configuration/flow-configuration.effects';
+import { ComponentStateEffects } from
'./state/component-state/component-state.effects';
@NgModule({
declarations: [AppComponent],
@@ -65,7 +66,8 @@ import { FlowConfigurationEffects } from
'./state/flow-configuration/flow-config
FlowConfigurationEffects,
StatusHistoryEffects,
ControllerServiceStateEffects,
- SystemDiagnosticsEffects
+ SystemDiagnosticsEffects,
+ ComponentStateEffects
),
StoreDevtoolsModule.instrument({
maxAge: 25,
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
index 1210caef12..5e27e1cab8 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
@@ -55,6 +55,7 @@ import {
ContextMenuItemDefinition
} from '../../../ui/common/context-menu/context-menu.component';
import { promptEmptyQueueRequest, promptEmptyQueuesRequest } from
'../state/queue/queue.actions';
+import { getComponentStateAndOpenDialog } from
'../../../state/component-state/component-state.actions';
@Injectable({ providedIn: 'root' })
export class CanvasContextMenu implements ContextMenuDefinitionProvider {
@@ -679,13 +680,21 @@ export class CanvasContextMenu implements
ContextMenuDefinitionProvider {
},
{
condition: (selection: any) => {
- // TODO - isStatefulProcessor
- return false;
+ return this.canvasUtils.isStatefulProcessor(selection);
},
clazz: 'fa fa-tasks',
text: 'View state',
- action: () => {
- // TODO - viewState
+ action: (selection: any) => {
+ const selectionData = selection.datum();
+ this.store.dispatch(
+ getComponentStateAndOpenDialog({
+ request: {
+ componentName: selectionData.component.name,
+ componentUri: selectionData.uri,
+ canClear:
this.canvasUtils.isConfigurable(selection)
+ }
+ })
+ );
}
},
{
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
index 4e51520609..1f13e28667 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
@@ -464,6 +464,28 @@ export class CanvasUtils {
return selection.size() === 1 && selection.classed('funnel');
}
+ /**
+ * Determines whether the current selection is a stateful processor.
+ *
+ * @param {selection} selection
+ */
+ public isStatefulProcessor(selection: any): boolean {
+ // ensure the correct number of components are selected
+ if (selection.size() !== 1) {
+ return false;
+ }
+ if (this.canRead(selection) === false || this.canModify(selection) ===
false) {
+ return false;
+ }
+
+ if (this.isProcessor(selection)) {
+ const processorData: any = selection.datum();
+ return processorData.component.persistsState === true;
+ } else {
+ return false;
+ }
+ }
+
/**
* Determines whether the user can configure or open the policy management
page.
*/
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
index b950e7567c..0b3b547be5 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
@@ -48,6 +48,7 @@
(configureControllerService)="configureControllerService($event)"
(enableControllerService)="enableControllerService($event)"
(disableControllerService)="disableControllerService($event)"
+
(viewStateControllerService)="viewStateControllerService($event)"
(deleteControllerService)="deleteControllerService($event)"></controller-service-table>
</div>
<div class="flex justify-between">
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
index 435846b74a..34cf6502be 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
@@ -21,15 +21,23 @@ import { ControllerServices } from
'./controller-services.component';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from
'../../state/controller-services/controller-services.reducer';
import { RouterTestingModule } from '@angular/router/testing';
+import { Component } from '@angular/core';
describe('ControllerServices', () => {
let component: ControllerServices;
let fixture: ComponentFixture<ControllerServices>;
+ @Component({
+ selector: 'navigation',
+ standalone: true,
+ template: ''
+ })
+ class MockNavigation {}
+
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ControllerServices],
- imports: [RouterTestingModule],
+ imports: [RouterTestingModule, MockNavigation],
providers: [
provideMockStore({
initialState
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
index 0f2b78db14..6117b2894c 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
@@ -45,6 +45,7 @@ import { selectCurrentUser } from
'../../../../state/current-user/current-user.s
import { selectFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.selectors';
import { NiFiState } from '../../../../state';
import { loadFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.actions';
+import { getComponentStateAndOpenDialog } from
'../../../../state/component-state/component-state.actions';
@Component({
selector: 'controller-services',
@@ -189,6 +190,18 @@ export class ControllerServices implements OnInit,
OnDestroy {
);
}
+ viewStateControllerService(entity: ControllerServiceEntity): void {
+ this.store.dispatch(
+ getComponentStateAndOpenDialog({
+ request: {
+ componentUri: entity.uri,
+ componentName: entity.component.name,
+ canClear: entity.component.state === 'DISABLED'
+ }
+ })
+ );
+ }
+
deleteControllerService(entity: ControllerServiceEntity): void {
this.store.dispatch(
promptControllerServiceDeletion({
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
index 32802aa136..1b0d10065a 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
@@ -134,7 +134,11 @@
*ngIf="canDelete(item)"
(click)="deleteClicked(item)"
title="Delete"></div>
- <div class="pointer fa fa-tasks"
*ngIf="canViewState(item)" title="View State"></div>
+ <div
+ class="pointer fa fa-tasks"
+ *ngIf="canViewState(item)"
+ (click)="viewStateClicked(item)"
+ title="View State"></div>
</div>
</td>
</ng-container>
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
index 5108c5d352..16869ec6ef 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
@@ -60,6 +60,8 @@ export class FlowAnalysisRuleTable {
@Output() configureFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
@Output() enableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
+ @Output() viewStateFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
+ new EventEmitter<FlowAnalysisRuleEntity>();
@Output() disableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
new EventEmitter<FlowAnalysisRuleEntity>();
@@ -255,6 +257,10 @@ export class FlowAnalysisRuleTable {
return this.canRead(entity) && this.canWrite(entity) &&
entity.component.persistsState === true;
}
+ viewStateClicked(entity: FlowAnalysisRuleEntity): void {
+ this.viewStateFlowAnalysisRule.next(entity);
+ }
+
select(entity: FlowAnalysisRuleEntity): void {
this.selectFlowAnalysisRule.next(entity);
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
index 219b7dd22e..0c1b2e96bf 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
@@ -35,6 +35,7 @@
(selectFlowAnalysisRule)="selectFlowAnalysisRule($event)"
(enableFlowAnalysisRule)="enableFlowAnalysisRule($event)"
(disableFlowAnalysisRule)="disableFlowAnalysisRule($event)"
+
(viewStateFlowAnalysisRule)="viewStateFlowAnalysisRule($event)"
(deleteFlowAnalysisRule)="deleteFlowAnalysisRule($event)"></flow-analysis-rule-table>
</div>
<div class="flex justify-between">
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
index 42f882ceba..c1596d7295 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
@@ -41,6 +41,7 @@ import { selectCurrentUser } from
'../../../../state/current-user/current-user.s
import { NiFiState } from '../../../../state';
import { FlowAnalysisRuleEntity, FlowAnalysisRulesState } from
'../../state/flow-analysis-rules';
import { CurrentUser } from '../../../../state/current-user';
+import { getComponentStateAndOpenDialog } from
'../../../../state/component-state/component-state.actions';
@Component({
selector: 'flow-analysis-rules',
@@ -128,6 +129,19 @@ export class FlowAnalysisRules implements OnInit,
OnDestroy {
);
}
+ viewStateFlowAnalysisRule(entity: FlowAnalysisRuleEntity): void {
+ const canClear: boolean = entity.status.runStatus === 'DISABLED';
+ this.store.dispatch(
+ getComponentStateAndOpenDialog({
+ request: {
+ componentUri: entity.uri,
+ componentName: entity.component.name,
+ canClear
+ }
+ })
+ );
+ }
+
deleteFlowAnalysisRule(entity: FlowAnalysisRuleEntity): void {
this.store.dispatch(
promptFlowAnalysisRuleDeletion({
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
index 9545cb37ca..42de3d7de0 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
@@ -39,6 +39,7 @@
(configureControllerService)="configureControllerService($event)"
(enableControllerService)="enableControllerService($event)"
(disableControllerService)="disableControllerService($event)"
+
(viewStateControllerService)="viewStateControllerService($event)"
(deleteControllerService)="deleteControllerService($event)"></controller-service-table>
</div>
<div class="flex justify-between">
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
index 403bef96f7..7635b648cb 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
@@ -44,6 +44,7 @@ import { NiFiState } from '../../../../state';
import { selectFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.selectors';
import { loadFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.actions';
import { CurrentUser } from '../../../../state/current-user';
+import { getComponentStateAndOpenDialog } from
'../../../../state/component-state/component-state.actions';
@Component({
selector: 'management-controller-services',
@@ -139,6 +140,18 @@ export class ManagementControllerServices implements
OnInit, OnDestroy {
);
}
+ viewStateControllerService(entity: ControllerServiceEntity): void {
+ this.store.dispatch(
+ getComponentStateAndOpenDialog({
+ request: {
+ componentUri: entity.uri,
+ componentName: entity.component.name,
+ canClear: entity.component.state === 'DISABLED'
+ }
+ })
+ );
+ }
+
deleteControllerService(entity: ControllerServiceEntity): void {
this.store.dispatch(
promptControllerServiceDeletion({
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
index c0265f5d1b..ca78eaeaa7 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
@@ -124,7 +124,11 @@
*ngIf="canDelete(item)"
(click)="deleteClicked(item)"
title="Delete"></div>
- <div class="pointer fa fa-tasks"
*ngIf="canViewState(item)" title="View State"></div>
+ <div
+ class="pointer fa fa-tasks"
+ *ngIf="canViewState(item)"
+ (click)="viewStateClicked(item)"
+ title="View State"></div>
<div
class="pointer fa fa-key"
*ngIf="canManageAccessPolicies()"
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
index ca03470c8c..a09304f2ee 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
@@ -51,6 +51,7 @@ export class ReportingTaskTable {
@Output() deleteReportingTask: EventEmitter<ReportingTaskEntity> = new
EventEmitter<ReportingTaskEntity>();
@Output() startReportingTask: EventEmitter<ReportingTaskEntity> = new
EventEmitter<ReportingTaskEntity>();
@Output() configureReportingTask: EventEmitter<ReportingTaskEntity> = new
EventEmitter<ReportingTaskEntity>();
+ @Output() viewStateReportingTask: EventEmitter<ReportingTaskEntity> = new
EventEmitter<ReportingTaskEntity>();
@Output() stopReportingTask: EventEmitter<ReportingTaskEntity> = new
EventEmitter<ReportingTaskEntity>();
protected readonly TextTip = TextTip;
@@ -233,6 +234,10 @@ export class ReportingTaskTable {
return this.canRead(entity) && this.canWrite(entity) &&
entity.component.persistsState === true;
}
+ viewStateClicked(entity: ReportingTaskEntity): void {
+ this.viewStateReportingTask.next(entity);
+ }
+
canManageAccessPolicies(): boolean {
return this.flowConfiguration.supportsManagedAuthorizer &&
this.currentUser.tenantsPermissions.canRead;
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
index 771e6bc830..3414c21b1d 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
@@ -33,6 +33,7 @@
[currentUser]="currentUser"
[flowConfiguration]="(flowConfiguration$ | async)!"
(configureReportingTask)="configureReportingTask($event)"
+ (viewStateReportingTask)="viewStateReportingTask($event)"
(selectReportingTask)="selectReportingTask($event)"
(deleteReportingTask)="deleteReportingTask($event)"
(stopReportingTask)="stopReportingTask($event)"
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
index c35e36fee8..fd8c6471cf 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
@@ -42,6 +42,7 @@ import { selectCurrentUser } from
'../../../../state/current-user/current-user.s
import { NiFiState } from '../../../../state';
import { loadFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.actions';
import { selectFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.selectors';
+import { getComponentStateAndOpenDialog } from
'../../../../state/component-state/component-state.actions';
@Component({
selector: 'reporting-tasks',
@@ -127,6 +128,19 @@ export class ReportingTasks implements OnInit, OnDestroy {
);
}
+ viewStateReportingTask(entity: ReportingTaskEntity): void {
+ const canClear: boolean = entity.status.runStatus === 'STOPPED' &&
entity.status.activeThreadCount === 0;
+ this.store.dispatch(
+ getComponentStateAndOpenDialog({
+ request: {
+ componentUri: entity.uri,
+ componentName: entity.component.name,
+ canClear
+ }
+ })
+ );
+ }
+
startReportingTask(entity: ReportingTaskEntity): void {
this.store.dispatch(
startReportingTask({
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
new file mode 100644
index 0000000000..f87d93b7b4
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 { HttpClient } from '@angular/common/http';
+import { ClearComponentStateRequest, LoadComponentStateRequest } from
'../state/component-state';
+import { Observable } from 'rxjs';
+import { NiFiCommon } from './nifi-common.service';
+
+@Injectable({ providedIn: 'root' })
+export class ComponentStateService {
+ constructor(
+ private httpClient: HttpClient,
+ private nifiCommon: NiFiCommon
+ ) {}
+
+ /**
+ * The NiFi model contain the url for each component. That URL is an
absolute URL. Angular CSRF handling
+ * does not work on absolute URLs, so we need to strip off the proto for
the request header to be added.
+ *
+ * https://stackoverflow.com/a/59586462
+ *
+ * @param url
+ * @private
+ */
+ private stripProtocol(url: string): string {
+ return this.nifiCommon.substringAfterFirst(url, ':');
+ }
+
+ getComponentState(request: LoadComponentStateRequest): Observable<any> {
+ return
this.httpClient.get(`${this.stripProtocol(request.componentUri)}/state`);
+ }
+
+ clearComponentState(request: ClearComponentStateRequest): Observable<any> {
+ return
this.httpClient.post(`${this.stripProtocol(request.componentUri)}/state/clear-requests`,
{});
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts
new file mode 100644
index 0000000000..bb2ee31219
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.actions.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { ComponentStateRequest, ComponentStateResponse } from './index';
+
+const COMPONENT_STATE_PREFIX = '[Component State]';
+
+export const getComponentStateAndOpenDialog = createAction(
+ `${COMPONENT_STATE_PREFIX} Get Component State and Open Dialog`,
+ props<{ request: ComponentStateRequest }>()
+);
+
+export const loadComponentStateSuccess = createAction(
+ `${COMPONENT_STATE_PREFIX} Load Component State Success`,
+ props<{ response: ComponentStateResponse }>()
+);
+
+export const openComponentStateDialog =
createAction(`${COMPONENT_STATE_PREFIX} Open Component State Dialog`);
+
+export const componentStateApiError = createAction(
+ `${COMPONENT_STATE_PREFIX} Component State API error`,
+ props<{ error: string }>()
+);
+
+export const clearComponentState = createAction(`${COMPONENT_STATE_PREFIX}
Clear Component State`);
+
+export const reloadComponentState = createAction(`${COMPONENT_STATE_PREFIX}
Reload Component State`);
+
+export const reloadComponentStateSuccess = createAction(
+ `${COMPONENT_STATE_PREFIX} Reload Component State Success`,
+ props<{ response: ComponentStateResponse }>()
+);
+
+export const resetComponentState = createAction(`${COMPONENT_STATE_PREFIX}
Reset Component State`);
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts
new file mode 100644
index 0000000000..5f02df85a2
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.effects.ts
@@ -0,0 +1,139 @@
+/*
+ * 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, concatLatestFrom, createEffect, ofType } from
'@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../index';
+import * as ComponentStateActions from './component-state.actions';
+import { catchError, from, map, of, switchMap, tap } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { ComponentStateService } from '../../service/component-state.service';
+import { ComponentStateDialog } from
'../../ui/common/component-state/component-state.component';
+import { resetComponentState } from './component-state.actions';
+import { selectComponentUri } from './component-state.selectors';
+import { isDefinedAndNotNull } from '../shared';
+
+@Injectable()
+export class ComponentStateEffects {
+ constructor(
+ private actions$: Actions,
+ private store: Store<NiFiState>,
+ private componentStateService: ComponentStateService,
+ private dialog: MatDialog
+ ) {}
+
+ getComponentStateAndOpenDialog$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ComponentStateActions.getComponentStateAndOpenDialog),
+ map((action) => action.request),
+ switchMap((request) =>
+ from(
+ this.componentStateService.getComponentState({
componentUri: request.componentUri }).pipe(
+ map((response: any) =>
+ ComponentStateActions.loadComponentStateSuccess({
+ response: {
+ componentState: response.componentState
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ ComponentStateActions.componentStateApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+
+ loadComponentStateSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ComponentStateActions.loadComponentStateSuccess),
+ map((action) => action.response),
+ switchMap((response) =>
of(ComponentStateActions.openComponentStateDialog()))
+ )
+ );
+
+ openComponentStateDialog$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ComponentStateActions.openComponentStateDialog),
+ tap(() => {
+ const dialogReference =
this.dialog.open(ComponentStateDialog, {
+ panelClass: 'large-dialog'
+ });
+
+ dialogReference.afterClosed().subscribe((response) => {
+ this.store.dispatch(resetComponentState());
+ });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ clearComponentState$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ComponentStateActions.clearComponentState),
+ concatLatestFrom(() =>
this.store.select(selectComponentUri).pipe(isDefinedAndNotNull())),
+ switchMap(([action, componentUri]) =>
+ from(
+ this.componentStateService.clearComponentState({
componentUri }).pipe(
+ map((response: any) =>
ComponentStateActions.reloadComponentState()),
+ catchError((error) =>
+ of(
+ ComponentStateActions.componentStateApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+
+ reloadComponentState$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ComponentStateActions.reloadComponentState),
+ concatLatestFrom(() =>
this.store.select(selectComponentUri).pipe(isDefinedAndNotNull())),
+ switchMap(([action, componentUri]) =>
+ from(
+ this.componentStateService.getComponentState({
componentUri }).pipe(
+ map((response: any) =>
+ ComponentStateActions.reloadComponentStateSuccess({
+ response: {
+ componentState: response.componentState
+ }
+ })
+ ),
+ catchError((error) =>
+ of(
+ ComponentStateActions.componentStateApiError({
+ error: error.error
+ })
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts
similarity index 54%
copy from
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
copy to
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts
index 7f41618bf3..d8ee39c266 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.reducer.ts
@@ -15,53 +15,46 @@
* limitations under the License.
*/
-import { StatusHistoryEntity, StatusHistoryState } from './index';
+import { ComponentStateState } from './index';
import { createReducer, on } from '@ngrx/store';
import {
- clearStatusHistory,
- reloadStatusHistory,
- loadStatusHistorySuccess,
- statusHistoryApiError,
- viewStatusHistoryComplete,
- reloadStatusHistorySuccess,
- getStatusHistoryAndOpenDialog
-} from './status-history.actions';
-import { produce } from 'immer';
-
-export const initialState: StatusHistoryState = {
- statusHistory: {} as StatusHistoryEntity,
+ resetComponentState,
+ loadComponentStateSuccess,
+ componentStateApiError,
+ getComponentStateAndOpenDialog,
+ reloadComponentStateSuccess
+} from './component-state.actions';
+
+export const initialState: ComponentStateState = {
+ componentName: null,
+ componentUri: null,
+ componentState: null,
+ canClear: null,
status: 'pending',
- error: null,
- loadedTimestamp: ''
+ error: null
};
-export const statusHistoryReducer = createReducer(
+export const componentStateReducer = createReducer(
initialState,
-
- on(reloadStatusHistory, getStatusHistoryAndOpenDialog, (state) => ({
+ on(getComponentStateAndOpenDialog, (state, { request }) => ({
...state,
+ componentName: request.componentName,
+ componentUri: request.componentUri,
+ canClear: request.canClear,
status: 'loading' as const
})),
-
- on(loadStatusHistorySuccess, reloadStatusHistorySuccess, (state, {
response }) => ({
+ on(loadComponentStateSuccess, reloadComponentStateSuccess, (state, {
response }) => ({
...state,
error: null,
status: 'success' as const,
- loadedTimestamp: response.statusHistory.statusHistory.generated,
- statusHistory: response.statusHistory
+ componentState: response.componentState
})),
-
- on(statusHistoryApiError, (state, { error }) => ({
+ on(componentStateApiError, (state, { error }) => ({
...state,
error,
status: 'error' as const
})),
-
- on(clearStatusHistory, (state) => ({
- ...initialState
- })),
-
- on(viewStatusHistoryComplete, (state) => ({
+ on(resetComponentState, (state) => ({
...initialState
}))
);
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts
new file mode 100644
index 0000000000..e418e0d8a9
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/component-state.selectors.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 { createFeatureSelector, createSelector } from '@ngrx/store';
+import { componentStateFeatureKey, ComponentStateState } from './index';
+
+export const selectComponentStateState =
createFeatureSelector<ComponentStateState>(componentStateFeatureKey);
+
+export const selectComponentState = createSelector(
+ selectComponentStateState,
+ (state: ComponentStateState) => state.componentState
+);
+
+export const selectComponentName = createSelector(
+ selectComponentStateState,
+ (state: ComponentStateState) => state.componentName
+);
+
+export const selectComponentUri = createSelector(
+ selectComponentStateState,
+ (state: ComponentStateState) => state.componentUri
+);
+
+export const selectCanClear = createSelector(selectComponentStateState,
(state: ComponentStateState) => state.canClear);
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts
new file mode 100644
index 0000000000..7335e99fba
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/component-state/index.ts
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+export const componentStateFeatureKey = 'componentState';
+
+export interface ComponentStateRequest {
+ componentName: string;
+ componentUri: string;
+ canClear: boolean;
+}
+
+export interface LoadComponentStateRequest {
+ componentUri: string;
+}
+
+export interface ClearComponentStateRequest {
+ componentUri: string;
+}
+
+export interface ComponentStateResponse {
+ componentState: ComponentState;
+}
+
+export interface StateEntry {
+ key: string;
+ value: string;
+ clusterNodeId?: string;
+ clusterNodeAddress?: string;
+}
+
+export interface StateMap {
+ scope: string;
+ state: StateEntry[];
+ totalEntryCount: number;
+}
+
+export interface ComponentState {
+ componentId: string;
+ localState?: StateMap;
+ clusterState?: StateMap;
+ stateDescription: string;
+}
+
+export interface ComponentStateState {
+ componentName: string | null;
+ componentUri: string | null;
+ componentState: ComponentState | null;
+ canClear: boolean | null;
+ error: string | null;
+ status: 'pending' | 'loading' | 'error' | 'success';
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
index e4b7452e89..4ae6619448 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
@@ -31,6 +31,8 @@ import { systemDiagnosticsFeatureKey, SystemDiagnosticsState
} from './system-di
import { systemDiagnosticsReducer } from
'./system-diagnostics/system-diagnostics.reducer';
import { flowConfigurationFeatureKey, FlowConfigurationState } from
'./flow-configuration';
import { flowConfigurationReducer } from
'./flow-configuration/flow-configuration.reducer';
+import { componentStateFeatureKey, ComponentStateState } from
'./component-state';
+import { componentStateReducer } from
'./component-state/component-state.reducer';
export interface NiFiState {
router: RouterReducerState;
@@ -41,6 +43,7 @@ export interface NiFiState {
[statusHistoryFeatureKey]: StatusHistoryState;
[controllerServiceStateFeatureKey]: ControllerServiceState;
[systemDiagnosticsFeatureKey]: SystemDiagnosticsState;
+ [componentStateFeatureKey]: ComponentStateState;
}
export const rootReducers: ActionReducerMap<NiFiState> = {
@@ -51,5 +54,6 @@ export const rootReducers: ActionReducerMap<NiFiState> = {
[flowConfigurationFeatureKey]: flowConfigurationReducer,
[statusHistoryFeatureKey]: statusHistoryReducer,
[controllerServiceStateFeatureKey]: controllerServiceStateReducer,
- [systemDiagnosticsFeatureKey]: systemDiagnosticsReducer
+ [systemDiagnosticsFeatureKey]: systemDiagnosticsReducer,
+ [componentStateFeatureKey]: componentStateReducer
};
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
index 7f41618bf3..3f565207e0 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/status-history/status-history.reducer.ts
@@ -26,7 +26,6 @@ import {
reloadStatusHistorySuccess,
getStatusHistoryAndOpenDialog
} from './status-history.actions';
-import { produce } from 'immer';
export const initialState: StatusHistoryState = {
statusHistory: {} as StatusHistoryEntity,
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html
new file mode 100644
index 0000000000..b04dbeea01
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.html
@@ -0,0 +1,86 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="component-state-dialog" tabindex="0">
+ <h2 mat-dialog-title>Component State</h2>
+ <mat-dialog-content>
+ <div class="flex flex-col justify-between gap-y-5">
+ <div class="flex flex-col" *ngIf="componentName$ | async; let
componentName">
+ <div>Name</div>
+ <div class="value">{{ componentName }}</div>
+ </div>
+ <div class="flex flex-col">
+ <div>Description</div>
+ <div class="value">
+ {{ stateDescription }}
+ </div>
+ </div>
+ <div class="listing-table">
+ <form [formGroup]="filterForm" class="flex flex-col gap-y-2">
+ <div class="value">Displaying {{ filteredEntries }} of {{
totalEntries }}</div>
+ <div class="flex justify-between items-center">
+ <mat-form-field>
+ <mat-label>Filter</mat-label>
+ <input matInput type="text" class="small"
formControlName="filterTerm" />
+ </mat-form-field>
+ <ng-container *ngIf="{ value: (canClear$ | async)! }
as canClear">
+ <div *ngIf="canClear.value && totalEntries > 0">
+ <a (click)="clearState()">Clear state</a>
+ </div>
+ </ng-container>
+ </div>
+ </form>
+ <div class="h-72 overflow-y-auto overflow-x-hidden border">
+ <table
+ mat-table
+ [dataSource]="dataSource"
+ matSort
+ matSortDisableClear
+ (matSortChange)="sortData($event)"
+ [matSortActive]="initialSortColumn"
+ [matSortDirection]="initialSortDirection">
+ <!-- Key Column -->
+ <ng-container matColumnDef="key">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>Key</th>
+ <td mat-cell *matCellDef="let item">
+ {{ item.key }}
+ </td>
+ </ng-container>
+
+ <!-- Value Column -->
+ <ng-container matColumnDef="value">
+ <th mat-header-cell *matHeaderCellDef
mat-sort-header>Value</th>
+ <td mat-cell *matCellDef="let item"
[title]="item.value">
+ {{ item.value }}
+ </td>
+ </ng-container>
+
+ <tr mat-header-row *matHeaderRowDef="displayedColumns;
sticky: true"></tr>
+ <tr
+ mat-row
+ *matRowDef="let row; let even = even; columns:
displayedColumns"
+ [class.even]="even"></tr>
+ </table>
+ </div>
+ </div>
+ <div *ngIf="partialResults" class="-mt-3">Showing partial
results</div>
+ </div>
+ </mat-dialog-content>
+ <mat-dialog-actions align="end">
+ <button color="primary" mat-raised-button
mat-dialog-close>Close</button>
+ </mat-dialog-actions>
+</div>
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss
new file mode 100644
index 0000000000..17606c83f5
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.scss
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@use '@angular/material' as mat;
+
+.component-state-dialog {
+ @include mat.button-density(-1);
+
+ width: 760px;
+
+ .listing-table {
+ table {
+ .mat-column-key {
+ width: 200px;
+ }
+ }
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts
similarity index 64%
copy from
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
copy to
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts
index 435846b74a..033fe2a105 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.spec.ts
@@ -17,26 +17,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ControllerServices } from './controller-services.component';
+import { ComponentStateDialog } from './component-state.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideMockStore } from '@ngrx/store/testing';
-import { initialState } from
'../../state/controller-services/controller-services.reducer';
-import { RouterTestingModule } from '@angular/router/testing';
+import { initialState } from
'../../../state/component-state/component-state.reducer';
-describe('ControllerServices', () => {
- let component: ControllerServices;
- let fixture: ComponentFixture<ControllerServices>;
+describe('ComponentStateDialog', () => {
+ let component: ComponentStateDialog;
+ let fixture: ComponentFixture<ComponentStateDialog>;
beforeEach(() => {
TestBed.configureTestingModule({
- declarations: [ControllerServices],
- imports: [RouterTestingModule],
- providers: [
- provideMockStore({
- initialState
- })
- ]
+ imports: [ComponentStateDialog, BrowserAnimationsModule],
+ providers: [provideMockStore({ initialState })]
});
- fixture = TestBed.createComponent(ControllerServices);
+ fixture = TestBed.createComponent(ComponentStateDialog);
component = fixture.componentInstance;
fixture.detectChanges();
});
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts
new file mode 100644
index 0000000000..8f37bb96c7
--- /dev/null
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/component-state/component-state.component.ts
@@ -0,0 +1,169 @@
+/*
+ * 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 { AfterViewInit, Component, Input } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { NiFiCommon } from '../../../service/nifi-common.service';
+import { MatSortModule, Sort } from '@angular/material/sort';
+import { AsyncPipe, NgIf } from '@angular/common';
+import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive';
+import { NifiSpinnerDirective } from '../spinner/nifi-spinner.directive';
+import { ComponentStateState, StateEntry, StateMap } from
'../../../state/component-state';
+import { Store } from '@ngrx/store';
+import { clearComponentState } from
'../../../state/component-state/component-state.actions';
+import {
+ selectCanClear,
+ selectComponentName,
+ selectComponentState
+} from '../../../state/component-state/component-state.selectors';
+import { isDefinedAndNotNull } from '../../../state/shared';
+import { debounceTime, Observable } from 'rxjs';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+
+@Component({
+ selector: 'component-state',
+ standalone: true,
+ templateUrl: './component-state.component.html',
+ imports: [
+ MatButtonModule,
+ MatDialogModule,
+ MatTableModule,
+ MatSortModule,
+ NgIf,
+ NifiTooltipDirective,
+ NifiSpinnerDirective,
+ AsyncPipe,
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule
+ ],
+ styleUrls: ['./component-state.component.scss',
'../../../../assets/styles/listing-table.scss']
+})
+export class ComponentStateDialog implements AfterViewInit {
+ @Input() initialSortColumn: 'key' | 'value' = 'key';
+ @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
+
+ componentName$: Observable<string> =
this.store.select(selectComponentName).pipe(isDefinedAndNotNull());
+ canClear$: Observable<boolean> =
this.store.select(selectCanClear).pipe(isDefinedAndNotNull());
+
+ // TODO - need to include scope column when clustered
+ displayedColumns: string[] = ['key', 'value'];
+ dataSource: MatTableDataSource<StateEntry> = new
MatTableDataSource<StateEntry>();
+
+ filterForm: FormGroup;
+
+ stateDescription: string = '';
+ totalEntries: number = 0;
+ filteredEntries: number = 0;
+ partialResults: boolean = false;
+
+ constructor(
+ private store: Store<ComponentStateState>,
+ private formBuilder: FormBuilder,
+ private nifiCommon: NiFiCommon
+ ) {
+ this.filterForm = this.formBuilder.group({ filterTerm: '' });
+
+ this.store
+ .select(selectComponentState)
+ .pipe(isDefinedAndNotNull(), takeUntilDestroyed())
+ .subscribe((componentState) => {
+ this.stateDescription = componentState.stateDescription;
+
+ const stateItems: StateEntry[] = [];
+ if (componentState.localState) {
+ const localStateItems: StateEntry[] =
this.processStateMap(componentState.localState);
+ stateItems.push(...localStateItems);
+ }
+ if (componentState.clusterState) {
+ const clusterStateItems: StateEntry[] =
this.processStateMap(componentState.clusterState);
+ stateItems.push(...clusterStateItems);
+ }
+
+ this.dataSource.data = this.sortStateEntries(stateItems, {
+ active: this.initialSortColumn,
+ direction: this.initialSortDirection
+ });
+ this.filteredEntries = stateItems.length;
+
+ // apply any filtering to the new data
+ const filterTerm = this.filterForm.get('filterTerm')?.value;
+ if (filterTerm?.length > 0) {
+ this.applyFilter(filterTerm);
+ }
+ });
+ }
+
+ ngAfterViewInit(): void {
+ this.filterForm
+ .get('filterTerm')
+ ?.valueChanges.pipe(debounceTime(500))
+ .subscribe((filterTerm: string) => {
+ this.applyFilter(filterTerm);
+ });
+ }
+
+ processStateMap(stateMap: StateMap): StateEntry[] {
+ const stateItems: StateEntry[] = stateMap.state ? stateMap.state : [];
+
+ if (stateItems.length !== stateMap.totalEntryCount) {
+ this.partialResults = true;
+ }
+
+ this.totalEntries += stateMap.totalEntryCount;
+
+ return stateItems;
+ }
+
+ applyFilter(filterTerm: string) {
+ this.dataSource.filter = filterTerm.trim().toLowerCase();
+ this.filteredEntries = this.dataSource.filteredData.length;
+ }
+
+ sortData(sort: Sort) {
+ this.dataSource.data = this.sortStateEntries(this.dataSource.data,
sort);
+ }
+
+ private sortStateEntries(data: StateEntry[], sort: Sort): StateEntry[] {
+ if (!data) {
+ return [];
+ }
+
+ return data.slice().sort((a, b) => {
+ const isAsc = sort.direction === 'asc';
+ let retVal = 0;
+ switch (sort.active) {
+ case 'key':
+ retVal = this.nifiCommon.compareString(a.key, b.key);
+ break;
+ case 'value':
+ retVal = this.nifiCommon.compareString(a.value, b.value);
+ break;
+ }
+ return retVal * (isAsc ? 1 : -1);
+ });
+ }
+
+ clearState(): void {
+ this.store.dispatch(clearComponentState());
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
index 03076a9a03..0ae491ed2f 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
@@ -141,7 +141,11 @@
*ngIf="canDelete(item)"
(click)="deleteClicked(item, $event)"
title="Delete"></div>
- <div class="pointer fa fa-tasks"
*ngIf="canViewState(item)" title="View State"></div>
+ <div
+ class="pointer fa fa-tasks"
+ *ngIf="canViewState(item)"
+ (click)="viewStateClicked(item)"
+ title="View State"></div>
<div
class="pointer fa fa-key"
*ngIf="canManageAccessPolicies()"
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
index 81b968cd20..fe33870ba1 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
@@ -80,6 +80,8 @@ export class ControllerServiceTable {
new EventEmitter<ControllerServiceEntity>();
@Output() disableControllerService: EventEmitter<ControllerServiceEntity> =
new EventEmitter<ControllerServiceEntity>();
+ @Output() viewStateControllerService:
EventEmitter<ControllerServiceEntity> =
+ new EventEmitter<ControllerServiceEntity>();
protected readonly TextTip = TextTip;
protected readonly BulletinsTip = BulletinsTip;
@@ -251,6 +253,10 @@ export class ControllerServiceTable {
return this.canRead(entity) && this.canWrite(entity) &&
entity.component.persistsState === true;
}
+ viewStateClicked(entity: ControllerServiceEntity): void {
+ this.viewStateControllerService.next(entity);
+ }
+
canManageAccessPolicies(): boolean {
return this.flowConfiguration.supportsManagedAuthorizer &&
this.currentUser.tenantsPermissions.canRead;
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html
index ba83e572d8..e850ee7ef7 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.html
@@ -100,7 +100,6 @@
<button color="accent" mat-raised-button
mat-dialog-close>Cancel</button>
<button
[disabled]="selectedType == null || saving"
- type="submit"
color="primary"
(click)="createExtension(selectedType)"
mat-raised-button>