diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts index 418f9ae54f..8e6ec08eee 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts @@ -1213,7 +1213,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { }, { condition: (selection: d3.Selection) => { - return this.canvasUtils.isDisconnected(selection); + return this.canvasUtils.isDisconnected(selection) && this.canvasUtils.canModify(selection); }, clazz: 'fa icon-group', text: 'Group', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts index 5f2c30ae9f..c00c48bc3f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts @@ -426,6 +426,11 @@ export const openEditRemoteProcessGroupDialog = createAction( props<{ request: EditComponentDialogRequest }>() ); +export const openEditLabelDialog = createAction( + `${CANVAS_PREFIX} Open Edit Label Dialog`, + props<{ request: EditComponentDialogRequest }>() +); + export const navigateToManageRemotePorts = createAction( `${CANVAS_PREFIX} Open Remote Process Group Manage Remote Ports`, props<{ request: RpgManageRemotePortsRequest }>() diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts index eb92454cd2..d5f9d96b55 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts @@ -129,6 +129,7 @@ import { ExtensionTypesService } from '../../../../service/extension-types.servi import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog'; import { SnippetService } from '../../service/snippet.service'; import { selectTransform } from '../transform/transform.selectors'; +import { EditLabel } from '../../ui/canvas/items/label/edit-label/edit-label.component'; @Injectable() export class FlowEffects { @@ -1053,6 +1054,8 @@ export class FlowEffects { case ComponentType.InputPort: case ComponentType.OutputPort: return of(FlowActions.openEditPortDialog({ request })); + case ComponentType.Label: + return of(FlowActions.openEditLabelDialog({ request })); default: return of(FlowActions.flowApiError({ error: 'Unsupported type of Component.' })); } @@ -1119,6 +1122,38 @@ export class FlowEffects { { dispatch: false } ); + openEditLabelDialog$ = createEffect( + () => + this.actions$.pipe( + ofType(FlowActions.openEditLabelDialog), + map((action) => action.request), + tap((request) => { + this.dialog + .open(EditLabel, { + ...MEDIUM_DIALOG, + data: request + }) + .afterClosed() + .subscribe(() => { + this.store.dispatch(FlowActions.clearFlowApiError()); + this.store.dispatch( + FlowActions.selectComponents({ + request: { + components: [ + { + id: request.entity.id, + componentType: request.type + } + ] + } + }) + ); + }); + }) + ), + { dispatch: false } + ); + openEditProcessorDialog$ = createEffect( () => this.actions$.pipe( diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.html new file mode 100644 index 0000000000..8e66d176cc --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.html @@ -0,0 +1,58 @@ + + +

{{ readonly ? 'Label Details' : 'Edit Label' }}

+
+ + +
+ + Label Value + + +
+
+ + Font Size + + @for (option of fontSizeOptions; track option) { + + {{ option.text }} + + } + + +
+
+ @if ({ value: (saving$ | async)! }; as saving) { + + @if (readonly) { + + } @else { + + + } + + } +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.scss b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.scss new file mode 100644 index 0000000000..425fde60b0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.scss @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '@angular/material' as mat; + +.edit-label-form { + @include mat.button-density(-1); + + .mat-mdc-form-field { + width: 100%; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts new file mode 100644 index 0000000000..67007081c6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditLabel } from './edit-label.component'; +import { EditComponentDialogRequest } from '../../../../../state/flow'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ComponentType } from '../../../../../../../state/shared'; +import { provideMockStore } from '@ngrx/store/testing'; +import { initialState } from '../../../../../state/flow/flow.reducer'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service'; + +describe('EditLabel', () => { + let component: EditLabel; + let fixture: ComponentFixture; + + const data: EditComponentDialogRequest = { + type: ComponentType.Label, + uri: 'https://localhost:4200/nifi-api/labels/d7c98831-018e-1000-6a26-6d74b57897e5', + entity: { + revision: { + version: 0 + }, + id: 'd7c98831-018e-1000-6a26-6d74b57897e5', + uri: 'https://localhost:4200/nifi-api/labels/d7c98831-018e-1000-6a26-6d74b57897e5', + position: { + x: 424, + y: -432 + }, + permissions: { + canRead: true, + canWrite: true + }, + dimensions: { + width: 150, + height: 150 + }, + zIndex: 4, + component: { + id: 'd7c98831-018e-1000-6a26-6d74b57897e5', + versionedComponentId: 'c99e7867-73c6-3687-8f27-9b1ee5d761c4', + parentGroupId: 'f588fccd-018d-1000-e6dd-56d56dead186', + position: { + x: 424, + y: -432 + }, + width: 150, + height: 150, + zIndex: 4, + style: {} + } + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [EditLabel, NoopAnimationsModule], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + provideMockStore({ initialState }), + { + provide: ClusterConnectionService, + useValue: { + isDisconnectionAcknowledged: jest.fn() + } + } + ] + }); + fixture = TestBed.createComponent(EditLabel); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.ts new file mode 100644 index 0000000000..abf2ed0ce9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.ts @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { CanvasState } from '../../../../../state'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { updateComponent } from '../../../../../state/flow/flow.actions'; +import { Client } from '../../../../../../../service/client.service'; +import { EditComponentDialogRequest } from '../../../../../state/flow'; +import { SelectOption } from '../../../../../../../state/shared'; +import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component'; +import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatButtonModule } from '@angular/material/button'; +import { AsyncPipe } from '@angular/common'; +import { selectSaving } from '../../../../../state/flow/flow.selectors'; +import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive'; +import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service'; +import { MatOption } from '@angular/material/autocomplete'; +import { MatSelect } from '@angular/material/select'; +import { NifiTooltipDirective } from '../../../../../../../ui/common/tooltips/nifi-tooltip.directive'; + +@Component({ + selector: 'edit-label', + standalone: true, + templateUrl: './edit-label.component.html', + imports: [ + ReactiveFormsModule, + ErrorBanner, + MatDialogModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + AsyncPipe, + NifiSpinnerDirective, + MatOption, + MatSelect, + NifiTooltipDirective + ], + styleUrls: ['./edit-label.component.scss'] +}) +export class EditLabel { + saving$ = this.store.select(selectSaving); + + editLabelForm: FormGroup; + readonly: boolean; + + fontSizeOptions: SelectOption[] = [12, 14, 16, 18, 20, 22, 24].map((fontSize) => { + return { + text: `${fontSize}px`, + value: `${fontSize}px` + }; + }); + + constructor( + @Inject(MAT_DIALOG_DATA) public request: EditComponentDialogRequest, + private formBuilder: FormBuilder, + private store: Store, + private client: Client, + private clusterConnectionService: ClusterConnectionService + ) { + this.readonly = !request.entity.permissions.canWrite; + + let fontSize = this.fontSizeOptions[0].value; + if (request.entity.component.style['font-size']) { + fontSize = request.entity.component.style['font-size']; + } + + // build the form + this.editLabelForm = this.formBuilder.group({ + value: new FormControl(request.entity.component.label, Validators.required), + fontSize: new FormControl(fontSize, Validators.required) + }); + } + + editLabel() { + const payload: any = { + revision: this.client.getRevision(this.request.entity), + disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged(), + component: { + id: this.request.entity.id, + label: this.editLabelForm.get('value')?.value, + style: { + 'font-size': this.editLabelForm.get('fontSize')?.value + } + } + }; + + this.store.dispatch( + updateComponent({ + request: { + id: this.request.entity.id, + type: this.request.type, + uri: this.request.uri, + payload + } + }) + ); + } +}