mirror of
https://github.com/apache/nifi.git
synced 2025-03-04 00:19:44 +00:00
NIFI-12742: Error Handling in Summary, Users, and Queue Listing (#8366)
- Error handling in Users. - Error handling in Summary. - Error handling in Queue Listing. - Addressing review feedback. - Dispatching delete success to ensure the active request is reset upon completion. This closes #8366
This commit is contained in:
parent
4094c7f599
commit
439c59e733
@ -101,9 +101,9 @@ export interface FlowFileDialogRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QueueListingState {
|
export interface QueueListingState {
|
||||||
requestEntity: ListingRequestEntity | null;
|
activeListingRequest: ListingRequest | null;
|
||||||
|
completedListingRequest: ListingRequest | null;
|
||||||
connectionLabel: string;
|
connectionLabel: string;
|
||||||
loadedTimestamp: string;
|
loadedTimestamp: string;
|
||||||
error: string | null;
|
|
||||||
status: 'pending' | 'loading' | 'error' | 'success';
|
status: 'pending' | 'loading' | 'error' | 'success';
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,8 @@ export const stopPollingQueueListingRequest = createAction(`${QUEUE_PREFIX} Stop
|
|||||||
|
|
||||||
export const deleteQueueListingRequest = createAction(`${QUEUE_PREFIX} Delete Queue Listing Request`);
|
export const deleteQueueListingRequest = createAction(`${QUEUE_PREFIX} Delete Queue Listing Request`);
|
||||||
|
|
||||||
|
export const deleteQueueListingRequestSuccess = createAction(`${QUEUE_PREFIX} Delete Queue Listing Request Success`);
|
||||||
|
|
||||||
export const viewFlowFile = createAction(`${QUEUE_PREFIX} View FlowFile`, props<{ request: ViewFlowFileRequest }>());
|
export const viewFlowFile = createAction(`${QUEUE_PREFIX} View FlowFile`, props<{ request: ViewFlowFileRequest }>());
|
||||||
|
|
||||||
export const openFlowFileDialog = createAction(
|
export const openFlowFileDialog = createAction(
|
||||||
|
@ -21,7 +21,7 @@ import * as QueueListingActions from './queue-listing.actions';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { CanvasState } from '../../../flow-designer/state';
|
import { CanvasState } from '../../../flow-designer/state';
|
||||||
import { asyncScheduler, catchError, filter, from, interval, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
|
import { asyncScheduler, catchError, filter, from, interval, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
|
||||||
import { selectConnectionIdFromRoute, selectListingRequestEntity } from './queue-listing.selectors';
|
import { selectConnectionIdFromRoute, selectActiveListingRequest } from './queue-listing.selectors';
|
||||||
import { QueueService } from '../../service/queue.service';
|
import { QueueService } from '../../service/queue.service';
|
||||||
import { ListingRequest } from './index';
|
import { ListingRequest } from './index';
|
||||||
import { CancelDialog } from '../../../../ui/common/cancel-dialog/cancel-dialog.component';
|
import { CancelDialog } from '../../../../ui/common/cancel-dialog/cancel-dialog.component';
|
||||||
@ -30,6 +30,10 @@ import { selectAbout } from '../../../../state/about/about.selectors';
|
|||||||
import { FlowFileDialog } from '../../ui/queue-listing/flowfile-dialog/flowfile-dialog.component';
|
import { FlowFileDialog } from '../../ui/queue-listing/flowfile-dialog/flowfile-dialog.component';
|
||||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||||
import { isDefinedAndNotNull } from '../../../../state/shared';
|
import { isDefinedAndNotNull } from '../../../../state/shared';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import * as ErrorActions from '../../../../state/error/error.actions';
|
||||||
|
import { ErrorHelper } from '../../../../service/error-helper.service';
|
||||||
|
import { stopPollingQueueListingRequest } from './queue-listing.actions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueListingEffects {
|
export class QueueListingEffects {
|
||||||
@ -37,6 +41,7 @@ export class QueueListingEffects {
|
|||||||
private actions$: Actions,
|
private actions$: Actions,
|
||||||
private store: Store<CanvasState>,
|
private store: Store<CanvasState>,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private errorHelper: ErrorHelper,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private nifiCommon: NiFiCommon
|
private nifiCommon: NiFiCommon
|
||||||
) {}
|
) {}
|
||||||
@ -103,13 +108,19 @@ export class QueueListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((errorResponse: HttpErrorResponse) => {
|
||||||
of(
|
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
|
||||||
QueueListingActions.queueListingApiError({
|
return of(
|
||||||
error: error.error
|
QueueListingActions.queueListingApiError({
|
||||||
})
|
error: errorResponse.error
|
||||||
)
|
})
|
||||||
)
|
);
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(stopPollingQueueListingRequest());
|
||||||
|
|
||||||
|
return of(this.errorHelper.fullScreenError(errorResponse));
|
||||||
|
}
|
||||||
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -155,9 +166,9 @@ export class QueueListingEffects {
|
|||||||
pollQueueListingRequest$ = createEffect(() =>
|
pollQueueListingRequest$ = createEffect(() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(QueueListingActions.pollQueueListingRequest),
|
ofType(QueueListingActions.pollQueueListingRequest),
|
||||||
concatLatestFrom(() => this.store.select(selectListingRequestEntity).pipe(isDefinedAndNotNull())),
|
concatLatestFrom(() => this.store.select(selectActiveListingRequest).pipe(isDefinedAndNotNull())),
|
||||||
switchMap(([, requestEntity]) => {
|
switchMap(([, listingRequest]) => {
|
||||||
return from(this.queueService.pollQueueListingRequest(requestEntity.listingRequest)).pipe(
|
return from(this.queueService.pollQueueListingRequest(listingRequest)).pipe(
|
||||||
map((response) =>
|
map((response) =>
|
||||||
QueueListingActions.pollQueueListingRequestSuccess({
|
QueueListingActions.pollQueueListingRequestSuccess({
|
||||||
response: {
|
response: {
|
||||||
@ -165,13 +176,19 @@ export class QueueListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((errorResponse: HttpErrorResponse) => {
|
||||||
of(
|
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
|
||||||
QueueListingActions.queueListingApiError({
|
return of(
|
||||||
error: error.error
|
QueueListingActions.queueListingApiError({
|
||||||
})
|
error: errorResponse.error
|
||||||
)
|
})
|
||||||
)
|
);
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(stopPollingQueueListingRequest());
|
||||||
|
|
||||||
|
return of(this.errorHelper.fullScreenError(errorResponse));
|
||||||
|
}
|
||||||
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -193,20 +210,23 @@ export class QueueListingEffects {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
deleteQueueListingRequest$ = createEffect(
|
deleteQueueListingRequest$ = createEffect(() =>
|
||||||
() =>
|
this.actions$.pipe(
|
||||||
this.actions$.pipe(
|
ofType(QueueListingActions.deleteQueueListingRequest),
|
||||||
ofType(QueueListingActions.deleteQueueListingRequest),
|
concatLatestFrom(() => this.store.select(selectActiveListingRequest)),
|
||||||
concatLatestFrom(() => this.store.select(selectListingRequestEntity)),
|
tap(([, listingRequest]) => {
|
||||||
tap(([, requestEntity]) => {
|
this.dialog.closeAll();
|
||||||
this.dialog.closeAll();
|
|
||||||
|
|
||||||
if (requestEntity) {
|
if (listingRequest) {
|
||||||
this.queueService.deleteQueueListingRequest(requestEntity.listingRequest).subscribe();
|
this.queueService.deleteQueueListingRequest(listingRequest).subscribe({
|
||||||
}
|
error: (errorResponse: HttpErrorResponse) => {
|
||||||
})
|
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
|
||||||
),
|
}
|
||||||
{ dispatch: false }
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
switchMap(() => of(QueueListingActions.deleteQueueListingRequestSuccess()))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
viewFlowFile$ = createEffect(() =>
|
viewFlowFile$ = createEffect(() =>
|
||||||
@ -222,10 +242,10 @@ export class QueueListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
of(
|
of(
|
||||||
QueueListingActions.queueListingApiError({
|
ErrorActions.snackBarError({
|
||||||
error: error.error
|
error: errorResponse.error
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -298,12 +318,13 @@ export class QueueListingEffects {
|
|||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
queueListingApiError$ = createEffect(
|
queueListingApiError$ = createEffect(() =>
|
||||||
() =>
|
this.actions$.pipe(
|
||||||
this.actions$.pipe(
|
ofType(QueueListingActions.queueListingApiError),
|
||||||
ofType(QueueListingActions.queueListingApiError),
|
tap(() => {
|
||||||
tap(() => this.dialog.closeAll())
|
this.store.dispatch(QueueListingActions.stopPollingQueueListingRequest());
|
||||||
),
|
}),
|
||||||
{ dispatch: false }
|
switchMap(({ error }) => of(ErrorActions.addBannerError({ error })))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,33 @@ import {
|
|||||||
submitQueueListingRequestSuccess,
|
submitQueueListingRequestSuccess,
|
||||||
resetQueueListingState,
|
resetQueueListingState,
|
||||||
queueListingApiError,
|
queueListingApiError,
|
||||||
loadConnectionLabelSuccess
|
loadConnectionLabelSuccess,
|
||||||
|
deleteQueueListingRequestSuccess
|
||||||
} from './queue-listing.actions';
|
} from './queue-listing.actions';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
|
||||||
export const initialState: QueueListingState = {
|
export const initialState: QueueListingState = {
|
||||||
requestEntity: null,
|
activeListingRequest: null,
|
||||||
|
completedListingRequest: {
|
||||||
|
id: '',
|
||||||
|
uri: '',
|
||||||
|
submissionTime: '',
|
||||||
|
lastUpdated: '',
|
||||||
|
percentCompleted: 100,
|
||||||
|
finished: true,
|
||||||
|
failureReason: '',
|
||||||
|
maxResults: 0,
|
||||||
|
sourceRunning: false,
|
||||||
|
destinationRunning: false,
|
||||||
|
state: '',
|
||||||
|
queueSize: {
|
||||||
|
objectCount: 0,
|
||||||
|
byteCount: 0
|
||||||
|
},
|
||||||
|
flowFileSummaries: []
|
||||||
|
},
|
||||||
connectionLabel: 'Connection',
|
connectionLabel: 'Connection',
|
||||||
loadedTimestamp: 'N/A',
|
loadedTimestamp: 'N/A',
|
||||||
error: null,
|
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,16 +63,25 @@ export const queueListingReducer = createReducer(
|
|||||||
...state,
|
...state,
|
||||||
status: 'loading' as const
|
status: 'loading' as const
|
||||||
})),
|
})),
|
||||||
on(submitQueueListingRequestSuccess, pollQueueListingRequestSuccess, (state, { response }) => ({
|
on(submitQueueListingRequestSuccess, pollQueueListingRequestSuccess, (state, { response }) => {
|
||||||
|
return produce(state, (draftState) => {
|
||||||
|
const listingRequest = response.requestEntity.listingRequest;
|
||||||
|
|
||||||
|
if (listingRequest.finished) {
|
||||||
|
draftState.completedListingRequest = listingRequest;
|
||||||
|
draftState.loadedTimestamp = listingRequest.lastUpdated;
|
||||||
|
draftState.status = 'success' as const;
|
||||||
|
} else {
|
||||||
|
draftState.activeListingRequest = listingRequest;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
on(deleteQueueListingRequestSuccess, (state) => ({
|
||||||
...state,
|
...state,
|
||||||
requestEntity: response.requestEntity,
|
activeListingRequest: null
|
||||||
loadedTimestamp: response.requestEntity.listingRequest.lastUpdated,
|
|
||||||
error: null,
|
|
||||||
status: 'success' as const
|
|
||||||
})),
|
})),
|
||||||
on(queueListingApiError, (state, { error }) => ({
|
on(queueListingApiError, (state) => ({
|
||||||
...state,
|
...state,
|
||||||
error,
|
|
||||||
status: 'error' as const
|
status: 'error' as const
|
||||||
})),
|
})),
|
||||||
on(resetQueueListingState, () => ({
|
on(resetQueueListingState, () => ({
|
||||||
|
@ -25,15 +25,18 @@ export const selectQueueListingState = createSelector(
|
|||||||
(state: QueueState) => state[queueListingFeatureKey]
|
(state: QueueState) => state[queueListingFeatureKey]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectListingRequestEntity = createSelector(
|
export const selectActiveListingRequest = createSelector(
|
||||||
selectQueueListingState,
|
selectQueueListingState,
|
||||||
(state: QueueListingState) => state.requestEntity
|
(state: QueueListingState) => state.activeListingRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectCompletedListingRequest = createSelector(
|
||||||
|
selectQueueListingState,
|
||||||
|
(state: QueueListingState) => state.completedListingRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectStatus = createSelector(selectQueueListingState, (state: QueueListingState) => state.status);
|
export const selectStatus = createSelector(selectQueueListingState, (state: QueueListingState) => state.status);
|
||||||
|
|
||||||
export const selectError = createSelector(selectQueueListingState, (state: QueueListingState) => state.error);
|
|
||||||
|
|
||||||
export const selectConnectionLabel = createSelector(
|
export const selectConnectionLabel = createSelector(
|
||||||
selectQueueListingState,
|
selectQueueListingState,
|
||||||
(state: QueueListingState) => state.connectionLabel
|
(state: QueueListingState) => state.connectionLabel
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<div class="flowfile-table h-full flex flex-col gap-y-2">
|
<div class="flowfile-table h-full flex flex-col gap-y-2">
|
||||||
<h3 class="text-xl bold queue-listing-header">{{ connectionLabel }}</h3>
|
<h3 class="text-xl bold queue-listing-header">{{ connectionLabel }}</h3>
|
||||||
|
<error-banner></error-banner>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
Display {{ displayObjectCount }} of {{ formatCount(queueSizeObjectCount) }} ({{
|
Display {{ displayObjectCount }} of {{ formatCount(queueSizeObjectCount) }} ({{
|
||||||
|
@ -20,14 +20,29 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { FlowFileTable } from './flowfile-table.component';
|
import { FlowFileTable } from './flowfile-table.component';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialState } from '../../../../../state/error/error.reducer';
|
||||||
|
|
||||||
describe('FlowFileTable', () => {
|
describe('FlowFileTable', () => {
|
||||||
let component: FlowFileTable;
|
let component: FlowFileTable;
|
||||||
let fixture: ComponentFixture<FlowFileTable>;
|
let fixture: ComponentFixture<FlowFileTable>;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'error-banner',
|
||||||
|
standalone: true,
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
class MockErrorBanner {}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [FlowFileTable, MatTableModule, BrowserAnimationsModule]
|
imports: [FlowFileTable, MockErrorBanner, MatTableModule, BrowserAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
provideMockStore({
|
||||||
|
initialState
|
||||||
|
})
|
||||||
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(FlowFileTable);
|
fixture = TestBed.createComponent(FlowFileTable);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
@ -25,12 +25,13 @@ import { NgForOf, NgIf } from '@angular/common';
|
|||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { FlowFileSummary, ListingRequest } from '../../../state/queue-listing';
|
import { FlowFileSummary, ListingRequest } from '../../../state/queue-listing';
|
||||||
import { CurrentUser } from '../../../../../state/current-user';
|
import { CurrentUser } from '../../../../../state/current-user';
|
||||||
|
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'flowfile-table',
|
selector: 'flowfile-table',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
templateUrl: './flowfile-table.component.html',
|
templateUrl: './flowfile-table.component.html',
|
||||||
imports: [MatTableModule, NgForOf, NgIf, RouterLink],
|
imports: [MatTableModule, NgForOf, NgIf, RouterLink, ErrorBanner],
|
||||||
styleUrls: ['./flowfile-table.component.scss']
|
styleUrls: ['./flowfile-table.component.scss']
|
||||||
})
|
})
|
||||||
export class FlowFileTable {
|
export class FlowFileTable {
|
||||||
|
@ -17,23 +17,20 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-y-2 h-full" *ngIf="status$ | async; let status">
|
<div class="flex flex-col gap-y-2 h-full" *ngIf="status$ | async; let status">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="value" *ngIf="status === 'error'; else noError">
|
<ng-container *ngIf="listingRequest$ | async as listingRequest; else initialLoading">
|
||||||
{{ error$ | async }}
|
<ng-container *ngIf="about$ | async as about">
|
||||||
</div>
|
|
||||||
<ng-template #noError>
|
|
||||||
<ng-container *ngIf="listingRequestEntity$ | async as entity; else initialLoading">
|
|
||||||
<flowfile-table
|
<flowfile-table
|
||||||
[connectionLabel]="(connectionLabel$ | async)!"
|
[connectionLabel]="(connectionLabel$ | async)!"
|
||||||
[listingRequest]="entity.listingRequest"
|
[listingRequest]="listingRequest"
|
||||||
[currentUser]="(currentUser$ | async)!"
|
[currentUser]="(currentUser$ | async)!"
|
||||||
[contentViewerAvailable]="contentViewerAvailable((about$ | async)!)"
|
[contentViewerAvailable]="contentViewerAvailable(about)"
|
||||||
(viewFlowFile)="viewFlowFile($event)"
|
(viewFlowFile)="viewFlowFile($event)"
|
||||||
(downloadContent)="downloadContent($event)"
|
(downloadContent)="downloadContent($event)"
|
||||||
(viewContent)="viewContent($event)"></flowfile-table>
|
(viewContent)="viewContent($event)"></flowfile-table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #initialLoading>
|
</ng-container>
|
||||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
<ng-template #initialLoading>
|
||||||
</ng-template>
|
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
|
@ -21,8 +21,7 @@ import { distinctUntilChanged, filter } from 'rxjs';
|
|||||||
import {
|
import {
|
||||||
selectConnectionIdFromRoute,
|
selectConnectionIdFromRoute,
|
||||||
selectConnectionLabel,
|
selectConnectionLabel,
|
||||||
selectError,
|
selectCompletedListingRequest,
|
||||||
selectListingRequestEntity,
|
|
||||||
selectLoadedTimestamp,
|
selectLoadedTimestamp,
|
||||||
selectStatus
|
selectStatus
|
||||||
} from '../../state/queue-listing/queue-listing.selectors';
|
} from '../../state/queue-listing/queue-listing.selectors';
|
||||||
@ -41,6 +40,7 @@ import { NiFiState } from '../../../../state';
|
|||||||
import { selectAbout } from '../../../../state/about/about.selectors';
|
import { selectAbout } from '../../../../state/about/about.selectors';
|
||||||
import { About } from '../../../../state/about';
|
import { About } from '../../../../state/about';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { clearBannerErrors } from '../../../../state/error/error.actions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'queue-listing',
|
selector: 'queue-listing',
|
||||||
@ -49,10 +49,9 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|||||||
})
|
})
|
||||||
export class QueueListing implements OnDestroy {
|
export class QueueListing implements OnDestroy {
|
||||||
status$ = this.store.select(selectStatus);
|
status$ = this.store.select(selectStatus);
|
||||||
error$ = this.store.select(selectError);
|
|
||||||
connectionLabel$ = this.store.select(selectConnectionLabel);
|
connectionLabel$ = this.store.select(selectConnectionLabel);
|
||||||
loadedTimestamp$ = this.store.select(selectLoadedTimestamp);
|
loadedTimestamp$ = this.store.select(selectLoadedTimestamp);
|
||||||
listingRequestEntity$ = this.store.select(selectListingRequestEntity);
|
listingRequest$ = this.store.select(selectCompletedListingRequest);
|
||||||
currentUser$ = this.store.select(selectCurrentUser);
|
currentUser$ = this.store.select(selectCurrentUser);
|
||||||
about$ = this.store.select(selectAbout);
|
about$ = this.store.select(selectAbout);
|
||||||
|
|
||||||
@ -104,5 +103,6 @@ export class QueueListing implements OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.store.dispatch(resetQueueListingState());
|
this.store.dispatch(resetQueueListingState());
|
||||||
|
this.store.dispatch(clearBannerErrors());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import { StoreModule } from '@ngrx/store';
|
|||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { queueFeatureKey, reducers } from '../../state';
|
import { queueFeatureKey, reducers } from '../../state';
|
||||||
import { QueueListingEffects } from '../../state/queue-listing/queue-listing.effects';
|
import { QueueListingEffects } from '../../state/queue-listing/queue-listing.effects';
|
||||||
|
import { ErrorBanner } from '../../../../ui/common/error-banner/error-banner.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [QueueListing],
|
declarations: [QueueListing],
|
||||||
@ -37,7 +38,8 @@ import { QueueListingEffects } from '../../state/queue-listing/queue-listing.eff
|
|||||||
NifiTooltipDirective,
|
NifiTooltipDirective,
|
||||||
FlowFileTable,
|
FlowFileTable,
|
||||||
StoreModule.forFeature(queueFeatureKey, reducers),
|
StoreModule.forFeature(queueFeatureKey, reducers),
|
||||||
EffectsModule.forFeature(QueueListingEffects)
|
EffectsModule.forFeature(QueueListingEffects),
|
||||||
|
ErrorBanner
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class QueueListingModule {}
|
export class QueueListingModule {}
|
||||||
|
@ -17,17 +17,13 @@
|
|||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Client } from '../../../service/client.service';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ClusterSummaryService {
|
export class ClusterSummaryService {
|
||||||
private static readonly API: string = '../nifi-api';
|
private static readonly API: string = '../nifi-api';
|
||||||
|
|
||||||
constructor(
|
constructor(private httpClient: HttpClient) {}
|
||||||
private httpClient: HttpClient,
|
|
||||||
private client: Client
|
|
||||||
) {}
|
|
||||||
|
|
||||||
getClusterSummary(): Observable<any> {
|
getClusterSummary(): Observable<any> {
|
||||||
return this.httpClient.get(`${ClusterSummaryService.API}/flow/cluster/summary`);
|
return this.httpClient.get(`${ClusterSummaryService.API}/flow/cluster/summary`);
|
||||||
|
@ -17,17 +17,13 @@
|
|||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Client } from '../../../service/client.service';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ProcessGroupStatusService {
|
export class ProcessGroupStatusService {
|
||||||
private static readonly API: string = '../nifi-api';
|
private static readonly API: string = '../nifi-api';
|
||||||
|
|
||||||
constructor(
|
constructor(private httpClient: HttpClient) {}
|
||||||
private httpClient: HttpClient,
|
|
||||||
private client: Client
|
|
||||||
) {}
|
|
||||||
|
|
||||||
getProcessGroupsStatus(recursive?: boolean): Observable<any> {
|
getProcessGroupsStatus(recursive?: boolean): Observable<any> {
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
|
@ -199,6 +199,5 @@ export interface SummaryListingState {
|
|||||||
connectionStatusSnapshots: ConnectionStatusSnapshotEntity[];
|
connectionStatusSnapshots: ConnectionStatusSnapshotEntity[];
|
||||||
remoteProcessGroupStatusSnapshots: RemoteProcessGroupStatusSnapshotEntity[];
|
remoteProcessGroupStatusSnapshots: RemoteProcessGroupStatusSnapshotEntity[];
|
||||||
loadedTimestamp: string;
|
loadedTimestamp: string;
|
||||||
error: string | null;
|
status: 'pending' | 'loading' | 'success';
|
||||||
status: 'pending' | 'loading' | 'error' | 'success';
|
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,6 @@ export const loadSummaryListingSuccess = createAction(
|
|||||||
props<{ response: SummaryListingResponse }>()
|
props<{ response: SummaryListingResponse }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const summaryListingApiError = createAction(
|
|
||||||
`${SUMMARY_LISTING_PREFIX} Load Summary Listing error`,
|
|
||||||
props<{ error: string }>()
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectProcessorStatus = createAction(
|
export const selectProcessorStatus = createAction(
|
||||||
`${SUMMARY_LISTING_PREFIX} Select Processor Status`,
|
`${SUMMARY_LISTING_PREFIX} Select Processor Status`,
|
||||||
props<{ request: SelectProcessorStatusRequest }>()
|
props<{ request: SelectProcessorStatusRequest }>()
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { NiFiState } from '../../../../state';
|
import { NiFiState } from '../../../../state';
|
||||||
import { ClusterSummaryService } from '../../service/cluster-summary.service';
|
import { ClusterSummaryService } from '../../service/cluster-summary.service';
|
||||||
@ -27,6 +27,9 @@ import * as StatusHistoryActions from '../../../../state/status-history/status-h
|
|||||||
import { catchError, combineLatest, filter, map, of, switchMap, tap } from 'rxjs';
|
import { catchError, combineLatest, filter, map, of, switchMap, tap } from 'rxjs';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ComponentType } from '../../../../state/shared';
|
import { ComponentType } from '../../../../state/shared';
|
||||||
|
import { ErrorHelper } from '../../../../service/error-helper.service';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { selectSummaryListingStatus } from './summary-listing.selectors';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SummaryListingEffects {
|
export class SummaryListingEffects {
|
||||||
@ -35,6 +38,7 @@ export class SummaryListingEffects {
|
|||||||
private store: Store<NiFiState>,
|
private store: Store<NiFiState>,
|
||||||
private clusterSummaryService: ClusterSummaryService,
|
private clusterSummaryService: ClusterSummaryService,
|
||||||
private pgStatusService: ProcessGroupStatusService,
|
private pgStatusService: ProcessGroupStatusService,
|
||||||
|
private errorHelper: ErrorHelper,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -42,7 +46,8 @@ export class SummaryListingEffects {
|
|||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(SummaryListingActions.loadSummaryListing),
|
ofType(SummaryListingActions.loadSummaryListing),
|
||||||
map((action) => action.recursive),
|
map((action) => action.recursive),
|
||||||
switchMap((recursive) =>
|
concatLatestFrom(() => this.store.select(selectSummaryListingStatus)),
|
||||||
|
switchMap(([recursive, listingStatus]) =>
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.clusterSummaryService.getClusterSummary(),
|
this.clusterSummaryService.getClusterSummary(),
|
||||||
this.pgStatusService.getProcessGroupsStatus(recursive)
|
this.pgStatusService.getProcessGroupsStatus(recursive)
|
||||||
@ -55,7 +60,9 @@ export class SummaryListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) => of(SummaryListingActions.summaryListingApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
|
of(this.errorHelper.handleLoadingError(listingStatus, errorResponse))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -25,12 +25,7 @@ import {
|
|||||||
RemoteProcessGroupStatusSnapshotEntity,
|
RemoteProcessGroupStatusSnapshotEntity,
|
||||||
SummaryListingState
|
SummaryListingState
|
||||||
} from './index';
|
} from './index';
|
||||||
import {
|
import { loadSummaryListing, loadSummaryListingSuccess, resetSummaryState } from './summary-listing.actions';
|
||||||
loadSummaryListing,
|
|
||||||
loadSummaryListingSuccess,
|
|
||||||
resetSummaryState,
|
|
||||||
summaryListingApiError
|
|
||||||
} from './summary-listing.actions';
|
|
||||||
|
|
||||||
export const initialState: SummaryListingState = {
|
export const initialState: SummaryListingState = {
|
||||||
clusterSummary: null,
|
clusterSummary: null,
|
||||||
@ -42,7 +37,6 @@ export const initialState: SummaryListingState = {
|
|||||||
connectionStatusSnapshots: [],
|
connectionStatusSnapshots: [],
|
||||||
remoteProcessGroupStatusSnapshots: [],
|
remoteProcessGroupStatusSnapshots: [],
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
error: null,
|
|
||||||
loadedTimestamp: ''
|
loadedTimestamp: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,7 +79,6 @@ export const summaryListingReducer = createReducer(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: null,
|
|
||||||
status: 'success' as const,
|
status: 'success' as const,
|
||||||
loadedTimestamp: response.status.processGroupStatus.statsLastRefreshed,
|
loadedTimestamp: response.status.processGroupStatus.statsLastRefreshed,
|
||||||
processGroupStatus: response.status,
|
processGroupStatus: response.status,
|
||||||
@ -99,12 +92,6 @@ export const summaryListingReducer = createReducer(
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
on(summaryListingApiError, (state, { error }) => ({
|
|
||||||
...state,
|
|
||||||
error,
|
|
||||||
status: 'error' as const
|
|
||||||
})),
|
|
||||||
|
|
||||||
on(resetSummaryState, () => ({
|
on(resetSummaryState, () => ({
|
||||||
...initialState
|
...initialState
|
||||||
}))
|
}))
|
||||||
|
@ -116,6 +116,5 @@ export interface UserListingState {
|
|||||||
userGroups: UserGroupEntity[];
|
userGroups: UserGroupEntity[];
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
loadedTimestamp: string;
|
loadedTimestamp: string;
|
||||||
error: string | null;
|
status: 'pending' | 'loading' | 'success';
|
||||||
status: 'pending' | 'loading' | 'error' | 'success';
|
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,12 @@ export const loadTenantsSuccess = createAction(
|
|||||||
props<{ response: LoadTenantsSuccess }>()
|
props<{ response: LoadTenantsSuccess }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const usersApiError = createAction(`${USER_PREFIX} Users Api Error`, props<{ error: string }>());
|
export const usersApiSnackbarError = createAction(
|
||||||
|
`${USER_PREFIX} Users Api Snackbar Error`,
|
||||||
|
props<{ error: string }>()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const usersApiBannerError = createAction(`${USER_PREFIX} Users Api Banner Error`, props<{ error: string }>());
|
||||||
|
|
||||||
export const openCreateTenantDialog = createAction(`${USER_PREFIX} Open Create Tenant Dialog`);
|
export const openCreateTenantDialog = createAction(`${USER_PREFIX} Open Create Tenant Dialog`);
|
||||||
|
|
||||||
|
@ -26,12 +26,15 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { UsersService } from '../../service/users.service';
|
import { UsersService } from '../../service/users.service';
|
||||||
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 { EditTenantDialog } from '../../../../ui/common/edit-tenant/edit-tenant-dialog.component';
|
import { EditTenantDialog } from '../../../../ui/common/edit-tenant/edit-tenant-dialog.component';
|
||||||
import { selectSaving, selectUserGroups, selectUsers } from './user-listing.selectors';
|
import { selectSaving, selectStatus, selectUserGroups, selectUsers } from './user-listing.selectors';
|
||||||
import { EditTenantRequest, UserGroupEntity } from '../../../../state/shared';
|
import { EditTenantRequest, UserGroupEntity } from '../../../../state/shared';
|
||||||
import { selectTenant } from './user-listing.actions';
|
import { selectTenant } from './user-listing.actions';
|
||||||
import { Client } from '../../../../service/client.service';
|
import { Client } from '../../../../service/client.service';
|
||||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||||
import { UserAccessPolicies } from '../../ui/user-listing/user-access-policies/user-access-policies.component';
|
import { UserAccessPolicies } from '../../ui/user-listing/user-access-policies/user-access-policies.component';
|
||||||
|
import * as ErrorActions from '../../../../state/error/error.actions';
|
||||||
|
import { ErrorHelper } from '../../../../service/error-helper.service';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserListingEffects {
|
export class UserListingEffects {
|
||||||
@ -44,13 +47,15 @@ export class UserListingEffects {
|
|||||||
private store: Store<NiFiState>,
|
private store: Store<NiFiState>,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
|
private errorHelper: ErrorHelper,
|
||||||
private dialog: MatDialog
|
private dialog: MatDialog
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
loadTenants$ = createEffect(() =>
|
loadTenants$ = createEffect(() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(UserListingActions.loadTenants),
|
ofType(UserListingActions.loadTenants),
|
||||||
switchMap(() =>
|
concatLatestFrom(() => this.store.select(selectStatus)),
|
||||||
|
switchMap(([, status]) =>
|
||||||
combineLatest([this.usersService.getUsers(), this.usersService.getUserGroups()]).pipe(
|
combineLatest([this.usersService.getUsers(), this.usersService.getUserGroups()]).pipe(
|
||||||
map(([usersResponse, userGroupsResponse]) =>
|
map(([usersResponse, userGroupsResponse]) =>
|
||||||
UserListingActions.loadTenantsSuccess({
|
UserListingActions.loadTenantsSuccess({
|
||||||
@ -61,12 +66,8 @@ export class UserListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) =>
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
of(
|
of(this.errorHelper.handleLoadingError(status, errorResponse))
|
||||||
UserListingActions.usersApiError({
|
|
||||||
error: error.error
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -155,7 +156,10 @@ export class UserListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) => {
|
||||||
|
this.dialog.closeAll();
|
||||||
|
return of(UserListingActions.usersApiSnackbarError({ error: errorResponse.error }));
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -219,6 +223,22 @@ export class UserListingEffects {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
usersApiBannerError$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(UserListingActions.usersApiBannerError),
|
||||||
|
map((action) => action.error),
|
||||||
|
switchMap((error) => of(ErrorActions.addBannerError({ error })))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
usersApiSnackbarError$ = createEffect(() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(UserListingActions.usersApiSnackbarError),
|
||||||
|
map((action) => action.error),
|
||||||
|
switchMap((error) => of(ErrorActions.snackBarError({ error })))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
awaitUpdateUserGroupsForCreateUser$ = createEffect(() =>
|
awaitUpdateUserGroupsForCreateUser$ = createEffect(() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(UserListingActions.createUserSuccess),
|
ofType(UserListingActions.createUserSuccess),
|
||||||
@ -263,7 +283,10 @@ export class UserListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) => {
|
||||||
|
this.dialog.closeAll();
|
||||||
|
return of(UserListingActions.usersApiSnackbarError({ error: errorResponse.error }));
|
||||||
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -360,6 +383,8 @@ export class UserListingEffects {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dialogReference.afterClosed().subscribe(() => {
|
dialogReference.afterClosed().subscribe(() => {
|
||||||
|
this.store.dispatch(ErrorActions.clearBannerErrors());
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
selectTenant({
|
selectTenant({
|
||||||
id: request.user.id
|
id: request.user.id
|
||||||
@ -385,7 +410,9 @@ export class UserListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
|
of(UserListingActions.usersApiBannerError({ error: errorResponse.error }))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -560,6 +587,8 @@ export class UserListingEffects {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dialogReference.afterClosed().subscribe(() => {
|
dialogReference.afterClosed().subscribe(() => {
|
||||||
|
this.store.dispatch(ErrorActions.clearBannerErrors());
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
selectTenant({
|
selectTenant({
|
||||||
id: request.userGroup.id
|
id: request.userGroup.id
|
||||||
@ -585,7 +614,9 @@ export class UserListingEffects {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
|
of(UserListingActions.usersApiBannerError({ error: errorResponse.error }))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -674,7 +705,9 @@ export class UserListingEffects {
|
|||||||
switchMap((request) =>
|
switchMap((request) =>
|
||||||
from(this.usersService.deleteUser(request.user)).pipe(
|
from(this.usersService.deleteUser(request.user)).pipe(
|
||||||
map(() => UserListingActions.loadTenants()),
|
map(() => UserListingActions.loadTenants()),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
|
of(UserListingActions.usersApiSnackbarError({ error: errorResponse.error }))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -713,7 +746,9 @@ export class UserListingEffects {
|
|||||||
switchMap((request) =>
|
switchMap((request) =>
|
||||||
from(this.usersService.deleteUserGroup(request.userGroup)).pipe(
|
from(this.usersService.deleteUserGroup(request.userGroup)).pipe(
|
||||||
map(() => UserListingActions.loadTenants()),
|
map(() => UserListingActions.loadTenants()),
|
||||||
catchError((error) => of(UserListingActions.usersApiError({ error: error.error })))
|
catchError((errorResponse: HttpErrorResponse) =>
|
||||||
|
of(UserListingActions.usersApiSnackbarError({ error: errorResponse.error }))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -28,7 +28,9 @@ import {
|
|||||||
updateUser,
|
updateUser,
|
||||||
updateUserComplete,
|
updateUserComplete,
|
||||||
updateUserGroup,
|
updateUserGroup,
|
||||||
updateUserGroupSuccess
|
updateUserGroupSuccess,
|
||||||
|
usersApiBannerError,
|
||||||
|
usersApiSnackbarError
|
||||||
} from './user-listing.actions';
|
} from './user-listing.actions';
|
||||||
|
|
||||||
export const initialState: UserListingState = {
|
export const initialState: UserListingState = {
|
||||||
@ -36,7 +38,6 @@ export const initialState: UserListingState = {
|
|||||||
userGroups: [],
|
userGroups: [],
|
||||||
saving: false,
|
saving: false,
|
||||||
loadedTimestamp: '',
|
loadedTimestamp: '',
|
||||||
error: null,
|
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,7 +55,6 @@ export const userListingReducer = createReducer(
|
|||||||
users: response.users,
|
users: response.users,
|
||||||
userGroups: response.userGroups,
|
userGroups: response.userGroups,
|
||||||
loadedTimestamp: response.loadedTimestamp,
|
loadedTimestamp: response.loadedTimestamp,
|
||||||
error: null,
|
|
||||||
status: 'success' as const
|
status: 'success' as const
|
||||||
})),
|
})),
|
||||||
on(createUser, updateUser, createUserGroup, updateUserGroup, (state) => ({
|
on(createUser, updateUser, createUserGroup, updateUserGroup, (state) => ({
|
||||||
@ -76,5 +76,9 @@ export const userListingReducer = createReducer(
|
|||||||
on(updateUserGroupSuccess, (state, { response }) => ({
|
on(updateUserGroupSuccess, (state, { response }) => ({
|
||||||
...state,
|
...state,
|
||||||
saving: response.requestId == null ? false : state.saving
|
saving: response.requestId == null ? false : state.saving
|
||||||
|
})),
|
||||||
|
on(usersApiSnackbarError, usersApiBannerError, (state) => ({
|
||||||
|
...state,
|
||||||
|
saving: false
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -24,6 +24,8 @@ export const selectUserListingState = createSelector(selectUserState, (state: Us
|
|||||||
|
|
||||||
export const selectSaving = createSelector(selectUserListingState, (state: UserListingState) => state.saving);
|
export const selectSaving = createSelector(selectUserListingState, (state: UserListingState) => state.saving);
|
||||||
|
|
||||||
|
export const selectStatus = createSelector(selectUserListingState, (state: UserListingState) => state.status);
|
||||||
|
|
||||||
export const selectUsers = createSelector(selectUserListingState, (state: UserListingState) => state.users);
|
export const selectUsers = createSelector(selectUserListingState, (state: UserListingState) => state.users);
|
||||||
|
|
||||||
export const selectUserGroups = createSelector(selectUserListingState, (state: UserListingState) => state.userGroups);
|
export const selectUserGroups = createSelector(selectUserListingState, (state: UserListingState) => state.userGroups);
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<h2 mat-dialog-title>{{ isNew ? 'Add' : 'Edit' }} {{ isUser ? 'User' : 'User Group' }}</h2>
|
<h2 mat-dialog-title>{{ isNew ? 'Add' : 'Edit' }} {{ isUser ? 'User' : 'User Group' }}</h2>
|
||||||
<form class="edit-tenant-form" [formGroup]="editTenantForm">
|
<form class="edit-tenant-form" [formGroup]="editTenantForm">
|
||||||
|
<error-banner></error-banner>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<mat-radio-group formControlName="tenantType" (change)="tenantTypeChanged()">
|
<mat-radio-group formControlName="tenantType" (change)="tenantTypeChanged()">
|
||||||
|
@ -21,6 +21,9 @@ import { EditTenantDialog } from './edit-tenant-dialog.component';
|
|||||||
import { EditTenantRequest } from '../../../state/shared';
|
import { EditTenantRequest } from '../../../state/shared';
|
||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialState } from '../../../state/error/error.reducer';
|
||||||
|
|
||||||
describe('EditTenantDialog', () => {
|
describe('EditTenantDialog', () => {
|
||||||
let component: EditTenantDialog;
|
let component: EditTenantDialog;
|
||||||
@ -782,10 +785,22 @@ describe('EditTenantDialog', () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'error-banner',
|
||||||
|
standalone: true,
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
class MockErrorBanner {}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [EditTenantDialog, BrowserAnimationsModule],
|
imports: [EditTenantDialog, MockErrorBanner, BrowserAnimationsModule],
|
||||||
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
|
providers: [
|
||||||
|
{ provide: MAT_DIALOG_DATA, useValue: data },
|
||||||
|
provideMockStore({
|
||||||
|
initialState
|
||||||
|
})
|
||||||
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(EditTenantDialog);
|
fixture = TestBed.createComponent(EditTenantDialog);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
@ -40,6 +40,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { Client } from '../../../service/client.service';
|
import { Client } from '../../../service/client.service';
|
||||||
import { NiFiCommon } from '../../../service/nifi-common.service';
|
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||||
|
import { ErrorBanner } from '../error-banner/error-banner.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'edit-tenant-dialog',
|
selector: 'edit-tenant-dialog',
|
||||||
@ -57,7 +58,8 @@ import { NiFiCommon } from '../../../service/nifi-common.service';
|
|||||||
NgIf,
|
NgIf,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
MatListModule,
|
MatListModule,
|
||||||
NgForOf
|
NgForOf,
|
||||||
|
ErrorBanner
|
||||||
],
|
],
|
||||||
templateUrl: './edit-tenant-dialog.component.html',
|
templateUrl: './edit-tenant-dialog.component.html',
|
||||||
styleUrls: ['./edit-tenant-dialog.component.scss']
|
styleUrls: ['./edit-tenant-dialog.component.scss']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user