From 7dc696ecdca1a77f78be5f6169b39b400dd60f02 Mon Sep 17 00:00:00 2001 From: Rob Fellows Date: Fri, 9 Feb 2024 13:19:12 -0500 Subject: [PATCH] [NIFI-12665] fetch parameter provider parameters (#8367) * [NIFI-12665] - fetch parameter provider parameters * add permissions checks to parameter provider table actions for edit, delete, and fetch. * error handling * routing to fetch parameter provider dialog * list the parameter groups * parameter sensitivity selction/deselection * show banner error if any affected/referencing components are not readable and writeable * add indicators to parameters for changed, new, removed, ... * refactored parameter-references component to the common area. leveraged it in the fetch dialog. * validate the fetch form * submit the fetch parameter provider parameters * make the async update step completion icon color theme-aware * add missing license header * fixes for the initial round of review comments * fixing issues found in review * fix registry clients test * stop polling when there is an api error * use sort and join pipes in a couple of more places. * protect references to parameter provider in the context for read permissions * when full screen error is triggered, close any open dialog with the 'ROUTED' result to prevent unintended afterClosed actions taking place (like re-selection) * handle fetch parameter provider error * remove TODO comments * call fullScreenError correctly This closes #8367 --- .../nifi/src/app/_app.component-theme.scss | 6 + .../src/main/nifi/src/app/app.component.ts | 2 +- .../src/main/nifi/src/app/app.module.ts | 4 +- .../bulletin-board-list.component.ts | 28 +- .../counter-table/counter-table.component.ts | 17 +- .../canvas/header/search/search.component.ts | 4 +- ...-registry-clients-dialog.component.spec.ts | 4 +- .../state/parameter-context-listing/index.ts | 3 +- .../edit-parameter-context.component.html | 11 +- .../edit-parameter-context.component.ts | 21 +- ...parameter-context-inheritance.component.ts | 2 +- .../parameter-context-table.component.html | 7 +- .../parameter-context-table.component.ts | 24 +- .../parameter-table.component.html | 2 +- .../parameter-table.component.ts | 3 +- .../provenance-event-table.component.ts | 17 +- .../service/parameter-provider.service.ts | 35 +- .../state/parameter-providers/index.ts | 85 ++- .../parameter-providers.actions.ts | 66 +- .../parameter-providers.effects.ts | 331 +++++++++- .../parameter-providers.reducer.ts | 56 +- .../parameter-providers.selectors.ts | 19 +- .../edit-parameter-provider.component.html | 3 +- .../edit-parameter-provider.component.spec.ts | 8 +- .../edit-parameter-provider.component.ts | 4 +- ...rameter-provider-parameters.component.html | 342 ++++++++++ ...rameter-provider-parameters.component.scss | 47 ++ ...eter-provider-parameters.component.spec.ts | 177 +++++ ...parameter-provider-parameters.component.ts | 603 ++++++++++++++++++ .../parameter-groups-table.component.html | 58 ++ .../parameter-groups-table.component.scss | 25 + .../parameter-groups-table.component.spec.ts | 39 ++ .../parameter-groups-table.component.ts | 96 +++ .../parameter-providers-table.component.html | 3 + .../parameter-providers-table.component.ts | 28 + .../parameter-providers.component.html | 1 + .../parameter-providers.component.ts | 37 +- .../summary-table-filter.component.ts | 53 +- .../user-table/user-table.component.ts | 17 +- .../main/nifi/src/app/pipes/join.pipe.spec.ts | 46 ++ .../src/main/nifi/src/app/pipes/join.pipe.ts | 27 + .../main/nifi/src/app/pipes/pipes.module.ts | 28 + .../main/nifi/src/app/pipes/sort.pipe.spec.ts | 41 ++ .../src/main/nifi/src/app/pipes/sort.pipe.ts | 43 ++ .../service/interceptors/auth.interceptor.ts | 4 +- .../nifi/src/app/state/error/error.effects.ts | 5 +- .../main/nifi/src/app/state/shared/index.ts | 14 +- .../component-state.component.ts | 5 +- .../disable-controller-service.component.html | 2 +- .../enable-controller-service.component.html | 2 +- .../parameter-references.component.html | 0 .../parameter-references.component.scss | 0 .../parameter-references.component.spec.ts | 0 .../parameter-references.component.ts | 10 +- .../status-history-chart.component.ts | 5 +- .../status-history.component.ts | 118 ++-- .../src/main/nifi/src/styles.scss | 7 + 57 files changed, 2470 insertions(+), 175 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.html create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.scss create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.spec.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.html create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.scss create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.spec.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.spec.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/pipes.module.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.spec.ts create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.ts rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{pages/parameter-contexts/ui/parameter-context-listing => ui/common}/parameter-references/parameter-references.component.html (100%) rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{pages/parameter-contexts/ui/parameter-context-listing => ui/common}/parameter-references/parameter-references.component.scss (100%) rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{pages/parameter-contexts/ui/parameter-context-listing => ui/common}/parameter-references/parameter-references.component.spec.ts (100%) rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{pages/parameter-contexts/ui/parameter-context-listing => ui/common}/parameter-references/parameter-references.component.ts (92%) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/_app.component-theme.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/_app.component-theme.scss index b24559cab4..b2f43cc03f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/_app.component-theme.scss +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/_app.component-theme.scss @@ -28,6 +28,7 @@ $accent-palette: map.get($color-config, 'accent'); $warn-palette: map.get($color-config, 'warn'); $canvas-primary-palette: map.get($canvas-color-config, 'primary'); + $canvas-accent-palette: map.get($canvas-color-config, 'accent'); // Get hues from palette $primary-palette-300: mat.get-color-from-palette($primary-palette, 300); @@ -35,6 +36,7 @@ $accent-palette-A400: mat.get-color-from-palette($accent-palette, 'A400'); $warn-palette-500: mat.get-color-from-palette($warn-palette, 500); $canvas-primary-palette-A200: mat.get-color-from-palette($canvas-primary-palette, 'A200'); + $canvas-accent-palette-500: mat.get-color-from-palette($canvas-accent-palette, 500); .splash { background-color: $primary-palette-500; @@ -54,4 +56,8 @@ .nifi-snackbar .mat-mdc-button:not(:disabled) .mdc-button__label { color: $accent-palette-A400; } + + .fa.fa-check.complete { + color: $canvas-accent-palette-500; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.component.ts index f83feb0e05..afc26c0e1a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, Inject } from '@angular/core'; +import { Component } from '@angular/core'; import { GuardsCheckEnd, GuardsCheckStart, NavigationCancel, Router } from '@angular/router'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Storage } from './service/storage.service'; 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 56c94fe9c4..02adc60d74 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 @@ -44,6 +44,7 @@ import { FlowConfigurationEffects } from './state/flow-configuration/flow-config import { ComponentStateEffects } from './state/component-state/component-state.effects'; import { ErrorEffects } from './state/error/error.effects'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { PipesModule } from './pipes/pipes.module'; @NgModule({ declarations: [AppComponent], @@ -80,7 +81,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; MatProgressSpinnerModule, MatNativeDateModule, MatDialogModule, - MatSnackBarModule + MatSnackBarModule, + PipesModule ], providers: [ { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/bulletins/ui/bulletin-board/bulletin-board-list/bulletin-board-list.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/bulletins/ui/bulletin-board/bulletin-board-list/bulletin-board-list.component.ts index 4ee96ad8f5..027c52ebd6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/bulletins/ui/bulletin-board/bulletin-board-list/bulletin-board-list.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/bulletins/ui/bulletin-board/bulletin-board-list/bulletin-board-list.component.ts @@ -15,7 +15,17 @@ * limitations under the License. */ -import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + DestroyRef, + ElementRef, + EventEmitter, + inject, + Input, + Output, + ViewChild +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -27,6 +37,7 @@ import { BulletinBoardEvent, BulletinBoardFilterArgs, BulletinBoardItem } from ' import { BulletinEntity, ComponentType } from '../../../../../state/shared'; import { debounceTime, delay, Subject } from 'rxjs'; import { RouterLink } from '@angular/router'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'bulletin-board-list', @@ -51,6 +62,7 @@ export class BulletinBoardList implements AfterViewInit { private bulletinsChanged$: Subject = new Subject(); private _items: BulletinBoardItem[] = []; + private destroyRef: DestroyRef = inject(DestroyRef); @ViewChild('scrollContainer') private scroll!: ElementRef; @@ -58,6 +70,7 @@ export class BulletinBoardList implements AfterViewInit { this._items = items; this.bulletinsChanged$.next(); } + get bulletinBoardItems(): BulletinBoardItem[] { return this._items; } @@ -74,16 +87,19 @@ export class BulletinBoardList implements AfterViewInit { ngAfterViewInit(): void { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { const filterColumn = this.filterForm.get('filterColumn')?.value; this.applyFilter(filterTerm, filterColumn); }); - this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - this.applyFilter(filterTerm, filterColumn); - }); + this.filterForm + .get('filterColumn') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + this.applyFilter(filterTerm, filterColumn); + }); // scroll the initial chuck of bulletins this.scrollToBottom(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts index ae59e7cff8..42231f61e9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts @@ -15,13 +15,14 @@ * limitations under the License. */ -import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core'; import { CounterEntity } from '../../../state/counter-listing'; import { MatTableDataSource } from '@angular/material/table'; import { Sort } from '@angular/material/sort'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounceTime } from 'rxjs'; import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'counter-table', @@ -30,6 +31,7 @@ import { NiFiCommon } from '../../../../../service/nifi-common.service'; }) export class CounterTable implements AfterViewInit { private _canModifyCounters = false; + private destroyRef: DestroyRef = inject(DestroyRef); filterTerm = ''; filterColumn: 'context' | 'name' = 'name'; totalCount = 0; @@ -95,16 +97,19 @@ export class CounterTable implements AfterViewInit { ngAfterViewInit(): void { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { const filterColumn = this.filterForm.get('filterColumn')?.value; this.applyFilter(filterTerm, filterColumn); }); - this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - this.applyFilter(filterTerm, filterColumn); - }); + this.filterForm + .get('filterColumn') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + this.applyFilter(filterTerm, filterColumn); + }); } applyFilter(filterTerm: string, filterColumn: string) { 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/canvas/header/search/search.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/canvas/header/search/search.component.ts index eb233b32b2..508dfe03a9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/search/search.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/canvas/header/search/search.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, DestroyRef, ElementRef, inject, Input, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { initialState } from '../../../../state/flow/flow.reducer'; import { debounceTime, filter, switchMap, tap } from 'rxjs'; @@ -70,6 +70,7 @@ export class Search implements OnInit { overlayY: 'top' }; private position: ConnectionPositionPair = new ConnectionPositionPair(this.originPos, this.overlayPos, 0, 2); + private destroyRef: DestroyRef = inject(DestroyRef); public positions: ConnectionPositionPair[] = [this.position]; searchForm: FormGroup; @@ -116,6 +117,7 @@ export class Search implements OnInit { this.searchForm .get('searchBar') ?.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef), filter((data) => data?.trim().length > 0), debounceTime(500), tap(() => (this.searching = true)), 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/common/no-registry-clients-dialog/no-registry-clients-dialog.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/common/no-registry-clients-dialog/no-registry-clients-dialog.component.spec.ts index 64117f14a8..4f69c349b9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/common/no-registry-clients-dialog/no-registry-clients-dialog.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/common/no-registry-clients-dialog/no-registry-clients-dialog.component.spec.ts @@ -19,6 +19,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NoRegistryClientsDialog } from './no-registry-clients-dialog.component'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; describe('NoRegistryClientsDialog', () => { let component: NoRegistryClientsDialog; @@ -26,7 +28,7 @@ describe('NoRegistryClientsDialog', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoRegistryClientsDialog], + imports: [NoRegistryClientsDialog, RouterModule, RouterTestingModule], providers: [ { provide: MAT_DIALOG_DATA, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/index.ts index caa0c36768..80b07b6ba9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/index.ts @@ -19,6 +19,7 @@ import { ParameterContextReferenceEntity, ParameterContextUpdateRequestEntity, ParameterEntity, + ParameterProviderConfigurationEntity, Permissions, Revision } from '../../../../state/shared'; @@ -73,7 +74,7 @@ export interface ParameterContext { parameters: ParameterEntity[]; boundProcessGroups: BoundProcessGroup[]; inheritedParameterContexts: ParameterContextReferenceEntity[]; - // private ParameterProviderConfigurationEntity parameterProviderConfiguration; + parameterProviderConfiguration?: ParameterProviderConfigurationEntity; } // TODO - Replace this with ProcessGroupEntity was available diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html index 5c42abe107..f4c54708e7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.html @@ -26,7 +26,7 @@ *ngFor="let updateStep of requestEntity.request.updateSteps" class="flex justify-between items-center">
{{ updateStep.description }}
-
+
@@ -67,6 +67,14 @@
Id
{{ request.parameterContext?.id }}
+
Name @@ -97,6 +105,7 @@
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts index 1e33e33927..7e5f5828ba 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts @@ -30,10 +30,16 @@ import { EditParameterContextRequest, ParameterContextEntity } from '../../../st import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive'; import { Client } from '../../../../../service/client.service'; import { ParameterTable } from '../parameter-table/parameter-table.component'; -import { Parameter, ParameterContextUpdateRequestEntity, ParameterEntity } from '../../../../../state/shared'; +import { + Parameter, + ParameterContextUpdateRequestEntity, + ParameterEntity, + ParameterProviderConfiguration +} from '../../../../../state/shared'; import { ProcessGroupReferences } from '../process-group-references/process-group-references.component'; import { ParameterContextInheritance } from '../parameter-context-inheritance/parameter-context-inheritance.component'; -import { ParameterReferences } from '../parameter-references/parameter-references.component'; +import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'edit-parameter-context', @@ -56,7 +62,8 @@ import { ParameterReferences } from '../parameter-references/parameter-reference ParameterTable, ProcessGroupReferences, ParameterContextInheritance, - ParameterReferences + ParameterReferences, + RouterLink ], styleUrls: ['./edit-parameter-context.component.scss'] }) @@ -72,6 +79,7 @@ export class EditParameterContext { editParameterContextForm: FormGroup; isNew: boolean; + parameterProvider: ParameterProviderConfiguration | null = null; parameters!: ParameterEntity[]; @@ -91,6 +99,9 @@ export class EditParameterContext { request.parameterContext.component.inheritedParameterContexts ) }); + if (request.parameterContext.component.parameterProviderConfiguration) { + this.parameterProvider = request.parameterContext.component.parameterProviderConfiguration.component; + } } else { this.isNew = true; @@ -153,4 +164,8 @@ export class EditParameterContext { this.editParameterContext.next(payload); } } + + getParameterProviderLink(parameterProvider: ParameterProviderConfiguration): string[] { + return ['/settings', 'parameter-providers', parameterProvider.parameterProviderId]; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts index ae108cdd39..ad85753cfe 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-inheritance/parameter-context-inheritance.component.ts @@ -27,7 +27,7 @@ import { NiFiCommon } from '../../../../../service/nifi-common.service'; import { ParameterContextReferenceEntity, TextTipInput } from '../../../../../state/shared'; import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive'; import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component'; -import { ParameterReferences } from '../parameter-references/parameter-references.component'; +import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component'; import { ParameterContextEntity } from '../../../state/parameter-context-listing'; import { DragDropModule, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html index 5b42331e6c..929b275265 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.html @@ -83,7 +83,12 @@ (click)="$event.stopPropagation()" [routerLink]="getPolicyLink(item)" title="Access Policies">
- +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts index 2f298c5f8e..cc01d922fb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts @@ -22,6 +22,7 @@ import { NiFiCommon } from '../../../../../service/nifi-common.service'; import { ParameterContextEntity } from '../../../state/parameter-context-listing'; import { FlowConfiguration } from '../../../../../state/flow-configuration'; import { CurrentUser } from '../../../../../state/current-user'; +import { ParameterProviderConfigurationEntity } from '../../../../../state/shared'; @Component({ selector: 'parameter-context-table', @@ -66,7 +67,14 @@ export class ParameterContextTable { } formatProvider(entity: ParameterContextEntity): string { - return ''; + if (!this.canRead(entity)) { + return ''; + } + const paramProvider = entity.component.parameterProviderConfiguration; + if (!paramProvider) { + return ''; + } + return `${paramProvider.component.parameterGroupName} from ${paramProvider.component.parameterProviderName}`; } formatDescription(entity: ParameterContextEntity): string { @@ -94,6 +102,20 @@ export class ParameterContextTable { return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead; } + canGoToParameterProvider(entity: ParameterContextEntity): boolean { + if (!this.canRead(entity)) { + return false; + } + return !!entity.component.parameterProviderConfiguration; + } + + getParameterProviderLink(entity: ParameterContextEntity): string[] { + if (!entity.component.parameterProviderConfiguration) { + return []; + } + return ['/settings', 'parameter-providers', entity.component.parameterProviderConfiguration.id]; + } + getPolicyLink(entity: ParameterContextEntity): string[] { return ['/access-policies', 'read', 'component', 'parameter-contexts', entity.id]; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html index dab0d4ae1a..4713ce2471 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.html @@ -17,7 +17,7 @@
-
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts index 6bf6139598..65a5d48a52 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-table/parameter-table.component.ts @@ -28,7 +28,7 @@ import { Parameter, ParameterEntity, TextTipInput } from '../../../../../state/s import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive'; import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component'; import { Observable, take } from 'rxjs'; -import { ParameterReferences } from '../parameter-references/parameter-references.component'; +import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component'; import { Store } from '@ngrx/store'; import { ParameterContextListingState } from '../../../state/parameter-context-listing'; import { showOkDialog } from '../../../../flow-designer/state/flow/flow.actions'; @@ -69,6 +69,7 @@ export interface ParameterItem { export class ParameterTable implements AfterViewInit, ControlValueAccessor { @Input() createNewParameter!: (existingParameters: string[]) => Observable; @Input() editParameter!: (parameter: Parameter) => Observable; + @Input() canAddParameters = true; protected readonly TextTip = TextTip; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-event-table/provenance-event-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-event-table/provenance-event-table.component.ts index 3f04d8f966..3e0f98c828 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-event-table/provenance-event-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-event-table/provenance-event-table.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatSortModule, Sort } from '@angular/material/sort'; import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component'; @@ -37,6 +37,7 @@ import { Lineage, LineageRequest } from '../../../state/lineage'; import { LineageComponent } from './lineage/lineage.component'; import { GoToProvenanceEventSourceRequest, ProvenanceEventRequest } from '../../../state/provenance-event-listing'; import { MatSliderModule } from '@angular/material/slider'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'provenance-event-table', @@ -152,6 +153,7 @@ export class ProvenanceEventTable implements AfterViewInit { protected readonly TextTip = TextTip; protected readonly BulletinsTip = BulletinsTip; protected readonly ValidationErrorsTip = ValidationErrorsTip; + private destroyRef: DestroyRef = inject(DestroyRef); // TODO - conditionally include the cluster column displayedColumns: string[] = [ @@ -202,17 +204,20 @@ export class ProvenanceEventTable implements AfterViewInit { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { const filterColumn = this.filterForm.get('filterColumn')?.value; this.filterApplied = filterTerm.length > 0; this.applyFilter(filterTerm, filterColumn); }); - this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - this.applyFilter(filterTerm, filterColumn); - }); + this.filterForm + .get('filterColumn') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + this.applyFilter(filterTerm, filterColumn); + }); } updateSort(sort: Sort): void { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts index 4eb74f97db..e437e71c7c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts @@ -24,7 +24,10 @@ import { ConfigureParameterProviderRequest, CreateParameterProviderRequest, DeleteParameterProviderRequest, - ParameterProviderEntity + FetchParameterProviderParametersRequest, + ParameterProviderApplyParametersRequest, + ParameterProviderEntity, + ParameterProviderParameterApplicationEntity } from '../state/parameter-providers'; import { PropertyDescriptorRetriever } from '../../../state/shared'; @@ -75,4 +78,34 @@ export class ParameterProviderService implements PropertyDescriptorRetriever { updateParameterProvider(configureRequest: ConfigureParameterProviderRequest): Observable { return this.httpClient.put(this.nifiCommon.stripProtocol(configureRequest.uri), configureRequest.payload); } + + fetchParameters(request: FetchParameterProviderParametersRequest): Observable { + return this.httpClient.post( + `${ParameterProviderService.API}/parameter-providers/${request.id}/parameters/fetch-requests`, + { + id: request.id, + revision: request.revision + }, + { params: { disconnectedNodeAcknowledged: false } } + ); + } + + applyParameters(request: ParameterProviderParameterApplicationEntity): Observable { + return this.httpClient.post( + `${ParameterProviderService.API}/parameter-providers/${request.id}/apply-parameters-requests`, + request + ); + } + + pollParameterProviderParametersUpdateRequest( + updateRequest: ParameterProviderApplyParametersRequest + ): Observable { + return this.httpClient.get(this.nifiCommon.stripProtocol(updateRequest.uri)); + } + + deleteParameterProviderParametersUpdateRequest( + updateRequest: ParameterProviderApplyParametersRequest + ): Observable { + return this.httpClient.delete(this.nifiCommon.stripProtocol(updateRequest.uri)); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts index 957951e435..4bff4b69c7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts @@ -16,9 +16,11 @@ */ import { + AffectedComponentEntity, Bundle, DocumentedType, ParameterContextReferenceEntity, + ParameterEntity, Permissions, PropertyDescriptor, Revision @@ -26,7 +28,31 @@ import { export const parameterProvidersFeatureKey = 'parameterProviders'; +export interface ParameterSensitivity { + name: string; + sensitive: boolean; +} + +export interface ParameterGroupConfiguration { + groupName: string; + parameterContextName: string; + parameterSensitivities: { [key: string]: null | 'SENSITIVE' | 'NON_SENSITIVE' }; + synchronized?: boolean; +} + +export interface ParameterStatusEntity { + parameter?: ParameterEntity; + status: 'NEW' | 'CHANGED' | 'REMOVED' | 'MISSING_BUT_REFERENCED' | 'UNCHANGED'; +} + +export interface FetchedParameterMapping { + name: string; + sensitivity?: ParameterSensitivity; + status?: ParameterStatusEntity; +} + export interface ParameterProvider { + affectedComponents: AffectedComponentEntity[]; bundle: Bundle; comments: string; deprecated: boolean; @@ -35,7 +61,8 @@ export interface ParameterProvider { id: string; multipleVersionsAvailable: boolean; name: string; - parameterGroupConfigurations: any[]; + parameterGroupConfigurations: ParameterGroupConfiguration[]; + parameterStatus?: ParameterStatusEntity[]; persistsState: boolean; properties: { [key: string]: string }; referencingParameterContexts: ParameterContextReferenceEntity[]; @@ -54,12 +81,46 @@ export interface ParameterProviderEntity { uri: string; } +export interface ParameterProviderParameterApplicationEntity { + id: string; + revision: Revision; + disconnectedNodeAcknowledged: boolean; + parameterGroupConfigurations: ParameterGroupConfiguration[]; +} + +export interface UpdateStep { + description: string; + complete: boolean; + failureReason?: string; +} + +export interface ParameterContextUpdateRequest { + parameterContextRevision: Revision; + parameterContext: any; + referencingComponents: AffectedComponentEntity[]; +} + +// returned from '/apply-parameters-request' +export interface ParameterProviderApplyParametersRequest { + requestId: string; + complete: boolean; + lastUpdated: string; + percentComplete: number; + state: string; + uri: string; + parameterContextUpdates: ParameterContextUpdateRequest[]; + parameterProvider: ParameterProvider; + referencingComponents: AffectedComponentEntity[]; + updateSteps: UpdateStep[]; +} + export interface ParameterProvidersState { parameterProviders: ParameterProviderEntity[]; + fetched: ParameterProviderEntity | null; + applyParametersRequestEntity: ParameterProviderApplyParametersRequest | null; saving: boolean; loadedTimestamp: string; - error: string | null; - status: 'pending' | 'loading' | 'error' | 'success'; + status: 'pending' | 'loading' | 'success'; } export interface LoadParameterProvidersResponse { @@ -115,3 +176,21 @@ export interface UpdateParameterProviderRequest { payload: any; postUpdateNavigation?: string[]; } + +export interface FetchParameterProviderParametersRequest { + id: string; + revision: Revision; +} + +export interface FetchParameterProviderParametersResponse { + parameterProvider: ParameterProviderEntity; +} + +export interface FetchParameterProviderDialogRequest { + id: string; + parameterProvider: ParameterProviderEntity; +} + +export interface PollParameterProviderParametersUpdateSuccess { + request: ParameterProviderApplyParametersRequest; +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts index d59cbeb051..acbb90f9b5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts @@ -24,7 +24,11 @@ import { DeleteParameterProviderRequest, DeleteParameterProviderSuccess, EditParameterProviderRequest, + FetchParameterProviderParametersRequest, + FetchParameterProviderParametersResponse, LoadParameterProvidersResponse, + ParameterProviderParameterApplicationEntity, + PollParameterProviderParametersUpdateSuccess, SelectParameterProviderRequest } from './index'; @@ -39,7 +43,7 @@ export const loadParameterProvidersSuccess = createAction( props<{ response: LoadParameterProvidersResponse }>() ); -export const parameterProvidersApiError = createAction( +export const parameterProvidersBannerApiError = createAction( `${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Error`, props<{ error: string }>() ); @@ -83,6 +87,11 @@ export const navigateToEditParameterProvider = createAction( props<{ id: string }>() ); +export const navigateToFetchParameterProvider = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Navigate To Fetch Parameter Provider`, + props<{ id: string }>() +); + export const openConfigureParameterProviderDialog = createAction( `${PARAMETER_PROVIDERS_PREFIX} Open Configure Parameter Provider Dialog`, props<{ request: EditParameterProviderRequest }>() @@ -97,3 +106,58 @@ export const configureParameterProviderSuccess = createAction( `${PARAMETER_PROVIDERS_PREFIX} Configure Parameter Provider Success`, props<{ response: ConfigureParameterProviderSuccess }>() ); + +export const fetchParameterProviderParametersAndOpenDialog = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Fetch Parameter Provider Parameters and Open Dialog`, + props<{ request: FetchParameterProviderParametersRequest }>() +); + +export const fetchParameterProviderParametersSuccess = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Fetch Parameter Provider Parameters Success`, + props<{ response: FetchParameterProviderParametersResponse }>() +); + +export const openFetchParameterProviderDialog = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Open Fetch Parameter Provider Parameters Dialog`, + props<{ request: FetchParameterProviderParametersResponse }>() +); + +export const resetFetchedParameterProvider = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Reset Fetched Parameter Provider` +); + +// UPDATE FETCHED PARAMETERS +export const submitParameterProviderParametersUpdateRequest = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Submit Parameter Provider Parameters Update Request`, + props<{ request: ParameterProviderParameterApplicationEntity }>() +); + +export const submitParameterProviderParametersUpdateRequestSuccess = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Submit Parameter Provider Parameters Update Request Success`, + props<{ response: PollParameterProviderParametersUpdateSuccess }>() +); + +export const startPollingParameterProviderParametersUpdateRequest = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Start Polling Parameter Provider Parameters Update Request` +); + +export const pollParameterProviderParametersUpdateRequest = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Poll Parameter Provider Parameters Update Request` +); + +export const pollParameterProviderParametersUpdateRequestSuccess = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Poll Parameter Provider Parameters Update Request Success`, + props<{ response: PollParameterProviderParametersUpdateSuccess }>() +); + +export const stopPollingParameterProviderParametersUpdateRequest = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Stop Polling Parameter Provider Parameters Update Request` +); + +export const deleteParameterProviderParametersUpdateRequest = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Delete Parameter Provider Parameters Update Request` +); + +export const submitParameterProviderParametersUpdateComplete = createAction( + `${PARAMETER_PROVIDERS_PREFIX} Submit Parameter Provider Parameters Update Complete` +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts index 6df1a3fd76..927bd2d998 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts @@ -24,16 +24,37 @@ import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { ParameterProviderService } from '../../service/parameter-provider.service'; import * as ParameterProviderActions from './parameter-providers.actions'; -import { loadParameterProviders, selectParameterProvider } from './parameter-providers.actions'; -import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs'; -import { selectSaving } from './parameter-providers.selectors'; +import { loadParameterProviders } from './parameter-providers.actions'; +import { + asyncScheduler, + catchError, + filter, + from, + interval, + map, + NEVER, + of, + switchMap, + take, + takeUntil, + tap +} from 'rxjs'; +import { + selectApplyParameterProviderParametersRequest, + selectSaving, + selectStatus +} from './parameter-providers.selectors'; import { selectParameterProviderTypes } from '../../../../state/extension-types/extension-types.selectors'; import { CreateParameterProvider } from '../../ui/parameter-providers/create-parameter-provider/create-parameter-provider.component'; import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component'; import { EditParameterProvider } from '../../ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component'; import { PropertyTableHelperService } from '../../../../service/property-table-helper.service'; -import { UpdateParameterProviderRequest } from './index'; +import { ParameterProviderEntity, UpdateParameterProviderRequest } from './index'; import { ManagementControllerServiceService } from '../../service/management-controller-service.service'; +import { FetchParameterProviderParameters } from '../../ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component'; +import * as ErrorActions from '../../../../state/error/error.actions'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorHelper } from '../../../../service/error-helper.service'; @Injectable() export class ParameterProvidersEffects { @@ -45,13 +66,15 @@ export class ParameterProvidersEffects { private router: Router, private parameterProviderService: ParameterProviderService, private propertyTableHelperService: PropertyTableHelperService, - private managementControllerServiceService: ManagementControllerServiceService + private managementControllerServiceService: ManagementControllerServiceService, + private errorHelper: ErrorHelper ) {} loadParameterProviders$ = createEffect(() => this.actions$.pipe( ofType(loadParameterProviders), - switchMap(() => + concatLatestFrom(() => this.store.select(selectStatus)), + switchMap(([, status]) => from(this.parameterProviderService.getParameterProviders()).pipe( map((response) => ParameterProviderActions.loadParameterProvidersSuccess({ @@ -61,9 +84,7 @@ export class ParameterProvidersEffects { } }) ), - catchError((error) => - of(ParameterProviderActions.parameterProvidersApiError({ error: error.error })) - ) + catchError((error: HttpErrorResponse) => of(this.errorHelper.handleLoadingError(status, error))) ) ) ) @@ -72,7 +93,7 @@ export class ParameterProvidersEffects { selectParameterProvider$ = createEffect( () => this.actions$.pipe( - ofType(selectParameterProvider), + ofType(ParameterProviderActions.selectParameterProvider), map((action) => action.request), tap((request) => { this.router.navigate(['/settings', 'parameter-providers', request.id]); @@ -130,9 +151,10 @@ export class ParameterProvidersEffects { } }) ), - catchError((error) => - of(ParameterProviderActions.parameterProvidersApiError({ error: error.error })) - ) + catchError((error: HttpErrorResponse) => { + this.dialog.closeAll(); + return of(ErrorActions.snackBarError({ error: error.error })); + }) ) ) ) @@ -196,13 +218,7 @@ export class ParameterProvidersEffects { } }) ), - catchError((error) => - of( - ParameterProviderActions.parameterProvidersApiError({ - error: error.error - }) - ) - ) + catchError((error) => of(ErrorActions.snackBarError({ error: error.error }))) ) ) ) @@ -220,6 +236,18 @@ export class ParameterProvidersEffects { { dispatch: false } ); + navigateToFetchParameterProvider$ = createEffect( + () => + this.actions$.pipe( + ofType(ParameterProviderActions.navigateToFetchParameterProvider), + map((action) => action.id), + tap((id) => { + this.router.navigate(['settings', 'parameter-providers', id, 'fetch']); + }) + ), + { dispatch: false } + ); + openConfigureParameterProviderDialog$ = createEffect( () => this.actions$.pipe( @@ -298,6 +326,8 @@ export class ParameterProvidersEffects { }); editDialogReference.afterClosed().subscribe((response) => { + this.store.dispatch(ErrorActions.clearBannerErrors()); + if (response !== 'ROUTED') { this.store.dispatch( ParameterProviderActions.selectParameterProvider({ @@ -328,13 +358,18 @@ export class ParameterProvidersEffects { } }) ), - catchError((error) => - of( - ParameterProviderActions.parameterProvidersApiError({ - error: error.error - }) - ) - ) + catchError((error: HttpErrorResponse) => { + if (this.errorHelper.showErrorInContext(error.status)) { + return of( + ParameterProviderActions.parameterProvidersBannerApiError({ + error: error.error + }) + ); + } else { + this.dialog.getDialogById(request.id)?.close('ROUTED'); + return of(this.errorHelper.fullScreenError(error)); + } + }) ) ) ) @@ -356,4 +391,246 @@ export class ParameterProvidersEffects { ), { dispatch: false } ); + + fetchParameterProviderParametersAndOpenDialog$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.fetchParameterProviderParametersAndOpenDialog), + map((action) => action.request), + switchMap((request) => + from(this.parameterProviderService.fetchParameters(request)).pipe( + map((response: ParameterProviderEntity) => + ParameterProviderActions.fetchParameterProviderParametersSuccess({ + response: { parameterProvider: response } + }) + ), + catchError((error) => { + if (this.errorHelper.showErrorInContext(error.status)) { + this.store.dispatch( + ParameterProviderActions.selectParameterProvider({ + request: { + id: request.id + } + }) + ); + return of(ErrorActions.snackBarError({ error: error.error })); + } else { + return of(ErrorActions.fullScreenError(error)); + } + }) + ) + ) + ) + ); + + fetchParameterProviderParametersSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.fetchParameterProviderParametersSuccess), + map((action) => action.response), + switchMap((response) => + of(ParameterProviderActions.openFetchParameterProviderDialog({ request: response })) + ) + ) + ); + + openFetchParameterProvidersParametersDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(ParameterProviderActions.openFetchParameterProviderDialog), + map((action) => action.request), + tap((request) => { + const dialogRef = this.dialog.open(FetchParameterProviderParameters, { + panelClass: 'xl-dialog', + data: request + }); + + const referencingParameterContexts = + request.parameterProvider.component.referencingParameterContexts; + if (referencingParameterContexts?.length > 0) { + // add an error if one of the referenced parameter contexts is not readable/writeable + const canReadWriteAll = referencingParameterContexts.every( + (paramContextRef) => + paramContextRef.permissions.canRead && paramContextRef.permissions.canWrite + ); + if (!canReadWriteAll) { + this.store.dispatch( + ParameterProviderActions.parameterProvidersBannerApiError({ + error: 'You do not have permissions to modify one or more synced parameter contexts.' + }) + ); + } + } + const affectedComponents = request.parameterProvider.component.affectedComponents; + if (affectedComponents?.length > 0) { + // add an error if one of the affected components is not readable/writeable + const canReadWriteAll = affectedComponents.every( + (paramContextRef) => + paramContextRef.permissions.canRead && paramContextRef.permissions.canWrite + ); + if (!canReadWriteAll) { + this.store.dispatch( + ParameterProviderActions.parameterProvidersBannerApiError({ + error: 'You do not have permissions to modify one or more affected components.' + }) + ); + } + } + + dialogRef.componentInstance.updateRequest = this.store.select( + selectApplyParameterProviderParametersRequest + ); + dialogRef.componentInstance.saving$ = this.store.select(selectSaving); + + dialogRef.afterClosed().subscribe((response) => { + this.store.dispatch(ParameterProviderActions.resetFetchedParameterProvider()); + this.store.dispatch(ErrorActions.clearBannerErrors()); + + if (response !== 'ROUTED') { + this.store.dispatch( + ParameterProviderActions.selectParameterProvider({ + request: { + id: request.parameterProvider.id + } + }) + ); + this.store.dispatch( + ParameterProviderActions.submitParameterProviderParametersUpdateComplete() + ); + } + }); + }) + ), + { dispatch: false } + ); + + parameterProvidersBannerApiError$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.parameterProvidersBannerApiError), + map((action) => action.error), + tap(() => + this.store.dispatch(ParameterProviderActions.stopPollingParameterProviderParametersUpdateRequest()) + ), + switchMap((error) => of(ErrorActions.addBannerError({ error }))) + ) + ); + + submitParameterProviderParametersUpdateRequest$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.submitParameterProviderParametersUpdateRequest), + map((action) => action.request), + switchMap((request) => + from( + this.parameterProviderService.applyParameters(request).pipe( + map((response: any) => + ParameterProviderActions.submitParameterProviderParametersUpdateRequestSuccess({ + response: { + request: response.request + } + }) + ), + catchError((error) => + of( + ParameterProviderActions.parameterProvidersBannerApiError({ + error: error.error + }) + ) + ) + ) + ) + ) + ) + ); + + submitParameterProviderParametersUpdateRequestSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.submitParameterProviderParametersUpdateRequestSuccess), + map((action) => action.response), + switchMap((response) => { + const updateRequest = response.request; + if (updateRequest.complete) { + return of(ParameterProviderActions.deleteParameterProviderParametersUpdateRequest()); + } else { + return of(ParameterProviderActions.startPollingParameterProviderParametersUpdateRequest()); + } + }) + ) + ); + + startPollingParameterProviderParametersUpdateRequest$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.startPollingParameterProviderParametersUpdateRequest), + switchMap(() => + interval(2000, asyncScheduler).pipe( + takeUntil( + this.actions$.pipe( + ofType(ParameterProviderActions.stopPollingParameterProviderParametersUpdateRequest) + ) + ) + ) + ), + switchMap(() => of(ParameterProviderActions.pollParameterProviderParametersUpdateRequest())) + ) + ); + + pollParameterProviderParametersUpdateRequest$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.pollParameterProviderParametersUpdateRequest), + concatLatestFrom(() => this.store.select(selectApplyParameterProviderParametersRequest)), + switchMap(([, updateRequest]) => { + if (updateRequest) { + return from( + this.parameterProviderService.pollParameterProviderParametersUpdateRequest(updateRequest) + ).pipe( + map((response) => + ParameterProviderActions.pollParameterProviderParametersUpdateRequestSuccess({ + response: { + request: response.request + } + }) + ), + catchError((error) => + of( + ParameterProviderActions.parameterProvidersBannerApiError({ + error: error.error + }) + ) + ) + ); + } else { + return NEVER; + } + }) + ) + ); + + pollParameterProviderParametersUpdateRequestSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.pollParameterProviderParametersUpdateRequestSuccess), + map((action) => action.response), + filter((response) => response.request.complete), + switchMap(() => { + return of(ParameterProviderActions.stopPollingParameterProviderParametersUpdateRequest()); + }) + ) + ); + + stopPollingParameterProviderParametersUpdateRequest$ = createEffect(() => + this.actions$.pipe( + ofType(ParameterProviderActions.stopPollingParameterProviderParametersUpdateRequest), + switchMap(() => of(ParameterProviderActions.deleteParameterProviderParametersUpdateRequest())) + ) + ); + + deleteParameterProviderParametersUpdateRequest$ = createEffect( + () => + this.actions$.pipe( + ofType(ParameterProviderActions.deleteParameterProviderParametersUpdateRequest), + concatLatestFrom(() => this.store.select(selectApplyParameterProviderParametersRequest)), + tap(([, updateRequest]) => { + if (updateRequest) { + this.parameterProviderService.deleteParameterProviderParametersUpdateRequest(updateRequest); + } + }) + ), + { dispatch: false } + ); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts index 571a48ca49..624063548d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts @@ -23,19 +23,26 @@ import { createParameterProvider, createParameterProviderSuccess, deleteParameterProvider, + deleteParameterProviderParametersUpdateRequest, deleteParameterProviderSuccess, + fetchParameterProviderParametersSuccess, loadParameterProviders, loadParameterProvidersSuccess, - parameterProvidersApiError, - resetParameterProvidersState + parameterProvidersBannerApiError, + pollParameterProviderParametersUpdateRequestSuccess, + resetFetchedParameterProvider, + resetParameterProvidersState, + submitParameterProviderParametersUpdateRequest, + submitParameterProviderParametersUpdateRequestSuccess } from './parameter-providers.actions'; import { produce } from 'immer'; export const initialParameterProvidersState: ParameterProvidersState = { parameterProviders: [], + fetched: null, + applyParametersRequestEntity: null, saving: false, loadedTimestamp: '', - error: null, status: 'pending' }; @@ -55,15 +62,12 @@ export const parameterProvidersReducer = createReducer( ...state, parameterProviders: response.parameterProviders, loadedTimestamp: response.loadedTimestamp, - error: null, status: 'success' as const })), - on(parameterProvidersApiError, (state: ParameterProvidersState, { error }) => ({ + on(parameterProvidersBannerApiError, (state: ParameterProvidersState) => ({ ...state, - saving: false, - error, - status: 'error' as const + saving: false })), on(createParameterProvider, configureParameterProvider, deleteParameterProvider, (state) => ({ @@ -102,5 +106,39 @@ export const parameterProvidersReducer = createReducer( draftState.saving = false; }); - }) + }), + + on(fetchParameterProviderParametersSuccess, (state, { response }) => { + return { + ...state, + fetched: response.parameterProvider + }; + }), + + on(resetFetchedParameterProvider, (state) => { + return { + ...state, + fetched: null, + applyParametersRequestEntity: null + }; + }), + + on(submitParameterProviderParametersUpdateRequest, (state) => ({ + ...state, + saving: true + })), + + on( + submitParameterProviderParametersUpdateRequestSuccess, + pollParameterProviderParametersUpdateRequestSuccess, + (state, { response }) => ({ + ...state, + applyParametersRequestEntity: response.request + }) + ), + + on(deleteParameterProviderParametersUpdateRequest, (state) => ({ + ...state, + saving: false + })) ); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts index 0afa75b379..71c8a7d527 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts @@ -30,6 +30,11 @@ export const selectSaving = createSelector( (state: ParameterProvidersState) => state.saving ); +export const selectStatus = createSelector( + selectParameterProvidersState, + (state: ParameterProvidersState) => state.status +); + export const selectParameterProviderIdFromRoute = createSelector(selectCurrentRoute, (route) => { if (route) { // always select the parameter provider from the route @@ -45,6 +50,13 @@ export const selectSingleEditedParameterProvider = createSelector(selectCurrentR return null; }); +export const selectSingleFetchParameterProvider = createSelector(selectCurrentRoute, (route) => { + if (route?.routeConfig?.path == 'fetch') { + return route.params.id; + } + return null; +}); + export const selectParameterProviders = createSelector( selectParameterProvidersState, (state: ParameterProvidersState) => state.parameterProviders @@ -52,5 +64,10 @@ export const selectParameterProviders = createSelector( export const selectParameterProvider = (id: string) => createSelector(selectParameterProviders, (entities: ParameterProviderEntity[]) => - entities.find((entity) => id == entity.id) + entities.find((entity) => id === entity.id) ); + +export const selectApplyParameterProviderParametersRequest = createSelector( + selectParameterProvidersState, + (state: ParameterProvidersState) => state.applyParametersRequestEntity +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html index a2eaf019a2..b32d8b22c1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html @@ -17,6 +17,7 @@

Edit Parameter Provider

+ @@ -78,7 +79,7 @@ - + + + + + + + +
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.scss new file mode 100644 index 0000000000..f427c8f81f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.scss @@ -0,0 +1,47 @@ +/*! + * 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; + +.parameter-provider-fetch-form { + @include mat.button-density(-1); + + .mdc-dialog__content { + padding: 0 16px; + font-size: 14px; + + .dialog-content { + height: 475px; + overflow-y: auto; + } + } + + .mat-mdc-form-field { + width: 100%; + } + + .listing-table { + .mat-column-sensitive { + width: 32px; + min-width: 32px; + } + .mat-column-indicators { + width: 48px; + min-width: 48px; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.spec.ts new file mode 100644 index 0000000000..275f2a5b0c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.spec.ts @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FetchParameterProviderParameters } from './fetch-parameter-provider-parameters.component'; +import { FetchParameterProviderDialogRequest } from '../../../state/parameter-providers'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialParameterProvidersState } from '../../../state/parameter-providers/parameter-providers.reducer'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('FetchParameterProviderParameters', () => { + let component: FetchParameterProviderParameters; + let fixture: ComponentFixture; + + const data: FetchParameterProviderDialogRequest = { + id: 'id', + parameterProvider: { + revision: { + clientId: '36ba1cc1-018d-1000-bc2c-787bc552d63d', + version: 6 + }, + id: '369487d7-018d-1000-817a-1d8d9a8f4a91', + uri: 'https://localhost:8443/nifi-api/parameter-providers/369487d7-018d-1000-817a-1d8d9a8f4a91', + permissions: { + canRead: true, + canWrite: true + }, + bulletins: [], + component: { + id: '369487d7-018d-1000-817a-1d8d9a8f4a91', + name: 'Group 1 - FileParameterProvider', + type: 'org.apache.nifi.parameter.FileParameterProvider', + bundle: { + group: 'org.apache.nifi', + artifact: 'nifi-standard-nar', + version: '2.0.0-SNAPSHOT' + }, + comments: '', + persistsState: false, + restricted: true, + deprecated: false, + multipleVersionsAvailable: false, + properties: { + 'parameter-group-directories': '/Users/rfellows/tmp/parameterProviders/group1', + 'parameter-value-byte-limit': '256 B', + 'parameter-value-encoding': 'plaintext' + }, + affectedComponents: [], + descriptors: { + 'parameter-group-directories': { + name: 'parameter-group-directories', + displayName: 'Parameter Group Directories', + description: + 'A comma-separated list of directory absolute paths that will map to named parameter groups. Each directory that contains files will map to a parameter group, named after the innermost directory in the path. Files inside the directory will map to parameter names, whose values are the content of each respective file.', + required: true, + sensitive: false, + dynamic: false, + supportsEl: false, + expressionLanguageScope: 'Not Supported', + dependencies: [] + }, + 'parameter-value-byte-limit': { + name: 'parameter-value-byte-limit', + displayName: 'Parameter Value Byte Limit', + description: + 'The maximum byte size of a parameter value. Since parameter values are pulled from the contents of files, this is a safeguard that can prevent memory issues if large files are included.', + defaultValue: '256 B', + required: true, + sensitive: false, + dynamic: false, + supportsEl: false, + expressionLanguageScope: 'Not Supported', + dependencies: [] + }, + 'parameter-value-encoding': { + name: 'parameter-value-encoding', + displayName: 'Parameter Value Encoding', + description: 'Indicates how parameter values are encoded inside Parameter files.', + defaultValue: 'base64', + allowableValues: [ + { + allowableValue: { + displayName: 'Base64', + value: 'base64', + description: + 'File content is Base64-encoded, and will be decoded before providing the value as a Parameter.' + }, + canRead: true + }, + { + allowableValue: { + displayName: 'Plain text', + value: 'plaintext', + description: + 'File content is not encoded, and will be provided directly as a Parameter value.' + }, + canRead: true + } + ], + required: true, + sensitive: false, + dynamic: false, + supportsEl: false, + expressionLanguageScope: 'Not Supported', + dependencies: [] + } + }, + parameterGroupConfigurations: [ + { + groupName: 'group1', + parameterContextName: 'group1', + parameterSensitivities: { + bytes: 'NON_SENSITIVE', + password: 'SENSITIVE', + username: 'NON_SENSITIVE' + }, + synchronized: true + } + ], + referencingParameterContexts: [ + { + id: '3716e18d-018d-1000-f203-4f6d571d572e', + permissions: { + canRead: true, + canWrite: true + }, + bulletins: [], + component: { + id: '3716e18d-018d-1000-f203-4f6d571d572e', + name: 'group1' + } + } + ], + validationStatus: 'VALID', + extensionMissing: false + } + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [FetchParameterProviderParameters, NoopAnimationsModule], + providers: [ + { + provide: MAT_DIALOG_DATA, + useValue: data + }, + provideMockStore({ + initialState: initialParameterProvidersState + }) + ] + }); + fixture = TestBed.createComponent(FetchParameterProviderParameters); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.ts new file mode 100644 index 0000000000..ee7388d315 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component.ts @@ -0,0 +1,603 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, DestroyRef, inject, Inject, Input, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component'; +import { MatButtonModule } from '@angular/material/button'; +import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive'; +import { Client } from '../../../../../service/client.service'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { + FetchedParameterMapping, + FetchParameterProviderDialogRequest, + ParameterGroupConfiguration, + ParameterProviderApplyParametersRequest, + ParameterProviderEntity, + ParameterProviderParameterApplicationEntity, + ParameterProvidersState, + ParameterSensitivity, + ParameterStatusEntity +} from '../../../state/parameter-providers'; +import { debounceTime, Observable, Subject } from 'rxjs'; +import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component'; +import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { ParameterGroupsTable } from './parameter-groups-table/parameter-groups-table.component'; +import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox'; +import { MatInputModule } from '@angular/material/input'; +import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component'; +import { AffectedComponentEntity } from '../../../../../state/shared'; +import * as ParameterProviderActions from '../../../state/parameter-providers/parameter-providers.actions'; +import { Store } from '@ngrx/store'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { PipesModule } from '../../../../../pipes/pipes.module'; + +@Component({ + selector: 'fetch-parameter-provider-parameters', + standalone: true, + imports: [ + CommonModule, + MatDialogModule, + ReactiveFormsModule, + ErrorBanner, + MatButtonModule, + NifiSpinnerDirective, + NifiTooltipDirective, + MatTableModule, + MatSortModule, + ParameterGroupsTable, + MatCheckboxModule, + MatInputModule, + ParameterReferences, + PipesModule + ], + templateUrl: './fetch-parameter-provider-parameters.component.html', + styleUrls: ['./fetch-parameter-provider-parameters.component.scss'] +}) +export class FetchParameterProviderParameters implements OnInit { + fetchParametersForm: FormGroup; + parameterProvider: ParameterProviderEntity; + selectedParameterGroup: ParameterGroupConfiguration | null = null; + parameterGroupConfigurations: ParameterGroupConfiguration[]; + parameterGroupNames: string[] = []; + parameterContextsToCreate: { [key: string]: string } = {}; + parameterContextsToUpdate: string[] = []; + + displayedColumns = ['sensitive', 'name', 'indicators']; + + // each group has a different set of parameters, map by groupName + dataSources: { [key: string]: MatTableDataSource } = {}; + + // each group's parameter table can have a different sort active, map by groupName + activeSorts: { [key: string]: Sort } = {}; + + // each group can have a selected parameter, map by groupName + selectedParameters: { [key: string]: FetchedParameterMapping } = {}; + + // as the selected parameter of the current group changes + selectParameterChanged: Subject = new Subject(); + + parameterReferences: AffectedComponentEntity[] = []; + + @Input() saving$!: Observable; + @Input() updateRequest!: Observable; + + protected readonly TextTip = TextTip; + protected readonly Object = Object; + + private destroyRef: DestroyRef = inject(DestroyRef); + + constructor( + private formBuilder: FormBuilder, + private client: Client, + private nifiCommon: NiFiCommon, + private store: Store, + @Inject(MAT_DIALOG_DATA) public request: FetchParameterProviderDialogRequest + ) { + this.parameterProvider = request.parameterProvider; + + this.fetchParametersForm = this.formBuilder.group({}); + this.parameterGroupConfigurations = this.parameterProvider.component.parameterGroupConfigurations.slice(); + this.parameterGroupConfigurations.forEach((parameterGroupConfig) => { + const params = this.getParameterSensitivitiesAsFormControls(parameterGroupConfig); + this.fetchParametersForm.addControl( + parameterGroupConfig.groupName, + this.formBuilder.group({ + createParameterContext: new FormControl(), + parameterContextName: new FormControl( + parameterGroupConfig.parameterContextName, + Validators.required + ), + parameterSensitivities: this.formBuilder.group(params) + }) + ); + this.parameterGroupNames.push(parameterGroupConfig.groupName); + }); + + if (this.parameterProvider.component.referencingParameterContexts) { + this.parameterContextsToUpdate = this.parameterProvider.component.referencingParameterContexts + .map((parameterContext) => parameterContext.component?.name ?? '') + .filter((name) => name.length > 0); + } + } + + ngOnInit(): void { + this.selectParameterChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((selectedParameter) => { + const parameterGroupName = this.selectedParameterGroup?.groupName; + if (selectedParameter) { + // keep track of the currently selected parameter for each group + if (parameterGroupName) { + this.selectedParameters[parameterGroupName] = selectedParameter; + + this.parameterReferences = + selectedParameter.status?.parameter?.parameter.referencingComponents ?? []; + } + } else { + if (parameterGroupName) { + delete this.selectedParameters[parameterGroupName]; + this.parameterReferences = []; + } + } + }); + + if (this.parameterGroupConfigurations.length > 0) { + // select the first parameter group + const initialParamGroup = this.parameterGroupConfigurations[0]; + + // preload the first datasource into the map + this.getParameterMappingDataSource(initialParamGroup); + this.autoSelectParameter(); + + // watch for changes to the parameter context name inputs, update the local map + this.parameterGroupConfigurations.forEach((groupConfig) => { + this.fetchParametersForm + .get(`${groupConfig.groupName}.parameterContextName`) + ?.valueChanges.pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)) + .subscribe((name) => { + if (Object.hasOwn(this.parameterContextsToCreate, groupConfig.groupName)) { + this.parameterContextsToCreate[groupConfig.groupName] = name; + } + }); + }); + } + } + + submitForm() { + const data = this.getFormData(); + this.store.dispatch( + ParameterProviderActions.submitParameterProviderParametersUpdateRequest({ + request: data + }) + ); + } + + parameterGroupSelected(parameterGroup: ParameterGroupConfiguration) { + this.selectedParameterGroup = parameterGroup; + this.autoSelectParameter(); + } + + private autoSelectParameter() { + if (this.selectedParameterGroup) { + const selectedParam = this.selectedParameters[this.selectedParameterGroup.groupName]; + if (selectedParam) { + this.selectParameterChanged.next(selectedParam); + } else { + // select the first param + const paramsDs = this.dataSources[this.selectedParameterGroup.groupName]; + if (paramsDs) { + this.selectParameterChanged.next(paramsDs.data[0]); + } + } + } + } + + canCreateParameterContext(parameterGroupConfig: ParameterGroupConfiguration): boolean { + // the passed in parameter group could have changed its sync status due to user input, + // check the original parameter group from the server. + const originalParameterGroupConfig = this.parameterProvider.component.parameterGroupConfigurations.find( + (g) => g.groupName === parameterGroupConfig.groupName + ); + if (originalParameterGroupConfig) { + return !this.isSynced(originalParameterGroupConfig); + } + return false; + } + + canEditParameterContextName(parameterGroupConfig: ParameterGroupConfiguration): boolean { + // can only edit the context name if the create parameter context checkbox is checked. + return this.fetchParametersForm.get(`${parameterGroupConfig.groupName}.createParameterContext`)?.value; + } + + showParameterList(parameterGroupConfig: ParameterGroupConfiguration): boolean { + // show only a list of parameters if the group is not synced with a parameter context and the user isn't actively trying to create a context for it + return ( + !this.isSynced(parameterGroupConfig) && + !this.fetchParametersForm.get(`${parameterGroupConfig.groupName}.createParameterContext`)?.value + ); + } + + isSynced(parameterGroupConfig: ParameterGroupConfiguration): boolean { + return !!parameterGroupConfig.synchronized; + } + + getParameterMappingDataSource(parameterGroupConfig: ParameterGroupConfiguration) { + if (!this.dataSources[parameterGroupConfig.groupName]) { + const ds = new MatTableDataSource(); + + ds.data = this.sortEntities( + this.parameterMappingArray(parameterGroupConfig), + this.getActiveSort(parameterGroupConfig) + ); + + this.dataSources[parameterGroupConfig.groupName] = ds; + } + return this.dataSources[parameterGroupConfig.groupName]; + } + + getActiveSort(parameterGroupConfig: ParameterGroupConfiguration): Sort { + if (!this.activeSorts[parameterGroupConfig.groupName]) { + this.activeSorts[parameterGroupConfig.groupName] = { + active: 'name', + direction: 'asc' + }; + } + return this.activeSorts[parameterGroupConfig.groupName]; + } + + setActiveSort(sort: Sort, parameterGroupConfig: ParameterGroupConfiguration) { + this.activeSorts[parameterGroupConfig.groupName] = sort; + } + + getFormControl(parameter: ParameterSensitivity, parameterGroupConfig: ParameterGroupConfiguration): FormControl { + return this.fetchParametersForm.get( + `${parameterGroupConfig.groupName}.parameterSensitivities.${parameter.name}` + ) as FormControl; + } + + private getParameterMapping(parameterGroupConfig: ParameterGroupConfiguration): { + [key: string]: FetchedParameterMapping; + } { + const map: { [key: string]: FetchedParameterMapping } = {}; + + // get all the parameter status for the selected group, add them to the map by param name + if (this.parameterProvider.component.parameterStatus) { + this.parameterProvider.component.parameterStatus + .filter((parameterStatus: ParameterStatusEntity) => { + if (!parameterStatus?.parameter?.parameter) { + return false; + } + const param = parameterStatus.parameter.parameter; + return param.parameterContext?.component?.name === parameterGroupConfig.parameterContextName; + }) + .forEach((parameterStatus: ParameterStatusEntity) => { + if (parameterStatus.parameter) { + const parameterName = parameterStatus.parameter.parameter.name; + map[parameterName] = { + name: parameterName, + status: parameterStatus, + sensitivity: { + name: parameterName, + sensitive: parameterStatus.parameter.parameter.sensitive + } + } as FetchedParameterMapping; + } + }); + } + + // get all the known parameter sensitivities, add them to the map by param name + if (parameterGroupConfig.parameterSensitivities) { + Object.entries(parameterGroupConfig.parameterSensitivities).forEach((entry) => { + const parameterName = entry[0]; + if (map[parameterName]) { + map[parameterName].sensitivity = { + name: parameterName, + sensitive: entry[1] !== 'NON_SENSITIVE' + }; + } else { + map[parameterName] = { + name: parameterName, + sensitivity: { + name: parameterName, + sensitive: entry[1] !== 'NON_SENSITIVE' + } + } as FetchedParameterMapping; + } + }); + } + + Object.entries(map).forEach((entry) => { + const paramName = entry[0]; + const mapping: FetchedParameterMapping = entry[1]; + + mapping.name = paramName; + if (!mapping.sensitivity) { + // no known sensitivity, provide one + mapping.sensitivity = { + name: paramName, + sensitive: true + }; + } + + if (!mapping.status) { + mapping.status = { + status: 'UNCHANGED' + }; + } + }); + + return map; + } + + private parameterMappingArray(parameterGroupConfig: ParameterGroupConfiguration): FetchedParameterMapping[] { + return Object.values(this.getParameterMapping(parameterGroupConfig)); + } + + private getParameterSensitivitiesAsFormControls(parameterGroupConfig: ParameterGroupConfiguration): { + [key: string]: FormControl; + } { + const data: { [key: string]: FormControl } = {}; + Object.entries(this.getParameterMapping(parameterGroupConfig)).forEach((entry) => { + const parameterName = entry[0]; + const param: FetchedParameterMapping = entry[1]; + if (parameterName && param?.sensitivity) { + data[parameterName] = new FormControl({ + value: param.sensitivity.sensitive, + disabled: this.isReferenced(param) + }); + } + }); + return data; + } + + sort(sort: Sort) { + this.setActiveSort(sort, this.selectedParameterGroup!); + const dataSource: MatTableDataSource = this.getParameterMappingDataSource( + this.selectedParameterGroup! + ); + dataSource.data = this.sortEntities(dataSource.data, sort); + } + + private sortEntities(data: FetchedParameterMapping[], sort: Sort): FetchedParameterMapping[] { + if (!data) { + return []; + } + return data.slice().sort((a, b) => { + const isAsc = sort.direction === 'asc'; + const retVal = this.nifiCommon.compareString(a.name, b.name); + return retVal * (isAsc ? 1 : -1); + }); + } + + selectAllChanged(event: MatCheckboxChange) { + const checked: boolean = event.checked; + const currentParamGroup = this.selectedParameterGroup!; + const dataSource = this.getParameterMappingDataSource(currentParamGroup); + dataSource.data.forEach((p) => { + if (p.sensitivity) { + const formControl = this.getFormControl(p.sensitivity, currentParamGroup); + if (formControl && !formControl.disabled) { + formControl.setValue(checked); + formControl.markAsDirty(); + } + } + }); + } + + areAllSelected(parameterGroupConfig: ParameterGroupConfiguration): boolean { + const dataSource = this.getParameterMappingDataSource(parameterGroupConfig); + let allSensitive = true; + dataSource.data.forEach((p) => { + if (p.sensitivity) { + const formControl = this.getFormControl(p.sensitivity, parameterGroupConfig); + if (formControl) { + allSensitive = allSensitive && formControl.value; + } + } + }); + + return allSensitive; + } + + areAnySelected(parameterGroupConfig: ParameterGroupConfiguration): boolean { + const dataSource = this.getParameterMappingDataSource(parameterGroupConfig); + let anySensitive = false; + let allSensitive = true; + dataSource.data.forEach((p) => { + if (p.sensitivity) { + const formControl = this.getFormControl(p.sensitivity, parameterGroupConfig); + if (formControl) { + anySensitive = anySensitive || formControl.value; + allSensitive = allSensitive && formControl.value; + } + } + }); + + return anySensitive && !allSensitive; + } + + isReferenced(item: FetchedParameterMapping): boolean { + const parameterStatus = item.status; + if (!parameterStatus?.parameter) { + return false; + } + const hasReferencingComponents = parameterStatus?.parameter.parameter.referencingComponents?.length; + return !!hasReferencingComponents; + } + + isAffected(item: FetchedParameterMapping): boolean { + const parameterStatus = item.status; + if (!parameterStatus) { + return false; + } + return parameterStatus.status !== 'UNCHANGED'; + } + + getAffectedTooltip(item: FetchedParameterMapping): string | null { + switch (item.status?.status) { + case 'NEW': + return 'Newly discovered parameter.'; + case 'CHANGED': + return 'Value has changed.'; + case 'REMOVED': + return 'Parameter has been removed from its source. Apply to remove from the synced parameter context.'; + case 'MISSING_BUT_REFERENCED': + return 'Parameter has been removed from its source and is still being referenced in a component. To remove the parameter from the parameter context, first un-reference the parameter, then re-fetch and apply.'; + } + return null; + } + + createParameterContextToggled(event: MatCheckboxChange) { + const checked: boolean = event.checked; + const currentParamGroup = this.selectedParameterGroup!; + this.parameterGroupConfigurations = this.parameterGroupConfigurations.map((config) => { + if (config.groupName === currentParamGroup.groupName) { + config = { + ...config, + synchronized: checked + }; + if (checked) { + this.parameterContextsToCreate[config.groupName] = config.parameterContextName; + + // preload the datasource into the map + this.getParameterMappingDataSource(currentParamGroup); + this.autoSelectParameter(); + } else { + delete this.parameterContextsToCreate[config.groupName]; + // set the selected parameter to nothing + this.removeParameterSelection(); + } + } + return config; + }); + } + + selectParameter(item: FetchedParameterMapping) { + this.selectParameterChanged.next(item); + } + + isParameterSelected(item: FetchedParameterMapping): boolean { + const parameterGroupName = this.selectedParameterGroup?.groupName; + if (!parameterGroupName) { + return false; + } + + const selectedParameter = this.selectedParameters[parameterGroupName]; + if (!selectedParameter) { + return false; + } + + if ( + selectedParameter.name === item.name && + selectedParameter.status?.parameter?.parameter.parameterContext?.id === + item.status?.parameter?.parameter.parameterContext?.id + ) { + return true; + } + return false; + } + + private removeParameterSelection() { + this.selectParameterChanged.next(null); + } + + canSubmitForm(): boolean { + // user needs to have read/write permissions on the component + const referencingParameterContexts = this.parameterProvider.component.referencingParameterContexts; + if (referencingParameterContexts?.length > 0) { + // disable the submit if one of the referenced parameter contexts is not readable/writeable + const canReadWriteAllParamContexts = referencingParameterContexts.every( + (paramContextRef) => paramContextRef.permissions.canRead && paramContextRef.permissions.canWrite + ); + if (!canReadWriteAllParamContexts) { + return false; + } + } + + const affectedComponents = this.parameterProvider.component.affectedComponents; + if (affectedComponents?.length > 0) { + // disable the submit if one of the affected components is not readable/writeable + const canReadWriteAllAffectedComponents = affectedComponents.every( + (affected) => affected.permissions.canRead && affected.permissions.canWrite + ); + if (!canReadWriteAllAffectedComponents) { + return false; + } + } + + // check if a parameter is new, removed, missing but referenced, or has a changed value + const parameterStatus = this.parameterProvider.component.parameterStatus; + let anyParametersChangedInProvider = false; + if (parameterStatus && parameterStatus.length > 0) { + anyParametersChangedInProvider = parameterStatus.some((paramStatus) => { + return paramStatus.status !== 'UNCHANGED'; + }); + } + + // if a fetched parameter is new, removed, missing but referenced, or has a changed value... consider the form dirty. + const isDirty = anyParametersChangedInProvider || this.fetchParametersForm.dirty; + + return isDirty && !this.fetchParametersForm.invalid; + } + + private getFormData(): ParameterProviderParameterApplicationEntity { + const groupConfigs: ParameterGroupConfiguration[] = this.parameterGroupConfigurations + .filter((initialGroup) => { + // filter out any non-synchronized groups that the user hasn't decided to create a parameter context for + const createParameterContext = this.fetchParametersForm.get( + `${initialGroup.groupName}.createParameterContext` + ); + return initialGroup.synchronized || !!createParameterContext?.value; + }) + .map((initialGroup) => { + const parameterSensitivities: { [key: string]: null | 'SENSITIVE' | 'NON_SENSITIVE' } = {}; + + const parameterContextName = this.fetchParametersForm.get( + `${initialGroup.groupName}.parameterContextName` + )?.value; + + // convert to the backend model for sensitivities + Object.entries(initialGroup.parameterSensitivities).forEach(([key, value]) => { + const formParamSensitivity = this.fetchParametersForm.get( + `${initialGroup.groupName}.parameterSensitivities.${key}` + ); + if (formParamSensitivity) { + parameterSensitivities[key] = formParamSensitivity.value ? 'SENSITIVE' : 'NON_SENSITIVE'; + } else { + // if there is no value defined by the form, send the known value + parameterSensitivities[key] = value; + } + }); + + return { + ...initialGroup, + parameterContextName, + parameterSensitivities + }; + }); + + return { + id: this.parameterProvider.id, + revision: this.parameterProvider.revision, + disconnectedNodeAcknowledged: false, + parameterGroupConfigurations: groupConfigs + }; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-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/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.html new file mode 100644 index 0000000000..3f405833e0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.html @@ -0,0 +1,58 @@ + + +
+
+
+ + + + + + + + + + + + + +
Parameter Group Name +
{{ item.groupName }}
+
+
+
+
+
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.scss new file mode 100644 index 0000000000..bf3d1a6f20 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.scss @@ -0,0 +1,25 @@ +/*! + * 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. + */ + +.parameter-group-table { + .listing-table { + .mat-column-indicators { + width: 32px; + min-width: 32px; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.spec.ts new file mode 100644 index 0000000000..da75554efe --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.spec.ts @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ParameterGroupsTable } from './parameter-groups-table.component'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('ParameterGroupsTable', () => { + let component: ParameterGroupsTable; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ParameterGroupsTable, NoopAnimationsModule] + }); + fixture = TestBed.createComponent(ParameterGroupsTable); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-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/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.ts new file mode 100644 index 0000000000..23f53498c3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/fetch-parameter-provider-parameters/parameter-groups-table/parameter-groups-table.component.ts @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { ParameterGroupConfiguration } from '../../../../state/parameter-providers'; +import { NiFiCommon } from '../../../../../../service/nifi-common.service'; + +@Component({ + selector: 'parameter-groups-table', + standalone: true, + imports: [CommonModule, MatSortModule, MatTableModule], + templateUrl: './parameter-groups-table.component.html', + styleUrls: ['./parameter-groups-table.component.scss'] +}) +export class ParameterGroupsTable { + parameterGroupsDataSource: MatTableDataSource = + new MatTableDataSource(); + selectedParameterGroup: ParameterGroupConfiguration | null = null; + displayedColumns: string[] = ['groupName', 'indicators']; + + activeParameterGroupSort: Sort = { + active: 'groupName', + direction: 'asc' + }; + + constructor(private nifiCommon: NiFiCommon) {} + + @Input() set parameterGroups(parameterGroups: ParameterGroupConfiguration[]) { + this.parameterGroupsDataSource.data = this.sortEntities(parameterGroups, this.activeParameterGroupSort); + if (this.parameterGroupsDataSource.data.length > 0) { + let selectedIndex = 0; + // try to re-select the currently selected group if it still exists + if (this.selectedParameterGroup) { + const idx = this.parameterGroupsDataSource.data.findIndex( + (g) => g.groupName === this.selectedParameterGroup?.groupName + ); + if (idx >= 0) { + selectedIndex = idx; + } + } + this.select(this.parameterGroupsDataSource.data[selectedIndex]); + } + } + + @Output() selected: EventEmitter = new EventEmitter(); + + sort(sort: Sort) { + this.activeParameterGroupSort = sort; + this.parameterGroupsDataSource.data = this.sortEntities(this.parameterGroupsDataSource.data, sort); + } + + private sortEntities(data: ParameterGroupConfiguration[], sort: Sort) { + if (!data) { + return []; + } + return data.slice().sort((a, b) => { + const isAsc = sort.direction === 'asc'; + const retVal = this.nifiCommon.compareString(a.groupName, b.groupName); + return retVal * (isAsc ? 1 : -1); + }); + } + + select(item: ParameterGroupConfiguration) { + this.selectedParameterGroup = item; + this.selected.next(item); + } + + isSelected(item: ParameterGroupConfiguration): boolean { + if (this.selectedParameterGroup) { + return item.groupName === this.selectedParameterGroup.groupName; + } + + return false; + } + + isSyncedToParameterContext(item: ParameterGroupConfiguration): boolean { + return !!item.synchronized; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-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/parameter-providers/parameter-providers-table/parameter-providers-table.component.html index 7c3eb1e925..17a4b67661 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-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/parameter-providers/parameter-providers-table/parameter-providers-table.component.html @@ -86,14 +86,17 @@
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-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/parameter-providers/parameter-providers-table/parameter-providers-table.component.ts index 9d4c1ef008..eef6a5bd39 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-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/parameter-providers/parameter-providers-table/parameter-providers-table.component.ts @@ -94,6 +94,34 @@ export class ParameterProvidersTable { return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead; } + canConfigure(entity: ParameterProviderEntity): boolean { + return this.canRead(entity) && this.canWrite(entity); + } + + canDelete(entity: ParameterProviderEntity): boolean { + return ( + this.canRead(entity) && + this.canWrite(entity) && + this.currentUser.controllerPermissions.canRead && + this.currentUser.controllerPermissions.canWrite + ); + } + + canFetch(entity: ParameterProviderEntity): boolean { + let hasReadParameterContextsPermissions = true; + if (this.canRead(entity) && entity.component.referencingParameterContexts) { + hasReadParameterContextsPermissions = entity.component.referencingParameterContexts.every( + (context) => context.permissions.canRead + ); + } + return ( + this.canRead(entity) && + this.canWrite(entity) && + hasReadParameterContextsPermissions && + !this.hasErrors(entity) + ); + } + isSelected(parameterProvider: ParameterProviderEntity): boolean { if (this.selectedParameterProviderId) { return parameterProvider.id === this.selectedParameterProviderId; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html index d0b715d847..9bd90b9a64 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html @@ -37,6 +37,7 @@ [selectedParameterProviderId]="selectedParameterProviderId$ | async" (deleteParameterProvider)="deleteParameterProvider($event)" (configureParameterProvider)="openConfigureParameterProviderDialog($event)" + (fetchParameterProvider)="fetchParameterProviderParameters($event)" (selectParameterProvider)="selectParameterProvider($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/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts index 0fe04a284f..18c5534bb5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts @@ -24,7 +24,8 @@ import { selectParameterProvider, selectParameterProviderIdFromRoute, selectParameterProvidersState, - selectSingleEditedParameterProvider + selectSingleEditedParameterProvider, + selectSingleFetchParameterProvider } from '../../state/parameter-providers/parameter-providers.selectors'; import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions'; @@ -55,12 +56,34 @@ export class ParameterProviders implements OnInit, OnDestroy { ), takeUntilDestroyed() ) + .subscribe((entity) => { + if (entity) { + this.store.dispatch( + ParameterProviderActions.openConfigureParameterProviderDialog({ + request: { + id: entity.id, + parameterProvider: entity + } + }) + ); + } + }); + + this.store + .select(selectSingleFetchParameterProvider) + .pipe( + isDefinedAndNotNull(), + switchMap((id: string) => + this.store.select(selectParameterProvider(id)).pipe(isDefinedAndNotNull(), take(1)) + ), + takeUntilDestroyed() + ) .subscribe((entity) => { this.store.dispatch( - ParameterProviderActions.openConfigureParameterProviderDialog({ + ParameterProviderActions.fetchParameterProviderParametersAndOpenDialog({ request: { id: entity.id, - parameterProvider: entity + revision: entity.revision } }) ); @@ -116,4 +139,12 @@ export class ParameterProviders implements OnInit, OnDestroy { }) ); } + + fetchParameterProviderParameters(parameterProvider: ParameterProviderEntity) { + this.store.dispatch( + ParameterProviderActions.navigateToFetchParameterProvider({ + id: parameterProvider.component.id + }) + ); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/common/summary-table-filter/summary-table-filter.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/common/summary-table-filter/summary-table-filter.component.ts index d11fc4d66d..1dfdb7893d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/common/summary-table-filter/summary-table-filter.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/ui/common/summary-table-filter/summary-table-filter.component.ts @@ -15,14 +15,16 @@ * limitations under the License. */ -import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounceTime } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export interface SummaryTableFilterColumn { key: string; label: string; } + export interface SummaryTableFilterArgs { filterTerm: string; filterColumn: string; @@ -40,6 +42,7 @@ export class SummaryTableFilter implements AfterViewInit { private _filteredCount = 0; private _totalCount = 0; private _initialFilterColumn = 'name'; + private destroyRef: DestroyRef = inject(DestroyRef); showFilterMatchedLabel = false; @Input() filterableColumns: SummaryTableFilterColumn[] = []; @@ -50,6 +53,7 @@ export class SummaryTableFilter implements AfterViewInit { @Input() set filterTerm(term: string) { this.filterForm.get('filterTerm')?.value(term); } + @Input() set filterColumn(column: string) { this._initialFilterColumn = column; if (this.filterableColumns?.length > 0) { @@ -97,7 +101,7 @@ export class SummaryTableFilter implements AfterViewInit { ngAfterViewInit(): void { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { const filterColumn = this.filterForm.get('filterColumn')?.value; const filterStatus = this.filterForm.get('filterStatus')?.value; @@ -105,26 +109,35 @@ export class SummaryTableFilter implements AfterViewInit { this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); }); - this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - const filterStatus = this.filterForm.get('filterStatus')?.value; - const primaryOnly = this.filterForm.get('primaryOnly')?.value; - this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); - }); + this.filterForm + .get('filterColumn') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + const filterStatus = this.filterForm.get('filterStatus')?.value; + const primaryOnly = this.filterForm.get('primaryOnly')?.value; + this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); + }); - this.filterForm.get('filterStatus')?.valueChanges.subscribe((filterStatus: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - const filterColumn = this.filterForm.get('filterColumn')?.value; - const primaryOnly = this.filterForm.get('primaryOnly')?.value; - this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); - }); + this.filterForm + .get('filterStatus') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterStatus: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + const filterColumn = this.filterForm.get('filterColumn')?.value; + const primaryOnly = this.filterForm.get('primaryOnly')?.value; + this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); + }); - this.filterForm.get('primaryOnly')?.valueChanges.subscribe((primaryOnly: boolean) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - const filterColumn = this.filterForm.get('filterColumn')?.value; - const filterStatus = this.filterForm.get('filterStatus')?.value; - this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); - }); + this.filterForm + .get('primaryOnly') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((primaryOnly: boolean) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + const filterColumn = this.filterForm.get('filterColumn')?.value; + const filterStatus = this.filterForm.get('filterStatus')?.value; + this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly); + }); } applyFilter(filterTerm: string, filterColumn: string, filterStatus: string, primaryOnly: boolean) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts index bf51505df5..173368e3f9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatSortModule, Sort } from '@angular/material/sort'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; @@ -27,6 +27,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { NgIf } from '@angular/common'; import { MatInputModule } from '@angular/material/input'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export interface TenantItem { id: string; @@ -68,6 +69,7 @@ export class UserTable implements AfterViewInit { userLookup: Map = new Map(); userGroupLookup: Map = new Map(); + private destroyRef: DestroyRef = inject(DestroyRef); @Input() set tenants(tenants: Tenants) { this.userLookup.clear(); @@ -141,16 +143,19 @@ export class UserTable implements AfterViewInit { ngAfterViewInit(): void { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { const filterColumn = this.filterForm.get('filterColumn')?.value; this.applyFilter(filterTerm, filterColumn); }); - this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => { - const filterTerm = this.filterForm.get('filterTerm')?.value; - this.applyFilter(filterTerm, filterColumn); - }); + this.filterForm + .get('filterColumn') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterColumn: string) => { + const filterTerm = this.filterForm.get('filterTerm')?.value; + this.applyFilter(filterTerm, filterColumn); + }); } applyFilter(filterTerm: string, filterColumn: string) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.spec.ts new file mode 100644 index 0000000000..a082da55a3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.spec.ts @@ -0,0 +1,46 @@ +/* + * 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 { JoinPipe } from './join.pipe'; + +describe('JoinPipe', () => { + const values: string[] = ['alpha', 'omega', 'beta', 'theta', 'phi']; + const numbers: number[] = [1, 2, 3]; + + it('create an instance', () => { + const pipe = new JoinPipe(); + expect(pipe).toBeTruthy(); + }); + + it('should join by comma-space by default', () => { + const pipe = new JoinPipe(); + const joined = pipe.transform(values); + expect(joined).toEqual('alpha, omega, beta, theta, phi'); + }); + + it('should join by a specified separator', () => { + const pipe = new JoinPipe(); + const joined = pipe.transform(values, '-'); + expect(joined).toEqual('alpha-omega-beta-theta-phi'); + }); + + it('should join numbers', () => { + const pipe = new JoinPipe(); + const joined = pipe.transform(numbers, ' | '); + expect(joined).toEqual('1 | 2 | 3'); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.ts new file mode 100644 index 0000000000..d12c901e9c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/join.pipe.ts @@ -0,0 +1,27 @@ +/* + * 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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'join' +}) +export class JoinPipe implements PipeTransform { + transform(array: any[], separator = ', '): string { + return array.join(separator); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/pipes.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/pipes.module.ts new file mode 100644 index 0000000000..cbbe94c2fa --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/pipes.module.ts @@ -0,0 +1,28 @@ +/* + * 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { JoinPipe } from './join.pipe'; +import { SortPipe } from './sort.pipe'; + +@NgModule({ + declarations: [SortPipe, JoinPipe], + exports: [SortPipe, JoinPipe], + imports: [CommonModule] +}) +export class PipesModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.spec.ts new file mode 100644 index 0000000000..126135b915 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.spec.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 { SortPipe } from './sort.pipe'; + +describe('SortPipe', () => { + const values: string[] = ['alpha', 'omega', 'beta', 'theta', 'phi']; + + it('create an instance', () => { + const pipe = new SortPipe(); + expect(pipe).toBeTruthy(); + }); + + it('sorts ascending by default', () => { + const pipe = new SortPipe(); + const sorted: string[] = pipe.transform(values); + + expect(sorted).toEqual(['alpha', 'beta', 'omega', 'phi', 'theta']); + }); + + it('sorts descending', () => { + const pipe = new SortPipe(); + const sorted: string[] = pipe.transform(values, 'desc'); + + expect(sorted).toEqual(['theta', 'phi', 'omega', 'beta', 'alpha']); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.ts new file mode 100644 index 0000000000..f64f00938a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pipes/sort.pipe.ts @@ -0,0 +1,43 @@ +/* + * 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 { Pipe, PipeTransform } from '@angular/core'; + +type Direction = 'asc' | 'desc'; + +@Pipe({ + name: 'sort' +}) +export class SortPipe implements PipeTransform { + transform(array: string[], direction: Direction = 'asc'): string[] { + if (!array || array.length === 0) { + return []; + } + return array.slice().sort((a, b) => { + const isAsc = direction === 'asc'; + const retVal = this.compareString(a, b); + return retVal * (isAsc ? 1 : -1); + }); + } + + private compareString(a: string, b: string): number { + if (a === b) { + return 0; + } + return a < b ? -1 : 1; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts index 5daf5c7323..c5607237f9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts @@ -43,6 +43,8 @@ export class AuthInterceptor implements HttpInterceptor { if (errorResponse instanceof HttpErrorResponse) { if (errorResponse.status === 401) { if (this.authStorage.hasToken()) { + this.routedToFullScreenError = true; + this.authStorage.removeToken(); let message: string = errorResponse.error; @@ -52,8 +54,6 @@ export class AuthInterceptor implements HttpInterceptor { message += '. Please navigate home to log in again.'; } - this.routedToFullScreenError = true; - this.store.dispatch( fullScreenError({ errorDetail: { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts index 93c48de1ba..bc6515941f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts @@ -21,13 +21,15 @@ import * as ErrorActions from './error.actions'; import { map, tap } from 'rxjs'; import { Router } from '@angular/router'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatDialog } from '@angular/material/dialog'; @Injectable() export class ErrorEffects { constructor( private actions$: Actions, private router: Router, - private snackBar: MatSnackBar + private snackBar: MatSnackBar, + private dialog: MatDialog ) {} fullScreenError$ = createEffect( @@ -35,6 +37,7 @@ export class ErrorEffects { this.actions$.pipe( ofType(ErrorActions.fullScreenError), tap(() => { + this.dialog.openDialogs.forEach((dialog) => dialog.close('ROUTED')); this.router.navigate(['/error'], { replaceUrl: true }); }) ), diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts index 63eed665d9..6883d30a58 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts @@ -21,7 +21,7 @@ export function isDefinedAndNotNull() { return (source$: Observable) => source$.pipe( filter((input: null | undefined | T): input is T => { - return input !== null && typeof input !== undefined; + return input !== null && typeof input !== 'undefined'; }) ); } @@ -594,3 +594,15 @@ export interface CreateControllerServiceRequest { export interface ControllerServiceCreator { createControllerService(createControllerService: CreateControllerServiceRequest): Observable; } + +export interface ParameterProviderConfiguration { + parameterGroupName: string; + parameterProviderId: string; + parameterProviderName: string; + synchronized: boolean; +} + +export interface ParameterProviderConfigurationEntity { + id: string; + component: ParameterProviderConfiguration; +} 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 index 2aa782319f..ad62d6ce7e 100644 --- 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 @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AfterViewInit, Component, Input } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, inject, Input } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; @@ -75,6 +75,7 @@ export class ComponentStateDialog implements AfterViewInit { totalEntries = 0; filteredEntries = 0; partialResults = false; + private destroyRef: DestroyRef = inject(DestroyRef); constructor( private store: Store, @@ -116,7 +117,7 @@ export class ComponentStateDialog implements AfterViewInit { ngAfterViewInit(): void { this.filterForm .get('filterTerm') - ?.valueChanges.pipe(debounceTime(500)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe((filterTerm: string) => { this.applyFilter(filterTerm); }); 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/disable-controller-service/disable-controller-service.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/disable-controller-service/disable-controller-service.component.html index b6ad993bd3..7b33c226b5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/disable-controller-service/disable-controller-service.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/disable-controller-service/disable-controller-service.component.html @@ -136,7 +136,7 @@
-
+
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/enable-controller-service/enable-controller-service.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/enable-controller-service/enable-controller-service.component.html index 3ada0f87e7..699774d678 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/enable-controller-service/enable-controller-service.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/enable-controller-service/enable-controller-service.component.html @@ -165,7 +165,7 @@
-
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.html similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.html rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.html diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.scss similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.scss rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.scss diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.spec.ts similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.spec.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.spec.ts diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts similarity index 92% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.ts rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts index 04b5a6c09f..961932a245 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-references/parameter-references.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts @@ -22,17 +22,17 @@ import { MatButtonModule } from '@angular/material/button'; import { NgClass, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { RouterLink } from '@angular/router'; import { MatDialogModule } from '@angular/material/dialog'; -import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive'; +import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive'; import { AffectedComponent, AffectedComponentEntity, BulletinsTipInput, ProcessGroupName, ValidationErrorsTipInput -} from '../../../../../state/shared'; -import { NiFiCommon } from '../../../../../service/nifi-common.service'; -import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component'; -import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component'; +} from '../../../state/shared'; +import { NiFiCommon } from '../../../service/nifi-common.service'; +import { ValidationErrorsTip } from '../tooltips/validation-errors-tip/validation-errors-tip.component'; +import { BulletinsTip } from '../tooltips/bulletins-tip/bulletins-tip.component'; @Component({ selector: 'parameter-references', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history-chart/status-history-chart.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history-chart/status-history-chart.component.ts index 37fb1a5c9f..f3354edc92 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history-chart/status-history-chart.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history-chart/status-history-chart.component.ts @@ -22,6 +22,7 @@ import * as d3 from 'd3'; import { NiFiCommon } from '../../../../service/nifi-common.service'; import { Instance, NIFI_NODE_CONFIG, Stats, VisibleInstances } from '../index'; import { debounceTime, Subject } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'status-history-chart', @@ -82,11 +83,11 @@ export class StatusHistoryChart { constructor(private nifiCommon: NiFiCommon) { // don't need constantly fire the stats changing as a result of brush drag/move - this.nodeStats$.pipe(debounceTime(20)).subscribe((stats: Stats) => { + this.nodeStats$.pipe(debounceTime(20), takeUntilDestroyed()).subscribe((stats: Stats) => { this.nodeStats.next(stats); }); - this.clusterStats$.pipe(debounceTime(20)).subscribe((stats: Stats) => { + this.clusterStats$.pipe(debounceTime(20), takeUntilDestroyed()).subscribe((stats: Stats) => { this.clusterStats.next(stats); }); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.ts index 5c662808eb..d60120ecb9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AfterViewInit, Component, Inject, OnInit } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, inject, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { StatusHistoryService } from '../../../service/status-history.service'; import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; @@ -50,6 +50,7 @@ import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox import { Resizable } from '../resizable/resizable.component'; import { Instance, NIFI_NODE_CONFIG, Stats } from './index'; import { StatusHistoryChart } from './status-history-chart/status-history-chart.component'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'status-history', @@ -100,6 +101,7 @@ export class StatusHistory implements OnInit, AfterViewInit { instances: Instance[] = []; instanceVisibility: any = {}; selectedDescriptor: FieldDescriptor | null = null; + private destroyRef: DestroyRef = inject(DestroyRef); constructor( private statusHistoryService: StatusHistoryService, @@ -115,60 +117,65 @@ export class StatusHistory implements OnInit, AfterViewInit { } ngOnInit(): void { - this.statusHistory$.pipe(filter((entity) => !!entity)).subscribe((entity: StatusHistoryEntity) => { - if (entity) { - this.instances = []; - if (entity.statusHistory?.aggregateSnapshots?.length > 1) { - this.instances.push({ - id: NIFI_NODE_CONFIG.nifiInstanceId, - label: NIFI_NODE_CONFIG.nifiInstanceLabel, - snapshots: entity.statusHistory.aggregateSnapshots - }); - // if this is the first time this instance is being rendered, make it visible - if (this.instanceVisibility[NIFI_NODE_CONFIG.nifiInstanceId] === undefined) { - this.instanceVisibility[NIFI_NODE_CONFIG.nifiInstanceId] = true; - } - } - - // get the status for each node in the cluster if applicable - if (entity.statusHistory?.nodeSnapshots && entity.statusHistory?.nodeSnapshots.length > 1) { - entity.statusHistory.nodeSnapshots.forEach((nodeSnapshot: NodeSnapshot) => { + this.statusHistory$ + .pipe( + filter((entity) => !!entity), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((entity: StatusHistoryEntity) => { + if (entity) { + this.instances = []; + if (entity.statusHistory?.aggregateSnapshots?.length > 1) { this.instances.push({ - id: nodeSnapshot.nodeId, - label: `${nodeSnapshot.address}:${nodeSnapshot.apiPort}`, - snapshots: nodeSnapshot.statusSnapshots + id: NIFI_NODE_CONFIG.nifiInstanceId, + label: NIFI_NODE_CONFIG.nifiInstanceLabel, + snapshots: entity.statusHistory.aggregateSnapshots }); // if this is the first time this instance is being rendered, make it visible - if (this.instanceVisibility[nodeSnapshot.nodeId] === undefined) { - this.instanceVisibility[nodeSnapshot.nodeId] = true; + if (this.instanceVisibility[NIFI_NODE_CONFIG.nifiInstanceId] === undefined) { + this.instanceVisibility[NIFI_NODE_CONFIG.nifiInstanceId] = true; } + } + + // get the status for each node in the cluster if applicable + if (entity.statusHistory?.nodeSnapshots && entity.statusHistory?.nodeSnapshots.length > 1) { + entity.statusHistory.nodeSnapshots.forEach((nodeSnapshot: NodeSnapshot) => { + this.instances.push({ + id: nodeSnapshot.nodeId, + label: `${nodeSnapshot.address}:${nodeSnapshot.apiPort}`, + snapshots: nodeSnapshot.statusSnapshots + }); + // if this is the first time this instance is being rendered, make it visible + if (this.instanceVisibility[nodeSnapshot.nodeId] === undefined) { + this.instanceVisibility[nodeSnapshot.nodeId] = true; + } + }); + } + + // identify all nodes and sort + this.nodes = this.instances + .filter((status) => { + return status.id !== NIFI_NODE_CONFIG.nifiInstanceId; + }) + .sort((a: any, b: any) => { + return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; + }); + + // determine the min/max date + const minDate: any = d3.min(this.instances, (d) => { + return d3.min(d.snapshots, (s) => { + return s.timestamp; + }); }); + const maxDate: any = d3.max(this.instances, (d) => { + return d3.max(d.snapshots, (s) => { + return s.timestamp; + }); + }); + this.minDate = this.nifiCommon.formatDateTime(new Date(minDate)); + this.maxDate = this.nifiCommon.formatDateTime(new Date(maxDate)); } - - // identify all nodes and sort - this.nodes = this.instances - .filter((status) => { - return status.id !== NIFI_NODE_CONFIG.nifiInstanceId; - }) - .sort((a: any, b: any) => { - return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; - }); - - // determine the min/max date - const minDate: any = d3.min(this.instances, (d) => { - return d3.min(d.snapshots, (s) => { - return s.timestamp; - }); - }); - const maxDate: any = d3.max(this.instances, (d) => { - return d3.max(d.snapshots, (s) => { - return s.timestamp; - }); - }); - this.minDate = this.nifiCommon.formatDateTime(new Date(minDate)); - this.maxDate = this.nifiCommon.formatDateTime(new Date(maxDate)); - } - }); + }); this.fieldDescriptors$ .pipe( filter((descriptors) => !!descriptors), @@ -185,11 +192,14 @@ export class StatusHistory implements OnInit, AfterViewInit { ngAfterViewInit(): void { // when the selected descriptor changes, update the chart - this.statusHistoryForm.get('fieldDescriptor')?.valueChanges.subscribe((descriptor: FieldDescriptor) => { - if (this.instances.length > 0) { - this.selectedDescriptor = descriptor; - } - }); + this.statusHistoryForm + .get('fieldDescriptor') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((descriptor: FieldDescriptor) => { + if (this.instances.length > 0) { + this.selectedDescriptor = descriptor; + } + }); } isInitialLoading(state: StatusHistoryState) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss index 4a26174ebc..f45fd9af17 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss @@ -388,6 +388,13 @@ $appFontPath: '~roboto-fontface/fonts'; min-width: 760px; } + .xl-dialog { + max-height: 72%; + max-width: 85%; + min-height: 560px; + min-width: 1024px; + } + .edit-parameter-context-dialog { max-height: 72%; max-width: 55%;