mcgilman commented on code in PR #9548:
URL: https://github.com/apache/nifi/pull/9548#discussion_r1873884611
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.html:
##########
@@ -15,19 +15,89 @@
~ 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">
+ <div>
+ @if (isStoppable()) {
+ <button type="button" 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" 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 {
+ <button type="button" mat-stroked-button
[matMenuTriggerFor]="operateMenu">
+ <div class="flex items-center">
+ @if (isInvalid()) {
+ <i class="mr-2 fa fa-warning
caution-color"></i>
+ } @else if (isDisabled()) {
+ <i class="mr-2 icon icon-enable-false"></i>
+ } @else {
+ <i class="mr-2 fa fa-circle-o-notch fa-spin
primary-color"></i>
+ }
+ {{ formatRunStatus() }}
Review Comment:
Was it intended to leave off `<i class="ml-2 -mt-1 fa fa-sort-desc"></i>`
for these run statuses?
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts:
##########
@@ -1556,6 +1569,113 @@ export class FlowEffects {
);
});
+ this.store
+ .select(selectProcessor(processorId))
+ .pipe(
+ takeUntil(editDialogReference.afterClosed()),
+ isDefinedAndNotNull(),
+ filter((processorEntity) => {
+ return (
+ processorEntity.revision.clientId ===
this.client.getClientId() ||
+ (runStatusChanged
+ ? false
+ : processorEntity.revision.clientId
=== request.entity.revision.clientId)
+ );
Review Comment:
This condition can be simplified to
```
return (
processorEntity.revision.clientId === this.client.getClientId() ||
(!runStatusChanged &&
processorEntity.revision.clientId ===
request.entity.revision.clientId)
);
```
However, this logic isn't quite right. The first condition is intended to
allow through updates issued but the current client. Once the dialog is opened,
that would only include changes to the scheduled state which impacts the run
status.
The second condition is designed to allow any changes issued by the client
who made the most recent change as long as the current client hasn't changed
the schedule state. The issue I see here is that the other client could have
made any modification. For instance, changing a property. The
`processorUpdates` in the dialog does not account this. It only considers the
status, revision, bulletins, and permissions.
Since the system is currently based on optimistic locking, I think we should
ignore changes from other clients like we would for other cases (like regular
Processor updates). If another client makes a change, the current client will
need to get the most up to date changes by refreshing the canvas. I think we
could simple this filter to simply limit to changes made by the current client
once they have modified the run status.
```
return (
runStatusChanged && processorEntity.revision.clientId ===
this.client.getClientId()
);
```
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts:
##########
@@ -401,8 +460,144 @@ 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 {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Running';
+ }
+
+ isInvalid(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Invalid';
+ }
+
+ isDisabled(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Disabled';
+ }
+
+ isRunnable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Stopped'
+ );
+ }
+
+ isDisableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) &&
+ (this.status.aggregateSnapshot.runStatus === 'Stopped' ||
+ this.status.aggregateSnapshot.runStatus === 'Invalid')
+ );
+ }
+
+ isEnableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Disabled'
+ );
+ }
+
+ private canOperate(): boolean {
+ return this.request.entity.permissions.canWrite ||
this.request.entity.operatePermissions?.canWrite;
+ }
+
+ stop(entity: any) {
+ this.stopComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ start(entity: any) {
+ this.startComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ disable(entity: any) {
+ this.disableComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ enable(entity: any) {
+ this.enableComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
Review Comment:
```suggestion
revision: this.client.getRevision({
...this.request.entity,
revision: this.revision
}),
```
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts:
##########
@@ -401,8 +460,144 @@ 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 {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Running';
+ }
+
+ isInvalid(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Invalid';
+ }
+
+ isDisabled(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Disabled';
+ }
+
+ isRunnable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Stopped'
+ );
+ }
+
+ isDisableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) &&
+ (this.status.aggregateSnapshot.runStatus === 'Stopped' ||
+ this.status.aggregateSnapshot.runStatus === 'Invalid')
+ );
+ }
+
+ isEnableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Disabled'
+ );
+ }
+
+ private canOperate(): boolean {
+ return this.request.entity.permissions.canWrite ||
this.request.entity.operatePermissions?.canWrite;
+ }
+
+ stop(entity: any) {
+ this.stopComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ start(entity: any) {
+ this.startComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
Review Comment:
```suggestion
revision: this.client.getRevision({
...this.request.entity,
revision: this.revision
}),
```
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.html:
##########
@@ -15,19 +15,89 @@
~ 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">
+ <div>
+ @if (isStoppable()) {
+ <button type="button" 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" mat-stroked-button
[matMenuTriggerFor]="operateMenu">
Review Comment:
This button has focus when this dialog opens. I'm not sure that's
problematic but I wanted to raise it here to ensure that is in fact what we
want to do.

##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts:
##########
@@ -253,6 +275,32 @@ export class EditProcessor extends TabbedDialog {
new FormControl({ value: this.runDurationMillis, disabled:
this.readonly }, Validators.required)
);
}
+
+ this.initialize(request.entity);
+ }
+
+ initialize(entity: any) {
Review Comment:
```suggestion
processRunStateUpdates(entity: any) {
```
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.html:
##########
@@ -15,19 +15,89 @@
~ 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">
+ <div>
+ @if (isStoppable()) {
+ <button type="button" 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" 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 {
+ <button type="button" mat-stroked-button
[matMenuTriggerFor]="operateMenu">
+ <div class="flex items-center">
+ @if (isInvalid()) {
+ <i class="mr-2 fa fa-warning
caution-color"></i>
+ } @else if (isDisabled()) {
+ <i class="mr-2 icon icon-enable-false"></i>
+ } @else {
+ <i class="mr-2 fa fa-circle-o-notch fa-spin
primary-color"></i>
+ }
+ {{ formatRunStatus() }}
+ </div>
+ </button>
+ }
+ <mat-menu #operateMenu="matMenu" xPosition="before">
Review Comment:
We should do something when this menu is empty. For instance when the
component is `Stopping`

##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts:
##########
@@ -401,8 +460,144 @@ 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 {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Running';
+ }
+
+ isInvalid(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Invalid';
+ }
+
+ isDisabled(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Disabled';
+ }
+
+ isRunnable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Stopped'
+ );
+ }
+
+ isDisableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) &&
+ (this.status.aggregateSnapshot.runStatus === 'Stopped' ||
+ this.status.aggregateSnapshot.runStatus === 'Invalid')
+ );
+ }
+
+ isEnableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Disabled'
+ );
+ }
+
+ private canOperate(): boolean {
+ return this.request.entity.permissions.canWrite ||
this.request.entity.operatePermissions?.canWrite;
+ }
+
+ stop(entity: any) {
+ this.stopComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ start(entity: any) {
+ this.startComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
+ errorStrategy: 'snackbar'
+ });
+ }
+
+ disable(entity: any) {
+ this.disableComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
Review Comment:
```suggestion
revision: this.client.getRevision({
...this.request.entity,
revision: this.revision
}),
```
##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts:
##########
@@ -401,8 +460,144 @@ 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 {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Running';
+ }
+
+ isInvalid(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Invalid';
+ }
+
+ isDisabled(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return this.status.aggregateSnapshot.runStatus === 'Disabled';
+ }
+
+ isRunnable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Stopped'
+ );
+ }
+
+ isDisableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) &&
+ (this.status.aggregateSnapshot.runStatus === 'Stopped' ||
+ this.status.aggregateSnapshot.runStatus === 'Invalid')
+ );
+ }
+
+ isEnableable(): boolean {
+ if (!this.canOperate()) {
+ return false;
+ }
+
+ return (
+ !(
+ this.status.aggregateSnapshot.runStatus === 'Running' ||
+ this.status.aggregateSnapshot.activeThreadCount > 0
+ ) && this.status.aggregateSnapshot.runStatus === 'Disabled'
+ );
+ }
+
+ private canOperate(): boolean {
+ return this.request.entity.permissions.canWrite ||
this.request.entity.operatePermissions?.canWrite;
+ }
+
+ stop(entity: any) {
+ this.stopComponentRequest.next({
+ id: entity.id,
+ uri: entity.uri,
+ type: ComponentType.Processor,
+ revision: this.revision,
Review Comment:
```suggestion
revision: this.client.getRevision({
...this.request.entity,
revision: this.revision
}),
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]