NIFI-13047: Adding property history to the property tooltip (#8652)

* NIFI-13047:
- Adding property history to the property tooltip in the Edit dialogs for Processors, Controller Services, Reporting Tasks, Parameter Providers, and Flow Analysis Rules.

* NIFI-13054:
- Addressing review feedback.

This closes #8652
This commit is contained in:
Matt Gilman 2024-04-16 13:47:23 -04:00 committed by GitHub
parent 44b1353440
commit 05558ca2de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 222 additions and 46 deletions

View File

@ -30,6 +30,7 @@ import { EditControllerService } from '../../../../ui/common/controller-service/
import {
ComponentType,
ControllerServiceReferencingComponent,
EditControllerServiceDialogRequest,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { Router } from '@angular/router';
@ -196,6 +197,30 @@ export class ControllerServicesEffects {
this.actions$.pipe(
ofType(ControllerServicesActions.openConfigureControllerServiceDialog),
map((action) => action.request),
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
switchMap(([request, processGroupId]) =>
from(this.propertyTableHelperService.getComponentHistory(request.id)).pipe(
map((history) => {
return {
...request,
history: history.componentHistory
} as EditControllerServiceDialogRequest;
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ControllerServicesActions.selectControllerService({
request: {
processGroupId,
id: request.id
}
})
);
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
}
})
)
),
concatLatestFrom(() => [
this.store.select(selectParameterContext),
this.store.select(selectCurrentProcessGroupId)
@ -205,9 +230,7 @@ export class ControllerServicesEffects {
const editDialogReference = this.dialog.open(EditControllerService, {
...LARGE_DIALOG,
data: {
controllerService: request.controllerService
},
data: request,
id: serviceId
});

View File

@ -1102,11 +1102,15 @@ export class FlowEffects {
ofType(FlowActions.openEditProcessorDialog),
map((action) => action.request),
switchMap((request) =>
from(this.flowService.getProcessor(request.entity.id)).pipe(
map((entity) => {
combineLatest([
this.flowService.getProcessor(request.entity.id),
this.propertyTableHelperService.getComponentHistory(request.entity.id)
]).pipe(
map(([entity, history]) => {
return {
...request,
entity
entity,
history: history.componentHistory
};
}),
tap({

View File

@ -19,6 +19,7 @@ import { BreadcrumbEntity, Position } from '../shared';
import {
BulletinEntity,
Bundle,
ComponentHistory,
ComponentType,
DocumentedType,
ParameterContextReferenceEntity,
@ -346,6 +347,7 @@ export interface EditComponentDialogRequest {
type: ComponentType;
uri: string;
entity: any;
history?: ComponentHistory;
}
export interface EditRemotePortDialogRequest extends EditComponentDialogRequest {

View File

@ -170,6 +170,7 @@
[parameterContext]="parameterContext"
[convertToParameter]="convertToParameter"
[goToService]="goToService"
[propertyHistory]="request.history"
[supportsSensitiveDynamicProperties]="
request.entity.component.supportsSensitiveDynamicProperties
"></property-table>

View File

@ -31,7 +31,7 @@ import { Router } from '@angular/router';
import { selectSaving } from '../management-controller-services/management-controller-services.selectors';
import { UpdateControllerServiceRequest } from '../../../../state/shared';
import { EditFlowAnalysisRule } from '../../ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component';
import { CreateFlowAnalysisRuleSuccess } from './index';
import { CreateFlowAnalysisRuleSuccess, EditFlowAnalysisRuleDialogRequest } from './index';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import * as ErrorActions from '../../../../state/error/error.actions';
import { ErrorHelper } from '../../../../service/error-helper.service';
@ -218,14 +218,38 @@ export class FlowAnalysisRulesEffects {
this.actions$.pipe(
ofType(FlowAnalysisRuleActions.openConfigureFlowAnalysisRuleDialog),
map((action) => action.request),
switchMap((request) =>
from(this.propertyTableHelperService.getComponentHistory(request.id)).pipe(
map((history) => {
return {
...request,
history: history.componentHistory
} as EditFlowAnalysisRuleDialogRequest;
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
FlowAnalysisRuleActions.selectFlowAnalysisRule({
request: {
id: request.id
}
})
);
this.store.dispatch(
FlowAnalysisRuleActions.flowAnalysisRuleSnackbarApiError({
error: errorResponse.error
})
);
}
})
)
),
tap((request) => {
const ruleId: string = request.id;
const editDialogReference = this.dialog.open(EditFlowAnalysisRule, {
...LARGE_DIALOG,
data: {
flowAnalysisRule: request.flowAnalysisRule
},
data: request,
id: ruleId
});

View File

@ -15,7 +15,14 @@
* limitations under the License.
*/
import { BulletinEntity, Bundle, DocumentedType, Permissions, Revision } from '../../../../state/shared';
import {
BulletinEntity,
Bundle,
ComponentHistory,
DocumentedType,
Permissions,
Revision
} from '../../../../state/shared';
export const flowAnalysisRulesFeatureKey = 'flowAnalysisRules';
@ -88,6 +95,7 @@ export interface ConfigureFlowAnalysisRuleRequest {
export interface EditFlowAnalysisRuleDialogRequest {
id: string;
flowAnalysisRule: FlowAnalysisRuleEntity;
history?: ComponentHistory;
}
export interface DeleteFlowAnalysisRuleRequest {

View File

@ -32,6 +32,7 @@ import { EditControllerService } from '../../../../ui/common/controller-service/
import {
ComponentType,
ControllerServiceReferencingComponent,
EditControllerServiceDialogRequest,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { Router } from '@angular/router';
@ -188,14 +189,38 @@ export class ManagementControllerServicesEffects {
this.actions$.pipe(
ofType(ManagementControllerServicesActions.openConfigureControllerServiceDialog),
map((action) => action.request),
switchMap((request) =>
from(this.propertyTableHelperService.getComponentHistory(request.id)).pipe(
map((history) => {
return {
...request,
history: history.componentHistory
} as EditControllerServiceDialogRequest;
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ManagementControllerServicesActions.selectControllerService({
request: {
id: request.id
}
})
);
this.store.dispatch(
ManagementControllerServicesActions.managementControllerServicesSnackbarApiError({
error: errorResponse.error
})
);
}
})
)
),
tap((request) => {
const serviceId: string = request.id;
const editDialogReference = this.dialog.open(EditControllerService, {
...LARGE_DIALOG,
data: {
controllerService: request.controllerService
},
data: request,
id: serviceId
});

View File

@ -18,6 +18,7 @@
import {
AffectedComponentEntity,
Bundle,
ComponentHistory,
DocumentedType,
ParameterContextReferenceEntity,
ParameterEntity,
@ -158,6 +159,7 @@ export interface DeleteParameterProviderSuccess {
export interface EditParameterProviderRequest {
id: string;
parameterProvider: ParameterProviderEntity;
history?: ComponentHistory;
}
export interface ConfigureParameterProviderRequest {

View File

@ -49,7 +49,7 @@ import { CreateParameterProvider } from '../../ui/parameter-providers/create-par
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { EditParameterProvider } from '../../ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import { ParameterProviderEntity, UpdateParameterProviderRequest } from './index';
import { EditParameterProviderRequest, ParameterProviderEntity, UpdateParameterProviderRequest } from './index';
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
import { FetchParameterProviderParameters } from '../../ui/parameter-providers/fetch-parameter-provider-parameters/fetch-parameter-provider-parameters.component';
import * as ErrorActions from '../../../../state/error/error.actions';
@ -266,13 +266,33 @@ export class ParameterProvidersEffects {
this.actions$.pipe(
ofType(ParameterProviderActions.openConfigureParameterProviderDialog),
map((action) => action.request),
switchMap((request) =>
from(this.propertyTableHelperService.getComponentHistory(request.id)).pipe(
map((history) => {
return {
...request,
history: history.componentHistory
} as EditParameterProviderRequest;
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ParameterProviderActions.selectParameterProvider({
request: {
id: request.id
}
})
);
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
}
})
)
),
tap((request) => {
const id = request.id;
const editDialogReference = this.dialog.open(EditParameterProvider, {
...LARGE_DIALOG,
data: {
parameterProvider: request.parameterProvider
},
data: request,
id
});

View File

@ -15,7 +15,14 @@
* limitations under the License.
*/
import { BulletinEntity, Bundle, DocumentedType, Permissions, Revision } from '../../../../state/shared';
import {
BulletinEntity,
Bundle,
ComponentHistory,
DocumentedType,
Permissions,
Revision
} from '../../../../state/shared';
export const reportingTasksFeatureKey = 'reportingTasks';
@ -66,6 +73,7 @@ export interface UpdateReportingTaskRequest {
export interface EditReportingTaskDialogRequest {
id: string;
reportingTask: ReportingTaskEntity;
history?: ComponentHistory;
}
export interface StartReportingTaskRequest {

View File

@ -30,7 +30,7 @@ import { Router } from '@angular/router';
import { selectSaving } from '../management-controller-services/management-controller-services.selectors';
import { UpdateControllerServiceRequest } from '../../../../state/shared';
import { EditReportingTask } from '../../ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component';
import { CreateReportingTaskSuccess } from './index';
import { CreateReportingTaskSuccess, EditReportingTaskDialogRequest } from './index';
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import * as ErrorActions from '../../../../state/error/error.actions';
@ -232,14 +232,36 @@ export class ReportingTasksEffects {
this.actions$.pipe(
ofType(ReportingTaskActions.openConfigureReportingTaskDialog),
map((action) => action.request),
switchMap((request) =>
from(this.propertyTableHelperService.getComponentHistory(request.id)).pipe(
map((history) => {
return {
...request,
history: history.componentHistory
} as EditReportingTaskDialogRequest;
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ReportingTaskActions.selectReportingTask({
request: {
id: request.id
}
})
);
this.store.dispatch(
ReportingTaskActions.reportingTasksSnackbarApiError({ error: errorResponse.error })
);
}
})
)
),
tap((request) => {
const taskId: string = request.id;
const editDialogReference = this.dialog.open(EditReportingTask, {
...LARGE_DIALOG,
data: {
reportingTask: request.reportingTask
},
data: request,
id: taskId
});

View File

@ -73,6 +73,7 @@
formControlName="properties"
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[propertyHistory]="request.history"
[goToService]="goToService">
</property-table>
</div>

View File

@ -71,6 +71,7 @@
formControlName="properties"
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[propertyHistory]="request.history"
[goToService]="goToService"></property-table>
</div>
</mat-tab>

View File

@ -89,6 +89,7 @@
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[goToService]="goToService"
[propertyHistory]="request.history"
[supportsSensitiveDynamicProperties]="
request.reportingTask.component.supportsSensitiveDynamicProperties
">

View File

@ -19,6 +19,7 @@ import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { catchError, EMPTY, map, Observable, switchMap, take, takeUntil, tap } from 'rxjs';
import {
ComponentHistoryEntity,
ControllerServiceCreator,
ControllerServiceEntity,
CreateControllerServiceRequest,
@ -37,20 +38,29 @@ import { Client } from './client.service';
import { NiFiState } from '../state';
import { Store } from '@ngrx/store';
import { snackBarError } from '../state/error/error.actions';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { LARGE_DIALOG, SMALL_DIALOG } from '../index';
@Injectable({
providedIn: 'root'
})
export class PropertyTableHelperService {
private static readonly API: string = '../nifi-api';
constructor(
private httpClient: HttpClient,
private dialog: MatDialog,
private store: Store<NiFiState>,
private extensionTypesService: ExtensionTypesService,
private client: Client
) {}
getComponentHistory(componentId: string): Observable<ComponentHistoryEntity> {
return this.httpClient.get<ComponentHistoryEntity>(
`${PropertyTableHelperService.API}/flow/history/components/${componentId}`
);
}
/**
* Returns a function that can be used to pass into a PropertyTable to support creating a new property
* @param id id of the component to create the property for

View File

@ -128,6 +128,7 @@ export interface CreateControllerServiceDialogRequest {
export interface EditControllerServiceDialogRequest {
id: string;
controllerService: ControllerServiceEntity;
history?: ComponentHistory;
}
export interface UpdateControllerServiceRequest {
@ -204,6 +205,25 @@ export interface ProvenanceEventDialogRequest {
event: ProvenanceEvent;
}
export interface PreviousValue {
previousValue: string;
timestamp: string;
userIdentity: string;
}
export interface PropertyHistory {
previousValues: PreviousValue[];
}
export interface ComponentHistory {
componentId: string;
propertyHistory: { [key: string]: PropertyHistory };
}
export interface ComponentHistoryEntity {
componentHistory: ComponentHistory;
}
export interface TextTipInput {
text: string;
}
@ -232,6 +252,7 @@ export interface BulletinsTipInput {
export interface PropertyTipInput {
descriptor: PropertyDescriptor;
propertyHistory?: PropertyHistory;
}
export interface ParameterTipInput {

View File

@ -102,6 +102,7 @@
[goToParameter]="goToParameter"
[convertToParameter]="convertToParameter"
[goToService]="goToService"
[propertyHistory]="request.history"
[supportsSensitiveDynamicProperties]="
request.controllerService.component.supportsSensitiveDynamicProperties
"></property-table>

View File

@ -37,14 +37,12 @@
{{ item.descriptor.displayName }}
</div>
<div>
@if (hasInfo(item.descriptor)) {
<div
class="fa fa-question-circle primary-color"
nifiTooltip
[tooltipComponentType]="PropertyTip"
[tooltipInputData]="getPropertyTipData(item)"
[delayClose]="false"></div>
}
<div
class="fa fa-question-circle primary-color"
nifiTooltip
[tooltipComponentType]="PropertyTip"
[tooltipInputData]="getPropertyTipData(item)"
[delayClose]="false"></div>
</div>
</div>
</td>

View File

@ -34,6 +34,7 @@ import { NiFiCommon } from '../../../service/nifi-common.service';
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import {
AllowableValueEntity,
ComponentHistory,
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
@ -105,6 +106,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() goToService!: (serviceId: string) => void;
@Input() supportsSensitiveDynamicProperties = false;
@Input() propertyHistory: ComponentHistory | undefined;
private static readonly PARAM_REF_REGEX: RegExp = /#{[a-zA-Z0-9-_. ]+}/;
@ -348,14 +350,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
return 'property-' + item.id;
}
hasInfo(descriptor: PropertyDescriptor): boolean {
return (
!this.nifiCommon.isBlank(descriptor.description) ||
!this.nifiCommon.isBlank(descriptor.defaultValue) ||
descriptor.supportsEl
);
}
isSensitiveProperty(descriptor: PropertyDescriptor): boolean {
return descriptor.sensitive;
}
@ -396,7 +390,8 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
getPropertyTipData(item: PropertyItem): PropertyTipInput {
return {
descriptor: item.descriptor
descriptor: item.descriptor,
propertyHistory: this.propertyHistory?.propertyHistory[item.property]
};
}

View File

@ -16,8 +16,8 @@
-->
<div class="tooltip" [style.left.px]="left" [style.top.px]="top">
@if (data?.descriptor; as descriptor) {
<div class="flex flex-col gap-y-3">
<div class="flex flex-col gap-y-3">
@if (data?.descriptor; as descriptor) {
@if (hasDescription(descriptor)) {
<div>{{ descriptor.description }}</div>
}
@ -38,7 +38,16 @@
[bundle]="descriptor.identifiesControllerServiceBundle"></controller-service-api>
</div>
}
<!-- TODO - Property History -->
</div>
}
}
@if (data?.propertyHistory; as propertyHistory) {
<div>
<b>History</b>
<ul class="px-2">
@for (previousValue of propertyHistory.previousValues; track previousValue) {
<li>{{ previousValue.previousValue }} - {{ previousValue.timestamp }} ({{ previousValue.userIdentity }})</li>
}
</ul>
</div>
}
</div>
</div>