mirror of https://github.com/apache/nifi.git
NIFI-12425: Controller Service Listing (#8091)
* NIFI-12425: - Controller Service Listing. - Adding lazy loading to the Canvas with the introduction of the Controller Service listing. - Reorganizing existing components in the Flow Designer. - Allowing the current Process Group to be configured. - Inline Service creation. * NIFI-12425: - Removing unused imports. This closes #8091
This commit is contained in:
parent
d40bb3eda6
commit
387a101931
|
@ -102,6 +102,7 @@ import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEnti
|
|||
import org.apache.nifi.web.api.entity.CurrentUserEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisResultEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowBreadcrumbEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowComparisonEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowConfigurationEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowEntity;
|
||||
|
@ -1008,6 +1009,14 @@ public interface NiFiServiceFacade {
|
|||
*/
|
||||
ProcessGroupFlowEntity getProcessGroupFlow(String groupId, boolean uiOnly);
|
||||
|
||||
/**
|
||||
* Returns the breadcrumbs for the specified group.
|
||||
*
|
||||
* @param groupId group id
|
||||
* @return the breadcrumbs
|
||||
*/
|
||||
FlowBreadcrumbEntity getProcessGroupBreadcrumbs(String groupId);
|
||||
|
||||
// ----------------------------------------
|
||||
// ProcessGroup methods
|
||||
// ----------------------------------------
|
||||
|
|
|
@ -297,6 +297,7 @@ import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEnti
|
|||
import org.apache.nifi.web.api.entity.CurrentUserEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisResultEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowBreadcrumbEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowComparisonEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowConfigurationEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowEntity;
|
||||
|
@ -4783,6 +4784,12 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
|||
return entityFactory.createProcessGroupFlowEntity(dtoFactory.createProcessGroupFlowDto(processGroup, groupStatus, revisionManager, this::getProcessGroupBulletins, uiOnly), permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlowBreadcrumbEntity getProcessGroupBreadcrumbs(final String groupId) {
|
||||
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
|
||||
return dtoFactory.createBreadcrumbEntity(processGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessGroupEntity getProcessGroup(final String groupId) {
|
||||
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
|
||||
|
|
|
@ -88,6 +88,7 @@ import org.apache.nifi.web.api.entity.ControllerStatusEntity;
|
|||
import org.apache.nifi.web.api.entity.CurrentUserEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisResultEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowAnalysisRuleTypesEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowBreadcrumbEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowConfigurationEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowRegistryBucketEntity;
|
||||
import org.apache.nifi.web.api.entity.FlowRegistryBucketsEntity;
|
||||
|
@ -401,6 +402,43 @@ public class FlowResource extends ApplicationResource {
|
|||
return generateOkResponse(entity).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes(MediaType.WILDCARD)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("process-groups/{id}/breadcrumbs")
|
||||
@ApiOperation(
|
||||
value = "Gets the breadcrumbs for a process group",
|
||||
response = FlowBreadcrumbEntity.class,
|
||||
authorizations = {
|
||||
@Authorization(value = "Read - /flow")
|
||||
}
|
||||
)
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
|
||||
@ApiResponse(code = 401, message = "Client could not be authenticated."),
|
||||
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
|
||||
@ApiResponse(code = 404, message = "The specified resource could not be found."),
|
||||
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
|
||||
}
|
||||
)
|
||||
public Response getBreadcrumbs(
|
||||
@ApiParam(
|
||||
value = "The process group id."
|
||||
)
|
||||
@PathParam("id") final String groupId) {
|
||||
|
||||
authorizeFlow();
|
||||
|
||||
if (isReplicateRequest()) {
|
||||
return replicate(HttpMethod.GET);
|
||||
}
|
||||
|
||||
// get the breadcrumbs for this process group
|
||||
final FlowBreadcrumbEntity breadcrumbEntity = serviceFacade.getProcessGroupBreadcrumbs(groupId);
|
||||
return generateOkResponse(breadcrumbEntity).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metrics of the entire flow.
|
||||
*
|
||||
|
|
|
@ -1842,7 +1842,6 @@ public final class DtoFactory {
|
|||
Collection<ValidationResult> validationErrors = null;
|
||||
if (component instanceof ProcessorNode) {
|
||||
final ProcessorNode node = ((ProcessorNode) component);
|
||||
dto.setGroupId(node.getProcessGroup().getIdentifier());
|
||||
dto.setState(node.getScheduledState().name());
|
||||
dto.setActiveThreadCount(node.getActiveThreadCount());
|
||||
dto.setType(node.getComponentType());
|
||||
|
@ -1919,6 +1918,7 @@ public final class DtoFactory {
|
|||
orderedProperties.putAll(sortedProperties);
|
||||
|
||||
// build the descriptor and property dtos
|
||||
dto.setGroupId(processGroupId);
|
||||
dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
|
||||
dto.setProperties(new LinkedHashMap<String, String>());
|
||||
for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
|
||||
|
@ -2091,7 +2091,7 @@ public final class DtoFactory {
|
|||
* @param group group
|
||||
* @return dto
|
||||
*/
|
||||
private FlowBreadcrumbEntity createBreadcrumbEntity(final ProcessGroup group) {
|
||||
public FlowBreadcrumbEntity createBreadcrumbEntity(final ProcessGroup group) {
|
||||
if (group == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,8 @@ const routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
canMatch: [authGuard],
|
||||
loadChildren: () => import('./pages/canvas/feature/flow-designer.module').then((m) => m.FlowDesignerModule)
|
||||
loadChildren: () =>
|
||||
import('./pages/flow-designer/feature/flow-designer.module').then((m) => m.FlowDesignerModule)
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FlowDesignerModule } from './pages/canvas/feature/flow-designer.module';
|
||||
import { FlowDesignerModule } from './pages/flow-designer/feature/flow-designer.module';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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 { NgModule } from '@angular/core';
|
||||
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import { HeaderComponent } from './header.component';
|
||||
import { FlowStatus } from './flow-status/flow-status.component';
|
||||
import { CdkDrag } from '@angular/cdk/drag-drop';
|
||||
import { NewCanvasItem } from './new-canvas-item/new-canvas-item.component';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { Search } from './search/search.component';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [HeaderComponent, NewCanvasItem, FlowStatus, Search],
|
||||
exports: [HeaderComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgOptimizedImage,
|
||||
CdkDrag,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
MatDividerModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatAutocompleteModule,
|
||||
CdkConnectedOverlay,
|
||||
CdkOverlayOrigin,
|
||||
RouterLink,
|
||||
NifiTooltipDirective
|
||||
]
|
||||
})
|
||||
export class HeaderModule {}
|
|
@ -25,6 +25,7 @@ import { EffectsModule } from '@ngrx/effects';
|
|||
import { CounterListingEffects } from '../state/counter-listing/counter-listing.effects';
|
||||
import { CounterListingModule } from '../ui/counter-listing/counter-listing.module';
|
||||
import { ParameterContextListingModule } from '../../parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Counters],
|
||||
|
@ -35,7 +36,8 @@ import { ParameterContextListingModule } from '../../parameter-contexts/ui/param
|
|||
StoreModule.forFeature(countersFeatureKey, reducers),
|
||||
EffectsModule.forFeature(CounterListingEffects),
|
||||
CounterListingModule,
|
||||
ParameterContextListingModule
|
||||
ParameterContextListingModule,
|
||||
MatDialogModule
|
||||
]
|
||||
})
|
||||
export class CountersModule {}
|
||||
|
|
|
@ -20,22 +20,28 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
import { FlowDesigner } from './flow-designer.component';
|
||||
import { RootGroupRedirector } from '../ui/root/redirector/root-group-redirector.component';
|
||||
import { rootGroupGuard } from '../ui/root/guard/root-group.guard';
|
||||
import { Canvas } from '../ui/canvas/canvas.component';
|
||||
import { ControllerServices } from '../ui/controller-service/controller-services.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'process-groups/:processGroupId',
|
||||
component: FlowDesigner,
|
||||
children: [
|
||||
{ path: 'bulk/:ids', component: FlowDesigner },
|
||||
{
|
||||
path: ':type/:id',
|
||||
component: FlowDesigner,
|
||||
children: [{ path: 'edit', component: FlowDesigner }]
|
||||
path: 'controller-services',
|
||||
loadChildren: () =>
|
||||
import('../ui/controller-service/controller-services.module').then(
|
||||
(m) => m.ControllerServicesModule
|
||||
)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('../ui/canvas/canvas.module').then((m) => m.CanvasModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{ path: '', component: RootGroupRedirector, canActivate: [rootGroupGuard] }
|
||||
// { path: '**', component: FlowDesignerComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
|
@ -15,8 +15,4 @@
|
|||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="flex flex-col h-screen justify-between">
|
||||
<fd-header></fd-header>
|
||||
<fd-canvas class="flex-1"></fd-canvas>
|
||||
<fd-footer></fd-footer>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
|
@ -20,33 +20,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
import { FlowDesigner } from './flow-designer.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../state/flow/flow.reducer';
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
describe('FlowDesigner', () => {
|
||||
let component: FlowDesigner;
|
||||
let fixture: ComponentFixture<FlowDesigner>;
|
||||
|
||||
@Component({
|
||||
selector: 'fd-header',
|
||||
template: ''
|
||||
})
|
||||
class MockHeader {}
|
||||
|
||||
@Component({
|
||||
selector: 'fd-canvas',
|
||||
template: ''
|
||||
})
|
||||
class MockCanvas {}
|
||||
|
||||
@Component({
|
||||
selector: 'fd-footer',
|
||||
template: ''
|
||||
})
|
||||
class MockFooter {}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FlowDesigner, MockHeader, MockCanvas, MockFooter],
|
||||
declarations: [FlowDesigner],
|
||||
imports: [RouterTestingModule],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
|
@ -19,9 +19,6 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import { FlowDesigner } from './flow-designer.component';
|
||||
import { FlowDesignerRoutingModule } from './flow-designer-routing.module';
|
||||
import { HeaderModule } from '../ui/header/header.module';
|
||||
import { FooterModule } from '../ui/footer/footer.module';
|
||||
import { CanvasModule } from '../ui/canvas/canvas.module';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { FlowEffects } from '../state/flow/flow.effects';
|
||||
|
@ -29,18 +26,16 @@ import { TransformEffects } from '../state/transform/transform.effects';
|
|||
import { VersionControlTip } from '../ui/common/tooltips/version-control-tip/version-control-tip.component';
|
||||
import { canvasFeatureKey, reducers } from '../state';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { ControllerServicesEffects } from '../state/controller-services/controller-services.effects';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FlowDesigner, VersionControlTip],
|
||||
exports: [FlowDesigner],
|
||||
imports: [
|
||||
CommonModule,
|
||||
HeaderModule,
|
||||
CanvasModule,
|
||||
FooterModule,
|
||||
FlowDesignerRoutingModule,
|
||||
StoreModule.forFeature(canvasFeatureKey, reducers),
|
||||
EffectsModule.forFeature(FlowEffects, TransformEffects),
|
||||
EffectsModule.forFeature(FlowEffects, TransformEffects, ControllerServicesEffects),
|
||||
NgOptimizedImage,
|
||||
MatDialogModule
|
||||
]
|
|
@ -25,6 +25,8 @@ import { flowFeatureKey } from '../../state/flow';
|
|||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { CanvasState } from '../../state';
|
||||
import { transformFeatureKey } from '../../state/transform';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('ConnectableBehavior', () => {
|
||||
let service: ConnectableBehavior;
|
||||
|
@ -32,7 +34,8 @@ describe('ConnectableBehavior', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -22,11 +22,12 @@ import * as fromFlow from '../../state/flow/flow.reducer';
|
|||
import * as fromTransform from '../../state/transform/transform.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { initialState } from '../../state/transform/transform.reducer';
|
||||
import { CanvasState } from '../../state';
|
||||
import { flowFeatureKey } from '../../state/flow';
|
||||
import { transformFeatureKey } from '../../state/transform';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('DraggableBehavior', () => {
|
||||
let service: DraggableBehavior;
|
||||
|
@ -34,7 +35,8 @@ describe('DraggableBehavior', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,13 +26,16 @@ import * as fromFlow from '../../state/flow/flow.reducer';
|
|||
import { transformFeatureKey } from '../../state/transform';
|
||||
import * as fromTransform from '../../state/transform/transform.reducer';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('EditableBehaviorService', () => {
|
||||
let service: EditableBehavior;
|
||||
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
|
@ -19,13 +19,14 @@ import { TestBed } from '@angular/core/testing';
|
|||
|
||||
import { QuickSelectBehavior } from './quick-select-behavior.service';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../state/flow/flow.reducer';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { CanvasState } from '../../state';
|
||||
import { flowFeatureKey } from '../../state/flow';
|
||||
import * as fromFlow from '../../state/flow/flow.reducer';
|
||||
import { transformFeatureKey } from '../../state/transform';
|
||||
import * as fromTransform from '../../state/transform/transform.reducer';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('QuickSelectBehavior', () => {
|
||||
let service: QuickSelectBehavior;
|
||||
|
@ -33,7 +34,8 @@ describe('QuickSelectBehavior', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -24,6 +24,8 @@ import { transformFeatureKey } from '../../state/transform';
|
|||
import * as fromTransform from '../../state/transform/transform.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('SelectableBehavior', () => {
|
||||
let service: SelectableBehavior;
|
||||
|
@ -31,7 +33,8 @@ describe('SelectableBehavior', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../state/controller-services';
|
||||
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('BirdseyeView', () => {
|
||||
let service: BirdseyeView;
|
||||
|
@ -33,7 +35,8 @@ describe('BirdseyeView', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -25,7 +25,8 @@ import { transformFeatureKey } from '../state/transform';
|
|||
import * as fromTransform from '../state/transform/transform.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../state/controller-services';
|
||||
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('CanvasUtils', () => {
|
||||
let service: CanvasUtils;
|
||||
|
@ -33,7 +34,8 @@ describe('CanvasUtils', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../state/controller-services';
|
||||
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('CanvasView', () => {
|
||||
let service: CanvasView;
|
||||
|
@ -33,7 +35,8 @@ describe('CanvasView', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Client } from '../../../service/client.service';
|
||||
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||
import { ControllerServiceEntity } from '../../../state/shared';
|
||||
import {
|
||||
ConfigureControllerServiceRequest,
|
||||
CreateControllerServiceRequest,
|
||||
DeleteControllerServiceRequest
|
||||
} from '../state/controller-services';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ControllerServiceService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
/**
|
||||
* The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
|
||||
* does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
|
||||
*
|
||||
* https://stackoverflow.com/a/59586462
|
||||
*
|
||||
* @param url
|
||||
* @private
|
||||
*/
|
||||
private stripProtocol(url: string): string {
|
||||
return this.nifiCommon.substringAfterFirst(url, ':');
|
||||
}
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private client: Client,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {}
|
||||
|
||||
getControllerServices(processGroupId: string): Observable<any> {
|
||||
const uiOnly: any = { uiOnly: true };
|
||||
return this.httpClient.get(
|
||||
`${ControllerServiceService.API}/flow/process-groups/${processGroupId}/controller-services`,
|
||||
{
|
||||
params: uiOnly
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getBreadcrumbs(processGroupId: string): Observable<any> {
|
||||
return this.httpClient.get(`${ControllerServiceService.API}/flow/process-groups/${processGroupId}/breadcrumbs`);
|
||||
}
|
||||
|
||||
getControllerService(id: string): Observable<any> {
|
||||
return this.httpClient.get(`${ControllerServiceService.API}/controller-services/${id}`);
|
||||
}
|
||||
|
||||
createControllerService(createControllerService: CreateControllerServiceRequest): Observable<any> {
|
||||
const processGroupId: string = createControllerService.processGroupId;
|
||||
return this.httpClient.post(
|
||||
`${ControllerServiceService.API}/process-groups/${processGroupId}/controller-services`,
|
||||
{
|
||||
revision: createControllerService.revision,
|
||||
component: {
|
||||
bundle: createControllerService.controllerServiceBundle,
|
||||
type: createControllerService.controllerServiceType
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
|
||||
const params: any = {
|
||||
propertyName,
|
||||
sensitive
|
||||
};
|
||||
return this.httpClient.get(`${ControllerServiceService.API}/controller-services/${id}/descriptors`, {
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
updateControllerService(configureControllerService: ConfigureControllerServiceRequest): Observable<any> {
|
||||
return this.httpClient.put(
|
||||
this.stripProtocol(configureControllerService.uri),
|
||||
configureControllerService.payload
|
||||
);
|
||||
}
|
||||
|
||||
deleteControllerService(deleteControllerService: DeleteControllerServiceRequest): Observable<any> {
|
||||
const entity: ControllerServiceEntity = deleteControllerService.controllerService;
|
||||
const revision: any = this.client.getRevision(entity);
|
||||
return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('ConnectionManager', () => {
|
||||
let service: ConnectionManager;
|
||||
|
@ -33,7 +35,8 @@ describe('ConnectionManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('FunnelManager', () => {
|
||||
let service: FunnelManager;
|
||||
|
@ -33,7 +35,8 @@ describe('FunnelManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('LabelManager', () => {
|
||||
let service: LabelManager;
|
||||
|
@ -33,7 +35,8 @@ describe('LabelManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('PortManager', () => {
|
||||
let service: PortManager;
|
||||
|
@ -33,7 +35,8 @@ describe('PortManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('ProcessGroupManager', () => {
|
||||
let service: ProcessGroupManager;
|
||||
|
@ -33,7 +35,8 @@ describe('ProcessGroupManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('ProcessorManager', () => {
|
||||
let service: ProcessorManager;
|
||||
|
@ -33,7 +35,8 @@ describe('ProcessorManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -26,6 +26,8 @@ import * as fromTransform from '../../state/transform/transform.reducer';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { selectFlowState } from '../../state/flow/flow.selectors';
|
||||
import { selectTransform } from '../../state/transform/transform.selectors';
|
||||
import { controllerServicesFeatureKey } from '../../state/controller-services';
|
||||
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
|
||||
|
||||
describe('RemoteProcessGroupManager', () => {
|
||||
let service: RemoteProcessGroupManager;
|
||||
|
@ -33,7 +35,8 @@ describe('RemoteProcessGroupManager', () => {
|
|||
beforeEach(() => {
|
||||
const initialState: CanvasState = {
|
||||
[flowFeatureKey]: fromFlow.initialState,
|
||||
[transformFeatureKey]: fromTransform.initialState
|
||||
[transformFeatureKey]: fromTransform.initialState,
|
||||
[controllerServicesFeatureKey]: fromControllerServices.initialState
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { createAction, props } from '@ngrx/store';
|
||||
import {
|
||||
ConfigureControllerServiceRequest,
|
||||
ConfigureControllerServiceSuccess,
|
||||
CreateControllerServiceRequest,
|
||||
CreateControllerServiceSuccess,
|
||||
DeleteControllerServiceRequest,
|
||||
DeleteControllerServiceSuccess,
|
||||
LoadControllerServicesRequest,
|
||||
LoadControllerServicesResponse,
|
||||
SelectControllerServiceRequest
|
||||
} from './index';
|
||||
import { EditControllerServiceDialogRequest } from '../../../../state/shared';
|
||||
|
||||
export const loadControllerServices = createAction(
|
||||
'[Controller Services] Load Controller Services',
|
||||
props<{ request: LoadControllerServicesRequest }>()
|
||||
);
|
||||
|
||||
export const loadControllerServicesSuccess = createAction(
|
||||
'[Controller Services] Load Controller Services Success',
|
||||
props<{ response: LoadControllerServicesResponse }>()
|
||||
);
|
||||
|
||||
export const controllerServicesApiError = createAction(
|
||||
'[Controller Services] Load Controller Service Error',
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
export const openNewControllerServiceDialog = createAction('[Controller Services] Open New Controller Service Dialog');
|
||||
|
||||
export const createControllerService = createAction(
|
||||
'[Controller Services] Create Controller Service',
|
||||
props<{ request: CreateControllerServiceRequest }>()
|
||||
);
|
||||
|
||||
export const createControllerServiceSuccess = createAction(
|
||||
'[Controller Services] Create Controller Service Success',
|
||||
props<{ response: CreateControllerServiceSuccess }>()
|
||||
);
|
||||
|
||||
export const inlineCreateControllerServiceSuccess = createAction(
|
||||
'[Controller Services] Inline Create Controller Service Success',
|
||||
props<{ response: CreateControllerServiceSuccess }>()
|
||||
);
|
||||
|
||||
export const navigateToEditService = createAction(
|
||||
'[Controller Services] Navigate To Edit Service',
|
||||
props<{ id: string }>()
|
||||
);
|
||||
|
||||
export const openConfigureControllerServiceDialog = createAction(
|
||||
'[Controller Services] Open Configure Controller Service Dialog',
|
||||
props<{ request: EditControllerServiceDialogRequest }>()
|
||||
);
|
||||
|
||||
export const configureControllerService = createAction(
|
||||
'[Controller Services] Configure Controller Service',
|
||||
props<{ request: ConfigureControllerServiceRequest }>()
|
||||
);
|
||||
|
||||
export const configureControllerServiceSuccess = createAction(
|
||||
'[Controller Services] Configure Controller Service Success',
|
||||
props<{ response: ConfigureControllerServiceSuccess }>()
|
||||
);
|
||||
|
||||
export const promptControllerServiceDeletion = createAction(
|
||||
'[Controller Services] Prompt Controller Service Deletion',
|
||||
props<{ request: DeleteControllerServiceRequest }>()
|
||||
);
|
||||
|
||||
export const deleteControllerService = createAction(
|
||||
'[Controller Services] Delete Controller Service',
|
||||
props<{ request: DeleteControllerServiceRequest }>()
|
||||
);
|
||||
|
||||
export const deleteControllerServiceSuccess = createAction(
|
||||
'[Controller Services] Delete Controller Service Success',
|
||||
props<{ response: DeleteControllerServiceSuccess }>()
|
||||
);
|
||||
|
||||
export const selectControllerService = createAction(
|
||||
'[Controller Services] Select Controller Service',
|
||||
props<{ request: SelectControllerServiceRequest }>()
|
||||
);
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
* 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';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as ControllerServicesActions from './controller-services.actions';
|
||||
import {
|
||||
catchError,
|
||||
combineLatest,
|
||||
from,
|
||||
map,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
withLatestFrom
|
||||
} from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { selectControllerServiceTypes } from '../../../../state/extension-types/extension-types.selectors';
|
||||
import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
|
||||
import { EditControllerService } from '../../../../ui/common/controller-service/edit-controller-service/edit-controller-service.component';
|
||||
import {
|
||||
InlineServiceCreationRequest,
|
||||
InlineServiceCreationResponse,
|
||||
NewPropertyDialogRequest,
|
||||
NewPropertyDialogResponse,
|
||||
Property,
|
||||
PropertyDescriptor
|
||||
} from '../../../../state/shared';
|
||||
import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
|
||||
import { Router } from '@angular/router';
|
||||
import { ExtensionTypesService } from '../../../../service/extension-types.service';
|
||||
import { selectCurrentProcessGroupId, selectSaving } from './controller-services.selectors';
|
||||
import { ControllerServiceService } from '../../service/controller-service.service';
|
||||
|
||||
@Injectable()
|
||||
export class ControllerServicesEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private client: Client,
|
||||
private controllerServiceService: ControllerServiceService,
|
||||
private extensionTypesService: ExtensionTypesService,
|
||||
private dialog: MatDialog,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
loadControllerServices$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.loadControllerServices),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
combineLatest([
|
||||
this.controllerServiceService.getControllerServices(request.processGroupId),
|
||||
this.controllerServiceService.getBreadcrumbs(request.processGroupId)
|
||||
]).pipe(
|
||||
map(([controllerServicesResponse, breadcrumbsResponse]) =>
|
||||
ControllerServicesActions.loadControllerServicesSuccess({
|
||||
response: {
|
||||
processGroupId: breadcrumbsResponse.id,
|
||||
controllerServices: controllerServicesResponse.controllerServices,
|
||||
loadedTimestamp: controllerServicesResponse.currentTime,
|
||||
breadcrumb: breadcrumbsResponse
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
openNewControllerServiceDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.openNewControllerServiceDialog),
|
||||
withLatestFrom(
|
||||
this.store.select(selectControllerServiceTypes),
|
||||
this.store.select(selectCurrentProcessGroupId)
|
||||
),
|
||||
tap(([action, controllerServiceTypes, processGroupId]) => {
|
||||
const dialogReference = this.dialog.open(CreateControllerService, {
|
||||
data: {
|
||||
controllerServiceTypes
|
||||
},
|
||||
panelClass: 'medium-dialog'
|
||||
});
|
||||
|
||||
dialogReference.componentInstance.saving$ = this.store.select(selectSaving);
|
||||
|
||||
dialogReference.componentInstance.createControllerService
|
||||
.pipe(take(1))
|
||||
.subscribe((controllerServiceType) => {
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.createControllerService({
|
||||
request: {
|
||||
revision: {
|
||||
clientId: this.client.getClientId(),
|
||||
version: 0
|
||||
},
|
||||
processGroupId,
|
||||
controllerServiceType: controllerServiceType.type,
|
||||
controllerServiceBundle: controllerServiceType.bundle
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createControllerService$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.createControllerService),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.controllerServiceService.createControllerService(request)).pipe(
|
||||
map((response) =>
|
||||
ControllerServicesActions.createControllerServiceSuccess({
|
||||
response: {
|
||||
controllerService: response
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
createControllerServiceSuccess$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.createControllerServiceSuccess),
|
||||
tap(() => {
|
||||
this.dialog.closeAll();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToEditService$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.navigateToEditService),
|
||||
map((action) => action.id),
|
||||
withLatestFrom(this.store.select(selectCurrentProcessGroupId)),
|
||||
tap(([id, processGroupId]) => {
|
||||
this.router.navigate(['/process-groups', processGroupId, 'controller-services', id, 'edit']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openConfigureControllerServiceDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.openConfigureControllerServiceDialog),
|
||||
map((action) => action.request),
|
||||
withLatestFrom(this.store.select(selectCurrentProcessGroupId)),
|
||||
tap(([request, processGroupId]) => {
|
||||
const serviceId: string = request.id;
|
||||
|
||||
const editDialogReference = this.dialog.open(EditControllerService, {
|
||||
data: {
|
||||
controllerService: request.controllerService
|
||||
},
|
||||
panelClass: 'large-dialog'
|
||||
});
|
||||
|
||||
editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
|
||||
|
||||
editDialogReference.componentInstance.createNewProperty = (
|
||||
existingProperties: string[],
|
||||
allowsSensitive: boolean
|
||||
): Observable<Property> => {
|
||||
const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
|
||||
const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
|
||||
data: dialogRequest,
|
||||
panelClass: 'small-dialog'
|
||||
});
|
||||
|
||||
return newPropertyDialogReference.componentInstance.newProperty.pipe(
|
||||
take(1),
|
||||
switchMap((dialogResponse: NewPropertyDialogResponse) => {
|
||||
return this.controllerServiceService
|
||||
.getPropertyDescriptor(request.id, dialogResponse.name, dialogResponse.sensitive)
|
||||
.pipe(
|
||||
take(1),
|
||||
map((response) => {
|
||||
newPropertyDialogReference.close();
|
||||
|
||||
return {
|
||||
property: dialogResponse.name,
|
||||
value: null,
|
||||
descriptor: response.propertyDescriptor
|
||||
};
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
|
||||
return this.controllerServiceService.getControllerService(serviceId).pipe(
|
||||
take(1),
|
||||
map((serviceEntity) => {
|
||||
return [
|
||||
'/process-groups',
|
||||
serviceEntity.component.parentGroupId,
|
||||
'controller-services',
|
||||
serviceEntity.id
|
||||
];
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.createNewService = (
|
||||
serviceRequest: InlineServiceCreationRequest
|
||||
): Observable<InlineServiceCreationResponse> => {
|
||||
const descriptor: PropertyDescriptor = serviceRequest.descriptor;
|
||||
|
||||
// fetch all services that implement the requested service api
|
||||
return this.extensionTypesService
|
||||
.getImplementingControllerServiceTypes(
|
||||
// @ts-ignore
|
||||
descriptor.identifiesControllerService,
|
||||
descriptor.identifiesControllerServiceBundle
|
||||
)
|
||||
.pipe(
|
||||
take(1),
|
||||
switchMap((implementingTypesResponse) => {
|
||||
// show the create controller service dialog with the types that implemented the interface
|
||||
const createServiceDialogReference = this.dialog.open(CreateControllerService, {
|
||||
data: {
|
||||
controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
|
||||
},
|
||||
panelClass: 'medium-dialog'
|
||||
});
|
||||
|
||||
return createServiceDialogReference.componentInstance.createControllerService.pipe(
|
||||
take(1),
|
||||
switchMap((controllerServiceType) => {
|
||||
// typically this sequence would be implemented with ngrx actions, however we are
|
||||
// currently in an edit session and we need to return both the value (new service id)
|
||||
// and updated property descriptor so the table renders correctly
|
||||
return this.controllerServiceService
|
||||
.createControllerService({
|
||||
revision: {
|
||||
clientId: this.client.getClientId(),
|
||||
version: 0
|
||||
},
|
||||
processGroupId,
|
||||
controllerServiceType: controllerServiceType.type,
|
||||
controllerServiceBundle: controllerServiceType.bundle
|
||||
})
|
||||
.pipe(
|
||||
take(1),
|
||||
switchMap((createReponse) => {
|
||||
// dispatch an inline create service success action so the new service is in the state
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.inlineCreateControllerServiceSuccess(
|
||||
{
|
||||
response: {
|
||||
controllerService: createReponse
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// fetch an updated property descriptor
|
||||
return this.controllerServiceService
|
||||
.getPropertyDescriptor(serviceId, descriptor.name, false)
|
||||
.pipe(
|
||||
take(1),
|
||||
map((descriptorResponse) => {
|
||||
createServiceDialogReference.close();
|
||||
|
||||
return {
|
||||
value: createReponse.id,
|
||||
descriptor:
|
||||
descriptorResponse.propertyDescriptor
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
// TODO - show error
|
||||
return NEVER;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.editControllerService
|
||||
.pipe(take(1))
|
||||
.subscribe((payload: any) => {
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.configureControllerService({
|
||||
request: {
|
||||
id: request.controllerService.id,
|
||||
uri: request.controllerService.uri,
|
||||
payload
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
editDialogReference.afterClosed().subscribe((response) => {
|
||||
if (response != 'ROUTED') {
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.selectControllerService({
|
||||
request: {
|
||||
processGroupId,
|
||||
id: serviceId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
configureControllerService$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.configureControllerService),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.controllerServiceService.updateControllerService(request)).pipe(
|
||||
map((response) =>
|
||||
ControllerServicesActions.configureControllerServiceSuccess({
|
||||
response: {
|
||||
id: request.id,
|
||||
controllerService: response
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
configureControllerServiceSuccess$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.configureControllerServiceSuccess),
|
||||
tap(() => {
|
||||
this.dialog.closeAll();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
promptControllerServiceDeletion$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.promptControllerServiceDeletion),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
const dialogReference = this.dialog.open(YesNoDialog, {
|
||||
data: {
|
||||
title: 'Delete Controller Service',
|
||||
message: `Delete controller service ${request.controllerService.component.name}?`
|
||||
},
|
||||
panelClass: 'small-dialog'
|
||||
});
|
||||
|
||||
dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.deleteControllerService({
|
||||
request
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
deleteControllerService$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.deleteControllerService),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.controllerServiceService.deleteControllerService(request)).pipe(
|
||||
map((response) =>
|
||||
ControllerServicesActions.deleteControllerServiceSuccess({
|
||||
response: {
|
||||
controllerService: response
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
selectControllerService$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.selectControllerService),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
this.router.navigate([
|
||||
'/process-groups',
|
||||
request.processGroupId,
|
||||
'controller-services',
|
||||
request.id
|
||||
]);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
configureControllerService,
|
||||
configureControllerServiceSuccess,
|
||||
controllerServicesApiError,
|
||||
createControllerService,
|
||||
createControllerServiceSuccess,
|
||||
deleteControllerServiceSuccess,
|
||||
inlineCreateControllerServiceSuccess,
|
||||
loadControllerServices,
|
||||
loadControllerServicesSuccess
|
||||
} from './controller-services.actions';
|
||||
import { produce } from 'immer';
|
||||
import { ControllerServicesState } from './index';
|
||||
|
||||
export const initialState: ControllerServicesState = {
|
||||
processGroupId: 'root',
|
||||
controllerServices: [],
|
||||
breadcrumb: {
|
||||
id: '',
|
||||
permissions: {
|
||||
canRead: false,
|
||||
canWrite: false
|
||||
},
|
||||
versionedFlowState: '',
|
||||
breadcrumb: {
|
||||
id: '',
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
saving: false,
|
||||
loadedTimestamp: '',
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
export const controllerServicesReducer = createReducer(
|
||||
initialState,
|
||||
on(loadControllerServices, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
on(loadControllerServicesSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
processGroupId: response.processGroupId,
|
||||
controllerServices: response.controllerServices,
|
||||
breadcrumb: response.breadcrumb,
|
||||
loadedTimestamp: response.loadedTimestamp,
|
||||
error: null,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(controllerServicesApiError, (state, { error }) => ({
|
||||
...state,
|
||||
saving: false,
|
||||
error,
|
||||
status: 'error' as const
|
||||
})),
|
||||
on(createControllerService, (state, { request }) => ({
|
||||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(createControllerServiceSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
draftState.controllerServices.push(response.controllerService);
|
||||
draftState.saving = false;
|
||||
});
|
||||
}),
|
||||
on(inlineCreateControllerServiceSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
draftState.controllerServices.push(response.controllerService);
|
||||
});
|
||||
}),
|
||||
on(configureControllerService, (state, { request }) => ({
|
||||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(configureControllerServiceSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
const componentIndex: number = draftState.controllerServices.findIndex((f: any) => response.id === f.id);
|
||||
if (componentIndex > -1) {
|
||||
draftState.controllerServices[componentIndex] = response.controllerService;
|
||||
}
|
||||
draftState.saving = false;
|
||||
});
|
||||
}),
|
||||
on(deleteControllerServiceSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
const componentIndex: number = draftState.controllerServices.findIndex(
|
||||
(f: any) => response.controllerService.id === f.id
|
||||
);
|
||||
if (componentIndex > -1) {
|
||||
draftState.controllerServices.splice(componentIndex, 1);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { createSelector } from '@ngrx/store';
|
||||
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
|
||||
import { ControllerServiceEntity } from '../../../../state/shared';
|
||||
import { CanvasState, selectCanvasState } from '../index';
|
||||
import { controllerServicesFeatureKey, ControllerServicesState } from './index';
|
||||
|
||||
export const selectControllerServicesState = createSelector(
|
||||
selectCanvasState,
|
||||
(state: CanvasState) => state[controllerServicesFeatureKey]
|
||||
);
|
||||
|
||||
export const selectSaving = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.saving
|
||||
);
|
||||
|
||||
export const selectCurrentProcessGroupId = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.processGroupId
|
||||
);
|
||||
|
||||
export const selectProcessGroupIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route) {
|
||||
// always select the process group from the route
|
||||
return route.params.processGroupId;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectControllerServiceIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route) {
|
||||
// always select the controller service from the route
|
||||
return route.params.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectSingleEditedService = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route?.routeConfig?.path == 'edit') {
|
||||
return route.params.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectServices = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.controllerServices
|
||||
);
|
||||
|
||||
export const selectService = (id: string) =>
|
||||
createSelector(selectServices, (services: ControllerServiceEntity[]) =>
|
||||
services.find((service) => id == service.id)
|
||||
);
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 { Bundle, ControllerServiceEntity, Revision } from '../../../../state/shared';
|
||||
import { BreadcrumbEntity } from '../shared';
|
||||
|
||||
export const controllerServicesFeatureKey = 'controllerServiceListing';
|
||||
|
||||
export interface LoadControllerServicesRequest {
|
||||
processGroupId: string;
|
||||
}
|
||||
|
||||
export interface LoadControllerServicesResponse {
|
||||
processGroupId: string;
|
||||
breadcrumb: BreadcrumbEntity;
|
||||
controllerServices: ControllerServiceEntity[];
|
||||
loadedTimestamp: string;
|
||||
}
|
||||
|
||||
export interface CreateControllerServiceRequest {
|
||||
processGroupId: string;
|
||||
controllerServiceType: string;
|
||||
controllerServiceBundle: Bundle;
|
||||
revision: Revision;
|
||||
}
|
||||
|
||||
export interface CreateControllerServiceSuccess {
|
||||
controllerService: ControllerServiceEntity;
|
||||
}
|
||||
|
||||
export interface ConfigureControllerServiceRequest {
|
||||
id: string;
|
||||
uri: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export interface ConfigureControllerServiceSuccess {
|
||||
id: string;
|
||||
controllerService: ControllerServiceEntity;
|
||||
}
|
||||
|
||||
export interface DeleteControllerServiceRequest {
|
||||
controllerService: ControllerServiceEntity;
|
||||
}
|
||||
|
||||
export interface DeleteControllerServiceSuccess {
|
||||
controllerService: ControllerServiceEntity;
|
||||
}
|
||||
|
||||
export interface SelectControllerServiceRequest {
|
||||
processGroupId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ControllerServicesState {
|
||||
processGroupId: string;
|
||||
breadcrumb: BreadcrumbEntity;
|
||||
controllerServices: ControllerServiceEntity[];
|
||||
saving: boolean;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
|
@ -51,7 +51,9 @@ import {
|
|||
UpdateConnectionRequest,
|
||||
UpdateConnectionSuccess,
|
||||
UpdatePositionsRequest,
|
||||
UploadProcessGroupRequest
|
||||
UploadProcessGroupRequest,
|
||||
EditCurrentProcessGroupRequest,
|
||||
NavigateToControllerServicesRequest
|
||||
} from './index';
|
||||
|
||||
/*
|
||||
|
@ -243,6 +245,20 @@ export const navigateToEditComponent = createAction(
|
|||
|
||||
export const editComponent = createAction('[Canvas] Edit Component', props<{ request: EditComponentDialogRequest }>());
|
||||
|
||||
export const navigateToEditCurrentProcessGroup = createAction('[Canvas] Navigate To Edit Current Process Group');
|
||||
|
||||
export const navigateToControllerServicesForProcessGroup = createAction(
|
||||
'[Canvas] Navigate To Controller Services For Process Group',
|
||||
props<{ request: NavigateToControllerServicesRequest }>()
|
||||
);
|
||||
|
||||
export const editCurrentProcessGroup = createAction(
|
||||
'[Canvas] Edit Current Process Group',
|
||||
props<{
|
||||
request: EditCurrentProcessGroupRequest;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const openEditPortDialog = createAction(
|
||||
'[Canvas] Open Edit Port Dialog',
|
||||
props<{ request: EditComponentDialogRequest }>()
|
|
@ -28,6 +28,7 @@ import {
|
|||
interval,
|
||||
map,
|
||||
mergeMap,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
|
@ -60,15 +61,18 @@ import {
|
|||
} from './flow.selectors';
|
||||
import { ConnectionManager } from '../../service/manager/connection-manager.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CreatePort } from '../../ui/port/create-port/create-port.component';
|
||||
import { EditPort } from '../../ui/port/edit-port/edit-port.component';
|
||||
import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.component';
|
||||
import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component';
|
||||
import {
|
||||
ComponentType,
|
||||
InlineServiceCreationRequest,
|
||||
InlineServiceCreationResponse,
|
||||
NewPropertyDialogRequest,
|
||||
NewPropertyDialogResponse,
|
||||
Parameter,
|
||||
ParameterEntity,
|
||||
Property
|
||||
Property,
|
||||
PropertyDescriptor
|
||||
} from '../../../../state/shared';
|
||||
import { Router } from '@angular/router';
|
||||
import { Client } from '../../../../service/client.service';
|
||||
|
@ -76,16 +80,20 @@ import { CanvasUtils } from '../../service/canvas-utils.service';
|
|||
import { CanvasView } from '../../service/canvas-view.service';
|
||||
import { selectProcessorTypes } from '../../../../state/extension-types/extension-types.selectors';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { CreateProcessor } from '../../ui/processor/create-processor/create-processor.component';
|
||||
import { EditProcessor } from '../../ui/processor/edit-processor/edit-processor.component';
|
||||
import { CreateProcessor } from '../../ui/canvas/items/processor/create-processor/create-processor.component';
|
||||
import { EditProcessor } from '../../ui/canvas/items/processor/edit-processor/edit-processor.component';
|
||||
import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
|
||||
import { BirdseyeView } from '../../service/birdseye-view.service';
|
||||
import { CreateProcessGroup } from '../../ui/process-group/create-process-group/create-process-group.component';
|
||||
import { CreateConnection } from '../../ui/connection/create-connection/create-connection.component';
|
||||
import { EditConnectionComponent } from '../../ui/connection/edit-connection/edit-connection.component';
|
||||
import { CreateProcessGroup } from '../../ui/canvas/items/process-group/create-process-group/create-process-group.component';
|
||||
import { CreateConnection } from '../../ui/canvas/items/connection/create-connection/create-connection.component';
|
||||
import { EditConnectionComponent } from '../../ui/canvas/items/connection/edit-connection/edit-connection.component';
|
||||
import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
|
||||
import { GroupComponents } from '../../ui/process-group/group-components/group-components.component';
|
||||
import { EditProcessGroup } from '../../ui/process-group/edit-process-group/edit-process-group.component';
|
||||
import { GroupComponents } from '../../ui/canvas/items/process-group/group-components/group-components.component';
|
||||
import { EditProcessGroup } from '../../ui/canvas/items/process-group/edit-process-group/edit-process-group.component';
|
||||
import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
|
||||
import * as ControllerServicesActions from '../controller-services/controller-services.actions';
|
||||
import { ExtensionTypesService } from '../../../../service/extension-types.service';
|
||||
import { ControllerServiceService } from '../../service/controller-service.service';
|
||||
|
||||
@Injectable()
|
||||
export class FlowEffects {
|
||||
|
@ -93,6 +101,8 @@ export class FlowEffects {
|
|||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private flowService: FlowService,
|
||||
private extensionTypesService: ExtensionTypesService,
|
||||
private controllerServiceService: ControllerServiceService,
|
||||
private client: Client,
|
||||
private canvasUtils: CanvasUtils,
|
||||
private canvasView: CanvasView,
|
||||
|
@ -642,7 +652,31 @@ export class FlowEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
editComponentRequest$ = createEffect(() =>
|
||||
navigateToEditCurrentProcessGroup$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.navigateToEditCurrentProcessGroup),
|
||||
withLatestFrom(this.store.select(selectCurrentProcessGroupId)),
|
||||
tap(([action, processGroupId]) => {
|
||||
this.router.navigate(['/process-groups', processGroupId, 'edit']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToControllerServicesForProcessGroup$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.navigateToControllerServicesForProcessGroup),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
this.router.navigate(['/process-groups', request.id, 'controller-services']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
editComponent$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.editComponent),
|
||||
map((action) => action.request),
|
||||
|
@ -664,6 +698,33 @@ export class FlowEffects {
|
|||
)
|
||||
);
|
||||
|
||||
editCurrentProcessGroup$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.editCurrentProcessGroup),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.flowService.getProcessGroup(request.id)).pipe(
|
||||
map((response) =>
|
||||
FlowActions.openEditProcessGroupDialog({
|
||||
request: {
|
||||
type: ComponentType.ProcessGroup,
|
||||
uri: response.uri,
|
||||
entity: response
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
FlowActions.flowApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
openEditPortDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
@ -701,8 +762,13 @@ export class FlowEffects {
|
|||
this.actions$.pipe(
|
||||
ofType(FlowActions.openEditProcessorDialog),
|
||||
map((action) => action.request),
|
||||
withLatestFrom(this.store.select(selectCurrentParameterContext)),
|
||||
tap(([request, parameterContext]) => {
|
||||
withLatestFrom(
|
||||
this.store.select(selectCurrentParameterContext),
|
||||
this.store.select(selectCurrentProcessGroupId)
|
||||
),
|
||||
tap(([request, parameterContext, processGroupId]) => {
|
||||
const processorId: string = request.entity.id;
|
||||
|
||||
const editDialogReference = this.dialog.open(EditProcessor, {
|
||||
data: request,
|
||||
panelClass: 'large-dialog'
|
||||
|
@ -724,11 +790,7 @@ export class FlowEffects {
|
|||
take(1),
|
||||
switchMap((dialogResponse: NewPropertyDialogResponse) => {
|
||||
return this.flowService
|
||||
.getPropertyDescriptor(
|
||||
request.entity.id,
|
||||
dialogResponse.name,
|
||||
dialogResponse.sensitive
|
||||
)
|
||||
.getPropertyDescriptor(processorId, dialogResponse.name, dialogResponse.sensitive)
|
||||
.pipe(
|
||||
take(1),
|
||||
map((response) => {
|
||||
|
@ -763,7 +825,6 @@ export class FlowEffects {
|
|||
return this.flowService.getControllerService(serviceId).pipe(
|
||||
take(1),
|
||||
map((serviceEntity) => {
|
||||
// TODO - finalize once route is defined
|
||||
return [
|
||||
'/process-groups',
|
||||
serviceEntity.component.parentGroupId,
|
||||
|
@ -774,7 +835,85 @@ export class FlowEffects {
|
|||
);
|
||||
};
|
||||
|
||||
// TODO - inline service creation...
|
||||
editDialogReference.componentInstance.createNewService = (
|
||||
serviceRequest: InlineServiceCreationRequest
|
||||
): Observable<InlineServiceCreationResponse> => {
|
||||
const descriptor: PropertyDescriptor = serviceRequest.descriptor;
|
||||
|
||||
// fetch all services that implement the requested service api
|
||||
return this.extensionTypesService
|
||||
.getImplementingControllerServiceTypes(
|
||||
// @ts-ignore
|
||||
descriptor.identifiesControllerService,
|
||||
descriptor.identifiesControllerServiceBundle
|
||||
)
|
||||
.pipe(
|
||||
take(1),
|
||||
switchMap((implementingTypesResponse) => {
|
||||
// show the create controller service dialog with the types that implemented the interface
|
||||
const createServiceDialogReference = this.dialog.open(CreateControllerService, {
|
||||
data: {
|
||||
controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
|
||||
},
|
||||
panelClass: 'medium-dialog'
|
||||
});
|
||||
|
||||
return createServiceDialogReference.componentInstance.createControllerService.pipe(
|
||||
take(1),
|
||||
switchMap((controllerServiceType) => {
|
||||
// typically this sequence would be implemented with ngrx actions, however we are
|
||||
// currently in an edit session and we need to return both the value (new service id)
|
||||
// and updated property descriptor so the table renders correctly
|
||||
return this.controllerServiceService
|
||||
.createControllerService({
|
||||
revision: {
|
||||
clientId: this.client.getClientId(),
|
||||
version: 0
|
||||
},
|
||||
processGroupId,
|
||||
controllerServiceType: controllerServiceType.type,
|
||||
controllerServiceBundle: controllerServiceType.bundle
|
||||
})
|
||||
.pipe(
|
||||
take(1),
|
||||
switchMap((createReponse) => {
|
||||
// dispatch an inline create service success action so the new service is in the state
|
||||
this.store.dispatch(
|
||||
ControllerServicesActions.inlineCreateControllerServiceSuccess(
|
||||
{
|
||||
response: {
|
||||
controllerService: createReponse
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// fetch an updated property descriptor
|
||||
return this.flowService
|
||||
.getPropertyDescriptor(processorId, descriptor.name, false)
|
||||
.pipe(
|
||||
take(1),
|
||||
map((descriptorResponse) => {
|
||||
createServiceDialogReference.close();
|
||||
|
||||
return {
|
||||
value: createReponse.id,
|
||||
descriptor:
|
||||
descriptorResponse.propertyDescriptor
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
// TODO - show error
|
||||
return NEVER;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.editProcessor
|
||||
.pipe(takeUntil(editDialogReference.afterClosed()))
|
||||
|
@ -782,7 +921,7 @@ export class FlowEffects {
|
|||
this.store.dispatch(
|
||||
FlowActions.updateProcessor({
|
||||
request: {
|
||||
id: request.entity.id,
|
||||
id: processorId,
|
||||
uri: request.uri,
|
||||
type: request.type,
|
||||
payload
|
||||
|
@ -791,20 +930,23 @@ export class FlowEffects {
|
|||
);
|
||||
});
|
||||
|
||||
editDialogReference.afterClosed().subscribe(() => {
|
||||
editDialogReference.afterClosed().subscribe((response) => {
|
||||
this.store.dispatch(FlowActions.clearFlowApiError());
|
||||
this.store.dispatch(
|
||||
FlowActions.selectComponents({
|
||||
request: {
|
||||
components: [
|
||||
{
|
||||
id: request.entity.id,
|
||||
componentType: request.type
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (response != 'ROUTED') {
|
||||
this.store.dispatch(
|
||||
FlowActions.selectComponents({
|
||||
request: {
|
||||
components: [
|
||||
{
|
||||
id: processorId,
|
||||
componentType: request.type
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
|
@ -111,6 +111,16 @@ export const selectSingleEditedComponent = createSelector(selectCurrentRoute, (r
|
|||
return selectedComponent;
|
||||
});
|
||||
|
||||
export const selectEditedCurrentProcessGroup = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route?.routeConfig?.path == 'edit') {
|
||||
if (route.params.ids == null && route.params.type == null) {
|
||||
return route.params.processGroupId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectTransitionRequired = createSelector(selectFlowState, (state: FlowState) => state.transitionRequired);
|
||||
|
||||
export const selectDragging = createSelector(selectFlowState, (state: FlowState) => state.dragging);
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Position } from '../shared';
|
||||
import { BreadcrumbEntity, Position } from '../shared';
|
||||
import {
|
||||
BulletinEntity,
|
||||
Bundle,
|
||||
|
@ -220,6 +220,14 @@ export interface EditComponentDialogRequest {
|
|||
entity: any;
|
||||
}
|
||||
|
||||
export interface NavigateToControllerServicesRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface EditCurrentProcessGroupRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface EditConnectionDialogRequest extends EditComponentDialogRequest {
|
||||
newDestination?: {
|
||||
type: ComponentType | null;
|
||||
|
@ -362,20 +370,6 @@ export interface ComponentEntity {
|
|||
component: any;
|
||||
}
|
||||
|
||||
export interface Breadcrumb {
|
||||
id: string;
|
||||
name: string;
|
||||
versionControlInformation?: VersionControlInformation;
|
||||
}
|
||||
|
||||
export interface BreadcrumbEntity {
|
||||
id: string;
|
||||
permissions: Permissions;
|
||||
versionedFlowState: string;
|
||||
breadcrumb: Breadcrumb;
|
||||
parentBreadcrumb?: BreadcrumbEntity;
|
||||
}
|
||||
|
||||
export interface Relationship {
|
||||
autoTerminate: boolean;
|
||||
description: string;
|
|
@ -24,18 +24,22 @@ import { flowFeatureKey, FlowState } from './flow';
|
|||
import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
|
||||
import { transformReducer } from './transform/transform.reducer';
|
||||
import { flowReducer } from './flow/flow.reducer';
|
||||
import { controllerServicesFeatureKey, ControllerServicesState } from './controller-services';
|
||||
import { controllerServicesReducer } from './controller-services/controller-services.reducer';
|
||||
|
||||
export const canvasFeatureKey = 'canvas';
|
||||
|
||||
export interface CanvasState {
|
||||
[flowFeatureKey]: FlowState;
|
||||
[transformFeatureKey]: CanvasTransform;
|
||||
[controllerServicesFeatureKey]: ControllerServicesState;
|
||||
}
|
||||
|
||||
export function reducers(state: CanvasState | undefined, action: Action) {
|
||||
return combineReducers({
|
||||
[flowFeatureKey]: flowReducer,
|
||||
[transformFeatureKey]: transformReducer
|
||||
[transformFeatureKey]: transformReducer,
|
||||
[controllerServicesFeatureKey]: controllerServicesReducer
|
||||
})(state, action);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { Permissions } from '../../../../state/shared';
|
||||
import { VersionControlInformation } from '../flow';
|
||||
|
||||
export interface Dimension {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Breadcrumb {
|
||||
id: string;
|
||||
name: string;
|
||||
versionControlInformation?: VersionControlInformation;
|
||||
}
|
||||
|
||||
export interface BreadcrumbEntity {
|
||||
id: string;
|
||||
permissions: Permissions;
|
||||
versionedFlowState: string;
|
||||
breadcrumb: Breadcrumb;
|
||||
parentBreadcrumb?: BreadcrumbEntity;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Canvas } from './canvas.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: Canvas,
|
||||
children: [
|
||||
{ path: 'bulk/:ids', component: Canvas },
|
||||
{ path: 'edit', component: Canvas },
|
||||
{
|
||||
path: ':type/:id',
|
||||
component: Canvas,
|
||||
children: [{ path: 'edit', component: Canvas }]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class CanvasRoutingModule {}
|
|
@ -15,6 +15,12 @@
|
|||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div id="canvas-container" class="canvas-background h-full" [cdkContextMenuTriggerFor]="contextMenu.menu"></div>
|
||||
<fd-context-menu #contextMenu menuId="root"></fd-context-menu>
|
||||
<graph-controls></graph-controls>
|
||||
<div class="flex flex-col h-screen justify-between">
|
||||
<fd-header></fd-header>
|
||||
<div class="flex-1">
|
||||
<div id="canvas-container" class="canvas-background h-full" [cdkContextMenuTriggerFor]="contextMenu.menu"></div>
|
||||
<fd-context-menu #contextMenu menuId="root"></fd-context-menu>
|
||||
<graph-controls></graph-controls>
|
||||
</div>
|
||||
<fd-footer></fd-footer>
|
||||
</div>
|
|
@ -21,23 +21,35 @@ import { Canvas } from './canvas.component';
|
|||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../state/flow/flow.reducer';
|
||||
import { ContextMenu } from './context-menu/context-menu.component';
|
||||
import { GraphControls } from './graph-controls/graph-controls.component';
|
||||
import { OperationControl } from './operation-control/operation-control.component';
|
||||
import { NavigationControl } from './navigation-control/navigation-control.component';
|
||||
import { Component } from '@angular/core';
|
||||
import { CdkContextMenuTrigger } from '@angular/cdk/menu';
|
||||
import { selectBreadcrumbs } from '../../state/flow/flow.selectors';
|
||||
import { BreadcrumbEntity } from '../../state/flow';
|
||||
import { BreadcrumbEntity } from '../../state/shared';
|
||||
|
||||
describe('Canvas', () => {
|
||||
let component: Canvas;
|
||||
let fixture: ComponentFixture<Canvas>;
|
||||
|
||||
@Component({
|
||||
selector: 'birdseye',
|
||||
selector: 'fd-header',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockBirdseye {}
|
||||
class MockHeader {}
|
||||
|
||||
@Component({
|
||||
selector: 'fd-footer',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockFooter {}
|
||||
|
||||
@Component({
|
||||
selector: 'graph-controls',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockGraphControls {}
|
||||
|
||||
beforeEach(() => {
|
||||
const breadcrumbEntity: BreadcrumbEntity = {
|
||||
|
@ -54,8 +66,8 @@ describe('Canvas', () => {
|
|||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Canvas, ContextMenu, GraphControls, OperationControl, NavigationControl, MockBirdseye],
|
||||
imports: [CdkContextMenuTrigger],
|
||||
declarations: [Canvas],
|
||||
imports: [CdkContextMenuTrigger, ContextMenu, MockGraphControls, MockHeader, MockFooter],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState,
|
|
@ -23,6 +23,7 @@ import {
|
|||
centerSelectedComponent,
|
||||
deselectAllComponents,
|
||||
editComponent,
|
||||
editCurrentProcessGroup,
|
||||
loadProcessGroup,
|
||||
selectComponents,
|
||||
setSkipTransform,
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
selectBulkSelectedComponentIds,
|
||||
selectConnection,
|
||||
selectCurrentProcessGroupId,
|
||||
selectEditedCurrentProcessGroup,
|
||||
selectFunnel,
|
||||
selectInputPort,
|
||||
selectLabel,
|
||||
|
@ -216,6 +218,23 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
// edit the current process group from the route
|
||||
this.store
|
||||
.select(selectEditedCurrentProcessGroup)
|
||||
.pipe(
|
||||
filter((processGroupId) => processGroupId != null),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe((processGroupId) => {
|
||||
this.store.dispatch(
|
||||
editCurrentProcessGroup({
|
||||
request: {
|
||||
id: processGroupId
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
|
@ -21,13 +21,24 @@ import { Canvas } from './canvas.component';
|
|||
import { ContextMenu } from './context-menu/context-menu.component';
|
||||
import { CdkContextMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
|
||||
import { GraphControls } from './graph-controls/graph-controls.component';
|
||||
import { NavigationControl } from './navigation-control/navigation-control.component';
|
||||
import { OperationControl } from './operation-control/operation-control.component';
|
||||
import { Birdseye } from './birdseye/birdseye.component';
|
||||
import { CanvasRoutingModule } from './canvas-routing.module';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Canvas, ContextMenu, GraphControls, NavigationControl, Birdseye, OperationControl],
|
||||
declarations: [Canvas],
|
||||
exports: [Canvas],
|
||||
imports: [CommonModule, CdkMenu, CdkMenuItem, CdkMenuTrigger, CdkContextMenuTrigger]
|
||||
imports: [
|
||||
CommonModule,
|
||||
CdkMenu,
|
||||
CdkMenuItem,
|
||||
CdkMenuTrigger,
|
||||
CdkContextMenuTrigger,
|
||||
CanvasRoutingModule,
|
||||
GraphControls,
|
||||
ContextMenu,
|
||||
HeaderComponent,
|
||||
FooterComponent
|
||||
]
|
||||
})
|
||||
export class CanvasModule {}
|
|
@ -27,7 +27,7 @@ describe('ContextMenu', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ContextMenu],
|
||||
imports: [ContextMenu],
|
||||
providers: [provideMockStore({ initialState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(ContextMenu);
|
|
@ -27,12 +27,16 @@ import {
|
|||
leaveProcessGroup,
|
||||
moveComponents,
|
||||
navigateToComponent,
|
||||
navigateToControllerServicesForProcessGroup,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
reloadFlow
|
||||
} from '../../../state/flow/flow.actions';
|
||||
import { CanvasUtils } from '../../../service/canvas-utils.service';
|
||||
import { DeleteComponentRequest, MoveComponentRequest } from '../../../state/flow';
|
||||
import { ComponentType } from '../../../../../state/shared';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
|
||||
|
||||
export interface ContextMenuItemDefinition {
|
||||
isSeparator?: boolean;
|
||||
|
@ -50,7 +54,9 @@ export interface ContextMenuDefinition {
|
|||
|
||||
@Component({
|
||||
selector: 'fd-context-menu',
|
||||
standalone: true,
|
||||
templateUrl: './context-menu.component.html',
|
||||
imports: [NgForOf, AsyncPipe, CdkMenu, CdkMenuItem, NgIf, CdkMenuTrigger],
|
||||
styleUrls: ['./context-menu.component.scss']
|
||||
})
|
||||
export class ContextMenu implements OnInit {
|
||||
|
@ -286,8 +292,9 @@ export class ContextMenu implements OnInit {
|
|||
clazz: 'fa fa-gear',
|
||||
text: 'Configure',
|
||||
action: function (store: Store<CanvasState>, selection: any) {
|
||||
// TODO - when selection is empty support configuring the current Process Group
|
||||
if (!selection.empty()) {
|
||||
if (selection.empty()) {
|
||||
store.dispatch(navigateToEditCurrentProcessGroup());
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
store.dispatch(
|
||||
navigateToEditComponent({
|
||||
|
@ -300,6 +307,33 @@ export class ContextMenu implements OnInit {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: function (canvasUtils: CanvasUtils, selection: any) {
|
||||
return canvasUtils.isProcessGroup(selection) || selection.empty();
|
||||
},
|
||||
clazz: 'fa fa-list',
|
||||
text: 'Controller Services',
|
||||
action: function (store: Store<CanvasState>, selection: any, canvasUtils: CanvasUtils) {
|
||||
if (selection.empty()) {
|
||||
store.dispatch(
|
||||
navigateToControllerServicesForProcessGroup({
|
||||
request: {
|
||||
id: canvasUtils.getProcessGroupId()
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
store.dispatch(
|
||||
navigateToControllerServicesForProcessGroup({
|
||||
request: {
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
condition: function (canvasUtils: CanvasUtils, selection: any) {
|
||||
// TODO - hasDetails
|
|
@ -16,7 +16,7 @@
|
|||
-->
|
||||
|
||||
<footer>
|
||||
<div class="bg-nifi-accent">
|
||||
<div class="bg-nifi-accent breadcrumb-container">
|
||||
<breadcrumbs
|
||||
[entity]="(breadcrumbs$ | async)!"
|
||||
[currentProcessGroupId]="(currentProcessGroupId$ | async)!"></breadcrumbs>
|
|
@ -14,3 +14,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.breadcrumb-container {
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
|
||||
background-color: rgba(249, 250, 251, 0.9);
|
||||
border-top: 1px solid #aabbc3;
|
||||
color: #598599;
|
||||
z-index: 3;
|
||||
}
|
|
@ -19,12 +19,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { FooterComponent } from './footer.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../state/flow/flow.reducer';
|
||||
import { Breadcrumbs } from './breadcrumbs/breadcrumbs.component';
|
||||
import { BreadcrumbEntity } from '../../state/flow';
|
||||
import { selectBreadcrumbs } from '../../state/flow/flow.selectors';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { selectBreadcrumbs } from '../../../state/flow/flow.selectors';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { BreadcrumbEntity } from '../../../state/shared';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
|
@ -45,7 +44,6 @@ describe('FooterComponent', () => {
|
|||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FooterComponent, Breadcrumbs],
|
||||
imports: [RouterModule, RouterTestingModule],
|
||||
providers: [
|
||||
provideMockStore({
|
|
@ -16,13 +16,17 @@
|
|||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { selectBreadcrumbs, selectCurrentProcessGroupId } from '../../state/flow/flow.selectors';
|
||||
import { selectBreadcrumbs, selectCurrentProcessGroupId } from '../../../state/flow/flow.selectors';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CanvasState } from '../../state';
|
||||
import { CanvasState } from '../../../state';
|
||||
import { Breadcrumbs } from '../../common/breadcrumbs/breadcrumbs.component';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'fd-footer',
|
||||
standalone: true,
|
||||
templateUrl: './footer.component.html',
|
||||
imports: [Breadcrumbs, AsyncPipe],
|
||||
styleUrls: ['./footer.component.scss']
|
||||
})
|
||||
export class FooterComponent {
|
|
@ -20,11 +20,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
import { GraphControls } from './graph-controls.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { NavigationControl } from '../navigation-control/navigation-control.component';
|
||||
import { OperationControl } from '../operation-control/operation-control.component';
|
||||
import { NavigationControl } from './navigation-control/navigation-control.component';
|
||||
import { OperationControl } from './operation-control/operation-control.component';
|
||||
import { Component } from '@angular/core';
|
||||
import { BreadcrumbEntity } from '../../../state/flow';
|
||||
import { selectBreadcrumbs } from '../../../state/flow/flow.selectors';
|
||||
import { Birdseye } from './navigation-control/birdseye/birdseye.component';
|
||||
import { BreadcrumbEntity } from '../../../state/shared';
|
||||
|
||||
describe('GraphControls', () => {
|
||||
let component: GraphControls;
|
||||
|
@ -32,6 +33,7 @@ describe('GraphControls', () => {
|
|||
|
||||
@Component({
|
||||
selector: 'birdseye',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockBirdseye {}
|
||||
|
@ -51,7 +53,7 @@ describe('GraphControls', () => {
|
|||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GraphControls, NavigationControl, OperationControl, MockBirdseye],
|
||||
imports: [GraphControls, NavigationControl, OperationControl, MockBirdseye],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState,
|
||||
|
@ -63,6 +65,13 @@ describe('GraphControls', () => {
|
|||
]
|
||||
})
|
||||
]
|
||||
}).overrideComponent(NavigationControl, {
|
||||
remove: {
|
||||
imports: [Birdseye]
|
||||
},
|
||||
add: {
|
||||
imports: [MockBirdseye]
|
||||
}
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(GraphControls);
|
|
@ -23,10 +23,15 @@ import {
|
|||
selectNavigationCollapsed,
|
||||
selectOperationCollapsed
|
||||
} from '../../../state/flow/flow.selectors';
|
||||
import { NavigationControl } from './navigation-control/navigation-control.component';
|
||||
import { OperationControl } from './operation-control/operation-control.component';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'graph-controls',
|
||||
standalone: true,
|
||||
templateUrl: './graph-controls.component.html',
|
||||
imports: [NavigationControl, OperationControl, AsyncPipe],
|
||||
styleUrls: ['./graph-controls.component.scss']
|
||||
})
|
||||
export class GraphControls {
|
|
@ -18,7 +18,7 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Birdseye } from './birdseye.component';
|
||||
import { BirdseyeView } from '../../../service/birdseye-view.service';
|
||||
import { BirdseyeView } from '../../../../../service/birdseye-view.service';
|
||||
import SpyObj = jasmine.SpyObj;
|
||||
import createSpyObj = jasmine.createSpyObj;
|
||||
|
||||
|
@ -31,7 +31,7 @@ describe('Birdseye', () => {
|
|||
birdseyeViewSpy = createSpyObj('BirdseyeView', ['init', 'refresh']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Birdseye],
|
||||
imports: [Birdseye],
|
||||
providers: [{ provide: BirdseyeView, useValue: birdseyeViewSpy }]
|
||||
});
|
||||
fixture = TestBed.createComponent(Birdseye);
|
|
@ -16,10 +16,11 @@
|
|||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BirdseyeView } from '../../../service/birdseye-view.service';
|
||||
import { BirdseyeView } from '../../../../../service/birdseye-view.service';
|
||||
|
||||
@Component({
|
||||
selector: 'birdseye',
|
||||
standalone: true,
|
||||
templateUrl: './birdseye.component.html',
|
||||
styleUrls: ['./birdseye.component.scss']
|
||||
})
|
|
@ -19,8 +19,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { NavigationControl } from './navigation-control.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||
import { Component } from '@angular/core';
|
||||
import { Birdseye } from './birdseye/birdseye.component';
|
||||
|
||||
describe('NavigationControl', () => {
|
||||
let component: NavigationControl;
|
||||
|
@ -28,19 +29,28 @@ describe('NavigationControl', () => {
|
|||
|
||||
@Component({
|
||||
selector: 'birdseye',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockBirdseye {}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [NavigationControl, MockBirdseye],
|
||||
imports: [NavigationControl, MockBirdseye],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
||||
})
|
||||
]
|
||||
}).overrideComponent(NavigationControl, {
|
||||
remove: {
|
||||
imports: [Birdseye]
|
||||
},
|
||||
add: {
|
||||
imports: [MockBirdseye]
|
||||
}
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(NavigationControl);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
|
@ -17,16 +17,20 @@
|
|||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CanvasState } from '../../../state';
|
||||
import { zoomActual, zoomFit, zoomIn, zoomOut } from '../../../state/transform/transform.actions';
|
||||
import { leaveProcessGroup, setNavigationCollapsed } from '../../../state/flow/flow.actions';
|
||||
import { CanvasUtils } from '../../../service/canvas-utils.service';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { Storage } from '../../../../../service/storage.service';
|
||||
import { CanvasState } from '../../../../state';
|
||||
import { zoomActual, zoomFit, zoomIn, zoomOut } from '../../../../state/transform/transform.actions';
|
||||
import { leaveProcessGroup, setNavigationCollapsed } from '../../../../state/flow/flow.actions';
|
||||
import { CanvasUtils } from '../../../../service/canvas-utils.service';
|
||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||
import { Storage } from '../../../../../../service/storage.service';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Birdseye } from './birdseye/birdseye.component';
|
||||
|
||||
@Component({
|
||||
selector: 'navigation-control',
|
||||
standalone: true,
|
||||
templateUrl: './navigation-control.component.html',
|
||||
imports: [NgIf, Birdseye],
|
||||
styleUrls: ['./navigation-control.component.scss']
|
||||
})
|
||||
export class NavigationControl {
|
|
@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { OperationControl } from './operation-control.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||
|
||||
describe('OperationControl', () => {
|
||||
let component: OperationControl;
|
||||
|
@ -27,7 +27,7 @@ describe('OperationControl', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [OperationControl],
|
||||
imports: [OperationControl],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
|
@ -21,17 +21,21 @@ import {
|
|||
getParameterContextsAndOpenGroupComponentsDialog,
|
||||
navigateToEditComponent,
|
||||
setOperationCollapsed
|
||||
} from '../../../state/flow/flow.actions';
|
||||
} from '../../../../state/flow/flow.actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CanvasState } from '../../../state';
|
||||
import { CanvasUtils } from '../../../service/canvas-utils.service';
|
||||
import { initialState } from '../../../state/flow/flow.reducer';
|
||||
import { Storage } from '../../../../../service/storage.service';
|
||||
import { BreadcrumbEntity, DeleteComponentRequest, MoveComponentRequest } from '../../../state/flow';
|
||||
import { CanvasState } from '../../../../state';
|
||||
import { CanvasUtils } from '../../../../service/canvas-utils.service';
|
||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||
import { Storage } from '../../../../../../service/storage.service';
|
||||
import { DeleteComponentRequest, MoveComponentRequest } from '../../../../state/flow';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { BreadcrumbEntity } from '../../../../state/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'operation-control',
|
||||
standalone: true,
|
||||
templateUrl: './operation-control.component.html',
|
||||
imports: [NgIf],
|
||||
styleUrls: ['./operation-control.component.scss']
|
||||
})
|
||||
export class OperationControl {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue