mirror of https://github.com/apache/nifi.git
[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
This commit is contained in:
parent
16eadc88da
commit
7dc696ecdc
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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<void> = new Subject<void>();
|
||||
|
||||
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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
*ngFor="let updateStep of requestEntity.request.updateSteps"
|
||||
class="flex justify-between items-center">
|
||||
<div class="value">{{ updateStep.description }}</div>
|
||||
<div *ngIf="updateStep.complete; else stepInProgress" class="fa fa-check text-green-500"></div>
|
||||
<div *ngIf="updateStep.complete; else stepInProgress" class="fa fa-check complete"></div>
|
||||
<ng-template #stepInProgress>
|
||||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||
</ng-template>
|
||||
|
@ -67,6 +67,14 @@
|
|||
<div>Id</div>
|
||||
<div class="value">{{ request.parameterContext?.id }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col mb-5" *ngIf="parameterProvider">
|
||||
<div>Parameter Provider</div>
|
||||
<a [routerLink]="getParameterProviderLink(parameterProvider)" mat-dialog-close="ROUTED">
|
||||
{{ parameterProvider.parameterGroupName }}
|
||||
from
|
||||
{{ parameterProvider.parameterProviderName }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Name</mat-label>
|
||||
|
@ -97,6 +105,7 @@
|
|||
<div class="tab-content py-4">
|
||||
<parameter-table
|
||||
formControlName="parameters"
|
||||
[canAddParameters]="!request.parameterContext?.component?.parameterProviderConfiguration"
|
||||
[createNewParameter]="createNewParameter"
|
||||
[editParameter]="editParameter"></parameter-table>
|
||||
</div>
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -83,7 +83,12 @@
|
|||
(click)="$event.stopPropagation()"
|
||||
[routerLink]="getPolicyLink(item)"
|
||||
title="Access Policies"></div>
|
||||
<!-- TODO go to parameter provider -->
|
||||
<div
|
||||
class="pointer fa fa-long-arrow-right"
|
||||
*ngIf="canGoToParameterProvider(item)"
|
||||
(click)="$event.stopPropagation()"
|
||||
[routerLink]="getParameterProviderLink(item)"
|
||||
title="Go to Parameter Provider"></div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<div class="parameter-table listing-table flex gap-x-3">
|
||||
<div class="flex flex-col gap-y-3" style="flex-grow: 3">
|
||||
<div class="flex justify-end items-center">
|
||||
<div class="flex justify-end items-center" *ngIf="canAddParameters">
|
||||
<button class="nifi-button" type="button" (click)="newParameterClicked()">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
|
|
|
@ -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<Parameter>;
|
||||
@Input() editParameter!: (parameter: Parameter) => Observable<Parameter>;
|
||||
@Input() canAddParameters = true;
|
||||
|
||||
protected readonly TextTip = TextTip;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<any> {
|
||||
return this.httpClient.put(this.nifiCommon.stripProtocol(configureRequest.uri), configureRequest.payload);
|
||||
}
|
||||
|
||||
fetchParameters(request: FetchParameterProviderParametersRequest): Observable<any> {
|
||||
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<any> {
|
||||
return this.httpClient.post(
|
||||
`${ParameterProviderService.API}/parameter-providers/${request.id}/apply-parameters-requests`,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
pollParameterProviderParametersUpdateRequest(
|
||||
updateRequest: ParameterProviderApplyParametersRequest
|
||||
): Observable<any> {
|
||||
return this.httpClient.get(this.nifiCommon.stripProtocol(updateRequest.uri));
|
||||
}
|
||||
|
||||
deleteParameterProviderParametersUpdateRequest(
|
||||
updateRequest: ParameterProviderApplyParametersRequest
|
||||
): Observable<any> {
|
||||
return this.httpClient.delete(this.nifiCommon.stripProtocol(updateRequest.uri));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
<h2 mat-dialog-title>Edit Parameter Provider</h2>
|
||||
<form class="parameter-provider-edit-form" [formGroup]="editParameterProviderForm">
|
||||
<error-banner></error-banner>
|
||||
<mat-dialog-content>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Settings">
|
||||
|
@ -78,7 +79,7 @@
|
|||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
||||
<button color="accent" mat-raised-button mat-dialog-close>Cancel</button>
|
||||
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
[disabled]="!editParameterProviderForm.dirty || editParameterProviderForm.invalid || saving.value"
|
||||
type="button"
|
||||
|
|
|
@ -21,6 +21,8 @@ import { EditParameterProvider } from './edit-parameter-provider.component';
|
|||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { EditParameterProviderRequest } from '../../../state/parameter-providers';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialParameterProvidersState } from '../../../state/parameter-providers/parameter-providers.reducer';
|
||||
|
||||
describe('EditParameterProvider', () => {
|
||||
let component: EditParameterProvider;
|
||||
|
@ -58,6 +60,7 @@ describe('EditParameterProvider', () => {
|
|||
'parameter-value-byte-limit': '256 B',
|
||||
'parameter-value-encoding': 'plaintext'
|
||||
},
|
||||
affectedComponents: [],
|
||||
descriptors: {
|
||||
'parameter-group-directories': {
|
||||
name: 'parameter-group-directories',
|
||||
|
@ -156,7 +159,10 @@ describe('EditParameterProvider', () => {
|
|||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: data
|
||||
}
|
||||
},
|
||||
provideMockStore({
|
||||
initialState: initialParameterProvidersState
|
||||
})
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(EditParameterProvider);
|
||||
|
|
|
@ -41,6 +41,7 @@ import { MatInputModule } from '@angular/material/input';
|
|||
import { ControllerServiceReferences } from '../../../../../ui/common/controller-service/controller-service-references/controller-service-references.component';
|
||||
import { ParameterProviderReferences } from '../parameter-context-references/parameter-provider-references.component';
|
||||
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
||||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
|
||||
@Component({
|
||||
selector: 'edit-parameter-provider',
|
||||
|
@ -56,7 +57,8 @@ import { PropertyTable } from '../../../../../ui/common/property-table/property-
|
|||
MatInputModule,
|
||||
ControllerServiceReferences,
|
||||
ParameterProviderReferences,
|
||||
PropertyTable
|
||||
PropertyTable,
|
||||
ErrorBanner
|
||||
],
|
||||
templateUrl: './edit-parameter-provider.component.html',
|
||||
styleUrls: ['./edit-parameter-provider.component.scss']
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div tabindex="0">
|
||||
<h2 mat-dialog-title>Fetch Parameters</h2>
|
||||
<form class="parameter-provider-fetch-form" [formGroup]="fetchParametersForm">
|
||||
<error-banner></error-banner>
|
||||
|
||||
<mat-dialog-content *ngIf="(updateRequest | async)! as requestEntity; else fetchFormContent">
|
||||
<div class="dialog-content flex gap-x-4 h-full w-full pt-2">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div>Name</div>
|
||||
<div class="value">{{ parameterProvider.component.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-4">
|
||||
<div>Parameter Groups</div>
|
||||
<div class="value">
|
||||
{{ parameterGroupNames | sort | join }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div>Steps To Update Parameters</div>
|
||||
<div class="flex flex-col gap-y-1.5">
|
||||
<div
|
||||
*ngFor="let updateStep of requestEntity.updateSteps"
|
||||
class="flex justify-between items-center">
|
||||
<div class="value">{{ updateStep.description }}</div>
|
||||
<div
|
||||
*ngIf="updateStep.complete; else stepInProgress"
|
||||
class="fa fa-check complete"></div>
|
||||
<ng-template #stepInProgress>
|
||||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Parameter Contexts To Create
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Parameter groups set to be created as parameter contexts, pending apply."></i>
|
||||
</div>
|
||||
<div *ngIf="Object.keys(parameterContextsToCreate).length > 0; else none" class="value">
|
||||
{{ Object.values(parameterContextsToCreate) | sort | join }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Parameter Contexts To Update
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Synced parameter contexts to be updated, pending apply."></i>
|
||||
</div>
|
||||
<div *ngIf="parameterContextsToUpdate.length > 0; else none" class="value">
|
||||
{{ parameterContextsToUpdate | sort | join }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Affected Referencing Components
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Affected components referencing this parameter provider."></i>
|
||||
</div>
|
||||
<div class="relative h-full border">
|
||||
<div class="absolute inset-0 overflow-y-auto p-1">
|
||||
<parameter-references
|
||||
[parameterReferences]="
|
||||
parameterProvider.component.affectedComponents
|
||||
"></parameter-references>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #none>
|
||||
<div class="unset">None</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<ng-template #fetchFormContent>
|
||||
<mat-dialog-content>
|
||||
<div class="dialog-content flex gap-x-4 h-full w-full pt-2">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div>Name</div>
|
||||
<div class="value">{{ parameterProvider.component.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Select To Configure a Parameter Group
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Discovered parameter groups from this parameter provider. Select a group to create a parameter context, then configure its parameter sensitivities."></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<parameter-groups-table
|
||||
[parameterGroups]="parameterGroupConfigurations"
|
||||
(selected)="parameterGroupSelected($event)"></parameter-groups-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1">
|
||||
<ng-container *ngFor="let parameterGroupConfig of parameterGroupConfigurations">
|
||||
<!-- Only show the parameters associated with the selected group -->
|
||||
<div
|
||||
[formGroupName]="parameterGroupConfig.groupName"
|
||||
[ngClass]="{
|
||||
hidden: parameterGroupConfig.groupName !== selectedParameterGroup?.groupName
|
||||
}"
|
||||
class="flex gap-y-4 h-full w-full flex-col">
|
||||
<ng-container
|
||||
*ngIf="canCreateParameterContext(parameterGroupConfig); else paramContextSynced">
|
||||
<!-- Not synced, give the user the option to create a parameter context for the group -->
|
||||
<div class="flex items-center">
|
||||
<mat-checkbox
|
||||
color="primary"
|
||||
formControlName="createParameterContext"
|
||||
(change)="createParameterContextToggled($event)">
|
||||
<mat-label>Create Parameter Context</mat-label>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col"
|
||||
*ngIf="canEditParameterContextName(parameterGroupConfig)">
|
||||
<mat-form-field>
|
||||
<mat-label>Parameter Context Name</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="parameterContextName"
|
||||
[value]="parameterGroupConfig.parameterContextName" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- If the group is synchronized, show the parameter context name in read-only mode -->
|
||||
<ng-template #paramContextSynced>
|
||||
<div class="flex flex-col">
|
||||
<div>Parameter Context Name</div>
|
||||
<div class="value">{{ parameterGroupConfig.parameterContextName }}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngIf="showParameterList(parameterGroupConfig); else parameterMapping">
|
||||
<!-- Show the parameters defined -->
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<div class="flex items-center gap-x-2">
|
||||
Fetched Parameters
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Discovered parameters from the selected parameter group."></i>
|
||||
</div>
|
||||
<ul class="flex-1 overflow-y-auto border px-2">
|
||||
<li
|
||||
*ngFor="
|
||||
let param of Object.entries(
|
||||
parameterGroupConfig.parameterSensitivities
|
||||
)
|
||||
"
|
||||
class="value">
|
||||
{{ param[0] }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #parameterMapping>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Select Parameters To Be Set As Sensitive
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Only parameters that are not referenced can be modified."></i>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative">
|
||||
<div class="listing-table overflow-y-auto border absolute inset-0">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="getParameterMappingDataSource(parameterGroupConfig)"
|
||||
matSort
|
||||
matSortDisableClear
|
||||
matSortActive="name"
|
||||
matSortDirection="asc"
|
||||
(matSortChange)="sort($event)">
|
||||
<ng-container matColumnDef="sensitive">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox
|
||||
color="primary"
|
||||
[checked]="areAllSelected(parameterGroupConfig)"
|
||||
[indeterminate]="areAnySelected(parameterGroupConfig)"
|
||||
(change)="selectAllChanged($event)"></mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" class="items-center">
|
||||
<mat-checkbox
|
||||
color="primary"
|
||||
[formControl]="
|
||||
getFormControl(item, parameterGroupConfig)
|
||||
"
|
||||
[checked]="item.sensitivity.sensitive"></mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
Parameter Name
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" class="items-center">
|
||||
<div>{{ item.name }}</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="indicators">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3 justify-end">
|
||||
<div
|
||||
class="fa fa-hashtag"
|
||||
title="Parameter is currently referenced by a property. The sensitivity cannot be changed."
|
||||
*ngIf="isReferenced(item)"></div>
|
||||
<div
|
||||
class="fa fa-asterisk"
|
||||
[title]="getAffectedTooltip(item)"
|
||||
*ngIf="isAffected(item)"></div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr
|
||||
mat-header-row
|
||||
*matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||
(click)="selectParameter(row)"
|
||||
[class.selected]="isParameterSelected(row)"
|
||||
[class.even]="even"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="flex flex-1">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Parameter Contexts To Create
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Parameter groups set to be created as parameter contexts, pending apply."></i>
|
||||
</div>
|
||||
<div *ngIf="Object.keys(parameterContextsToCreate).length > 0; else none" class="value">
|
||||
{{ Object.values(parameterContextsToCreate) | sort | join }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Parameter Contexts To Update
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Synced parameter contexts to be updated, pending apply."></i>
|
||||
</div>
|
||||
<div *ngIf="parameterContextsToUpdate.length > 0; else none" class="value">
|
||||
{{ parameterContextsToUpdate | sort | join }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="flex flex-row items-center gap-x-2">
|
||||
Referencing Components
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
title="Components referencing this selected parameter."></i>
|
||||
</div>
|
||||
<div class="relative h-full border">
|
||||
<div class="absolute inset-0 overflow-y-auto p-1">
|
||||
<parameter-references
|
||||
[parameterReferences]="parameterReferences"></parameter-references>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #none>
|
||||
<div class="unset">None</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
</ng-template>
|
||||
|
||||
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
||||
<ng-container *ngIf="updateRequest | async; else normalActions">
|
||||
<!-- an update to the associated parameter context(s) has been triggered -->
|
||||
<button color="primary" mat-stroked-button mat-dialog-close>
|
||||
<span *nifiSpinner="saving.value">Close</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-template #normalActions>
|
||||
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
[disabled]="!canSubmitForm() || saving.value"
|
||||
type="button"
|
||||
color="primary"
|
||||
(click)="submitForm()"
|
||||
mat-raised-button>
|
||||
<span *nifiSpinner="saving.value">Apply</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</mat-dialog-actions>
|
||||
</form>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<FetchParameterProviderParameters>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<FetchedParameterMapping> } = {};
|
||||
|
||||
// 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<FetchedParameterMapping | null> = new Subject<FetchedParameterMapping | null>();
|
||||
|
||||
parameterReferences: AffectedComponentEntity[] = [];
|
||||
|
||||
@Input() saving$!: Observable<boolean>;
|
||||
@Input() updateRequest!: Observable<ParameterProviderApplyParametersRequest | null>;
|
||||
|
||||
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<ParameterProvidersState>,
|
||||
@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<FetchedParameterMapping>();
|
||||
|
||||
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<FetchedParameterMapping> = 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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="parameter-group-table h-full flex flex-col">
|
||||
<div class="flex-1 relative">
|
||||
<div class="listing-table overflow-y-auto border absolute inset-0">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="parameterGroupsDataSource"
|
||||
matSort
|
||||
matSortDisableClear
|
||||
matSortActive="groupName"
|
||||
matSortDirection="asc"
|
||||
(matSortChange)="sort($event)">
|
||||
<ng-container matColumnDef="groupName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Parameter Group Name</th>
|
||||
<td mat-cell *matCellDef="let item" class="items-center">
|
||||
<div>{{ item.groupName }}</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="indicators">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div
|
||||
class="fa fa-star"
|
||||
title="Synced to a parameter context."
|
||||
*ngIf="isSyncedToParameterContext(item)"></div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||
(click)="select(row)"
|
||||
[class.selected]="isSelected(row)"
|
||||
[class.even]="even"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ParameterGroupsTable>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ParameterGroupsTable, NoopAnimationsModule]
|
||||
});
|
||||
fixture = TestBed.createComponent(ParameterGroupsTable);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<ParameterGroupConfiguration> =
|
||||
new MatTableDataSource<ParameterGroupConfiguration>();
|
||||
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<ParameterGroupConfiguration> = new EventEmitter<ParameterGroupConfiguration>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -86,14 +86,17 @@
|
|||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div
|
||||
*ngIf="canConfigure(item)"
|
||||
class="pointer fa fa-pencil"
|
||||
(click)="configureClicked(item, $event)"
|
||||
title="Edit"></div>
|
||||
<div
|
||||
*ngIf="canFetch(item)"
|
||||
class="pointer fa fa-arrow-circle-down"
|
||||
(click)="fetchClicked(item, $event)"
|
||||
title="Fetch Parameters"></div>
|
||||
<div
|
||||
*ngIf="canDelete(item)"
|
||||
class="pointer fa fa-trash"
|
||||
(click)="deleteClicked(item, $event)"
|
||||
title="Remove"></div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
[selectedParameterProviderId]="selectedParameterProviderId$ | async"
|
||||
(deleteParameterProvider)="deleteParameterProvider($event)"
|
||||
(configureParameterProvider)="openConfigureParameterProviderDialog($event)"
|
||||
(fetchParameterProvider)="fetchParameterProviderParameters($event)"
|
||||
(selectParameterProvider)="selectParameterProvider($event)"></parameter-providers-table>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
|
|
|
@ -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
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<string, UserEntity> = new Map<string, UserEntity>();
|
||||
userGroupLookup: Map<string, UserGroupEntity> = new Map<string, UserGroupEntity>();
|
||||
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) {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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']);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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: {
|
||||
|
|
|
@ -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 });
|
||||
})
|
||||
),
|
||||
|
|
|
@ -21,7 +21,7 @@ export function isDefinedAndNotNull<T>() {
|
|||
return (source$: Observable<null | undefined | T>) =>
|
||||
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<any>;
|
||||
}
|
||||
|
||||
export interface ParameterProviderConfiguration {
|
||||
parameterGroupName: string;
|
||||
parameterProviderId: string;
|
||||
parameterProviderName: string;
|
||||
synchronized: boolean;
|
||||
}
|
||||
|
||||
export interface ParameterProviderConfigurationEntity {
|
||||
id: string;
|
||||
component: ParameterProviderConfiguration;
|
||||
}
|
||||
|
|
|
@ -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<ComponentStateState>,
|
||||
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||
</ng-template>
|
||||
<ng-template #stepComplete>
|
||||
<div class="fa fa-check text-green-500"></div>
|
||||
<div class="fa fa-check complete"></div>
|
||||
</ng-template>
|
||||
<ng-template #stepError>
|
||||
<div class="fa fa-times text-red-400"></div>
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||
</ng-template>
|
||||
<ng-template #stepComplete>
|
||||
<div class="fa fa-check text-green-500"></div>
|
||||
<div class="fa fa-check complete"></div>
|
||||
</ng-template>
|
||||
<ng-template #stepError>
|
||||
<div class="fa fa-times text-red-400"></div>
|
||||
|
|
|
@ -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',
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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%;
|
||||
|
|
Loading…
Reference in New Issue