mirror of https://github.com/apache/nifi.git
[NIFI-13318] processor stop and configure UX (#9548)
* [NIFI-13318] processor stop and configure UX * handle invalid run status * display validation errors * invalid icon and tooltip alternative placement * remove validation errors tooltip, move invalid icon into status button, update edit processor entity and readonly updates * restore MAT_DIALOG_DATA and only enable/disable form controls via api * clean up * only allow updates by current client and poll until stopped and no active threads * update menu options to display when available * display processor bulletins * align dialog header text and run status button * update filter for incoming updated entities and submit appropriate revision on run status changes * disable button when stopping * code clean up * update method name * update error message * update types * add types and cleanup * move run status action button * review feedback * update run status action button to consider when user cannot operate processor * update to also handle disabled run status when user does not have operate * clean up pollingProcessor * disable button when stopping * prettier * prettier * readd thread count * poll when necessary This closes #9548
This commit is contained in:
parent
a5086a9eb2
commit
0271e926eb
|
@ -524,7 +524,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
|||
return;
|
||||
}
|
||||
|
||||
throw new InvalidRevisionException(revision + " is not the most up-to-date revision. This component appears to have been modified");
|
||||
throw new InvalidRevisionException(revision + " is not the most up-to-date revision. This component appears to have been modified. Retrieve the most up-to-date revision and try again.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -36,6 +36,7 @@ yarn-error.log
|
|||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
/.tool-versions
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
|
|
|
@ -87,6 +87,7 @@ import {
|
|||
StartComponentRequest,
|
||||
StartComponentResponse,
|
||||
StartComponentsRequest,
|
||||
StartPollingProcessorUntilStoppedRequest,
|
||||
StartProcessGroupRequest,
|
||||
StartProcessGroupResponse,
|
||||
StopComponentRequest,
|
||||
|
@ -776,6 +777,20 @@ export const pollChangeVersionSuccess = createAction(
|
|||
|
||||
export const stopPollingChangeVersion = createAction(`${CANVAS_PREFIX} Stop Polling Change Version`);
|
||||
|
||||
export const startPollingProcessorUntilStopped = createAction(
|
||||
`${CANVAS_PREFIX} Start Polling Processor Until Stopped`,
|
||||
props<{ request: StartPollingProcessorUntilStoppedRequest }>()
|
||||
);
|
||||
|
||||
export const pollProcessorUntilStopped = createAction(`${CANVAS_PREFIX} Poll Processor Until Stopped`);
|
||||
|
||||
export const pollProcessorUntilStoppedSuccess = createAction(
|
||||
`${CANVAS_PREFIX} Poll Processor Until Stopped Success`,
|
||||
props<{ response: LoadProcessorSuccess }>()
|
||||
);
|
||||
|
||||
export const stopPollingProcessor = createAction(`${CANVAS_PREFIX} Stop Polling Processor`);
|
||||
|
||||
export const openSaveVersionDialog = createAction(
|
||||
`${CANVAS_PREFIX} Open Save Flow Version Dialog`,
|
||||
props<{ request: SaveVersionDialogRequest }>()
|
||||
|
|
|
@ -45,6 +45,8 @@ import {
|
|||
CreateConnectionDialogRequest,
|
||||
CreateProcessGroupDialogRequest,
|
||||
DeleteComponentResponse,
|
||||
DisableComponentRequest,
|
||||
EnableComponentRequest,
|
||||
GroupComponentsDialogRequest,
|
||||
ImportFromRegistryDialogRequest,
|
||||
LoadProcessGroupResponse,
|
||||
|
@ -56,6 +58,8 @@ import {
|
|||
SaveVersionRequest,
|
||||
SelectedComponent,
|
||||
Snippet,
|
||||
StartComponentRequest,
|
||||
StopComponentRequest,
|
||||
StopVersionControlRequest,
|
||||
StopVersionControlResponse,
|
||||
UpdateComponentFailure,
|
||||
|
@ -80,6 +84,7 @@ import {
|
|||
selectParentProcessGroupId,
|
||||
selectProcessGroup,
|
||||
selectProcessor,
|
||||
selectPollingProcessor,
|
||||
selectRefreshRpgDetails,
|
||||
selectRemoteProcessGroup,
|
||||
selectSaving,
|
||||
|
@ -160,6 +165,13 @@ import { selectDocumentVisibilityState } from '../../../../state/document-visibi
|
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DocumentVisibility } from '../../../../state/document-visibility';
|
||||
import { ErrorContextKey } from '../../../../state/error';
|
||||
import {
|
||||
disableComponent,
|
||||
enableComponent,
|
||||
startComponent,
|
||||
startPollingProcessorUntilStopped,
|
||||
stopComponent
|
||||
} from './flow.actions';
|
||||
import { CopyPasteService } from '../../service/copy-paste.service';
|
||||
import { selectCopiedContent } from '../../../../state/copy/copy.selectors';
|
||||
import { CopyRequestContext, CopyResponseContext } from '../../../../state/copy';
|
||||
|
@ -1428,6 +1440,7 @@ export class FlowEffects {
|
|||
}),
|
||||
tap(([request, parameterContext, processGroupId]) => {
|
||||
const processorId: string = request.entity.id;
|
||||
let runStatusChanged: boolean = false;
|
||||
|
||||
const editDialogReference = this.dialog.open(EditProcessor, {
|
||||
...XL_DIALOG,
|
||||
|
@ -1555,6 +1568,116 @@ export class FlowEffects {
|
|||
})
|
||||
);
|
||||
});
|
||||
const startPollingIfNecessary = (processorEntity: any): boolean => {
|
||||
if (
|
||||
(processorEntity.status.aggregateSnapshot.runStatus === 'Stopped' &&
|
||||
processorEntity.status.aggregateSnapshot.activeThreadCount > 0) ||
|
||||
processorEntity.status.aggregateSnapshot.runStatus === 'Validating'
|
||||
) {
|
||||
this.store.dispatch(
|
||||
startPollingProcessorUntilStopped({
|
||||
request: {
|
||||
id: processorEntity.id
|
||||
}
|
||||
})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const pollingStarted = startPollingIfNecessary(request.entity);
|
||||
|
||||
this.store
|
||||
.select(selectProcessor(processorId))
|
||||
.pipe(
|
||||
takeUntil(editDialogReference.afterClosed()),
|
||||
isDefinedAndNotNull(),
|
||||
filter((processorEntity) => {
|
||||
return (
|
||||
(runStatusChanged || pollingStarted) &&
|
||||
processorEntity.revision.clientId === this.client.getClientId()
|
||||
);
|
||||
}),
|
||||
concatLatestFrom(() => this.store.select(selectPollingProcessor))
|
||||
)
|
||||
.subscribe(([processorEntity, pollingProcessor]) => {
|
||||
editDialogReference.componentInstance.processorUpdates = processorEntity;
|
||||
|
||||
// if we're already polling we do not want to start polling again
|
||||
if (!pollingProcessor) {
|
||||
startPollingIfNecessary(processorEntity);
|
||||
}
|
||||
});
|
||||
|
||||
editDialogReference.componentInstance.stopComponentRequest
|
||||
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||
.subscribe((stopComponentRequest: StopComponentRequest) => {
|
||||
runStatusChanged = true;
|
||||
this.store.dispatch(
|
||||
stopComponent({
|
||||
request: {
|
||||
id: stopComponentRequest.id,
|
||||
uri: stopComponentRequest.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: stopComponentRequest.revision,
|
||||
errorStrategy: 'snackbar'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
editDialogReference.componentInstance.disableComponentRequest
|
||||
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||
.subscribe((disableComponentsRequest: DisableComponentRequest) => {
|
||||
runStatusChanged = true;
|
||||
this.store.dispatch(
|
||||
disableComponent({
|
||||
request: {
|
||||
id: disableComponentsRequest.id,
|
||||
uri: disableComponentsRequest.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: disableComponentsRequest.revision,
|
||||
errorStrategy: 'snackbar'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
editDialogReference.componentInstance.enableComponentRequest
|
||||
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||
.subscribe((enableComponentsRequest: EnableComponentRequest) => {
|
||||
runStatusChanged = true;
|
||||
this.store.dispatch(
|
||||
enableComponent({
|
||||
request: {
|
||||
id: enableComponentsRequest.id,
|
||||
uri: enableComponentsRequest.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: enableComponentsRequest.revision,
|
||||
errorStrategy: 'snackbar'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
editDialogReference.componentInstance.startComponentRequest
|
||||
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||
.subscribe((startComponentRequest: StartComponentRequest) => {
|
||||
runStatusChanged = true;
|
||||
this.store.dispatch(
|
||||
startComponent({
|
||||
request: {
|
||||
id: startComponentRequest.id,
|
||||
uri: startComponentRequest.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: startComponentRequest.revision,
|
||||
errorStrategy: 'snackbar'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
editDialogReference.afterClosed().subscribe((response) => {
|
||||
this.store.dispatch(resetPropertyVerificationState());
|
||||
|
@ -1578,6 +1701,57 @@ export class FlowEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
startPollingProcessorUntilStopped = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.startPollingProcessorUntilStopped),
|
||||
switchMap(() =>
|
||||
interval(2000, asyncScheduler).pipe(
|
||||
takeUntil(this.actions$.pipe(ofType(FlowActions.stopPollingProcessor)))
|
||||
)
|
||||
),
|
||||
switchMap(() => of(FlowActions.pollProcessorUntilStopped()))
|
||||
)
|
||||
);
|
||||
|
||||
pollProcessorUntilStopped$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.pollProcessorUntilStopped),
|
||||
concatLatestFrom(() => [this.store.select(selectPollingProcessor).pipe(isDefinedAndNotNull())]),
|
||||
switchMap(([, pollingProcessor]) => {
|
||||
return from(
|
||||
this.flowService.getProcessor(pollingProcessor.id).pipe(
|
||||
map((response) =>
|
||||
FlowActions.pollProcessorUntilStoppedSuccess({
|
||||
response: {
|
||||
id: pollingProcessor.id,
|
||||
processor: response
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
this.store.dispatch(FlowActions.stopPollingProcessor());
|
||||
return of(this.snackBarOrFullScreenError(errorResponse));
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
pollProcessorUntilStoppedSuccess$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.pollProcessorUntilStoppedSuccess),
|
||||
map((action) => action.response),
|
||||
filter((response) => {
|
||||
return (
|
||||
response.processor.status.runStatus === 'Stopped' &&
|
||||
response.processor.status.aggregateSnapshot.activeThreadCount === 0
|
||||
);
|
||||
}),
|
||||
switchMap(() => of(FlowActions.stopPollingProcessor()))
|
||||
)
|
||||
);
|
||||
|
||||
openEditConnectionDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
navigateWithoutTransform,
|
||||
pasteSuccess,
|
||||
pollChangeVersionSuccess,
|
||||
pollProcessorUntilStoppedSuccess,
|
||||
pollRevertChangesSuccess,
|
||||
requestRefreshRemoteProcessGroup,
|
||||
resetFlowState,
|
||||
|
@ -68,10 +69,12 @@ import {
|
|||
setTransitionRequired,
|
||||
startComponent,
|
||||
startComponentSuccess,
|
||||
startPollingProcessorUntilStopped,
|
||||
startProcessGroupSuccess,
|
||||
startRemoteProcessGroupPolling,
|
||||
stopComponent,
|
||||
stopComponentSuccess,
|
||||
stopPollingProcessor,
|
||||
stopProcessGroupSuccess,
|
||||
stopRemoteProcessGroupPolling,
|
||||
stopVersionControl,
|
||||
|
@ -92,6 +95,7 @@ import { produce } from 'immer';
|
|||
export const initialState: FlowState = {
|
||||
id: 'root',
|
||||
changeVersionRequest: null,
|
||||
pollingProcessor: null,
|
||||
flow: {
|
||||
revision: {
|
||||
version: 0
|
||||
|
@ -297,7 +301,7 @@ export const flowReducer = createReducer(
|
|||
}
|
||||
});
|
||||
}),
|
||||
on(loadProcessorSuccess, (state, { response }) => {
|
||||
on(loadProcessorSuccess, pollProcessorUntilStoppedSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
const proposedProcessor = response.processor;
|
||||
const componentIndex: number = draftState.flow.processGroupFlow.flow.processors.findIndex(
|
||||
|
@ -373,6 +377,14 @@ export const flowReducer = createReducer(
|
|||
saving: false,
|
||||
versionSaving: false
|
||||
})),
|
||||
on(startPollingProcessorUntilStopped, (state, { request }) => ({
|
||||
...state,
|
||||
pollingProcessor: request
|
||||
})),
|
||||
on(stopPollingProcessor, (state) => ({
|
||||
...state,
|
||||
pollingProcessor: null
|
||||
})),
|
||||
on(
|
||||
createProcessor,
|
||||
createProcessGroup,
|
||||
|
|
|
@ -30,6 +30,8 @@ export const selectChangeVersionRequest = createSelector(
|
|||
(state: FlowState) => state.changeVersionRequest
|
||||
);
|
||||
|
||||
export const selectPollingProcessor = createSelector(selectFlowState, (state: FlowState) => state.pollingProcessor);
|
||||
|
||||
export const selectSaving = createSelector(selectFlowState, (state: FlowState) => state.saving);
|
||||
|
||||
export const selectVersionSaving = createSelector(selectFlowState, (state: FlowState) => state.versionSaving);
|
||||
|
|
|
@ -659,6 +659,7 @@ export interface FlowState {
|
|||
flowAnalysisOpen: boolean;
|
||||
versionSaving: boolean;
|
||||
changeVersionRequest: FlowUpdateRequestEntity | null;
|
||||
pollingProcessor: StartPollingProcessorUntilStoppedRequest | null;
|
||||
status: 'pending' | 'loading' | 'success' | 'complete';
|
||||
}
|
||||
|
||||
|
@ -792,6 +793,10 @@ export interface StopComponentRequest {
|
|||
errorStrategy: 'snackbar' | 'banner';
|
||||
}
|
||||
|
||||
export interface StartPollingProcessorUntilStoppedRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface StopProcessGroupRequest {
|
||||
id: string;
|
||||
type: ComponentType;
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
neutral,
|
||||
map.get(map.get($config, neutral), lighter)
|
||||
);
|
||||
$material-theme-neutral-palette-default: mat.get-theme-color(
|
||||
$material-theme,
|
||||
neutral,
|
||||
map.get(map.get($config, neutral), default)
|
||||
);
|
||||
|
||||
$material-theme-primary-palette-default: mat.get-theme-color(
|
||||
$material-theme,
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@use 'sass:map';
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin generate-theme($material-theme, $config) {
|
||||
$is-material-dark: if(mat.get-theme-type($material-theme) == dark, true, false);
|
||||
$material-theme-secondary-palette-default: mat.get-theme-color(
|
||||
$material-theme,
|
||||
secondary,
|
||||
map.get(map.get($config, secondary), default)
|
||||
);
|
||||
$material-theme-error-palette-default: mat.get-theme-color(
|
||||
$material-theme,
|
||||
error,
|
||||
map.get(map.get($config, error), default)
|
||||
);
|
||||
|
||||
$material-theme-primary-palette-default: mat.get-theme-color(
|
||||
$material-theme,
|
||||
primary,
|
||||
map.get(map.get($config, primary), default)
|
||||
);
|
||||
|
||||
$primary-contrast: map.get(map.get($config, primary), contrast);
|
||||
$caution-contrast: map.get(map.get($config, caution), contrast);
|
||||
$error-contrast: map.get(map.get($config, error), contrast);
|
||||
$success: map.get(map.get($config, success), default);
|
||||
$caution: map.get(map.get($config, caution), default);
|
||||
|
||||
#edit-processor-header {
|
||||
.bulletins {
|
||||
background-color: unset;
|
||||
|
||||
.fa {
|
||||
color: $material-theme-primary-palette-default;
|
||||
}
|
||||
}
|
||||
|
||||
.bulletins.has-bulletins {
|
||||
.fa {
|
||||
color: $primary-contrast;
|
||||
}
|
||||
|
||||
&.error {
|
||||
.fa {
|
||||
color: $error-contrast;
|
||||
}
|
||||
|
||||
background-color: $material-theme-error-palette-default;
|
||||
}
|
||||
&.warning {
|
||||
.fa {
|
||||
color: $caution-contrast;
|
||||
}
|
||||
|
||||
background-color: $caution;
|
||||
}
|
||||
&.info,
|
||||
&.debug,
|
||||
&.trace {
|
||||
background-color: $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,19 +15,37 @@
|
|||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<h2 mat-dialog-title>
|
||||
<div class="flex justify-between items-baseline">
|
||||
<div>
|
||||
{{ readonly ? 'Processor Details' : 'Edit Processor' }}
|
||||
<h2 id="edit-processor-header" mat-dialog-title>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-baseline">
|
||||
<div class="mr-2">
|
||||
{{ readonly ? 'Processor Details' : 'Edit Processor' }}
|
||||
</div>
|
||||
|
|
||||
<div class="ml-2 text-base">
|
||||
{{ formatType() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-base">
|
||||
{{ formatType(request.entity) }}
|
||||
<div class="flex">
|
||||
@if (hasBulletins()) {
|
||||
<div class="w-[48px]">
|
||||
<div
|
||||
nifiTooltip
|
||||
[delayClose]="true"
|
||||
[tooltipComponentType]="BulletinsTip"
|
||||
[tooltipInputData]="getBulletinsTipData()"
|
||||
[position]="getBulletinTooltipPosition()"
|
||||
[ngClass]="getMostSevereBulletinLevel()"
|
||||
class="absolute top-0 right-0 text-3xl h-14 w-14 bulletins has-bulletins flex justify-center items-center">
|
||||
<i class="fa fa-sticky-note-o"></i>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
<form class="processor-edit-form" [formGroup]="editProcessorForm">
|
||||
<context-error-banner [context]="ErrorContextKey.PROCESSOR"></context-error-banner>
|
||||
<!-- TODO - Stop & Configure -->
|
||||
<mat-tab-group [(selectedIndex)]="selectedIndex" (selectedIndexChange)="tabChanged($event)">
|
||||
<mat-tab label="Settings">
|
||||
<mat-dialog-content>
|
||||
|
@ -48,13 +66,13 @@
|
|||
<div class="flex flex-col mb-5">
|
||||
<div>Type</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
{{ formatType(request.entity) }}
|
||||
{{ formatType() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mb-6">
|
||||
<div>Bundle</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
{{ formatBundle(request.entity) }}
|
||||
{{ formatBundle() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-x-4">
|
||||
|
@ -290,19 +308,106 @@
|
|||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
@if ({ value: (saving$ | async)! }; as saving) {
|
||||
<mat-dialog-actions align="end">
|
||||
@if (readonly) {
|
||||
<button mat-flat-button mat-dialog-close>Close</button>
|
||||
} @else {
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
[disabled]="!editProcessorForm.dirty || editProcessorForm.invalid || saving.value"
|
||||
type="button"
|
||||
(click)="submitForm()"
|
||||
mat-flat-button>
|
||||
<span *nifiSpinner="saving.value">Apply</span>
|
||||
</button>
|
||||
}
|
||||
<mat-dialog-actions align="start">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div>
|
||||
@if (isStoppable()) {
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="!canOperate()"
|
||||
mat-stroked-button
|
||||
[matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 success-color-default fa fa-play"></i>Running<i
|
||||
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||
</div>
|
||||
</button>
|
||||
} @else if (isRunnable()) {
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="!canOperate()"
|
||||
mat-stroked-button
|
||||
[matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 error-color-variant fa fa-stop"></i>Stopped<i
|
||||
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||
</div>
|
||||
</button>
|
||||
} @else if (isDisabled()) {
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="!canOperate()"
|
||||
mat-stroked-button
|
||||
[matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 icon icon-enable-false primary-color"></i>Disable<i
|
||||
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||
</div>
|
||||
</button>
|
||||
} @else if (isStopping()) {
|
||||
<button type="button" [disabled]="true" mat-stroked-button [matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 fa fa-circle-o-notch fa-spin primary-color"></i>Stopping ({{
|
||||
status.aggregateSnapshot.activeThreadCount
|
||||
}})
|
||||
</div>
|
||||
</button>
|
||||
} @else if (isValidating()) {
|
||||
<button type="button" [disabled]="true" mat-stroked-button [matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 fa fa-circle-o-notch fa-spin primary-color"></i>Validating
|
||||
</div>
|
||||
</button>
|
||||
} @else if (isInvalid()) {
|
||||
<button
|
||||
type="button"
|
||||
mat-stroked-button
|
||||
[disabled]="!canOperate()"
|
||||
[matMenuTriggerFor]="operateMenu">
|
||||
<div class="flex items-center">
|
||||
<i class="mr-2 fa fa-warning caution-color"></i>Invalid<i
|
||||
class="ml-2 -mt-1 fa fa-sort-desc"></i>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
<mat-menu #operateMenu="matMenu" xPosition="before">
|
||||
@if (isStoppable() && canOperate()) {
|
||||
<button mat-menu-item (click)="stop()">
|
||||
<i class="mr-2 fa fa-stop primary-color"></i>Stop
|
||||
</button>
|
||||
}
|
||||
@if (isRunnable() && canOperate()) {
|
||||
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="start()">
|
||||
<i class="mr-2 fa fa-play primary-color"></i>Start
|
||||
</button>
|
||||
}
|
||||
@if (isDisableable() && canOperate()) {
|
||||
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="disable()">
|
||||
<i class="mr-2 icon icon-enable-false primary-color"></i>Disable
|
||||
</button>
|
||||
}
|
||||
@if (isEnableable() && canOperate()) {
|
||||
<button mat-menu-item [disabled]="editProcessorForm.dirty" (click)="enable()">
|
||||
<i class="mr-2 fa fa-flash primary-color"></i>Enable
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div>
|
||||
@if (readonly) {
|
||||
<button mat-flat-button mat-dialog-close>Close</button>
|
||||
} @else {
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
[disabled]="!editProcessorForm.dirty || editProcessorForm.invalid || saving.value"
|
||||
type="button"
|
||||
(click)="submitForm()"
|
||||
mat-flat-button>
|
||||
<span *nifiSpinner="saving.value">Apply</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
||||
}
|
||||
</form>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
|
@ -30,19 +31,29 @@ import {
|
|||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { AsyncPipe, NgClass } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import {
|
||||
BulletinEntity,
|
||||
BulletinsTipInput,
|
||||
InlineServiceCreationRequest,
|
||||
InlineServiceCreationResponse,
|
||||
ParameterContextEntity,
|
||||
Property
|
||||
Property,
|
||||
Revision
|
||||
} from '../../../../../../../state/shared';
|
||||
import { Client } from '../../../../../../../service/client.service';
|
||||
import { EditComponentDialogRequest, UpdateProcessorRequest } from '../../../../../state/flow';
|
||||
import {
|
||||
DisableComponentRequest,
|
||||
EditComponentDialogRequest,
|
||||
EnableComponentRequest,
|
||||
StartComponentRequest,
|
||||
StopComponentRequest,
|
||||
UpdateProcessorRequest
|
||||
} from '../../../../../state/flow';
|
||||
import { PropertyTable } from '../../../../../../../ui/common/property-table/property-table.component';
|
||||
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
import { NifiTooltipDirective, NiFiCommon, TextTip, CopyDirective } from '@nifi/shared';
|
||||
|
@ -51,7 +62,6 @@ import {
|
|||
RelationshipConfiguration,
|
||||
RelationshipSettings
|
||||
} from './relationship-settings/relationship-settings.component';
|
||||
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
|
||||
import { CanvasUtils } from '../../../../../service/canvas-utils.service';
|
||||
import { ConvertToParameterResponse } from '../../../../../service/parameter-helper.service';
|
||||
|
@ -65,6 +75,8 @@ import { TabbedDialog } from '../../../../../../../ui/common/tabbed-dialog/tabbe
|
|||
import { ComponentType, SelectOption } from 'libs/shared/src';
|
||||
import { ErrorContextKey } from '../../../../../../../state/error';
|
||||
import { ContextErrorBanner } from '../../../../../../../ui/common/context-error-banner/context-error-banner.component';
|
||||
import { BulletinsTip } from '../../../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
|
||||
import { ConnectedPosition } from '@angular/cdk/overlay';
|
||||
|
||||
@Component({
|
||||
selector: 'edit-processor',
|
||||
|
@ -79,20 +91,24 @@ import { ContextErrorBanner } from '../../../../../../../ui/common/context-error
|
|||
MatTabsModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
MatMenuModule,
|
||||
AsyncPipe,
|
||||
PropertyTable,
|
||||
NifiSpinnerDirective,
|
||||
NifiTooltipDirective,
|
||||
RunDurationSlider,
|
||||
RelationshipSettings,
|
||||
ErrorBanner,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
CopyDirective,
|
||||
NgClass
|
||||
],
|
||||
styleUrls: ['./edit-processor.component.scss']
|
||||
})
|
||||
export class EditProcessor extends TabbedDialog {
|
||||
@Input() set processorUpdates(processorUpdates: any | undefined) {
|
||||
this.processRunStateUpdates(processorUpdates);
|
||||
}
|
||||
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
|
||||
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
|
||||
@Input() parameterContext: ParameterContextEntity | undefined;
|
||||
|
@ -110,11 +126,20 @@ export class EditProcessor extends TabbedDialog {
|
|||
|
||||
@Output() verify: EventEmitter<VerifyPropertiesRequestContext> = new EventEmitter<VerifyPropertiesRequestContext>();
|
||||
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>();
|
||||
@Output() stopComponentRequest: EventEmitter<StopComponentRequest> = new EventEmitter<StopComponentRequest>();
|
||||
@Output() startComponentRequest: EventEmitter<StartComponentRequest> = new EventEmitter<StartComponentRequest>();
|
||||
@Output() disableComponentRequest: EventEmitter<DisableComponentRequest> =
|
||||
new EventEmitter<DisableComponentRequest>();
|
||||
@Output() enableComponentRequest: EventEmitter<EnableComponentRequest> = new EventEmitter<EnableComponentRequest>();
|
||||
|
||||
protected readonly TextTip = TextTip;
|
||||
protected readonly BulletinsTip = BulletinsTip;
|
||||
|
||||
editProcessorForm: FormGroup;
|
||||
readonly: boolean;
|
||||
readonly: boolean = true;
|
||||
status: any;
|
||||
revision!: Revision;
|
||||
bulletins!: BulletinEntity[];
|
||||
|
||||
bulletinLevels = [
|
||||
{
|
||||
|
@ -182,9 +207,6 @@ export class EditProcessor extends TabbedDialog {
|
|||
) {
|
||||
super('edit-processor-selected-index');
|
||||
|
||||
this.readonly =
|
||||
!request.entity.permissions.canWrite || !this.canvasUtils.runnableSupportsModification(request.entity);
|
||||
|
||||
const processorProperties: any = request.entity.component.config.properties;
|
||||
const properties: Property[] = Object.entries(processorProperties).map((entry: any) => {
|
||||
const [property, value] = entry;
|
||||
|
@ -253,6 +275,32 @@ export class EditProcessor extends TabbedDialog {
|
|||
new FormControl({ value: this.runDurationMillis, disabled: this.readonly }, Validators.required)
|
||||
);
|
||||
}
|
||||
|
||||
this.processRunStateUpdates(request.entity);
|
||||
}
|
||||
|
||||
private processRunStateUpdates(entity: any) {
|
||||
this.status = entity.status;
|
||||
this.revision = entity.revision;
|
||||
this.bulletins = entity.bulletins;
|
||||
|
||||
this.readonly = !entity.permissions.canWrite || !this.canvasUtils.runnableSupportsModification(entity);
|
||||
|
||||
if (this.readonly) {
|
||||
this.editProcessorForm.get('properties')?.disable();
|
||||
this.editProcessorForm.get('relationshipConfiguration')?.disable();
|
||||
|
||||
if (this.supportsBatching()) {
|
||||
this.editProcessorForm.get('runDuration')?.disable();
|
||||
}
|
||||
} else {
|
||||
this.editProcessorForm.get('properties')?.enable();
|
||||
this.editProcessorForm.get('relationshipConfiguration')?.enable();
|
||||
|
||||
if (this.supportsBatching()) {
|
||||
this.editProcessorForm.get('runDuration')?.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private relationshipConfigurationValidator(): ValidatorFn {
|
||||
|
@ -288,12 +336,12 @@ export class EditProcessor extends TabbedDialog {
|
|||
return this.request.entity.component.supportsBatching == true;
|
||||
}
|
||||
|
||||
formatType(entity: any): string {
|
||||
return this.nifiCommon.formatType(entity.component);
|
||||
formatType(): string {
|
||||
return this.nifiCommon.formatType(this.request.entity.component);
|
||||
}
|
||||
|
||||
formatBundle(entity: any): string {
|
||||
return this.nifiCommon.formatBundle(entity.component.bundle);
|
||||
formatBundle(): string {
|
||||
return this.nifiCommon.formatBundle(this.request.entity.component.bundle);
|
||||
}
|
||||
|
||||
concurrentTasksChanged(): void {
|
||||
|
@ -351,7 +399,10 @@ export class EditProcessor extends TabbedDialog {
|
|||
.map((relationship) => relationship.name);
|
||||
|
||||
const payload: any = {
|
||||
revision: this.client.getRevision(this.request.entity),
|
||||
revision: this.client.getRevision({
|
||||
...this.request.entity,
|
||||
revision: this.revision
|
||||
}),
|
||||
disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged(),
|
||||
component: {
|
||||
id: this.request.entity.id,
|
||||
|
@ -401,6 +452,140 @@ export class EditProcessor extends TabbedDialog {
|
|||
});
|
||||
}
|
||||
|
||||
hasBulletins(): boolean {
|
||||
return this.request.entity.permissions.canRead && !this.nifiCommon.isEmpty(this.bulletins);
|
||||
}
|
||||
|
||||
getBulletinsTipData(): BulletinsTipInput {
|
||||
return {
|
||||
bulletins: this.bulletins
|
||||
};
|
||||
}
|
||||
|
||||
getBulletinTooltipPosition(): ConnectedPosition {
|
||||
return {
|
||||
originX: 'end',
|
||||
originY: 'bottom',
|
||||
overlayX: 'end',
|
||||
overlayY: 'top',
|
||||
offsetX: -8,
|
||||
offsetY: 8
|
||||
};
|
||||
}
|
||||
|
||||
getMostSevereBulletinLevel(): string | null {
|
||||
// determine the most severe of the bulletins
|
||||
const mostSevere = this.canvasUtils.getMostSevereBulletin(this.bulletins);
|
||||
return mostSevere ? mostSevere.bulletin.level.toLowerCase() : null;
|
||||
}
|
||||
|
||||
isStoppable(): boolean {
|
||||
return this.status.aggregateSnapshot.runStatus === 'Running';
|
||||
}
|
||||
|
||||
isStopping(): boolean {
|
||||
return (
|
||||
this.status.aggregateSnapshot.runStatus === 'Stopped' && this.status.aggregateSnapshot.activeThreadCount > 0
|
||||
);
|
||||
}
|
||||
|
||||
isValidating(): boolean {
|
||||
return this.status.aggregateSnapshot.runStatus === 'Validating';
|
||||
}
|
||||
|
||||
isInvalid(): boolean {
|
||||
return this.status.aggregateSnapshot.runStatus === 'Invalid';
|
||||
}
|
||||
|
||||
isDisabled(): boolean {
|
||||
return this.status.aggregateSnapshot.runStatus === 'Disabled';
|
||||
}
|
||||
|
||||
isRunnable(): boolean {
|
||||
return (
|
||||
!(
|
||||
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||
) && this.status.aggregateSnapshot.runStatus === 'Stopped'
|
||||
);
|
||||
}
|
||||
|
||||
isDisableable(): boolean {
|
||||
return (
|
||||
!(
|
||||
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||
) &&
|
||||
(this.status.aggregateSnapshot.runStatus === 'Stopped' ||
|
||||
this.status.aggregateSnapshot.runStatus === 'Invalid')
|
||||
);
|
||||
}
|
||||
|
||||
isEnableable(): boolean {
|
||||
return (
|
||||
!(
|
||||
this.status.aggregateSnapshot.runStatus === 'Running' ||
|
||||
this.status.aggregateSnapshot.activeThreadCount > 0
|
||||
) && this.status.aggregateSnapshot.runStatus === 'Disabled'
|
||||
);
|
||||
}
|
||||
|
||||
canOperate(): boolean {
|
||||
return this.request.entity.permissions.canWrite || this.request.entity.operatePermissions?.canWrite;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stopComponentRequest.next({
|
||||
id: this.request.entity.id,
|
||||
uri: this.request.entity.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: this.client.getRevision({
|
||||
...this.request.entity,
|
||||
revision: this.revision
|
||||
}),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
}
|
||||
|
||||
start() {
|
||||
this.startComponentRequest.next({
|
||||
id: this.request.entity.id,
|
||||
uri: this.request.entity.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: this.client.getRevision({
|
||||
...this.request.entity,
|
||||
revision: this.revision
|
||||
}),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.disableComponentRequest.next({
|
||||
id: this.request.entity.id,
|
||||
uri: this.request.entity.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: this.client.getRevision({
|
||||
...this.request.entity,
|
||||
revision: this.revision
|
||||
}),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.enableComponentRequest.next({
|
||||
id: this.request.entity.id,
|
||||
uri: this.request.entity.uri,
|
||||
type: ComponentType.Processor,
|
||||
revision: this.client.getRevision({
|
||||
...this.request.entity,
|
||||
revision: this.revision
|
||||
}),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
}
|
||||
|
||||
private getModifiedProperties(): ModifiedProperties {
|
||||
const propertyControl: AbstractControl | null = this.editProcessorForm.get('properties');
|
||||
if (propertyControl && propertyControl.dirty) {
|
||||
|
|
|
@ -17,12 +17,24 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SessionStorageService } from '@nifi/shared';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class Client {
|
||||
private clientId: string = uuidv4();
|
||||
private clientId: string;
|
||||
|
||||
constructor(private sessionStorage: SessionStorageService) {
|
||||
let clientId = this.sessionStorage.getItem<string>('clientId');
|
||||
|
||||
if (clientId === null) {
|
||||
clientId = uuidv4();
|
||||
this.sessionStorage.setItem('clientId', clientId);
|
||||
}
|
||||
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public getClientId(): string {
|
||||
return this.clientId;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
birdseye-control;
|
||||
@use 'app/pages/flow-designer/ui/canvas/graph-controls/operation-control/operation-control.component-theme' as
|
||||
operation-control;
|
||||
@use 'app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component-theme' as edit-processor;
|
||||
@use 'app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component-theme' as flow-status;
|
||||
@use 'app/pages/flow-designer/ui/canvas/header/new-canvas-item/new-canvas-item.component-theme' as new-canvas-item;
|
||||
@use 'app/pages/flow-designer/ui/canvas/header/search/search.component-theme' as search;
|
||||
|
@ -94,6 +95,7 @@ html {
|
|||
@include navigation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include operation-control.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include flow-status.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include edit-processor.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include violation-details-dialog.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include new-canvas-item.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
@include search.generate-theme($m3-light-theme, $m3-light-theme-config);
|
||||
|
@ -127,6 +129,7 @@ html {
|
|||
@include navigation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include operation-control.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include flow-status.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include edit-processor.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include violation-details-dialog.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include new-canvas-item.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
@include search.generate-theme($m3-dark-theme, $m3-dark-theme-config);
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
--mdc-outlined-button-label-text-tracking: normal;
|
||||
--mdc-outlined-button-label-text-weight: 400;
|
||||
--mat-outlined-button-horizontal-padding: 15px;
|
||||
--mdc-outlined-button-container-height: 32px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-header {
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
|
||||
export * from './nifi-common.service';
|
||||
export * from './storage.service';
|
||||
export * from './session-storage.service';
|
||||
export * from './theming.service';
|
||||
export * from './map-table-helper.service';
|
||||
|
|
|
@ -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 { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SessionStorageService } from './session-storage.service';
|
||||
|
||||
describe('SessionStorageService', () => {
|
||||
let service: SessionStorageService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(SessionStorageService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).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 { Injectable } from '@angular/core';
|
||||
|
||||
interface SessionStorageEntry<T> {
|
||||
item: T;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SessionStorageService {
|
||||
/**
|
||||
* Gets an entry for the key. The entry expiration is not checked.
|
||||
*
|
||||
* @param {string} key
|
||||
*/
|
||||
private getEntry<T>(key: string): null | SessionStorageEntry<T> {
|
||||
try {
|
||||
// parse the entry
|
||||
const item = sessionStorage.getItem(key);
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entry = JSON.parse(item);
|
||||
|
||||
// ensure the entry is present
|
||||
if (entry) {
|
||||
return entry;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the specified item.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {object} item
|
||||
*/
|
||||
public setItem<T>(key: string, item: T): void {
|
||||
// create the entry
|
||||
const entry: SessionStorageEntry<T> = {
|
||||
item
|
||||
};
|
||||
|
||||
// store the item
|
||||
sessionStorage.setItem(key, JSON.stringify(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is an entry for this key. This will not check the expiration. If
|
||||
* the entry is expired, it will return null on a subsequent getItem invocation.
|
||||
*
|
||||
* @param {string} key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public hasItem(key: string): boolean {
|
||||
return this.getEntry(key) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item with the specified key. If an item with this key does
|
||||
* not exist, null is returned. If an item exists but cannot be parsed
|
||||
* or is malformed/unrecognized, null is returned.
|
||||
*
|
||||
* @param {type} key
|
||||
*/
|
||||
public getItem<T>(key: string): null | T {
|
||||
const entry: SessionStorageEntry<T> | null = this.getEntry(key);
|
||||
if (entry === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if the entry has the specified field return its value
|
||||
if (entry['item']) {
|
||||
return entry['item'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item with the specified key.
|
||||
*
|
||||
* @param {string} key
|
||||
*/
|
||||
public removeItem(key: string): void {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue