mirror of https://github.com/apache/nifi.git
[NIFI-14002] - Quick copy of IDs (#9514)
This commit is contained in:
parent
6c4ddf8631
commit
919b376c16
|
@ -22,7 +22,9 @@
|
|||
<div class="panel-content flex flex-col h-full w-full gap-y-4">
|
||||
<div>
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">{{ actionEntity.sourceId }}</div>
|
||||
<div [copy]="actionEntity.sourceId" class="tertiary-color font-medium">
|
||||
{{ actionEntity.sourceId }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (actionEntity.action; as action) {
|
||||
|
|
|
@ -28,13 +28,13 @@ import {
|
|||
PurgeActionDetails,
|
||||
RemoteProcessGroupDetails
|
||||
} from '../../../state/flow-configuration-history-listing';
|
||||
import { PipesModule, CloseOnEscapeDialog } from '@nifi/shared';
|
||||
import { PipesModule, CloseOnEscapeDialog, CopyDirective } from '@nifi/shared';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'action-details',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatDialogModule, PipesModule, MatButtonModule],
|
||||
imports: [CommonModule, MatDialogModule, PipesModule, MatButtonModule, CopyDirective],
|
||||
templateUrl: './action-details.component.html',
|
||||
styleUrls: ['./action-details.component.scss']
|
||||
})
|
||||
|
|
|
@ -107,7 +107,9 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">{{ dialogRequest.entity.id }}</div>
|
||||
<div [copy]="dialogRequest.entity.id" class="tertiary-color font-medium">
|
||||
{{ dialogRequest.entity.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
|
|
|
@ -33,7 +33,7 @@ import { MatInputModule } from '@angular/material/input';
|
|||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
import { NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { CopyDirective, NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { ComponentType } from 'libs/shared/src';
|
||||
import { NiFiState } from '../../../../../../../state';
|
||||
|
@ -88,7 +88,8 @@ import { ContextErrorBanner } from '../../../../../../../ui/common/context-error
|
|||
SourceRemoteProcessGroup,
|
||||
DestinationRemoteProcessGroup,
|
||||
ErrorBanner,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
templateUrl: './edit-connection.component.html',
|
||||
styleUrls: ['./edit-connection.component.scss']
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">{{ request.entity.id }}</div>
|
||||
<div [copy]="request.entity.id" class="tertiary-color font-medium">
|
||||
{{ request.entity.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Type</div>
|
||||
|
|
|
@ -45,7 +45,7 @@ import { Client } from '../../../../../../../service/client.service';
|
|||
import { EditComponentDialogRequest, 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 } from '@nifi/shared';
|
||||
import { NifiTooltipDirective, NiFiCommon, TextTip, CopyDirective } from '@nifi/shared';
|
||||
import { RunDurationSlider } from './run-duration-slider/run-duration-slider.component';
|
||||
import {
|
||||
RelationshipConfiguration,
|
||||
|
@ -87,7 +87,8 @@ import { ContextErrorBanner } from '../../../../../../../ui/common/context-error
|
|||
RelationshipSettings,
|
||||
ErrorBanner,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-processor.component.scss']
|
||||
})
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-4">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">{{ request.entity.component.id }}</div>
|
||||
<div [copy]="request.entity.component.id" class="tertiary-color font-medium">
|
||||
{{ request.entity.component.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-x-4">
|
||||
<div class="w-full">
|
||||
|
|
|
@ -30,7 +30,7 @@ import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nif
|
|||
import { EditComponentDialogRequest } from '../../../../../state/flow';
|
||||
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { CanvasUtils } from '../../../../../service/canvas-utils.service';
|
||||
import { NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { CopyDirective, NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { CloseOnEscapeDialog } from '@nifi/shared';
|
||||
import { ErrorContextKey } from '../../../../../../../state/error';
|
||||
import { ContextErrorBanner } from '../../../../../../../ui/common/context-error-banner/context-error-banner.component';
|
||||
|
@ -51,7 +51,8 @@ import { ContextErrorBanner } from '../../../../../../../ui/common/context-error
|
|||
FormsModule,
|
||||
ErrorBanner,
|
||||
NifiTooltipDirective,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-remote-process-group.component.scss']
|
||||
})
|
||||
|
|
|
@ -90,9 +90,13 @@
|
|||
@if (!isNew) {
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
{{ request.parameterContext?.id }}
|
||||
</div>
|
||||
@if (request.parameterContext) {
|
||||
<div [copy]="request.parameterContext.id" class="tertiary-color font-medium">
|
||||
{{ request.parameterContext.id }}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="font-medium unset neutral-color">No value set</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (parameterProvider) {
|
||||
|
|
|
@ -45,7 +45,7 @@ import { RouterLink } from '@angular/router';
|
|||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
|
||||
import { TabbedDialog } from '../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
|
||||
import { NiFiCommon, TextTip, NifiTooltipDirective } from '@nifi/shared';
|
||||
import { NiFiCommon, TextTip, NifiTooltipDirective, CopyDirective } from '@nifi/shared';
|
||||
import { ErrorContextKey } from '../../../../../state/error';
|
||||
import { ContextErrorBanner } from '../../../../../ui/common/context-error-banner/context-error-banner.component';
|
||||
|
||||
|
@ -72,7 +72,8 @@ import { ContextErrorBanner } from '../../../../../ui/common/context-error-banne
|
|||
RouterLink,
|
||||
ErrorBanner,
|
||||
NifiTooltipDirective,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-parameter-context.component.scss']
|
||||
})
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<div>UUID</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
formatCopyableValue;
|
||||
context: { $implicit: request.flowfile.uuid }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -131,7 +131,7 @@
|
|||
<div>Identifier</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatContentValue;
|
||||
formatCopyableContentValue;
|
||||
context: { $implicit: request.flowfile.contentClaimIdentifier }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -219,6 +219,25 @@
|
|||
<div class="unset neutral-color">No value set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #formatCopyableValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<ng-container *ngIf="value === ''; else nonEmptyValue">
|
||||
<div class="unset neutral-color">Empty string set</div>
|
||||
</ng-container>
|
||||
<ng-template #nonEmptyValue>
|
||||
<div [copy]="value" class="tertiary-color font-medium" *ngIf="title == null; else valueWithTitle">
|
||||
{{ value }}
|
||||
</div>
|
||||
<ng-template #valueWithTitle>
|
||||
<div [copy]="value" class="tertiary-color font-medium" [title]="title">{{ value }}</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #nullValue>
|
||||
<div class="unset neutral-color">No value set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #formatContentValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<div class="tertiary-color font-medium" *ngIf="title == null; else valueWithTitle">
|
||||
|
@ -232,6 +251,19 @@
|
|||
<div class="unset neutral-color">No value previously set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #formatCopyableContentValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<div [copy]="value" class="tertiary-color font-medium" *ngIf="title == null; else valueWithTitle">
|
||||
{{ value }}
|
||||
</div>
|
||||
<ng-template #valueWithTitle>
|
||||
<div [copy]="value" class="tertiary-color font-medium" [title]="title">{{ value }}</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #nullValue>
|
||||
<div class="unset neutral-color">No value previously set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-flat-button mat-dialog-close>Ok</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -25,7 +25,7 @@ import { AsyncPipe, KeyValuePipe, NgForOf, NgIf, NgTemplateOutlet } from '@angul
|
|||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { FlowFileDialogRequest } from '../../../state/queue-listing';
|
||||
import { NiFiCommon } from '@nifi/shared';
|
||||
import { CopyDirective, NiFiCommon } from '@nifi/shared';
|
||||
import { TabbedDialog } from '../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
|
||||
|
||||
@Component({
|
||||
|
@ -46,7 +46,8 @@ import { TabbedDialog } from '../../../../../ui/common/tabbed-dialog/tabbed-dial
|
|||
MatTabsModule,
|
||||
NgTemplateOutlet,
|
||||
FormsModule,
|
||||
KeyValuePipe
|
||||
KeyValuePipe,
|
||||
CopyDirective
|
||||
]
|
||||
})
|
||||
export class FlowFileDialog extends TabbedDialog {
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</mat-form-field>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
<div [copy]="request.flowAnalysisRule.id" class="tertiary-color font-medium">
|
||||
{{ request.flowAnalysisRule.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,7 +27,7 @@ import { AbstractControl, FormBuilder, FormControl, FormGroup, ReactiveFormsModu
|
|||
import { Observable, of } from 'rxjs';
|
||||
import { Client } from '../../../../../service/client.service';
|
||||
import { InlineServiceCreationRequest, InlineServiceCreationResponse, Property } from '../../../../../state/shared';
|
||||
import { NiFiCommon, NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { CopyDirective, NiFiCommon, NifiTooltipDirective, TextTip } from '@nifi/shared';
|
||||
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
||||
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
import {
|
||||
|
@ -68,7 +68,8 @@ import { ContextErrorBanner } from '../../../../../ui/common/context-error-banne
|
|||
FlowAnalysisRuleTable,
|
||||
ErrorBanner,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-flow-analysis-rule.component.scss']
|
||||
})
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
<div [copy]="request.parameterProvider.id" class="tertiary-color font-medium">
|
||||
{{ request.parameterProvider.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -43,7 +43,7 @@ import { PropertyTable } from '../../../../../ui/common/property-table/property-
|
|||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
|
||||
import { TextTip, NiFiCommon, NifiTooltipDirective } from '@nifi/shared';
|
||||
import { TextTip, NiFiCommon, NifiTooltipDirective, CopyDirective } from '@nifi/shared';
|
||||
import {
|
||||
ConfigVerificationResult,
|
||||
ModifiedProperties,
|
||||
|
@ -72,7 +72,8 @@ import { ContextErrorBanner } from '../../../../../ui/common/context-error-banne
|
|||
CommonModule,
|
||||
NifiTooltipDirective,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
templateUrl: './edit-parameter-provider.component.html',
|
||||
styleUrls: ['./edit-parameter-provider.component.scss']
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div class="dialog-tab-content flex flex-col">
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
<div [copy]="request.registryClient.id" class="tertiary-color font-medium">
|
||||
{{ request.registryClient.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,7 +33,7 @@ import { EditRegistryClientDialogRequest, EditRegistryClientRequest } from '../.
|
|||
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
import { Client } from '../../../../../service/client.service';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TextTip, NifiTooltipDirective, NiFiCommon } from '@nifi/shared';
|
||||
import { TextTip, NifiTooltipDirective, NiFiCommon, CopyDirective } from '@nifi/shared';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
|
||||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
|
@ -59,7 +59,8 @@ import { ContextErrorBanner } from '../../../../../ui/common/context-error-banne
|
|||
MatTabsModule,
|
||||
PropertyTable,
|
||||
ErrorBanner,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-registry-client.component.scss']
|
||||
})
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
<div [copy]="request.reportingTask.id" class="tertiary-color font-medium">
|
||||
{{ request.reportingTask.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
UpdateReportingTaskRequest
|
||||
} from '../../../state/reporting-tasks';
|
||||
import { ControllerServiceApi } from '../../../../../ui/common/controller-service/controller-service-api/controller-service-api.component';
|
||||
import { NifiTooltipDirective, NiFiCommon, TextTip } from '@nifi/shared';
|
||||
import { NifiTooltipDirective, NiFiCommon, TextTip, CopyDirective } from '@nifi/shared';
|
||||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
|
||||
import {
|
||||
|
@ -75,7 +75,8 @@ import { ContextErrorBanner } from '../../../../../ui/common/context-error-banne
|
|||
NifiTooltipDirective,
|
||||
ErrorBanner,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-reporting-task.component.scss']
|
||||
})
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Id</div>
|
||||
<div class="tertiary-color font-medium">
|
||||
<div [copy]="request.controllerService.id" class="tertiary-color font-medium">
|
||||
{{ request.controllerService.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
|||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { NiFiCommon, TextTip, NifiTooltipDirective } from '@nifi/shared';
|
||||
import { NiFiCommon, TextTip, NifiTooltipDirective, CopyDirective } from '@nifi/shared';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { PropertyTable } from '../../property-table/property-table.component';
|
||||
|
@ -76,7 +76,8 @@ import { ContextErrorBanner } from '../../context-error-banner/context-error-ban
|
|||
ErrorBanner,
|
||||
NifiTooltipDirective,
|
||||
PropertyVerification,
|
||||
ContextErrorBanner
|
||||
ContextErrorBanner,
|
||||
CopyDirective
|
||||
],
|
||||
styleUrls: ['./edit-controller-service.component.scss']
|
||||
})
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<div>FlowFile UUID</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
formatCopyableValue;
|
||||
context: { $implicit: request.event.flowFileUuid }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -78,7 +78,7 @@
|
|||
<div>Component Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
formatCopyableValue;
|
||||
context: { $implicit: request.event.componentId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -114,7 +114,7 @@
|
|||
<div>Source FlowFile Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatValue;
|
||||
formatCopyableValue;
|
||||
context: { $implicit: request.event.sourceSystemFlowFileId }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -288,7 +288,7 @@
|
|||
<div>Identifier</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatContentValue;
|
||||
formatCopyableContentValue;
|
||||
context: { $implicit: request.event.inputContentClaimIdentifier }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -350,7 +350,7 @@
|
|||
<div>Identifier</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatContentValue;
|
||||
formatCopyableContentValue;
|
||||
context: { $implicit: request.event.outputContentClaimIdentifier }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -399,7 +399,7 @@
|
|||
<div>Connection Id</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
formatContentValue;
|
||||
formatCopyableContentValue;
|
||||
context: { $implicit: request.event.sourceConnectionIdentifier }
|
||||
"></ng-container>
|
||||
</div>
|
||||
|
@ -443,6 +443,32 @@
|
|||
<div class="unset neutral-color">No value set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #formatCopyableValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<ng-container *ngIf="value === ''; else nonEmptyValue">
|
||||
<div class="unset neutral-color">Empty string set</div>
|
||||
</ng-container>
|
||||
<ng-template #nonEmptyValue>
|
||||
<div
|
||||
[copy]="value"
|
||||
class="tertiary-color font-medium overflow-ellipsis overflow-hidden whitespace-nowrap"
|
||||
*ngIf="title == null; else valueWithTitle">
|
||||
{{ value }}
|
||||
</div>
|
||||
<ng-template #valueWithTitle>
|
||||
<div
|
||||
[copy]="value"
|
||||
class="tertiary-color font-medium overflow-ellipsis overflow-hidden whitespace-nowrap"
|
||||
[title]="title">
|
||||
{{ value }}
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #nullValue>
|
||||
<div class="unset neutral-color">No value set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #formatContentValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<div
|
||||
|
@ -462,6 +488,27 @@
|
|||
<div class="unset neutral-color">No value previously set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #formatCopyableContentValue let-value let-title="title">
|
||||
<ng-container *ngIf="value != null; else nullValue">
|
||||
<div
|
||||
[copy]="value"
|
||||
class="tertiary-color font-medium overflow-ellipsis overflow-hidden whitespace-nowrap"
|
||||
*ngIf="title == null; else valueWithTitle">
|
||||
{{ value }}
|
||||
</div>
|
||||
<ng-template #valueWithTitle>
|
||||
<div
|
||||
[copy]="value"
|
||||
class="tertiary-color font-medium overflow-ellipsis overflow-hidden whitespace-nowrap"
|
||||
[title]="title">
|
||||
{{ value }}
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #nullValue>
|
||||
<div class="unset neutral-color">No value previously set</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Ok</button>
|
||||
|
|
|
@ -23,7 +23,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
|||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { NiFiCommon } from '@nifi/shared';
|
||||
import { CopyDirective, NiFiCommon } from '@nifi/shared';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { Attribute, ProvenanceEventDialogRequest } from '../../../state/shared';
|
||||
import { TabbedDialog } from '../tabbed-dialog/tabbed-dialog.component';
|
||||
|
@ -44,7 +44,8 @@ import { TabbedDialog } from '../tabbed-dialog/tabbed-dialog.component';
|
|||
MatDatepickerModule,
|
||||
MatTabsModule,
|
||||
NgTemplateOutlet,
|
||||
FormsModule
|
||||
FormsModule,
|
||||
CopyDirective
|
||||
]
|
||||
})
|
||||
export class ProvenanceEventDialog extends TabbedDialog {
|
||||
|
|
|
@ -99,6 +99,10 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-tab-content,
|
||||
.dialog-content {
|
||||
height: 50vh;
|
||||
|
|
|
@ -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 } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { CopyDirective } from './copy.directive';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `<div [copy]="copyText">test</div>`,
|
||||
imports: [CopyDirective]
|
||||
})
|
||||
class TestComponent {
|
||||
copyText = 'copied value';
|
||||
}
|
||||
describe('CopyDirective', () => {
|
||||
let component: TestComponent;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let directiveDebugEl: any;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CopyDirective, TestComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
directiveDebugEl = fixture.debugElement.query(By.directive(CopyDirective));
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
it('should create a copy button on mouse enter', () => {
|
||||
directiveDebugEl.triggerEventHandler('mouseenter', null);
|
||||
fixture.detectChanges();
|
||||
const copyButton = directiveDebugEl.nativeElement.querySelector('.copy-button');
|
||||
expect(copyButton).not.toBeNull();
|
||||
expect(copyButton?.classList).toContain('fa-copy');
|
||||
});
|
||||
it('should remove the copy button on mouse leave', () => {
|
||||
directiveDebugEl.triggerEventHandler('mouseenter', null);
|
||||
fixture.detectChanges();
|
||||
directiveDebugEl.triggerEventHandler('mouseleave', null);
|
||||
fixture.detectChanges();
|
||||
const copyButton = directiveDebugEl.nativeElement.querySelector('.copy-button');
|
||||
expect(copyButton).toBeNull();
|
||||
});
|
||||
it('should copy text to clipboard and change button appearance on click', async () => {
|
||||
// Mock the clipboard's writeText method
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: jest.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
});
|
||||
directiveDebugEl.triggerEventHandler('mouseenter', null);
|
||||
fixture.detectChanges();
|
||||
const copyButton = directiveDebugEl.nativeElement.querySelector('.copy-button');
|
||||
copyButton?.dispatchEvent(new MouseEvent('click'));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(component.copyText);
|
||||
expect(copyButton?.classList).toContain('copied');
|
||||
expect(copyButton?.classList).toContain('fa-check');
|
||||
expect(copyButton?.classList).toContain('success-color-default');
|
||||
});
|
||||
it('should unsubscribe from events on mouse leave', () => {
|
||||
directiveDebugEl.triggerEventHandler('mouseenter', null);
|
||||
fixture.detectChanges();
|
||||
const directiveInstance = directiveDebugEl.injector.get(CopyDirective);
|
||||
const unsubscribeSpy = jest.spyOn(directiveInstance.subscription!, 'unsubscribe');
|
||||
directiveDebugEl.triggerEventHandler('mouseleave', null);
|
||||
fixture.detectChanges();
|
||||
expect(unsubscribeSpy).toHaveBeenCalled();
|
||||
unsubscribeSpy.mockRestore();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { Directive, ElementRef, HostListener, Input, NgZone, Renderer2 } from '@angular/core';
|
||||
import { fromEvent, Subscription, switchMap, take } from 'rxjs';
|
||||
|
||||
@Directive({
|
||||
selector: '[copy]',
|
||||
standalone: true
|
||||
})
|
||||
export class CopyDirective {
|
||||
@Input({ required: true }) copy!: string;
|
||||
|
||||
private copyButton: HTMLElement | null = null;
|
||||
private subscription: Subscription | null = null;
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef<HTMLElement>,
|
||||
private renderer: Renderer2,
|
||||
private zone: NgZone
|
||||
) {}
|
||||
|
||||
@HostListener('mouseenter')
|
||||
onMouseEnter() {
|
||||
this.copyButton = this.renderer.createElement('i');
|
||||
if (this.copyButton) {
|
||||
const cb: HTMLElement = this.copyButton;
|
||||
cb.classList.add('copy-button', 'fa', 'fa-copy', 'ml-2', 'primary-color');
|
||||
|
||||
// run outside the angular zone to prevent unnecessary change detection cycles
|
||||
this.subscription = this.zone.runOutsideAngular(() => {
|
||||
return fromEvent(cb, 'click')
|
||||
.pipe(
|
||||
switchMap(() => navigator.clipboard.writeText(this.copy)),
|
||||
take(1)
|
||||
)
|
||||
.subscribe(() => {
|
||||
cb.classList.remove('copy-button', 'fa-copy');
|
||||
cb.classList.add('copied', 'fa-check', 'success-color-default');
|
||||
});
|
||||
});
|
||||
this.renderer.appendChild(this.elementRef.nativeElement, this.copyButton);
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('mouseleave')
|
||||
onMouseLeave() {
|
||||
// if the user leaves the element without clicking, the subscription needs closed
|
||||
if (this.subscription && !this.subscription.closed) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.copyButton) {
|
||||
this.renderer?.removeChild(this.elementRef.nativeElement, this.copyButton);
|
||||
this.copyButton = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,3 +16,4 @@
|
|||
*/
|
||||
|
||||
export * from './nifi-tooltip.directive';
|
||||
export * from './copy/copy.directive';
|
||||
|
|
Loading…
Reference in New Issue