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');
|
$accent-palette: map.get($color-config, 'accent');
|
||||||
$warn-palette: map.get($color-config, 'warn');
|
$warn-palette: map.get($color-config, 'warn');
|
||||||
$canvas-primary-palette: map.get($canvas-color-config, 'primary');
|
$canvas-primary-palette: map.get($canvas-color-config, 'primary');
|
||||||
|
$canvas-accent-palette: map.get($canvas-color-config, 'accent');
|
||||||
|
|
||||||
// Get hues from palette
|
// Get hues from palette
|
||||||
$primary-palette-300: mat.get-color-from-palette($primary-palette, 300);
|
$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');
|
$accent-palette-A400: mat.get-color-from-palette($accent-palette, 'A400');
|
||||||
$warn-palette-500: mat.get-color-from-palette($warn-palette, 500);
|
$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-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 {
|
.splash {
|
||||||
background-color: $primary-palette-500;
|
background-color: $primary-palette-500;
|
||||||
|
@ -54,4 +56,8 @@
|
||||||
.nifi-snackbar .mat-mdc-button:not(:disabled) .mdc-button__label {
|
.nifi-snackbar .mat-mdc-button:not(:disabled) .mdc-button__label {
|
||||||
color: $accent-palette-A400;
|
color: $accent-palette-A400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa.fa-check.complete {
|
||||||
|
color: $canvas-accent-palette-500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { GuardsCheckEnd, GuardsCheckStart, NavigationCancel, Router } from '@angular/router';
|
import { GuardsCheckEnd, GuardsCheckStart, NavigationCancel, Router } from '@angular/router';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { Storage } from './service/storage.service';
|
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 { ComponentStateEffects } from './state/component-state/component-state.effects';
|
||||||
import { ErrorEffects } from './state/error/error.effects';
|
import { ErrorEffects } from './state/error/error.effects';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
|
import { PipesModule } from './pipes/pipes.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
|
@ -80,7 +81,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatNativeDateModule,
|
MatNativeDateModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatSnackBarModule
|
MatSnackBarModule,
|
||||||
|
PipesModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,17 @@
|
||||||
* limitations under the License.
|
* 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 { CommonModule } from '@angular/common';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
@ -27,6 +37,7 @@ import { BulletinBoardEvent, BulletinBoardFilterArgs, BulletinBoardItem } from '
|
||||||
import { BulletinEntity, ComponentType } from '../../../../../state/shared';
|
import { BulletinEntity, ComponentType } from '../../../../../state/shared';
|
||||||
import { debounceTime, delay, Subject } from 'rxjs';
|
import { debounceTime, delay, Subject } from 'rxjs';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'bulletin-board-list',
|
selector: 'bulletin-board-list',
|
||||||
|
@ -51,6 +62,7 @@ export class BulletinBoardList implements AfterViewInit {
|
||||||
private bulletinsChanged$: Subject<void> = new Subject<void>();
|
private bulletinsChanged$: Subject<void> = new Subject<void>();
|
||||||
|
|
||||||
private _items: BulletinBoardItem[] = [];
|
private _items: BulletinBoardItem[] = [];
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
@ViewChild('scrollContainer') private scroll!: ElementRef;
|
@ViewChild('scrollContainer') private scroll!: ElementRef;
|
||||||
|
|
||||||
|
@ -58,6 +70,7 @@ export class BulletinBoardList implements AfterViewInit {
|
||||||
this._items = items;
|
this._items = items;
|
||||||
this.bulletinsChanged$.next();
|
this.bulletinsChanged$.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
get bulletinBoardItems(): BulletinBoardItem[] {
|
get bulletinBoardItems(): BulletinBoardItem[] {
|
||||||
return this._items;
|
return this._items;
|
||||||
}
|
}
|
||||||
|
@ -74,16 +87,19 @@ export class BulletinBoardList implements AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||||
this.applyFilter(filterTerm, filterColumn);
|
this.applyFilter(filterTerm, filterColumn);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterColumn')
|
||||||
this.applyFilter(filterTerm, 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
|
// scroll the initial chuck of bulletins
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
* limitations under the License.
|
* 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 { CounterEntity } from '../../../state/counter-listing';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { Sort } from '@angular/material/sort';
|
import { Sort } from '@angular/material/sort';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { debounceTime } from 'rxjs';
|
import { debounceTime } from 'rxjs';
|
||||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'counter-table',
|
selector: 'counter-table',
|
||||||
|
@ -30,6 +31,7 @@ import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
})
|
})
|
||||||
export class CounterTable implements AfterViewInit {
|
export class CounterTable implements AfterViewInit {
|
||||||
private _canModifyCounters = false;
|
private _canModifyCounters = false;
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
filterTerm = '';
|
filterTerm = '';
|
||||||
filterColumn: 'context' | 'name' = 'name';
|
filterColumn: 'context' | 'name' = 'name';
|
||||||
totalCount = 0;
|
totalCount = 0;
|
||||||
|
@ -95,16 +97,19 @@ export class CounterTable implements AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||||
this.applyFilter(filterTerm, filterColumn);
|
this.applyFilter(filterTerm, filterColumn);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterColumn')
|
||||||
this.applyFilter(filterTerm, 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) {
|
applyFilter(filterTerm: string, filterColumn: string) {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||||
import { debounceTime, filter, switchMap, tap } from 'rxjs';
|
import { debounceTime, filter, switchMap, tap } from 'rxjs';
|
||||||
|
@ -70,6 +70,7 @@ export class Search implements OnInit {
|
||||||
overlayY: 'top'
|
overlayY: 'top'
|
||||||
};
|
};
|
||||||
private position: ConnectionPositionPair = new ConnectionPositionPair(this.originPos, this.overlayPos, 0, 2);
|
private position: ConnectionPositionPair = new ConnectionPositionPair(this.originPos, this.overlayPos, 0, 2);
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
public positions: ConnectionPositionPair[] = [this.position];
|
public positions: ConnectionPositionPair[] = [this.position];
|
||||||
|
|
||||||
searchForm: FormGroup;
|
searchForm: FormGroup;
|
||||||
|
@ -116,6 +117,7 @@ export class Search implements OnInit {
|
||||||
this.searchForm
|
this.searchForm
|
||||||
.get('searchBar')
|
.get('searchBar')
|
||||||
?.valueChanges.pipe(
|
?.valueChanges.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
filter((data) => data?.trim().length > 0),
|
filter((data) => data?.trim().length > 0),
|
||||||
debounceTime(500),
|
debounceTime(500),
|
||||||
tap(() => (this.searching = true)),
|
tap(() => (this.searching = true)),
|
||||||
|
|
|
@ -19,6 +19,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { NoRegistryClientsDialog } from './no-registry-clients-dialog.component';
|
import { NoRegistryClientsDialog } from './no-registry-clients-dialog.component';
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
describe('NoRegistryClientsDialog', () => {
|
describe('NoRegistryClientsDialog', () => {
|
||||||
let component: NoRegistryClientsDialog;
|
let component: NoRegistryClientsDialog;
|
||||||
|
@ -26,7 +28,7 @@ describe('NoRegistryClientsDialog', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [NoRegistryClientsDialog],
|
imports: [NoRegistryClientsDialog, RouterModule, RouterTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: MAT_DIALOG_DATA,
|
provide: MAT_DIALOG_DATA,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
ParameterContextReferenceEntity,
|
ParameterContextReferenceEntity,
|
||||||
ParameterContextUpdateRequestEntity,
|
ParameterContextUpdateRequestEntity,
|
||||||
ParameterEntity,
|
ParameterEntity,
|
||||||
|
ParameterProviderConfigurationEntity,
|
||||||
Permissions,
|
Permissions,
|
||||||
Revision
|
Revision
|
||||||
} from '../../../../state/shared';
|
} from '../../../../state/shared';
|
||||||
|
@ -73,7 +74,7 @@ export interface ParameterContext {
|
||||||
parameters: ParameterEntity[];
|
parameters: ParameterEntity[];
|
||||||
boundProcessGroups: BoundProcessGroup[];
|
boundProcessGroups: BoundProcessGroup[];
|
||||||
inheritedParameterContexts: ParameterContextReferenceEntity[];
|
inheritedParameterContexts: ParameterContextReferenceEntity[];
|
||||||
// private ParameterProviderConfigurationEntity parameterProviderConfiguration;
|
parameterProviderConfiguration?: ParameterProviderConfigurationEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Replace this with ProcessGroupEntity was available
|
// TODO - Replace this with ProcessGroupEntity was available
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
*ngFor="let updateStep of requestEntity.request.updateSteps"
|
*ngFor="let updateStep of requestEntity.request.updateSteps"
|
||||||
class="flex justify-between items-center">
|
class="flex justify-between items-center">
|
||||||
<div class="value">{{ updateStep.description }}</div>
|
<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>
|
<ng-template #stepInProgress>
|
||||||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -67,6 +67,14 @@
|
||||||
<div>Id</div>
|
<div>Id</div>
|
||||||
<div class="value">{{ request.parameterContext?.id }}</div>
|
<div class="value">{{ request.parameterContext?.id }}</div>
|
||||||
</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>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
|
@ -97,6 +105,7 @@
|
||||||
<div class="tab-content py-4">
|
<div class="tab-content py-4">
|
||||||
<parameter-table
|
<parameter-table
|
||||||
formControlName="parameters"
|
formControlName="parameters"
|
||||||
|
[canAddParameters]="!request.parameterContext?.component?.parameterProviderConfiguration"
|
||||||
[createNewParameter]="createNewParameter"
|
[createNewParameter]="createNewParameter"
|
||||||
[editParameter]="editParameter"></parameter-table>
|
[editParameter]="editParameter"></parameter-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,10 +30,16 @@ import { EditParameterContextRequest, ParameterContextEntity } from '../../../st
|
||||||
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||||
import { Client } from '../../../../../service/client.service';
|
import { Client } from '../../../../../service/client.service';
|
||||||
import { ParameterTable } from '../parameter-table/parameter-table.component';
|
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 { ProcessGroupReferences } from '../process-group-references/process-group-references.component';
|
||||||
import { ParameterContextInheritance } from '../parameter-context-inheritance/parameter-context-inheritance.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({
|
@Component({
|
||||||
selector: 'edit-parameter-context',
|
selector: 'edit-parameter-context',
|
||||||
|
@ -56,7 +62,8 @@ import { ParameterReferences } from '../parameter-references/parameter-reference
|
||||||
ParameterTable,
|
ParameterTable,
|
||||||
ProcessGroupReferences,
|
ProcessGroupReferences,
|
||||||
ParameterContextInheritance,
|
ParameterContextInheritance,
|
||||||
ParameterReferences
|
ParameterReferences,
|
||||||
|
RouterLink
|
||||||
],
|
],
|
||||||
styleUrls: ['./edit-parameter-context.component.scss']
|
styleUrls: ['./edit-parameter-context.component.scss']
|
||||||
})
|
})
|
||||||
|
@ -72,6 +79,7 @@ export class EditParameterContext {
|
||||||
|
|
||||||
editParameterContextForm: FormGroup;
|
editParameterContextForm: FormGroup;
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
|
parameterProvider: ParameterProviderConfiguration | null = null;
|
||||||
|
|
||||||
parameters!: ParameterEntity[];
|
parameters!: ParameterEntity[];
|
||||||
|
|
||||||
|
@ -91,6 +99,9 @@ export class EditParameterContext {
|
||||||
request.parameterContext.component.inheritedParameterContexts
|
request.parameterContext.component.inheritedParameterContexts
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
if (request.parameterContext.component.parameterProviderConfiguration) {
|
||||||
|
this.parameterProvider = request.parameterContext.component.parameterProviderConfiguration.component;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.isNew = true;
|
this.isNew = true;
|
||||||
|
|
||||||
|
@ -153,4 +164,8 @@ export class EditParameterContext {
|
||||||
this.editParameterContext.next(payload);
|
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 { ParameterContextReferenceEntity, TextTipInput } from '../../../../../state/shared';
|
||||||
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||||
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
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 { ParameterContextEntity } from '../../../state/parameter-context-listing';
|
||||||
import {
|
import {
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
|
|
|
@ -83,7 +83,12 @@
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
[routerLink]="getPolicyLink(item)"
|
[routerLink]="getPolicyLink(item)"
|
||||||
title="Access Policies"></div>
|
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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||||
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
|
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
|
||||||
import { FlowConfiguration } from '../../../../../state/flow-configuration';
|
import { FlowConfiguration } from '../../../../../state/flow-configuration';
|
||||||
import { CurrentUser } from '../../../../../state/current-user';
|
import { CurrentUser } from '../../../../../state/current-user';
|
||||||
|
import { ParameterProviderConfigurationEntity } from '../../../../../state/shared';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'parameter-context-table',
|
selector: 'parameter-context-table',
|
||||||
|
@ -66,7 +67,14 @@ export class ParameterContextTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
formatProvider(entity: ParameterContextEntity): string {
|
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 {
|
formatDescription(entity: ParameterContextEntity): string {
|
||||||
|
@ -94,6 +102,20 @@ export class ParameterContextTable {
|
||||||
return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
|
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[] {
|
getPolicyLink(entity: ParameterContextEntity): string[] {
|
||||||
return ['/access-policies', 'read', 'component', 'parameter-contexts', entity.id];
|
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="parameter-table listing-table flex gap-x-3">
|
||||||
<div class="flex flex-col gap-y-3" style="flex-grow: 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()">
|
<button class="nifi-button" type="button" (click)="newParameterClicked()">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Parameter, ParameterEntity, TextTipInput } from '../../../../../state/s
|
||||||
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||||
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||||
import { Observable, take } from 'rxjs';
|
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 { Store } from '@ngrx/store';
|
||||||
import { ParameterContextListingState } from '../../../state/parameter-context-listing';
|
import { ParameterContextListingState } from '../../../state/parameter-context-listing';
|
||||||
import { showOkDialog } from '../../../../flow-designer/state/flow/flow.actions';
|
import { showOkDialog } from '../../../../flow-designer/state/flow/flow.actions';
|
||||||
|
@ -69,6 +69,7 @@ export interface ParameterItem {
|
||||||
export class ParameterTable implements AfterViewInit, ControlValueAccessor {
|
export class ParameterTable implements AfterViewInit, ControlValueAccessor {
|
||||||
@Input() createNewParameter!: (existingParameters: string[]) => Observable<Parameter>;
|
@Input() createNewParameter!: (existingParameters: string[]) => Observable<Parameter>;
|
||||||
@Input() editParameter!: (parameter: Parameter) => Observable<Parameter>;
|
@Input() editParameter!: (parameter: Parameter) => Observable<Parameter>;
|
||||||
|
@Input() canAddParameters = true;
|
||||||
|
|
||||||
protected readonly TextTip = TextTip;
|
protected readonly TextTip = TextTip;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
|
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 { LineageComponent } from './lineage/lineage.component';
|
||||||
import { GoToProvenanceEventSourceRequest, ProvenanceEventRequest } from '../../../state/provenance-event-listing';
|
import { GoToProvenanceEventSourceRequest, ProvenanceEventRequest } from '../../../state/provenance-event-listing';
|
||||||
import { MatSliderModule } from '@angular/material/slider';
|
import { MatSliderModule } from '@angular/material/slider';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'provenance-event-table',
|
selector: 'provenance-event-table',
|
||||||
|
@ -152,6 +153,7 @@ export class ProvenanceEventTable implements AfterViewInit {
|
||||||
protected readonly TextTip = TextTip;
|
protected readonly TextTip = TextTip;
|
||||||
protected readonly BulletinsTip = BulletinsTip;
|
protected readonly BulletinsTip = BulletinsTip;
|
||||||
protected readonly ValidationErrorsTip = ValidationErrorsTip;
|
protected readonly ValidationErrorsTip = ValidationErrorsTip;
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
// TODO - conditionally include the cluster column
|
// TODO - conditionally include the cluster column
|
||||||
displayedColumns: string[] = [
|
displayedColumns: string[] = [
|
||||||
|
@ -202,17 +204,20 @@ export class ProvenanceEventTable implements AfterViewInit {
|
||||||
|
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||||
this.filterApplied = filterTerm.length > 0;
|
this.filterApplied = filterTerm.length > 0;
|
||||||
this.applyFilter(filterTerm, filterColumn);
|
this.applyFilter(filterTerm, filterColumn);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterColumn')
|
||||||
this.applyFilter(filterTerm, filterColumn);
|
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
});
|
.subscribe((filterColumn: string) => {
|
||||||
|
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
||||||
|
this.applyFilter(filterTerm, filterColumn);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSort(sort: Sort): void {
|
updateSort(sort: Sort): void {
|
||||||
|
|
|
@ -24,7 +24,10 @@ import {
|
||||||
ConfigureParameterProviderRequest,
|
ConfigureParameterProviderRequest,
|
||||||
CreateParameterProviderRequest,
|
CreateParameterProviderRequest,
|
||||||
DeleteParameterProviderRequest,
|
DeleteParameterProviderRequest,
|
||||||
ParameterProviderEntity
|
FetchParameterProviderParametersRequest,
|
||||||
|
ParameterProviderApplyParametersRequest,
|
||||||
|
ParameterProviderEntity,
|
||||||
|
ParameterProviderParameterApplicationEntity
|
||||||
} from '../state/parameter-providers';
|
} from '../state/parameter-providers';
|
||||||
import { PropertyDescriptorRetriever } from '../../../state/shared';
|
import { PropertyDescriptorRetriever } from '../../../state/shared';
|
||||||
|
|
||||||
|
@ -75,4 +78,34 @@ export class ParameterProviderService implements PropertyDescriptorRetriever {
|
||||||
updateParameterProvider(configureRequest: ConfigureParameterProviderRequest): Observable<any> {
|
updateParameterProvider(configureRequest: ConfigureParameterProviderRequest): Observable<any> {
|
||||||
return this.httpClient.put(this.nifiCommon.stripProtocol(configureRequest.uri), configureRequest.payload);
|
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 {
|
import {
|
||||||
|
AffectedComponentEntity,
|
||||||
Bundle,
|
Bundle,
|
||||||
DocumentedType,
|
DocumentedType,
|
||||||
ParameterContextReferenceEntity,
|
ParameterContextReferenceEntity,
|
||||||
|
ParameterEntity,
|
||||||
Permissions,
|
Permissions,
|
||||||
PropertyDescriptor,
|
PropertyDescriptor,
|
||||||
Revision
|
Revision
|
||||||
|
@ -26,7 +28,31 @@ import {
|
||||||
|
|
||||||
export const parameterProvidersFeatureKey = 'parameterProviders';
|
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 {
|
export interface ParameterProvider {
|
||||||
|
affectedComponents: AffectedComponentEntity[];
|
||||||
bundle: Bundle;
|
bundle: Bundle;
|
||||||
comments: string;
|
comments: string;
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
|
@ -35,7 +61,8 @@ export interface ParameterProvider {
|
||||||
id: string;
|
id: string;
|
||||||
multipleVersionsAvailable: boolean;
|
multipleVersionsAvailable: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
parameterGroupConfigurations: any[];
|
parameterGroupConfigurations: ParameterGroupConfiguration[];
|
||||||
|
parameterStatus?: ParameterStatusEntity[];
|
||||||
persistsState: boolean;
|
persistsState: boolean;
|
||||||
properties: { [key: string]: string };
|
properties: { [key: string]: string };
|
||||||
referencingParameterContexts: ParameterContextReferenceEntity[];
|
referencingParameterContexts: ParameterContextReferenceEntity[];
|
||||||
|
@ -54,12 +81,46 @@ export interface ParameterProviderEntity {
|
||||||
uri: string;
|
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 {
|
export interface ParameterProvidersState {
|
||||||
parameterProviders: ParameterProviderEntity[];
|
parameterProviders: ParameterProviderEntity[];
|
||||||
|
fetched: ParameterProviderEntity | null;
|
||||||
|
applyParametersRequestEntity: ParameterProviderApplyParametersRequest | null;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
loadedTimestamp: string;
|
loadedTimestamp: string;
|
||||||
error: string | null;
|
status: 'pending' | 'loading' | 'success';
|
||||||
status: 'pending' | 'loading' | 'error' | 'success';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadParameterProvidersResponse {
|
export interface LoadParameterProvidersResponse {
|
||||||
|
@ -115,3 +176,21 @@ export interface UpdateParameterProviderRequest {
|
||||||
payload: any;
|
payload: any;
|
||||||
postUpdateNavigation?: string[];
|
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,
|
DeleteParameterProviderRequest,
|
||||||
DeleteParameterProviderSuccess,
|
DeleteParameterProviderSuccess,
|
||||||
EditParameterProviderRequest,
|
EditParameterProviderRequest,
|
||||||
|
FetchParameterProviderParametersRequest,
|
||||||
|
FetchParameterProviderParametersResponse,
|
||||||
LoadParameterProvidersResponse,
|
LoadParameterProvidersResponse,
|
||||||
|
ParameterProviderParameterApplicationEntity,
|
||||||
|
PollParameterProviderParametersUpdateSuccess,
|
||||||
SelectParameterProviderRequest
|
SelectParameterProviderRequest
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
|
@ -39,7 +43,7 @@ export const loadParameterProvidersSuccess = createAction(
|
||||||
props<{ response: LoadParameterProvidersResponse }>()
|
props<{ response: LoadParameterProvidersResponse }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const parameterProvidersApiError = createAction(
|
export const parameterProvidersBannerApiError = createAction(
|
||||||
`${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Error`,
|
`${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Error`,
|
||||||
props<{ error: string }>()
|
props<{ error: string }>()
|
||||||
);
|
);
|
||||||
|
@ -83,6 +87,11 @@ export const navigateToEditParameterProvider = createAction(
|
||||||
props<{ id: string }>()
|
props<{ id: string }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const navigateToFetchParameterProvider = createAction(
|
||||||
|
`${PARAMETER_PROVIDERS_PREFIX} Navigate To Fetch Parameter Provider`,
|
||||||
|
props<{ id: string }>()
|
||||||
|
);
|
||||||
|
|
||||||
export const openConfigureParameterProviderDialog = createAction(
|
export const openConfigureParameterProviderDialog = createAction(
|
||||||
`${PARAMETER_PROVIDERS_PREFIX} Open Configure Parameter Provider Dialog`,
|
`${PARAMETER_PROVIDERS_PREFIX} Open Configure Parameter Provider Dialog`,
|
||||||
props<{ request: EditParameterProviderRequest }>()
|
props<{ request: EditParameterProviderRequest }>()
|
||||||
|
@ -97,3 +106,58 @@ export const configureParameterProviderSuccess = createAction(
|
||||||
`${PARAMETER_PROVIDERS_PREFIX} Configure Parameter Provider Success`,
|
`${PARAMETER_PROVIDERS_PREFIX} Configure Parameter Provider Success`,
|
||||||
props<{ response: ConfigureParameterProviderSuccess }>()
|
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 { Router } from '@angular/router';
|
||||||
import { ParameterProviderService } from '../../service/parameter-provider.service';
|
import { ParameterProviderService } from '../../service/parameter-provider.service';
|
||||||
import * as ParameterProviderActions from './parameter-providers.actions';
|
import * as ParameterProviderActions from './parameter-providers.actions';
|
||||||
import { loadParameterProviders, selectParameterProvider } from './parameter-providers.actions';
|
import { loadParameterProviders } from './parameter-providers.actions';
|
||||||
import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
|
import {
|
||||||
import { selectSaving } from './parameter-providers.selectors';
|
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 { selectParameterProviderTypes } from '../../../../state/extension-types/extension-types.selectors';
|
||||||
import { CreateParameterProvider } from '../../ui/parameter-providers/create-parameter-provider/create-parameter-provider.component';
|
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 { 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 { EditParameterProvider } from '../../ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component';
|
||||||
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
|
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 { 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()
|
@Injectable()
|
||||||
export class ParameterProvidersEffects {
|
export class ParameterProvidersEffects {
|
||||||
|
@ -45,13 +66,15 @@ export class ParameterProvidersEffects {
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private parameterProviderService: ParameterProviderService,
|
private parameterProviderService: ParameterProviderService,
|
||||||
private propertyTableHelperService: PropertyTableHelperService,
|
private propertyTableHelperService: PropertyTableHelperService,
|
||||||
private managementControllerServiceService: ManagementControllerServiceService
|
private managementControllerServiceService: ManagementControllerServiceService,
|
||||||
|
private errorHelper: ErrorHelper
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
loadParameterProviders$ = createEffect(() =>
|
loadParameterProviders$ = createEffect(() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(loadParameterProviders),
|
ofType(loadParameterProviders),
|
||||||
switchMap(() =>
|
concatLatestFrom(() => this.store.select(selectStatus)),
|
||||||
|
switchMap(([, status]) =>
|
||||||
from(this.parameterProviderService.getParameterProviders()).pipe(
|
from(this.parameterProviderService.getParameterProviders()).pipe(
|
||||||
map((response) =>
|
map((response) =>
|
||||||
ParameterProviderActions.loadParameterProvidersSuccess({
|
ParameterProviderActions.loadParameterProvidersSuccess({
|
||||||
|
@ -61,9 +84,7 @@ export class ParameterProvidersEffects {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((error: HttpErrorResponse) => of(this.errorHelper.handleLoadingError(status, error)))
|
||||||
of(ParameterProviderActions.parameterProvidersApiError({ error: error.error }))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -72,7 +93,7 @@ export class ParameterProvidersEffects {
|
||||||
selectParameterProvider$ = createEffect(
|
selectParameterProvider$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(selectParameterProvider),
|
ofType(ParameterProviderActions.selectParameterProvider),
|
||||||
map((action) => action.request),
|
map((action) => action.request),
|
||||||
tap((request) => {
|
tap((request) => {
|
||||||
this.router.navigate(['/settings', 'parameter-providers', request.id]);
|
this.router.navigate(['/settings', 'parameter-providers', request.id]);
|
||||||
|
@ -130,9 +151,10 @@ export class ParameterProvidersEffects {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((error: HttpErrorResponse) => {
|
||||||
of(ParameterProviderActions.parameterProvidersApiError({ error: error.error }))
|
this.dialog.closeAll();
|
||||||
)
|
return of(ErrorActions.snackBarError({ error: error.error }));
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -196,13 +218,7 @@ export class ParameterProvidersEffects {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((error) => of(ErrorActions.snackBarError({ error: error.error })))
|
||||||
of(
|
|
||||||
ParameterProviderActions.parameterProvidersApiError({
|
|
||||||
error: error.error
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -220,6 +236,18 @@ export class ParameterProvidersEffects {
|
||||||
{ dispatch: false }
|
{ 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(
|
openConfigureParameterProviderDialog$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
|
@ -298,6 +326,8 @@ export class ParameterProvidersEffects {
|
||||||
});
|
});
|
||||||
|
|
||||||
editDialogReference.afterClosed().subscribe((response) => {
|
editDialogReference.afterClosed().subscribe((response) => {
|
||||||
|
this.store.dispatch(ErrorActions.clearBannerErrors());
|
||||||
|
|
||||||
if (response !== 'ROUTED') {
|
if (response !== 'ROUTED') {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
ParameterProviderActions.selectParameterProvider({
|
ParameterProviderActions.selectParameterProvider({
|
||||||
|
@ -328,13 +358,18 @@ export class ParameterProvidersEffects {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((error: HttpErrorResponse) => {
|
||||||
of(
|
if (this.errorHelper.showErrorInContext(error.status)) {
|
||||||
ParameterProviderActions.parameterProvidersApiError({
|
return of(
|
||||||
error: error.error
|
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 }
|
{ 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,
|
createParameterProvider,
|
||||||
createParameterProviderSuccess,
|
createParameterProviderSuccess,
|
||||||
deleteParameterProvider,
|
deleteParameterProvider,
|
||||||
|
deleteParameterProviderParametersUpdateRequest,
|
||||||
deleteParameterProviderSuccess,
|
deleteParameterProviderSuccess,
|
||||||
|
fetchParameterProviderParametersSuccess,
|
||||||
loadParameterProviders,
|
loadParameterProviders,
|
||||||
loadParameterProvidersSuccess,
|
loadParameterProvidersSuccess,
|
||||||
parameterProvidersApiError,
|
parameterProvidersBannerApiError,
|
||||||
resetParameterProvidersState
|
pollParameterProviderParametersUpdateRequestSuccess,
|
||||||
|
resetFetchedParameterProvider,
|
||||||
|
resetParameterProvidersState,
|
||||||
|
submitParameterProviderParametersUpdateRequest,
|
||||||
|
submitParameterProviderParametersUpdateRequestSuccess
|
||||||
} from './parameter-providers.actions';
|
} from './parameter-providers.actions';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
|
|
||||||
export const initialParameterProvidersState: ParameterProvidersState = {
|
export const initialParameterProvidersState: ParameterProvidersState = {
|
||||||
parameterProviders: [],
|
parameterProviders: [],
|
||||||
|
fetched: null,
|
||||||
|
applyParametersRequestEntity: null,
|
||||||
saving: false,
|
saving: false,
|
||||||
loadedTimestamp: '',
|
loadedTimestamp: '',
|
||||||
error: null,
|
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,15 +62,12 @@ export const parameterProvidersReducer = createReducer(
|
||||||
...state,
|
...state,
|
||||||
parameterProviders: response.parameterProviders,
|
parameterProviders: response.parameterProviders,
|
||||||
loadedTimestamp: response.loadedTimestamp,
|
loadedTimestamp: response.loadedTimestamp,
|
||||||
error: null,
|
|
||||||
status: 'success' as const
|
status: 'success' as const
|
||||||
})),
|
})),
|
||||||
|
|
||||||
on(parameterProvidersApiError, (state: ParameterProvidersState, { error }) => ({
|
on(parameterProvidersBannerApiError, (state: ParameterProvidersState) => ({
|
||||||
...state,
|
...state,
|
||||||
saving: false,
|
saving: false
|
||||||
error,
|
|
||||||
status: 'error' as const
|
|
||||||
})),
|
})),
|
||||||
|
|
||||||
on(createParameterProvider, configureParameterProvider, deleteParameterProvider, (state) => ({
|
on(createParameterProvider, configureParameterProvider, deleteParameterProvider, (state) => ({
|
||||||
|
@ -102,5 +106,39 @@ export const parameterProvidersReducer = createReducer(
|
||||||
|
|
||||||
draftState.saving = false;
|
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
|
(state: ParameterProvidersState) => state.saving
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectStatus = createSelector(
|
||||||
|
selectParameterProvidersState,
|
||||||
|
(state: ParameterProvidersState) => state.status
|
||||||
|
);
|
||||||
|
|
||||||
export const selectParameterProviderIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
export const selectParameterProviderIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||||
if (route) {
|
if (route) {
|
||||||
// always select the parameter provider from the route
|
// always select the parameter provider from the route
|
||||||
|
@ -45,6 +50,13 @@ export const selectSingleEditedParameterProvider = createSelector(selectCurrentR
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const selectSingleFetchParameterProvider = createSelector(selectCurrentRoute, (route) => {
|
||||||
|
if (route?.routeConfig?.path == 'fetch') {
|
||||||
|
return route.params.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
export const selectParameterProviders = createSelector(
|
export const selectParameterProviders = createSelector(
|
||||||
selectParameterProvidersState,
|
selectParameterProvidersState,
|
||||||
(state: ParameterProvidersState) => state.parameterProviders
|
(state: ParameterProvidersState) => state.parameterProviders
|
||||||
|
@ -52,5 +64,10 @@ export const selectParameterProviders = createSelector(
|
||||||
|
|
||||||
export const selectParameterProvider = (id: string) =>
|
export const selectParameterProvider = (id: string) =>
|
||||||
createSelector(selectParameterProviders, (entities: ParameterProviderEntity[]) =>
|
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>
|
<h2 mat-dialog-title>Edit Parameter Provider</h2>
|
||||||
<form class="parameter-provider-edit-form" [formGroup]="editParameterProviderForm">
|
<form class="parameter-provider-edit-form" [formGroup]="editParameterProviderForm">
|
||||||
|
<error-banner></error-banner>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab label="Settings">
|
<mat-tab label="Settings">
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
|
<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
|
<button
|
||||||
[disabled]="!editParameterProviderForm.dirty || editParameterProviderForm.invalid || saving.value"
|
[disabled]="!editParameterProviderForm.dirty || editParameterProviderForm.invalid || saving.value"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -21,6 +21,8 @@ import { EditParameterProvider } from './edit-parameter-provider.component';
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
import { EditParameterProviderRequest } from '../../../state/parameter-providers';
|
import { EditParameterProviderRequest } from '../../../state/parameter-providers';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialParameterProvidersState } from '../../../state/parameter-providers/parameter-providers.reducer';
|
||||||
|
|
||||||
describe('EditParameterProvider', () => {
|
describe('EditParameterProvider', () => {
|
||||||
let component: EditParameterProvider;
|
let component: EditParameterProvider;
|
||||||
|
@ -58,6 +60,7 @@ describe('EditParameterProvider', () => {
|
||||||
'parameter-value-byte-limit': '256 B',
|
'parameter-value-byte-limit': '256 B',
|
||||||
'parameter-value-encoding': 'plaintext'
|
'parameter-value-encoding': 'plaintext'
|
||||||
},
|
},
|
||||||
|
affectedComponents: [],
|
||||||
descriptors: {
|
descriptors: {
|
||||||
'parameter-group-directories': {
|
'parameter-group-directories': {
|
||||||
name: 'parameter-group-directories',
|
name: 'parameter-group-directories',
|
||||||
|
@ -156,7 +159,10 @@ describe('EditParameterProvider', () => {
|
||||||
{
|
{
|
||||||
provide: MAT_DIALOG_DATA,
|
provide: MAT_DIALOG_DATA,
|
||||||
useValue: data
|
useValue: data
|
||||||
}
|
},
|
||||||
|
provideMockStore({
|
||||||
|
initialState: initialParameterProvidersState
|
||||||
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(EditParameterProvider);
|
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 { ControllerServiceReferences } from '../../../../../ui/common/controller-service/controller-service-references/controller-service-references.component';
|
||||||
import { ParameterProviderReferences } from '../parameter-context-references/parameter-provider-references.component';
|
import { ParameterProviderReferences } from '../parameter-context-references/parameter-provider-references.component';
|
||||||
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
||||||
|
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'edit-parameter-provider',
|
selector: 'edit-parameter-provider',
|
||||||
|
@ -56,7 +57,8 @@ import { PropertyTable } from '../../../../../ui/common/property-table/property-
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
ControllerServiceReferences,
|
ControllerServiceReferences,
|
||||||
ParameterProviderReferences,
|
ParameterProviderReferences,
|
||||||
PropertyTable
|
PropertyTable,
|
||||||
|
ErrorBanner
|
||||||
],
|
],
|
||||||
templateUrl: './edit-parameter-provider.component.html',
|
templateUrl: './edit-parameter-provider.component.html',
|
||||||
styleUrls: ['./edit-parameter-provider.component.scss']
|
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">
|
<td mat-cell *matCellDef="let item">
|
||||||
<div class="flex items-center gap-x-3">
|
<div class="flex items-center gap-x-3">
|
||||||
<div
|
<div
|
||||||
|
*ngIf="canConfigure(item)"
|
||||||
class="pointer fa fa-pencil"
|
class="pointer fa fa-pencil"
|
||||||
(click)="configureClicked(item, $event)"
|
(click)="configureClicked(item, $event)"
|
||||||
title="Edit"></div>
|
title="Edit"></div>
|
||||||
<div
|
<div
|
||||||
|
*ngIf="canFetch(item)"
|
||||||
class="pointer fa fa-arrow-circle-down"
|
class="pointer fa fa-arrow-circle-down"
|
||||||
(click)="fetchClicked(item, $event)"
|
(click)="fetchClicked(item, $event)"
|
||||||
title="Fetch Parameters"></div>
|
title="Fetch Parameters"></div>
|
||||||
<div
|
<div
|
||||||
|
*ngIf="canDelete(item)"
|
||||||
class="pointer fa fa-trash"
|
class="pointer fa fa-trash"
|
||||||
(click)="deleteClicked(item, $event)"
|
(click)="deleteClicked(item, $event)"
|
||||||
title="Remove"></div>
|
title="Remove"></div>
|
||||||
|
|
|
@ -94,6 +94,34 @@ export class ParameterProvidersTable {
|
||||||
return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
|
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 {
|
isSelected(parameterProvider: ParameterProviderEntity): boolean {
|
||||||
if (this.selectedParameterProviderId) {
|
if (this.selectedParameterProviderId) {
|
||||||
return parameterProvider.id === this.selectedParameterProviderId;
|
return parameterProvider.id === this.selectedParameterProviderId;
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
[selectedParameterProviderId]="selectedParameterProviderId$ | async"
|
[selectedParameterProviderId]="selectedParameterProviderId$ | async"
|
||||||
(deleteParameterProvider)="deleteParameterProvider($event)"
|
(deleteParameterProvider)="deleteParameterProvider($event)"
|
||||||
(configureParameterProvider)="openConfigureParameterProviderDialog($event)"
|
(configureParameterProvider)="openConfigureParameterProviderDialog($event)"
|
||||||
|
(fetchParameterProvider)="fetchParameterProviderParameters($event)"
|
||||||
(selectParameterProvider)="selectParameterProvider($event)"></parameter-providers-table>
|
(selectParameterProvider)="selectParameterProvider($event)"></parameter-providers-table>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
|
|
|
@ -24,7 +24,8 @@ import {
|
||||||
selectParameterProvider,
|
selectParameterProvider,
|
||||||
selectParameterProviderIdFromRoute,
|
selectParameterProviderIdFromRoute,
|
||||||
selectParameterProvidersState,
|
selectParameterProvidersState,
|
||||||
selectSingleEditedParameterProvider
|
selectSingleEditedParameterProvider,
|
||||||
|
selectSingleFetchParameterProvider
|
||||||
} from '../../state/parameter-providers/parameter-providers.selectors';
|
} from '../../state/parameter-providers/parameter-providers.selectors';
|
||||||
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||||
|
@ -55,12 +56,34 @@ export class ParameterProviders implements OnInit, OnDestroy {
|
||||||
),
|
),
|
||||||
takeUntilDestroyed()
|
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) => {
|
.subscribe((entity) => {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
ParameterProviderActions.openConfigureParameterProviderDialog({
|
ParameterProviderActions.fetchParameterProviderParametersAndOpenDialog({
|
||||||
request: {
|
request: {
|
||||||
id: entity.id,
|
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.
|
* 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 { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { debounceTime } from 'rxjs';
|
import { debounceTime } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export interface SummaryTableFilterColumn {
|
export interface SummaryTableFilterColumn {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SummaryTableFilterArgs {
|
export interface SummaryTableFilterArgs {
|
||||||
filterTerm: string;
|
filterTerm: string;
|
||||||
filterColumn: string;
|
filterColumn: string;
|
||||||
|
@ -40,6 +42,7 @@ export class SummaryTableFilter implements AfterViewInit {
|
||||||
private _filteredCount = 0;
|
private _filteredCount = 0;
|
||||||
private _totalCount = 0;
|
private _totalCount = 0;
|
||||||
private _initialFilterColumn = 'name';
|
private _initialFilterColumn = 'name';
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
showFilterMatchedLabel = false;
|
showFilterMatchedLabel = false;
|
||||||
|
|
||||||
@Input() filterableColumns: SummaryTableFilterColumn[] = [];
|
@Input() filterableColumns: SummaryTableFilterColumn[] = [];
|
||||||
|
@ -50,6 +53,7 @@ export class SummaryTableFilter implements AfterViewInit {
|
||||||
@Input() set filterTerm(term: string) {
|
@Input() set filterTerm(term: string) {
|
||||||
this.filterForm.get('filterTerm')?.value(term);
|
this.filterForm.get('filterTerm')?.value(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input() set filterColumn(column: string) {
|
@Input() set filterColumn(column: string) {
|
||||||
this._initialFilterColumn = column;
|
this._initialFilterColumn = column;
|
||||||
if (this.filterableColumns?.length > 0) {
|
if (this.filterableColumns?.length > 0) {
|
||||||
|
@ -97,7 +101,7 @@ export class SummaryTableFilter implements AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||||
const filterStatus = this.filterForm.get('filterStatus')?.value;
|
const filterStatus = this.filterForm.get('filterStatus')?.value;
|
||||||
|
@ -105,26 +109,35 @@ export class SummaryTableFilter implements AfterViewInit {
|
||||||
this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly);
|
this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterColumn')
|
||||||
const filterStatus = this.filterForm.get('filterStatus')?.value;
|
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
const primaryOnly = this.filterForm.get('primaryOnly')?.value;
|
.subscribe((filterColumn: string) => {
|
||||||
this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly);
|
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) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterStatus')
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
const primaryOnly = this.filterForm.get('primaryOnly')?.value;
|
.subscribe((filterStatus: string) => {
|
||||||
this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly);
|
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) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('primaryOnly')
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
const filterStatus = this.filterForm.get('filterStatus')?.value;
|
.subscribe((primaryOnly: boolean) => {
|
||||||
this.applyFilter(filterTerm, filterColumn, filterStatus, primaryOnly);
|
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) {
|
applyFilter(filterTerm: string, filterColumn: string, filterStatus: string, primaryOnly: boolean) {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
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 { MatSelectModule } from '@angular/material/select';
|
||||||
import { NgIf } from '@angular/common';
|
import { NgIf } from '@angular/common';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export interface TenantItem {
|
export interface TenantItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -68,6 +69,7 @@ export class UserTable implements AfterViewInit {
|
||||||
|
|
||||||
userLookup: Map<string, UserEntity> = new Map<string, UserEntity>();
|
userLookup: Map<string, UserEntity> = new Map<string, UserEntity>();
|
||||||
userGroupLookup: Map<string, UserGroupEntity> = new Map<string, UserGroupEntity>();
|
userGroupLookup: Map<string, UserGroupEntity> = new Map<string, UserGroupEntity>();
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
@Input() set tenants(tenants: Tenants) {
|
@Input() set tenants(tenants: Tenants) {
|
||||||
this.userLookup.clear();
|
this.userLookup.clear();
|
||||||
|
@ -141,16 +143,19 @@ export class UserTable implements AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||||
this.applyFilter(filterTerm, filterColumn);
|
this.applyFilter(filterTerm, filterColumn);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
this.filterForm
|
||||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
.get('filterColumn')
|
||||||
this.applyFilter(filterTerm, 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) {
|
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 instanceof HttpErrorResponse) {
|
||||||
if (errorResponse.status === 401) {
|
if (errorResponse.status === 401) {
|
||||||
if (this.authStorage.hasToken()) {
|
if (this.authStorage.hasToken()) {
|
||||||
|
this.routedToFullScreenError = true;
|
||||||
|
|
||||||
this.authStorage.removeToken();
|
this.authStorage.removeToken();
|
||||||
|
|
||||||
let message: string = errorResponse.error;
|
let message: string = errorResponse.error;
|
||||||
|
@ -52,8 +54,6 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||||
message += '. Please navigate home to log in again.';
|
message += '. Please navigate home to log in again.';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.routedToFullScreenError = true;
|
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
fullScreenError({
|
fullScreenError({
|
||||||
errorDetail: {
|
errorDetail: {
|
||||||
|
|
|
@ -21,13 +21,15 @@ import * as ErrorActions from './error.actions';
|
||||||
import { map, tap } from 'rxjs';
|
import { map, tap } from 'rxjs';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ErrorEffects {
|
export class ErrorEffects {
|
||||||
constructor(
|
constructor(
|
||||||
private actions$: Actions,
|
private actions$: Actions,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private snackBar: MatSnackBar
|
private snackBar: MatSnackBar,
|
||||||
|
private dialog: MatDialog
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
fullScreenError$ = createEffect(
|
fullScreenError$ = createEffect(
|
||||||
|
@ -35,6 +37,7 @@ export class ErrorEffects {
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(ErrorActions.fullScreenError),
|
ofType(ErrorActions.fullScreenError),
|
||||||
tap(() => {
|
tap(() => {
|
||||||
|
this.dialog.openDialogs.forEach((dialog) => dialog.close('ROUTED'));
|
||||||
this.router.navigate(['/error'], { replaceUrl: true });
|
this.router.navigate(['/error'], { replaceUrl: true });
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
|
@ -21,7 +21,7 @@ export function isDefinedAndNotNull<T>() {
|
||||||
return (source$: Observable<null | undefined | T>) =>
|
return (source$: Observable<null | undefined | T>) =>
|
||||||
source$.pipe(
|
source$.pipe(
|
||||||
filter((input: null | undefined | T): input is T => {
|
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 {
|
export interface ControllerServiceCreator {
|
||||||
createControllerService(createControllerService: CreateControllerServiceRequest): Observable<any>;
|
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.
|
* 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 { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
|
@ -75,6 +75,7 @@ export class ComponentStateDialog implements AfterViewInit {
|
||||||
totalEntries = 0;
|
totalEntries = 0;
|
||||||
filteredEntries = 0;
|
filteredEntries = 0;
|
||||||
partialResults = false;
|
partialResults = false;
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<ComponentStateState>,
|
private store: Store<ComponentStateState>,
|
||||||
|
@ -116,7 +117,7 @@ export class ComponentStateDialog implements AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.filterForm
|
this.filterForm
|
||||||
.get('filterTerm')
|
.get('filterTerm')
|
||||||
?.valueChanges.pipe(debounceTime(500))
|
?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((filterTerm: string) => {
|
.subscribe((filterTerm: string) => {
|
||||||
this.applyFilter(filterTerm);
|
this.applyFilter(filterTerm);
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #stepComplete>
|
<ng-template #stepComplete>
|
||||||
<div class="fa fa-check text-green-500"></div>
|
<div class="fa fa-check complete"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #stepError>
|
<ng-template #stepError>
|
||||||
<div class="fa fa-times text-red-400"></div>
|
<div class="fa fa-times text-red-400"></div>
|
||||||
|
|
|
@ -165,7 +165,7 @@
|
||||||
<div class="fa fa-spin fa-circle-o-notch"></div>
|
<div class="fa fa-spin fa-circle-o-notch"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #stepComplete>
|
<ng-template #stepComplete>
|
||||||
<div class="fa fa-check text-green-500"></div>
|
<div class="fa fa-check complete"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #stepError>
|
<ng-template #stepError>
|
||||||
<div class="fa fa-times text-red-400"></div>
|
<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 { NgClass, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
|
import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive';
|
||||||
import {
|
import {
|
||||||
AffectedComponent,
|
AffectedComponent,
|
||||||
AffectedComponentEntity,
|
AffectedComponentEntity,
|
||||||
BulletinsTipInput,
|
BulletinsTipInput,
|
||||||
ProcessGroupName,
|
ProcessGroupName,
|
||||||
ValidationErrorsTipInput
|
ValidationErrorsTipInput
|
||||||
} from '../../../../../state/shared';
|
} from '../../../state/shared';
|
||||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||||
import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
|
import { ValidationErrorsTip } from '../tooltips/validation-errors-tip/validation-errors-tip.component';
|
||||||
import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
|
import { BulletinsTip } from '../tooltips/bulletins-tip/bulletins-tip.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'parameter-references',
|
selector: 'parameter-references',
|
|
@ -22,6 +22,7 @@ import * as d3 from 'd3';
|
||||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||||
import { Instance, NIFI_NODE_CONFIG, Stats, VisibleInstances } from '../index';
|
import { Instance, NIFI_NODE_CONFIG, Stats, VisibleInstances } from '../index';
|
||||||
import { debounceTime, Subject } from 'rxjs';
|
import { debounceTime, Subject } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'status-history-chart',
|
selector: 'status-history-chart',
|
||||||
|
@ -82,11 +83,11 @@ export class StatusHistoryChart {
|
||||||
|
|
||||||
constructor(private nifiCommon: NiFiCommon) {
|
constructor(private nifiCommon: NiFiCommon) {
|
||||||
// don't need constantly fire the stats changing as a result of brush drag/move
|
// 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.nodeStats.next(stats);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.clusterStats$.pipe(debounceTime(20)).subscribe((stats: Stats) => {
|
this.clusterStats$.pipe(debounceTime(20), takeUntilDestroyed()).subscribe((stats: Stats) => {
|
||||||
this.clusterStats.next(stats);
|
this.clusterStats.next(stats);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
import { StatusHistoryService } from '../../../service/status-history.service';
|
import { StatusHistoryService } from '../../../service/status-history.service';
|
||||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
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 { Resizable } from '../resizable/resizable.component';
|
||||||
import { Instance, NIFI_NODE_CONFIG, Stats } from './index';
|
import { Instance, NIFI_NODE_CONFIG, Stats } from './index';
|
||||||
import { StatusHistoryChart } from './status-history-chart/status-history-chart.component';
|
import { StatusHistoryChart } from './status-history-chart/status-history-chart.component';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'status-history',
|
selector: 'status-history',
|
||||||
|
@ -100,6 +101,7 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||||
instances: Instance[] = [];
|
instances: Instance[] = [];
|
||||||
instanceVisibility: any = {};
|
instanceVisibility: any = {};
|
||||||
selectedDescriptor: FieldDescriptor | null = null;
|
selectedDescriptor: FieldDescriptor | null = null;
|
||||||
|
private destroyRef: DestroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private statusHistoryService: StatusHistoryService,
|
private statusHistoryService: StatusHistoryService,
|
||||||
|
@ -115,60 +117,65 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.statusHistory$.pipe(filter((entity) => !!entity)).subscribe((entity: StatusHistoryEntity) => {
|
this.statusHistory$
|
||||||
if (entity) {
|
.pipe(
|
||||||
this.instances = [];
|
filter((entity) => !!entity),
|
||||||
if (entity.statusHistory?.aggregateSnapshots?.length > 1) {
|
takeUntilDestroyed(this.destroyRef)
|
||||||
this.instances.push({
|
)
|
||||||
id: NIFI_NODE_CONFIG.nifiInstanceId,
|
.subscribe((entity: StatusHistoryEntity) => {
|
||||||
label: NIFI_NODE_CONFIG.nifiInstanceLabel,
|
if (entity) {
|
||||||
snapshots: entity.statusHistory.aggregateSnapshots
|
this.instances = [];
|
||||||
});
|
if (entity.statusHistory?.aggregateSnapshots?.length > 1) {
|
||||||
// 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.instances.push({
|
this.instances.push({
|
||||||
id: nodeSnapshot.nodeId,
|
id: NIFI_NODE_CONFIG.nifiInstanceId,
|
||||||
label: `${nodeSnapshot.address}:${nodeSnapshot.apiPort}`,
|
label: NIFI_NODE_CONFIG.nifiInstanceLabel,
|
||||||
snapshots: nodeSnapshot.statusSnapshots
|
snapshots: entity.statusHistory.aggregateSnapshots
|
||||||
});
|
});
|
||||||
// if this is the first time this instance is being rendered, make it visible
|
// if this is the first time this instance is being rendered, make it visible
|
||||||
if (this.instanceVisibility[nodeSnapshot.nodeId] === undefined) {
|
if (this.instanceVisibility[NIFI_NODE_CONFIG.nifiInstanceId] === undefined) {
|
||||||
this.instanceVisibility[nodeSnapshot.nodeId] = true;
|
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$
|
this.fieldDescriptors$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter((descriptors) => !!descriptors),
|
filter((descriptors) => !!descriptors),
|
||||||
|
@ -185,11 +192,14 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
// when the selected descriptor changes, update the chart
|
// when the selected descriptor changes, update the chart
|
||||||
this.statusHistoryForm.get('fieldDescriptor')?.valueChanges.subscribe((descriptor: FieldDescriptor) => {
|
this.statusHistoryForm
|
||||||
if (this.instances.length > 0) {
|
.get('fieldDescriptor')
|
||||||
this.selectedDescriptor = descriptor;
|
?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
}
|
.subscribe((descriptor: FieldDescriptor) => {
|
||||||
});
|
if (this.instances.length > 0) {
|
||||||
|
this.selectedDescriptor = descriptor;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isInitialLoading(state: StatusHistoryState) {
|
isInitialLoading(state: StatusHistoryState) {
|
||||||
|
|
|
@ -388,6 +388,13 @@ $appFontPath: '~roboto-fontface/fonts';
|
||||||
min-width: 760px;
|
min-width: 760px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl-dialog {
|
||||||
|
max-height: 72%;
|
||||||
|
max-width: 85%;
|
||||||
|
min-height: 560px;
|
||||||
|
min-width: 1024px;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-parameter-context-dialog {
|
.edit-parameter-context-dialog {
|
||||||
max-height: 72%;
|
max-height: 72%;
|
||||||
max-width: 55%;
|
max-width: 55%;
|
||||||
|
|
Loading…
Reference in New Issue