mirror of https://github.com/apache/nifi.git
[NIFI-12754] - Flow Configuration History (#8399)
* [NIFI-12754] - Flow Configuration History * support selection * support pagination * support sorting * added time controls, also updated provenance to use them too * allow for clearing of filters * use date range filter * more details dialog for flow config history * support purge history * review feedback - use snackbar error where intended, add padding between header and page content * don't use route for flow config history item selection * Address review feedback * remove unused style * Review feedback * initial query is for the last 2 weeks * added timezone to purge confirmation message * reset pagination state on filter, clear filter, and purge * only resubmit query when filter by changes IF there is a filter term specified This closes #8399
This commit is contained in:
parent
22de416ffc
commit
2792aa7038
|
@ -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],
|
||||
|
|
|
@ -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 {}
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="pb-5 flex flex-col h-screen justify-between gap-y-5">
|
||||
<header class="nifi-header">
|
||||
<navigation></navigation>
|
||||
</header>
|
||||
<div class="px-5 flex-1 flex flex-col">
|
||||
<h3 class="text-xl bold counter-header">Flow Configuration History</h3>
|
||||
<flow-configuration-history-listing class="flex-1"></flow-configuration-history-listing>
|
||||
</div>
|
||||
</div>
|
|
@ -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.
|
||||
*/
|
|
@ -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<FlowConfigurationHistory>;
|
||||
|
||||
@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();
|
||||
});
|
||||
});
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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<any> {
|
||||
return this.httpClient.get(`${FlowConfigurationHistoryService.API}/flow/history`, { params: { ...request } });
|
||||
}
|
||||
|
||||
purgeHistory(request: PurgeHistoryRequest): Observable<any> {
|
||||
return this.httpClient.delete(`${FlowConfigurationHistoryService.API}/controller/history`, {
|
||||
params: { ...request }
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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`);
|
|
@ -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<NiFiState>,
|
||||
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 } }));
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
|
@ -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
|
||||
}))
|
||||
);
|
|
@ -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
|
||||
);
|
|
@ -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
|
||||
}
|
|
@ -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<FlowConfigurationHistoryState>(
|
||||
flowConfigurationHistoryFeatureKey
|
||||
);
|
|
@ -0,0 +1,213 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="action-details h-full">
|
||||
<h2 mat-dialog-title>Action Details</h2>
|
||||
<div class="action-details-content h-full">
|
||||
<mat-dialog-content>
|
||||
<div class="panel-content flex flex-col h-full w-full gap-y-4">
|
||||
<div>
|
||||
<div>Id</div>
|
||||
<div class="value">{{ actionEntity.sourceId }}</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="actionEntity.action?.componentDetails">
|
||||
<div *ngIf="isRemoteProcessGroup(actionEntity); else extension">
|
||||
<div>Uri</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: getRemoteProcessGroupDetails(actionEntity)?.uri }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #extension>
|
||||
<div>
|
||||
<div>Type</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: getExtensionDetails(actionEntity)?.type }
|
||||
"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="actionEntity.action?.actionDetails">
|
||||
<ng-container [ngSwitch]="actionEntity.action.operation">
|
||||
<ng-container *ngSwitchCase="'Configure'">
|
||||
<ng-container *ngIf="getConfigureActionDetails(actionEntity) as details">
|
||||
<div>
|
||||
<div>Name</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.name }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Value</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.value }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Previous Value</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.previousValue }
|
||||
"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Connect' || 'Disconnect'">
|
||||
<ng-container *ngIf="getConnectActionDetails(actionEntity) as details">
|
||||
<div>
|
||||
<div>Source Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.sourceId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Source Name</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.sourceName }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Source Type</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.sourceType }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Relationship(s)</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.relationship }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Destination Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.destinationId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Destination Name</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.destinationName }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Destination Type</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.destinationType }
|
||||
"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Move'">
|
||||
<ng-container *ngIf="getMoveActionDetails(actionEntity) as details">
|
||||
<div>
|
||||
<div>Group</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.group }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Group Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.groupId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Previous Group</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.previousGroup }
|
||||
"></ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<div>Previous Group Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.previousGroupId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Purge'">
|
||||
<ng-container *ngIf="getPurgeActionDetails(actionEntity) as details">
|
||||
<div>
|
||||
<div>End Date</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
context: { $implicit: details.endDate }
|
||||
"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #formatValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<ng-container *ngIf="value === ''; else nonEmptyValue">
|
||||
<div class="unset">Empty string set</div>
|
||||
</ng-container>
|
||||
<ng-template #nonEmptyValue>
|
||||
<div class="value" *ngIf="title == null; else valueWithTitle">{{ value }}</div>
|
||||
<ng-template #valueWithTitle>
|
||||
<div class="value" [title]="title">{{ value }}</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #nullValue>
|
||||
<div class="unset">No value set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button color="primary" mat-raised-button mat-dialog-close>Ok</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ActionDetails>;
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="historyListingState$ | async; let state">
|
||||
<div *ngIf="isInitialLoading(state); else loaded">
|
||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<ng-template #loaded>
|
||||
<div class="flow-configuration-history-listing flex flex-col h-full gap-y-2">
|
||||
<div class="flex align-middle justify-between">
|
||||
<div class="flow-configuration-filter flex-1">
|
||||
<form [formGroup]="filterForm">
|
||||
<div class="flex pt-2 gap-1 items-baseline">
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Filter</mat-label>
|
||||
<input matInput type="text" class="small" formControlName="filterTerm" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Filter By</mat-label>
|
||||
<mat-select formControlName="filterColumn">
|
||||
<ng-container *ngFor="let option of filterableColumns">
|
||||
<mat-option [value]="option.key"> {{ option.label }} </mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="flex ml-6 gap-x-2">
|
||||
<mat-form-field>
|
||||
<mat-label>Date Range</mat-label>
|
||||
<mat-date-range-input [rangePicker]="picker">
|
||||
<input
|
||||
matStartDate
|
||||
formControlName="filterStartDate"
|
||||
placeholder="Start date"
|
||||
title="The start date in the format 'mm/dd/yyyy'. Must also specify start time." />
|
||||
<input
|
||||
matEndDate
|
||||
formControlName="filterEndDate"
|
||||
placeholder="End date"
|
||||
title="The end date in the format 'mm/dd/yyyy'. Must also specify end time." />
|
||||
</mat-date-range-input>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-date-range-picker #picker></mat-date-range-picker>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Start Time ({{ (about$ | async)?.timezone }})</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="time"
|
||||
step="1"
|
||||
formControlName="filterStartTime"
|
||||
placeholder="hh:mm:ss"
|
||||
title="The start time in the format 'hh:mm:ss'. Must also specify start date." />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>End Time ({{ (about$ | async)?.timezone }})</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="time"
|
||||
step="1"
|
||||
formControlName="filterEndTime"
|
||||
placeholder="hh:mm:ss"
|
||||
title="The end time in the format 'hh:mm:ss'. Must also specify end date." />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex ml-4">
|
||||
<a (click)="resetFilter($event)">Clear Filter</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mt-4" *ngIf="(currentUser$ | async)?.controllerPermissions?.canWrite">
|
||||
<button class="nifi-button" (click)="purgeHistoryClicked()">
|
||||
<i class="fa fa-eraser"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<flow-configuration-history-table
|
||||
[historyActions]="state.actions"
|
||||
[selectedHistoryActionId]="selectedHistoryId$ | async"
|
||||
(moreDetailsClicked)="openMoreDetails($event)"
|
||||
(selectionChanged)="selectionChanged($event)"
|
||||
(sortChanged)="sortChanged($event)"></flow-configuration-history-table>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between align-middle">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<button class="nifi-button" (click)="refresh()">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="state.status === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ state.loadedTimestamp }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-paginator
|
||||
[length]="state.total"
|
||||
[pageSize]="pageSize"
|
||||
[pageIndex]="getPageIndex()"
|
||||
[hidePageSize]="true"
|
||||
[showFirstLastButtons]="true"
|
||||
(page)="paginationChanged($event)"></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<FlowConfigurationHistoryListing>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [FlowConfigurationHistoryListing],
|
||||
providers: [provideMockStore({ initialState: initialHistoryState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(FlowConfigurationHistoryListing);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<FlowConfigurationHistoryListingState>,
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="flow-configuration-history-table flex-1 relative h-full w-full">
|
||||
<div class="listing-table overflow-y-auto border absolute inset-0">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="dataSource"
|
||||
matSort
|
||||
matSortDisableClear
|
||||
(matSortChange)="sortData($event)"
|
||||
[matSortActive]="initialSortColumn"
|
||||
[matSortDirection]="initialSortDirection">
|
||||
<ng-container matColumnDef="moreDetails">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<ng-container>
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div
|
||||
*ngIf="canRead(item)"
|
||||
class="pointer fa fa-info-circle"
|
||||
title="View Details"
|
||||
(click)="moreDetails(item)"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="timestamp">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Date/Time</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatTimestamp(item)">
|
||||
{{ formatTimestamp(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="sourceName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
|
||||
<span [class.blank]="!item.action?.sourceName">{{ formatName(item) }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sourceType">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Type</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatType(item)">
|
||||
{{ formatType(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="operation">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Operation</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatOperation(item)">
|
||||
{{ formatOperation(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="userIdentity">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">User</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatUser(item)">
|
||||
{{ formatUser(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||
[class.even]="even"
|
||||
(click)="select(row)"
|
||||
[class.unset]="!canRead(row)"
|
||||
[class.selected]="isSelected(row)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.flow-configuration-history-table {
|
||||
.listing-table {
|
||||
.mat-column-moreDetails {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FlowConfigurationHistoryTable } from './flow-configuration-history-table.component';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('FlowConfigurationHistoryTable', () => {
|
||||
let component: FlowConfigurationHistoryTable;
|
||||
let fixture: ComponentFixture<FlowConfigurationHistoryTable>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [FlowConfigurationHistoryTable, NoopAnimationsModule]
|
||||
});
|
||||
fixture = TestBed.createComponent(FlowConfigurationHistoryTable);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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<ActionEntity> = new EventEmitter<ActionEntity>();
|
||||
@Output() sortChanged: EventEmitter<Sort> = new EventEmitter<Sort>();
|
||||
@Output() moreDetailsClicked: EventEmitter<ActionEntity> = new EventEmitter<ActionEntity>();
|
||||
|
||||
activeSort: Sort = {
|
||||
active: this.initialSortColumn,
|
||||
direction: this.initialSortDirection
|
||||
};
|
||||
|
||||
displayedColumns: string[] = ['moreDetails', 'timestamp', 'sourceName', 'sourceType', 'operation', 'userIdentity'];
|
||||
dataSource: MatTableDataSource<ActionEntity> = new MatTableDataSource<ActionEntity>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="purge-history">
|
||||
<h2 mat-dialog-title>Purge History</h2>
|
||||
<form class="purge-history-form" [formGroup]="purgeHistoryForm">
|
||||
<div class="purge-history-content h-full">
|
||||
<mat-dialog-content class="h-full">
|
||||
<div class="panel-content flex h-full w-full gap-y-4 gap-x-4 pt-4">
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Before Date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="endDatePicker"
|
||||
formControlName="endDate"
|
||||
placeholder="mm/dd/yyyy"
|
||||
title="The end date in the format 'mm/dd/yyyy'. Must also specify end time." />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="endDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #endDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Before Time ({{ (about$ | async)?.timezone }})</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="time"
|
||||
step="1"
|
||||
formControlName="endTime"
|
||||
placeholder="hh:mm:ss"
|
||||
title="The start time in the format 'hh:mm:ss'. Must also specify start date." />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
color="primary"
|
||||
(click)="submit()"
|
||||
[disabled]="!purgeHistoryForm.valid"
|
||||
mat-raised-button
|
||||
mat-dialog-close>
|
||||
Ok
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<PurgeHistory>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<PurgeHistoryRequest> = new EventEmitter<PurgeHistoryRequest>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private nifiCommon: NiFiCommon,
|
||||
private store: Store<FlowConfigurationHistoryListingState>
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@
|
|||
<div class="w-full flex flex-col">
|
||||
<mat-form-field>
|
||||
<mat-label>Start Time ({{ timezone }})</mat-label>
|
||||
<input matInput type="text" formControlName="startTime" />
|
||||
<input matInput type="time" step="1" formControlName="startTime" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Minimum File Size</mat-label>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<div class="w-full flex flex-col">
|
||||
<mat-form-field>
|
||||
<mat-label>End Time ({{ timezone }})</mat-label>
|
||||
<input matInput type="text" formControlName="endTime" />
|
||||
<input matInput type="time" step="1" formControlName="endTime" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Maximum File Size</mat-label>
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
<i class="fa fa-fw fa-cubes mr-2"></i>
|
||||
Cluster
|
||||
</button>
|
||||
<button mat-menu-item class="global-menu-item">
|
||||
<button mat-menu-item class="global-menu-item" [routerLink]="['/flow-configuration-history']">
|
||||
<i class="fa fa-fw fa-history mr-2"></i>
|
||||
Flow Configuration History
|
||||
</button>
|
||||
|
|
Loading…
Reference in New Issue