diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts index 704d6df973..08cb918730 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts @@ -77,6 +77,14 @@ const routes: Routes = [ canMatch: [authenticationGuard], loadChildren: () => import('./pages/queue/feature/queue.module').then((m) => m.QueueModule) }, + { + path: 'flow-configuration-history', + canMatch: [authenticationGuard], + loadChildren: () => + import('./pages/flow-configuration-history/feature/flow-configuration-history.module').then( + (m) => m.FlowConfigurationHistoryModule + ) + }, { path: '', canMatch: [authenticationGuard], diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history-routing.module.ts new file mode 100644 index 0000000000..bc27eef5d4 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history-routing.module.ts @@ -0,0 +1,33 @@ +/* + * 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 { RouterModule, Routes } from '@angular/router'; +import { FlowConfigurationHistory } from './flow-configuration-history.component'; +import { NgModule } from '@angular/core'; + +const routes: Routes = [ + { + path: '', + component: FlowConfigurationHistory + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class FlowConfigurationHistoryRoutingModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.html new file mode 100644 index 0000000000..157c33f058 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.html @@ -0,0 +1,26 @@ + + +
+
+ +
+
+

Flow Configuration History

+ +
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.scss new file mode 100644 index 0000000000..b33f7cac34 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.scss @@ -0,0 +1,16 @@ +/*! + * 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. + */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.spec.ts new file mode 100644 index 0000000000..f32a21f14a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.spec.ts @@ -0,0 +1,53 @@ +/* + * 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 { FlowConfigurationHistory } from './flow-configuration-history.component'; +import { RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialHistoryState } from '../state/flow-configuration-history-listing/flow-configuration-history-listing.reducer'; +import { FlowConfigurationHistoryListing } from '../ui/flow-configuration-history-listing/flow-configuration-history-listing.component'; + +describe('FlowConfigurationHistory', () => { + let component: FlowConfigurationHistory; + let fixture: ComponentFixture; + + @Component({ + selector: 'navigation', + standalone: true, + template: '' + }) + class MockNavigation {} + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [FlowConfigurationHistory], + imports: [RouterModule, RouterTestingModule, MockNavigation, FlowConfigurationHistoryListing], + providers: [provideMockStore({ initialState: initialHistoryState })] + }); + fixture = TestBed.createComponent(FlowConfigurationHistory); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.ts new file mode 100644 index 0000000000..00d26a42b6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.component.ts @@ -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. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'flow-configuration-history', + templateUrl: './flow-configuration-history.component.html', + styleUrls: ['./flow-configuration-history.component.scss'] +}) +export class FlowConfigurationHistory {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.module.ts new file mode 100644 index 0000000000..c26c9a4b6a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/feature/flow-configuration-history.module.ts @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FlowConfigurationHistory } from './flow-configuration-history.component'; +import { Navigation } from '../../../ui/common/navigation/navigation.component'; +import { FlowConfigurationHistoryRoutingModule } from './flow-configuration-history-routing.module'; +import { FlowConfigurationHistoryListing } from '../ui/flow-configuration-history-listing/flow-configuration-history-listing.component'; +import { StoreModule } from '@ngrx/store'; +import { flowConfigurationHistoryFeatureKey, reducers } from '../state'; +import { EffectsModule } from '@ngrx/effects'; +import { FlowConfigurationHistoryListingEffects } from '../state/flow-configuration-history-listing/flow-configuration-history-listing.effects'; + +@NgModule({ + imports: [ + CommonModule, + Navigation, + FlowConfigurationHistoryRoutingModule, + FlowConfigurationHistoryListing, + StoreModule.forFeature(flowConfigurationHistoryFeatureKey, reducers), + EffectsModule.forFeature(FlowConfigurationHistoryListingEffects) + ], + declarations: [FlowConfigurationHistory], + exports: [FlowConfigurationHistory] +}) +export class FlowConfigurationHistoryModule {} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/service/flow-configuration-history.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/service/flow-configuration-history.service.ts new file mode 100644 index 0000000000..4fab11fd72 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/service/flow-configuration-history.service.ts @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { HistoryQueryRequest, PurgeHistoryRequest } from '../state/flow-configuration-history-listing'; + +@Injectable({ providedIn: 'root' }) +export class FlowConfigurationHistoryService { + private static readonly API: string = '../nifi-api'; + + constructor(private httpClient: HttpClient) {} + + getHistory(request: HistoryQueryRequest): Observable { + return this.httpClient.get(`${FlowConfigurationHistoryService.API}/flow/history`, { params: { ...request } }); + } + + purgeHistory(request: PurgeHistoryRequest): Observable { + return this.httpClient.delete(`${FlowConfigurationHistoryService.API}/controller/history`, { + params: { ...request } + }); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts new file mode 100644 index 0000000000..63c80e6f06 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createAction, props } from '@ngrx/store'; +import { + ActionEntity, + HistoryEntity, + HistoryQueryRequest, + PurgeHistoryRequest, + SelectFlowConfigurationHistoryRequest +} from './index'; +import { HttpErrorResponse } from '@angular/common/http'; + +const HISTORY_PREFIX = '[Flow Configuration History Listing]'; + +export const loadHistory = createAction(`${HISTORY_PREFIX} Load Listing`, props<{ request: HistoryQueryRequest }>()); + +export const loadHistorySuccess = createAction( + `${HISTORY_PREFIX} Load Listing Success`, + props<{ response: HistoryEntity }>() +); + +export const resetHistoryState = createAction(`${HISTORY_PREFIX} Reset History State`); + +export const clearHistorySelection = createAction(`${HISTORY_PREFIX} Clear Selection`); + +export const selectHistoryItem = createAction( + `${HISTORY_PREFIX} Select History Item`, + props<{ request: SelectFlowConfigurationHistoryRequest }>() +); + +export const flowConfigurationHistorySnackbarError = createAction( + `${HISTORY_PREFIX} Flow Configuration History Snackbar Error`, + props<{ errorResponse: HttpErrorResponse }>() +); + +export const openMoreDetailsDialog = createAction( + `${HISTORY_PREFIX} Open More Details Dialog`, + props<{ request: ActionEntity }>() +); + +export const openPurgeHistoryDialog = createAction(`${HISTORY_PREFIX} Open Purge History Dialog`); + +export const purgeHistory = createAction(`${HISTORY_PREFIX} Purge History`, props<{ request: PurgeHistoryRequest }>()); + +export const purgeHistorySuccess = createAction(`${HISTORY_PREFIX} Purge History Success`); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts new file mode 100644 index 0000000000..6e62d66fac --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../../../../state'; +import { ErrorHelper } from '../../../../service/error-helper.service'; +import { MatDialog } from '@angular/material/dialog'; +import * as HistoryActions from './flow-configuration-history-listing.actions'; +import { catchError, from, map, of, switchMap, take, tap } from 'rxjs'; +import { selectHistoryQuery, selectHistoryStatus } from './flow-configuration-history-listing.selectors'; +import { FlowConfigurationHistoryService } from '../../service/flow-configuration-history.service'; +import { HistoryEntity } from './index'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { ActionDetails } from '../../ui/flow-configuration-history-listing/action-details/action-details.component'; +import { PurgeHistory } from '../../ui/flow-configuration-history-listing/purge-history/purge-history.component'; +import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component'; +import { isDefinedAndNotNull } from '../../../../state/shared'; +import * as ErrorActions from '../../../../state/error/error.actions'; +import { selectAbout } from '../../../../state/about/about.selectors'; + +@Injectable() +export class FlowConfigurationHistoryListingEffects { + constructor( + private actions$: Actions, + private store: Store, + private errorHelper: ErrorHelper, + private dialog: MatDialog, + private historyService: FlowConfigurationHistoryService, + private router: Router + ) {} + + loadHistory$ = createEffect(() => + this.actions$.pipe( + ofType(HistoryActions.loadHistory), + map((action) => action.request), + concatLatestFrom(() => this.store.select(selectHistoryStatus)), + switchMap(([request, status]) => + from(this.historyService.getHistory(request)).pipe( + map((response: HistoryEntity) => + HistoryActions.loadHistorySuccess({ + response: response + }) + ), + catchError((errorResponse: HttpErrorResponse) => + of(this.errorHelper.handleLoadingError(status, errorResponse)) + ) + ) + ) + ) + ); + + flowConfigurationHistorySnackbarError = createEffect(() => + this.actions$.pipe( + ofType(HistoryActions.flowConfigurationHistorySnackbarError), + map((action) => action.errorResponse), + switchMap((errorResponse) => of(ErrorActions.snackBarError({ error: errorResponse.error }))) + ) + ); + + openMoreDetailsDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(HistoryActions.openMoreDetailsDialog), + map((action) => action.request), + tap((actionEntity) => { + this.dialog.open(ActionDetails, { + data: actionEntity, + panelClass: 'medium-dialog' + }); + }) + ), + { dispatch: false } + ); + + openPurgeHistoryDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(HistoryActions.openPurgeHistoryDialog), + tap(() => { + const dialogReference = this.dialog.open(PurgeHistory, { + panelClass: 'medium-short-dialog' + }); + + dialogReference.componentInstance.submitPurgeRequest + .pipe( + isDefinedAndNotNull(), + concatLatestFrom(() => this.store.select(selectAbout).pipe(isDefinedAndNotNull())), + take(1) + ) + .subscribe(([result, about]) => { + const yesNoRef = this.dialog.open(YesNoDialog, { + data: { + title: 'Confirm History Purge', + message: `Are you sure you want to delete all history before '${result.endDate} ${about.timezone}'?` + }, + panelClass: 'small-dialog' + }); + + yesNoRef.componentInstance.yes.pipe(take(1)).subscribe(() => { + this.store.dispatch(HistoryActions.purgeHistory({ request: { ...result } })); + }); + }); + }) + ), + { + dispatch: false + } + ); + + purgeHistory$ = createEffect(() => + this.actions$.pipe( + ofType(HistoryActions.purgeHistory), + map((action) => action.request), + switchMap((request) => + from(this.historyService.purgeHistory(request)).pipe( + map(() => HistoryActions.purgeHistorySuccess()), + catchError((errorResponse: HttpErrorResponse) => + of(HistoryActions.flowConfigurationHistorySnackbarError({ errorResponse })) + ) + ) + ) + ) + ); + + purgeHistorySuccess$ = createEffect(() => + this.actions$.pipe( + ofType(HistoryActions.purgeHistorySuccess), + concatLatestFrom(() => this.store.select(selectHistoryQuery)), + switchMap(([, query]) => { + if (query) { + return of(HistoryActions.loadHistory({ request: { ...query } })); + } + return of(HistoryActions.loadHistory({ request: { count: 50, offset: 0 } })); + }) + ) + ); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts new file mode 100644 index 0000000000..09025ba471 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FlowConfigurationHistoryListingState } from './index'; +import { createReducer, on } from '@ngrx/store'; +import { + clearHistorySelection, + flowConfigurationHistorySnackbarError, + loadHistory, + loadHistorySuccess, + purgeHistory, + purgeHistorySuccess, + resetHistoryState, + selectHistoryItem +} from './flow-configuration-history-listing.actions'; + +export const initialHistoryState: FlowConfigurationHistoryListingState = { + actions: [], + total: 0, + status: 'pending', + loadedTimestamp: '', + query: null, + selectedId: null, + purging: false +}; + +export const flowConfigurationHistoryListingReducer = createReducer( + initialHistoryState, + + on(loadHistory, (state, { request }) => ({ + ...state, + status: 'loading' as const, + query: request + })), + + on(loadHistorySuccess, (state, { response }) => ({ + ...state, + status: 'success' as const, + loadedTimestamp: response.history.lastRefreshed, + actions: response.history.actions, + total: response.history.total + })), + + on(resetHistoryState, () => ({ + ...initialHistoryState + })), + + on(purgeHistory, (state) => ({ + ...state, + query: { + ...state.query, + count: 50, + offset: 0 + }, + purging: true + })), + + on(purgeHistorySuccess, flowConfigurationHistorySnackbarError, (state) => ({ + ...state, + purging: false + })), + + on(selectHistoryItem, (state, { request }) => ({ + ...state, + selectedId: request.id + })), + + on(clearHistorySelection, (state) => ({ + ...state, + selectedId: null + })) +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.selectors.ts new file mode 100644 index 0000000000..4e0d186c7e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.selectors.ts @@ -0,0 +1,55 @@ +/* + * 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 { createSelector } from '@ngrx/store'; +import { FlowConfigurationHistoryState, selectFlowConfigurationHistoryState } from '../index'; +import { flowConfigurationHistoryListingFeatureKey, FlowConfigurationHistoryListingState } from './index'; + +export const selectFlowConfigurationHistoryListingState = createSelector( + selectFlowConfigurationHistoryState, + (state: FlowConfigurationHistoryState) => state[flowConfigurationHistoryListingFeatureKey] +); + +export const selectHistoryActions = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.actions +); + +export const selectHistoryStatus = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.status +); + +export const selectHistoryLoadedTimestamp = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.loadedTimestamp +); + +export const selectHistoryQuery = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.query +); + +export const selectHistoryTotalResults = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.total +); + +export const selectedHistoryItem = createSelector( + selectFlowConfigurationHistoryListingState, + (state: FlowConfigurationHistoryListingState) => state.selectedId +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/index.ts new file mode 100644 index 0000000000..7961af0f12 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/index.ts @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const flowConfigurationHistoryListingFeatureKey = 'listing'; + +// Returned from API call to /nifi-api/flow/history +export interface HistoryEntity { + history: History; +} + +export interface History { + total: number; + lastRefreshed: string; + actions: ActionEntity[]; +} + +export interface ActionEntity { + id: number; + timestamp: string; + sourceId: string; + canRead: boolean; + action: Action; +} + +export interface Action { + id: number; + userIdentity: string; + timestamp: string; + sourceId: string; + sourceName: string; + sourceType: string; + componentDetails?: ExtensionDetails | RemoteProcessGroupDetails; + operation: string; + actionDetails?: ConfigureActionDetails | MoveActionDetails | ConnectionActionDetails | PurgeActionDetails; +} + +export interface ExtensionDetails { + type: string; +} + +export interface RemoteProcessGroupDetails { + uri: string; +} + +export interface ConfigureActionDetails { + name: string; + previousValue: string; + value: string; +} + +export interface MoveActionDetails { + previousGroupId: string; + previousGroup: string; + groupId: string; + group: string; +} + +export interface ConnectionActionDetails { + sourceId: string; + sourceName: string; + sourceType: string; + relationship: string; + destinationId: string; + destinationName: string; + destinationType: string; +} + +export interface PurgeActionDetails { + endDate: string; +} + +export interface HistoryQueryRequest { + count: number; + offset: number; + sortColumn?: string; + sortOrder?: 'asc' | 'desc'; + startDate?: string; // MM/dd/yyyy HH:mm:ss + endDate?: string; // MM/dd/yyyy HH:mm:ss + userIdentity?: string; + sourceId?: string; +} + +export interface FlowConfigurationHistoryListingState { + actions: ActionEntity[]; + total: number; + query: HistoryQueryRequest | null; + loadedTimestamp: string; + purging: boolean; + selectedId: number | null; + status: 'pending' | 'loading' | 'success'; +} + +export interface SelectFlowConfigurationHistoryRequest { + id: number; +} + +export interface PurgeHistoryRequest { + endDate: string; // MM/dd/yyyy HH:mm:ss +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/index.ts new file mode 100644 index 0000000000..66321ad520 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/state/index.ts @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + flowConfigurationHistoryListingFeatureKey, + FlowConfigurationHistoryListingState +} from './flow-configuration-history-listing'; +import { Action, combineReducers, createFeatureSelector } from '@ngrx/store'; +import { flowConfigurationHistoryListingReducer } from './flow-configuration-history-listing/flow-configuration-history-listing.reducer'; + +export const flowConfigurationHistoryFeatureKey = 'flowConfigurationHistory'; + +export interface FlowConfigurationHistoryState { + [flowConfigurationHistoryListingFeatureKey]: FlowConfigurationHistoryListingState; +} + +export function reducers(state: FlowConfigurationHistoryState | undefined, action: Action) { + return combineReducers({ + [flowConfigurationHistoryListingFeatureKey]: flowConfigurationHistoryListingReducer + })(state, action); +} + +export const selectFlowConfigurationHistoryState = createFeatureSelector( + flowConfigurationHistoryFeatureKey +); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.html new file mode 100644 index 0000000000..1ad9eac35e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.html @@ -0,0 +1,213 @@ + + +
+

Action Details

+
+ +
+
+
Id
+
{{ actionEntity.sourceId }}
+
+ + +
+
Uri
+ +
+ + +
+
Type
+ +
+
+
+ + + + + +
+
Name
+ +
+
+
Value
+ +
+
+
Previous Value
+ +
+
+
+ + +
+
Source Id
+ +
+
+
Source Name
+ +
+
+
Source Type
+ +
+
+
Relationship(s)
+ +
+
+
Destination Id
+ +
+
+
Destination Name
+ +
+
+
Destination Type
+ +
+
+
+ + +
+
Group
+ +
+
+
Group Id
+ +
+
+
Previous Group
+ +
+
+
Previous Group Id
+ +
+
+
+ + +
+
End Date
+ +
+
+
+
+
+
+ + + + +
Empty string set
+
+ +
{{ value }}
+ +
{{ value }}
+
+
+
+ +
No value set
+
+
+
+ + + +
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.scss new file mode 100644 index 0000000000..79924e5815 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.scss @@ -0,0 +1,31 @@ +/*! + * 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. + */ + +.action-details { + .action-details-content { + .mdc-dialog__content { + padding: 0 16px; + font-size: 14px; + + .panel-content { + position: relative; + height: 350px; + overflow-y: auto; + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.spec.ts new file mode 100644 index 0000000000..9e21545868 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.spec.ts @@ -0,0 +1,56 @@ +/* + * 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 { ActionDetails } from './action-details.component'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ActionEntity } from '../../../state/flow-configuration-history-listing'; + +describe('ActionDetails', () => { + let component: ActionDetails; + let fixture: ComponentFixture; + const data: ActionEntity = { + id: 276, + timestamp: '02/12/2024 12:52:54 EST', + sourceId: '9e721628-018d-1000-38cc-5ea304d451c7', + canRead: true, + action: { + id: 276, + userIdentity: 'test', + timestamp: '02/12/2024 12:52:54 EST', + sourceId: '9e721628-018d-1000-38cc-5ea304d451c7', + sourceName: 'dummy', + sourceType: 'ProcessGroup', + operation: 'Remove' + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ActionDetails], + providers: [{ provide: MAT_DIALOG_DATA, useValue: data }] + }); + fixture = TestBed.createComponent(ActionDetails); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.ts new file mode 100644 index 0000000000..a094c8369a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/action-details/action-details.component.ts @@ -0,0 +1,92 @@ +/* + * 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, Inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { + ActionEntity, + ConfigureActionDetails, + ConnectionActionDetails, + ExtensionDetails, + MoveActionDetails, + PurgeActionDetails, + RemoteProcessGroupDetails +} from '../../../state/flow-configuration-history-listing'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { PipesModule } from '../../../../../pipes/pipes.module'; +import { MatButtonModule } from '@angular/material/button'; + +@Component({ + selector: 'action-details', + standalone: true, + imports: [CommonModule, MatDialogModule, PipesModule, MatButtonModule], + templateUrl: './action-details.component.html', + styleUrls: ['./action-details.component.scss'] +}) +export class ActionDetails { + constructor( + @Inject(MAT_DIALOG_DATA) public actionEntity: ActionEntity, + private nifiCommon: NiFiCommon + ) {} + + isRemoteProcessGroup(actionEntity: ActionEntity): boolean { + return actionEntity.action.sourceType === 'RemoteProcessGroup'; + } + + getRemoteProcessGroupDetails(actionEntity: ActionEntity): RemoteProcessGroupDetails | null { + if (!this.isRemoteProcessGroup(actionEntity)) { + return null; + } + return actionEntity.action.componentDetails as RemoteProcessGroupDetails; + } + + getExtensionDetails(actionEntity: ActionEntity): ExtensionDetails | null { + if (this.isRemoteProcessGroup(actionEntity)) { + return null; + } + return actionEntity.action.componentDetails as ExtensionDetails; + } + + getConfigureActionDetails(actionEntity: ActionEntity): ConfigureActionDetails | null { + if (actionEntity.action.operation !== 'Configure') { + return null; + } + return actionEntity.action.actionDetails as ConfigureActionDetails; + } + + getConnectActionDetails(actionEntity: ActionEntity): ConnectionActionDetails | null { + if (!['Connect', 'Disconnect'].includes(actionEntity.action.operation)) { + return null; + } + return actionEntity.action.actionDetails as ConnectionActionDetails; + } + + getMoveActionDetails(actionEntity: ActionEntity): MoveActionDetails | null { + if (actionEntity.action.operation !== 'Move') { + return null; + } + return actionEntity.action.actionDetails as MoveActionDetails; + } + + getPurgeActionDetails(actionEntity: ActionEntity): PurgeActionDetails | null { + if (actionEntity.action.operation !== 'Purge') { + return null; + } + return actionEntity.action.actionDetails as PurgeActionDetails; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.html new file mode 100644 index 0000000000..81a0025ea6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.html @@ -0,0 +1,131 @@ + + + +
+ +
+ + +
+
+
+
+
+
+ + Filter + + +
+
+ + Filter By + + + {{ option.label }} + + + +
+
+ + Date Range + + + + + + + +
+ + Start Time ({{ (about$ | async)?.timezone }}) + + +
+
+ + End Time ({{ (about$ | async)?.timezone }}) + + +
+
+ +
+
+
+
+ +
+
+ +
+ +
+ +
+
+ +
Last updated:
+
{{ state.loadedTimestamp }}
+
+ +
+ +
+
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.scss new file mode 100644 index 0000000000..a5158b68d5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.scss @@ -0,0 +1,22 @@ +/*! + * 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. + */ + +.flow-configuration-history-listing { + .mdc-text-field__input::-webkit-calendar-picker-indicator { + display: block; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.spec.ts new file mode 100644 index 0000000000..4976194b06 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.spec.ts @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlowConfigurationHistoryListing } from './flow-configuration-history-listing.component'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialHistoryState } from '../../state/flow-configuration-history-listing/flow-configuration-history-listing.reducer'; + +describe('FlowConfigurationHistoryListing', () => { + let component: FlowConfigurationHistoryListing; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [FlowConfigurationHistoryListing], + providers: [provideMockStore({ initialState: initialHistoryState })] + }); + fixture = TestBed.createComponent(FlowConfigurationHistoryListing); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.ts new file mode 100644 index 0000000000..ade586ed38 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-listing.component.ts @@ -0,0 +1,317 @@ +/* + * 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, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as HistoryActions from '../../state/flow-configuration-history-listing/flow-configuration-history-listing.actions'; +import { loadHistory } from '../../state/flow-configuration-history-listing/flow-configuration-history-listing.actions'; +import { + ActionEntity, + FlowConfigurationHistoryListingState, + HistoryQueryRequest +} from '../../state/flow-configuration-history-listing'; +import { Store } from '@ngrx/store'; +import { + selectedHistoryItem, + selectFlowConfigurationHistoryListingState, + selectHistoryQuery +} from '../../state/flow-configuration-history-listing/flow-configuration-history-listing.selectors'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { initialHistoryState } from '../../state/flow-configuration-history-listing/flow-configuration-history-listing.reducer'; +import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; +import { FlowConfigurationHistoryTable } from './flow-configuration-history-table/flow-configuration-history-table.component'; +import { Sort } from '@angular/material/sort'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { isDefinedAndNotNull } from '../../../../state/shared'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatOptionModule } from '@angular/material/core'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { selectAbout } from '../../../../state/about/about.selectors'; +import { loadAbout } from '../../../../state/about/about.actions'; +import { debounceTime } from 'rxjs'; +import { NiFiCommon } from '../../../../service/nifi-common.service'; +import { MatButtonModule } from '@angular/material/button'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; + +interface FilterableColumn { + key: string; + label: string; +} + +@Component({ + selector: 'flow-configuration-history-listing', + standalone: true, + imports: [ + CommonModule, + NgxSkeletonLoaderModule, + MatPaginatorModule, + FlowConfigurationHistoryTable, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatOptionModule, + MatSelectModule, + MatDatepickerModule, + MatButtonModule + ], + templateUrl: './flow-configuration-history-listing.component.html', + styleUrls: ['./flow-configuration-history-listing.component.scss'] +}) +export class FlowConfigurationHistoryListing implements OnInit, OnDestroy { + private static readonly DEFAULT_START_TIME: string = '00:00:00'; + private static readonly DEFAULT_END_TIME: string = '23:59:59'; + private static readonly TIME_REGEX = /^([0-1]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; + private destroyRef = inject(DestroyRef); + + historyListingState$ = this.store.select(selectFlowConfigurationHistoryListingState); + selectedHistoryId$ = this.store.select(selectedHistoryItem); + queryRequest$ = this.store.select(selectHistoryQuery); + about$ = this.store.select(selectAbout); + currentUser$ = this.store.select(selectCurrentUser); + + pageSize = 50; + queryRequest: HistoryQueryRequest = { + count: this.pageSize, + offset: 0, + startDate: this.getFormattedStartDateTime(), + endDate: this.getFormattedEndDateTime() + }; + + filterForm: FormGroup; + filterableColumns: FilterableColumn[] = [ + { key: 'sourceId', label: 'id' }, + { key: 'userIdentity', label: 'user' } + ]; + + constructor( + private store: Store, + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon + ) { + this.queryRequest$ + .pipe(takeUntilDestroyed(), isDefinedAndNotNull()) + .subscribe((queryRequest) => (this.queryRequest = queryRequest)); + + const now: Date = new Date(); + const twoWeeksAgo: Date = new Date(now.getTime() - 1000 * 60 * 60 * 24 * 14); + this.filterForm = this.formBuilder.group({ + filterTerm: '', + filterColumn: this.filterableColumns[0].key, + filterStartDate: new FormControl(twoWeeksAgo), + filterStartTime: new FormControl(FlowConfigurationHistoryListing.DEFAULT_START_TIME, [ + Validators.required, + Validators.pattern(FlowConfigurationHistoryListing.TIME_REGEX) + ]), + filterEndDate: new FormControl(now), + filterEndTime: new FormControl(FlowConfigurationHistoryListing.DEFAULT_END_TIME, [ + Validators.required, + Validators.pattern(FlowConfigurationHistoryListing.TIME_REGEX) + ]) + }); + } + + ngOnDestroy(): void { + this.store.dispatch(HistoryActions.resetHistoryState()); + } + + ngOnInit(): void { + this.refresh(); + this.store.dispatch(loadAbout()); + + this.onFormChanges(); + } + + isInitialLoading(state: FlowConfigurationHistoryListingState): boolean { + return state.loadedTimestamp == initialHistoryState.loadedTimestamp; + } + + refresh() { + this.store.dispatch(HistoryActions.loadHistory({ request: this.queryRequest })); + } + + paginationChanged(pageEvent: PageEvent): void { + // Initiate the call to the backend for the requested page of data + this.store.dispatch( + HistoryActions.loadHistory({ + request: { + ...this.queryRequest, + count: this.pageSize, + offset: pageEvent.pageIndex * this.pageSize + } + }) + ); + + // clear out any selection + this.store.dispatch(HistoryActions.clearHistorySelection()); + } + + selectionChanged(historyItem: ActionEntity) { + if (historyItem) { + this.store.dispatch(HistoryActions.selectHistoryItem({ request: { id: historyItem.id } })); + } + } + + sortChanged(sort: Sort) { + if (this.queryRequest?.sortOrder !== sort.active || this.queryRequest.sortColumn !== sort.direction) { + // always reset the pagination when changing sort + this.store.dispatch( + HistoryActions.loadHistory({ + request: { + ...this.queryRequest, + count: this.pageSize, + offset: 0, + sortColumn: sort.active, + sortOrder: sort.direction ? `${sort.direction}` : 'asc' + } + }) + ); + + // clear out any selection + this.store.dispatch(HistoryActions.clearHistorySelection()); + } + } + + getPageIndex(): number { + if (this.queryRequest.offset >= 0) { + return this.queryRequest.offset / this.pageSize; + } else return 0; + } + + private onFormChanges() { + this.filterForm.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef), debounceTime(300)) + .subscribe((formValue) => { + if (!this.filterForm.valid) { + return; + } + // clear out any selection + this.store.dispatch(HistoryActions.clearHistorySelection()); + + // always reset the pagination state when a filter changes + const historyRequest: HistoryQueryRequest = { + ...this.queryRequest, + offset: 0 + }; + + if (formValue.filterTerm) { + if (formValue.filterColumn === 'sourceId') { + historyRequest.sourceId = formValue.filterTerm; + delete historyRequest.userIdentity; + } else { + historyRequest.userIdentity = formValue.filterTerm; + delete historyRequest.sourceId; + } + } else { + delete historyRequest.sourceId; + delete historyRequest.userIdentity; + } + + let start: Date = new Date(); + if (formValue.filterStartDate) { + historyRequest.startDate = this.getFormattedStartDateTime( + formValue.filterStartDate, + formValue.filterStartTime + ); + start = new Date(historyRequest.startDate); + } + + let end: Date = new Date(0); + if (formValue.filterEndDate) { + historyRequest.endDate = this.getFormattedEndDateTime( + formValue.filterEndDate, + formValue.filterEndTime + ); + end = new Date(historyRequest.endDate); + } + if (this.queryChanged(historyRequest) && start.getTime() <= end.getTime()) { + this.store.dispatch( + loadHistory({ + request: historyRequest + }) + ); + } + }); + } + + private queryChanged(historyRequest: HistoryQueryRequest) { + const before: HistoryQueryRequest = this.queryRequest; + const proposed: HistoryQueryRequest = historyRequest; + + return ( + proposed.endDate !== before.endDate || + proposed.startDate !== before.startDate || + proposed.sourceId != before.sourceId || + proposed.userIdentity != before.userIdentity + ); + } + + resetFilter(event: MouseEvent) { + event.stopPropagation(); + const now = new Date(); + const twoWeeksAgo: Date = new Date(now.getTime() - 1000 * 60 * 60 * 24 * 14); + this.filterForm.reset({ + filterTerm: '', + filterColumn: 'sourceId', + filterStartTime: FlowConfigurationHistoryListing.DEFAULT_START_TIME, + filterStartDate: twoWeeksAgo, + filterEndTime: FlowConfigurationHistoryListing.DEFAULT_END_TIME, + filterEndDate: now + }); + } + + openMoreDetails(actionEntity: ActionEntity) { + this.store.dispatch( + HistoryActions.openMoreDetailsDialog({ + request: actionEntity + }) + ); + } + + purgeHistoryClicked() { + this.store.dispatch(HistoryActions.openPurgeHistoryDialog()); + } + + private getFormatDateTime(date: Date, time: string): string { + let formatted = this.nifiCommon.formatDateTime(date); + // get just the date portion because the time is entered separately by the user + const formattedStartDateTime = formatted.split(' '); + if (formattedStartDateTime.length > 0) { + const formattedStartDate = formattedStartDateTime[0]; + + // combine the pieces into the format the api requires + formatted = `${formattedStartDate} ${time}`; + } + return formatted; + } + + private getFormattedStartDateTime(date?: Date, time?: string): string { + const now = new Date(); + const twoWeeksAgo: Date = new Date(now.getTime() - 1000 * 60 * 60 * 24 * 14); + const d: Date = date ? date : twoWeeksAgo; + const t: string = time ? time : FlowConfigurationHistoryListing.DEFAULT_START_TIME; + return this.getFormatDateTime(d, t); + } + + private getFormattedEndDateTime(date?: Date, time?: string): string { + const d: Date = date ? date : new Date(); + const t: string = time ? time : FlowConfigurationHistoryListing.DEFAULT_END_TIME; + return this.getFormatDateTime(d, t); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.html new file mode 100644 index 0000000000..2c568cbfdb --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.html @@ -0,0 +1,100 @@ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
Date/Time
+
+ {{ formatTimestamp(item) }} + +
Name
+
+ {{ formatName(item) }} + +
Type
+
+ {{ formatType(item) }} + +
Operation
+
+ {{ formatOperation(item) }} + +
User
+
+ {{ formatUser(item) }} +
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.scss new file mode 100644 index 0000000000..9e842e73d7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.scss @@ -0,0 +1,25 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.flow-configuration-history-table { + .listing-table { + .mat-column-moreDetails { + width: 32px; + min-width: 32px; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.spec.ts new file mode 100644 index 0000000000..1c46547d0f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.spec.ts @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlowConfigurationHistoryTable } from './flow-configuration-history-table.component'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('FlowConfigurationHistoryTable', () => { + let component: FlowConfigurationHistoryTable; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [FlowConfigurationHistoryTable, NoopAnimationsModule] + }); + fixture = TestBed.createComponent(FlowConfigurationHistoryTable); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.ts new file mode 100644 index 0000000000..2dd54e0fa8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/flow-configuration-history-table/flow-configuration-history-table.component.ts @@ -0,0 +1,110 @@ +/* + * 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 { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatSortModule, Sort } from '@angular/material/sort'; +import { ActionEntity } from '../../../state/flow-configuration-history-listing'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; + +@Component({ + selector: 'flow-configuration-history-table', + standalone: true, + imports: [CommonModule, MatTableModule, MatSortModule], + templateUrl: './flow-configuration-history-table.component.html', + styleUrls: ['./flow-configuration-history-table.component.scss'] +}) +export class FlowConfigurationHistoryTable { + @Input() selectedHistoryActionId: number | null = null; + @Input() initialSortColumn: 'timestamp' | 'sourceName' | 'sourceType' | 'operation' | 'userIdentity' = 'timestamp'; + @Input() initialSortDirection: 'asc' | 'desc' = 'desc'; + + @Input() set historyActions(historyActions: ActionEntity[]) { + if (historyActions) { + this.dataSource.data = historyActions; + } + } + + @Output() selectionChanged: EventEmitter = new EventEmitter(); + @Output() sortChanged: EventEmitter = new EventEmitter(); + @Output() moreDetailsClicked: EventEmitter = new EventEmitter(); + + activeSort: Sort = { + active: this.initialSortColumn, + direction: this.initialSortDirection + }; + + displayedColumns: string[] = ['moreDetails', 'timestamp', 'sourceName', 'sourceType', 'operation', 'userIdentity']; + dataSource: MatTableDataSource = new MatTableDataSource(); + + constructor(private nifiCommon: NiFiCommon) {} + + sortData(sort: Sort) { + this.sortChanged.next(sort); + } + + canRead(item: ActionEntity): boolean { + return item.canRead; + } + + private format(item: ActionEntity, property: string): string { + if (this.canRead(item) && Object.hasOwn(item.action, property)) { + const value = (item.action as any)[property]; + if (!value) { + return 'Empty String Set'; + } + return value; + } + return 'Not Authorized'; + } + + formatTimestamp(item: ActionEntity): string { + return this.format(item, 'timestamp'); + } + + formatName(item: ActionEntity): string { + return this.format(item, 'sourceName'); + } + + formatType(item: ActionEntity): string { + return this.format(item, 'sourceType'); + } + + formatOperation(item: ActionEntity): string { + return this.format(item, 'operation'); + } + + formatUser(item: ActionEntity): string { + return this.format(item, 'userIdentity'); + } + + select(item: ActionEntity) { + this.selectionChanged.next(item); + } + + isSelected(item: ActionEntity): boolean { + if (this.selectedHistoryActionId) { + return item.id === this.selectedHistoryActionId; + } + return false; + } + + moreDetails(item: ActionEntity) { + this.moreDetailsClicked.next(item); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.html new file mode 100644 index 0000000000..956a053ae9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.html @@ -0,0 +1,65 @@ + + +
+

Purge History

+
+
+ +
+
+ + Before Date + + + + +
+
+ + Before Time ({{ (about$ | async)?.timezone }}) + + +
+
+
+ + + + + +
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.scss new file mode 100644 index 0000000000..d6b3ef7552 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.scss @@ -0,0 +1,33 @@ +/*! + * 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. + */ + +.purge-history { + .purge-history-content { + .mdc-dialog__content { + padding: 0 16px; + font-size: 14px; + + .panel-content { + height: 150px; + } + } + + .mdc-text-field__input::-webkit-calendar-picker-indicator { + display: block; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.spec.ts new file mode 100644 index 0000000000..7bb87b9b1a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.spec.ts @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PurgeHistory } from './purge-history.component'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialHistoryState } from '../../../state/flow-configuration-history-listing/flow-configuration-history-listing.reducer'; +import { MatNativeDateModule } from '@angular/material/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('PurgeHistory', () => { + let component: PurgeHistory; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [PurgeHistory, MatNativeDateModule, NoopAnimationsModule], + providers: [provideMockStore({ initialState: initialHistoryState })] + }); + fixture = TestBed.createComponent(PurgeHistory); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.ts new file mode 100644 index 0000000000..3e5df57cd9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-configuration-history/ui/flow-configuration-history-listing/purge-history/purge-history.component.ts @@ -0,0 +1,93 @@ +/* + * 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, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { + FlowConfigurationHistoryListingState, + PurgeHistoryRequest +} from '../../../state/flow-configuration-history-listing'; +import { NiFiCommon } from '../../../../../service/nifi-common.service'; +import { MatInputModule } from '@angular/material/input'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { selectAbout } from '../../../../../state/about/about.selectors'; +import { Store } from '@ngrx/store'; + +@Component({ + selector: 'purge-history', + standalone: true, + imports: [CommonModule, MatDialogModule, MatButtonModule, ReactiveFormsModule, MatInputModule, MatDatepickerModule], + templateUrl: './purge-history.component.html', + styleUrls: ['./purge-history.component.scss'] +}) +export class PurgeHistory { + private static readonly DEFAULT_PURGE_TIME: string = '00:00:00'; + private static readonly TIME_REGEX = /^([0-1]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; + purgeHistoryForm: FormGroup; + about$ = this.store.select(selectAbout); + + @Output() submitPurgeRequest: EventEmitter = new EventEmitter(); + + constructor( + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon, + private store: Store + ) { + const now: Date = new Date(); + const aMonthAgo: Date = new Date(); + aMonthAgo.setMonth(now.getMonth() - 1); + + this.purgeHistoryForm = this.formBuilder.group({ + endDate: new FormControl(aMonthAgo, Validators.required), + endTime: new FormControl(PurgeHistory.DEFAULT_PURGE_TIME, [ + Validators.required, + Validators.pattern(PurgeHistory.TIME_REGEX) + ]) + }); + } + + submit() { + const formEndDate = this.purgeHistoryForm.get('endDate')?.value; + const formEndTime = this.purgeHistoryForm.get('endTime')?.value; + + const request: PurgeHistoryRequest = { + endDate: formEndDate + }; + + if (formEndDate && formEndTime) { + const formatted = this.nifiCommon.formatDateTime(formEndDate); + // get just the date portion because the time is entered separately by the user + const formattedEndDateTime = formatted.split(' '); + if (formattedEndDateTime.length > 0) { + const formattedEndDate = formattedEndDateTime[0]; + + let endTime: string = formEndTime; + if (!endTime) { + endTime = PurgeHistory.DEFAULT_PURGE_TIME; + } + + // combine the pieces into the format the api requires + request.endDate = `${formattedEndDate} ${endTime}`; + } + + this.submitPurgeRequest.next(request); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.html index f5aff0bc09..17a44280dd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.html @@ -47,7 +47,7 @@
Start Time ({{ timezone }}) - + Minimum File Size @@ -57,7 +57,7 @@
End Time ({{ timezone }}) - + Maximum File Size diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.ts index 79e232cdb8..5717ffba86 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/ui/provenance-event-listing/provenance-search-dialog/provenance-search-dialog.component.ts @@ -17,7 +17,7 @@ import { Component, EventEmitter, Inject, Input, Output } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; -import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatButtonModule } from '@angular/material/button'; @@ -54,6 +54,7 @@ export class ProvenanceSearchDialog { public static readonly MAX_RESULTS: number = 1000; private static readonly DEFAULT_START_TIME: string = '00:00:00'; private static readonly DEFAULT_END_TIME: string = '23:59:59'; + private static readonly TIME_REGEX = /^([0-1]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; provenanceOptionsForm: FormGroup; @@ -106,9 +107,15 @@ export class ProvenanceSearchDialog { this.provenanceOptionsForm = this.formBuilder.group({ startDate: new FormControl(startDate), - startTime: new FormControl(startTime), + startTime: new FormControl(startTime, [ + Validators.required, + Validators.pattern(ProvenanceSearchDialog.TIME_REGEX) + ]), endDate: new FormControl(endDate), - endTime: new FormControl(endTime), + endTime: new FormControl(endTime, [ + Validators.required, + Validators.pattern(ProvenanceSearchDialog.TIME_REGEX) + ]), minFileSize: new FormControl(minFileSize), maxFileSize: new FormControl(maxFileSize) }); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html index c7a3e32bfe..5a066a1322 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html @@ -89,7 +89,7 @@ Cluster -