NIFI-11658 Streamline using single Parameter Context for nested PGs

This closes #7353

Co-authored-by: Matt Gilman <mcgilman@apache.org>
Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Timea Barna 2023-06-02 10:58:44 +02:00 committed by exceptionfactory
parent a61add22c2
commit 42910e80d1
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
21 changed files with 473 additions and 205 deletions

View File

@ -56,6 +56,8 @@ public class ProcessGroupEntity extends ComponentEntity implements Permissible<P
private ParameterContextReferenceEntity parameterContext;
private String processGroupUpdateStrategy;
/**
* The ProcessGroupDTO that is being serialized.
*
@ -328,4 +330,16 @@ public class ProcessGroupEntity extends ComponentEntity implements Permissible<P
public void setParameterContext(ParameterContextReferenceEntity parameterContext) {
this.parameterContext = parameterContext;
}
@ApiModelProperty(
value = "Determines the process group update strategy",
allowableValues = "CURRENT_GROUP, CURRENT_GROUP_WITH_CHILDREN"
)
public String getProcessGroupUpdateStrategy() {
return processGroupUpdateStrategy;
}
public void setProcessGroupUpdateStrategy(String processGroupUpdateStrategy) {
this.processGroupUpdateStrategy = processGroupUpdateStrategy;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
package org.apache.nifi.web.api.entity;
public enum ProcessGroupUpdateStrategy {
CURRENT_GROUP,
CURRENT_GROUP_WITH_CHILDREN
}

View File

@ -90,7 +90,6 @@ import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
import org.apache.nifi.web.api.entity.FlowRegistryBucketEntity;
import org.apache.nifi.web.api.entity.BulletinEntity;
import org.apache.nifi.web.api.entity.ComponentValidationResultEntity;
import org.apache.nifi.web.api.entity.ConfigurationAnalysisEntity;
@ -105,6 +104,8 @@ import org.apache.nifi.web.api.entity.CurrentUserEntity;
import org.apache.nifi.web.api.entity.FlowComparisonEntity;
import org.apache.nifi.web.api.entity.FlowConfigurationEntity;
import org.apache.nifi.web.api.entity.FlowEntity;
import org.apache.nifi.web.api.entity.FlowRegistryBucketEntity;
import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
import org.apache.nifi.web.api.entity.FunnelEntity;
import org.apache.nifi.web.api.entity.LabelEntity;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
@ -115,11 +116,11 @@ import org.apache.nifi.web.api.entity.PortStatusEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import org.apache.nifi.web.api.entity.ProcessorDiagnosticsEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorsRunStatusDetailsEntity;
import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
@ -1209,9 +1210,10 @@ public interface NiFiServiceFacade {
* Gets all process groups in the specified parent group.
*
* @param parentGroupId The id of the parent group
* @return process group
* @param processGroupUpdateStrategy if process groups with its child groups should be included
* @return List of process groups
*/
Set<ProcessGroupEntity> getProcessGroups(String parentGroupId);
Set<ProcessGroupEntity> getProcessGroups(String parentGroupId, ProcessGroupUpdateStrategy processGroupUpdateStrategy);
/**
* Verifies the contents of the specified process group can be scheduled or unscheduled.

View File

@ -300,6 +300,7 @@ import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusSnapshotEntity;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import org.apache.nifi.web.api.entity.ProcessorDiagnosticsEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorRunStatusDetailsEntity;
@ -4542,8 +4543,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
public Set<ProcessGroupEntity> getProcessGroups(final String parentGroupId) {
final Set<ProcessGroup> groups = processGroupDAO.getProcessGroups(parentGroupId);
public Set<ProcessGroupEntity> getProcessGroups(final String parentGroupId, final ProcessGroupUpdateStrategy processGroupUpdateStrategy) {
final Set<ProcessGroup> groups = processGroupDAO.getProcessGroups(parentGroupId, processGroupUpdateStrategy);
return groups.stream()
.map(group -> createProcessGroupEntity(group))
.collect(Collectors.toSet());

View File

@ -111,6 +111,7 @@ import org.apache.nifi.web.api.entity.PortEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupImportEntity;
import org.apache.nifi.web.api.entity.ProcessGroupReplaceRequestEntity;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import org.apache.nifi.web.api.entity.ProcessGroupUploadEntity;
import org.apache.nifi.web.api.entity.ProcessGroupsEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
@ -169,6 +170,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -480,7 +482,7 @@ public class ProcessGroupResource extends FlowUpdateResource<ProcessGroupImportE
* @param httpServletRequest request
* @param id The id of the process group.
* @param requestProcessGroupEntity A processGroupEntity.
* @return A processGroupEntity.
* @return A processGroupEntity or the parent processGroupEntity for recursive requests.
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@ -536,6 +538,15 @@ public class ProcessGroupResource extends FlowUpdateResource<ProcessGroupImportE
}
}
final String processGroupUpdateStrategy = requestProcessGroupEntity.getProcessGroupUpdateStrategy();
final ProcessGroupUpdateStrategy updateStrategy;
if (processGroupUpdateStrategy == null) {
updateStrategy = ProcessGroupUpdateStrategy.CURRENT_GROUP;
} else {
updateStrategy = ProcessGroupUpdateStrategy.valueOf(processGroupUpdateStrategy);
}
if (isReplicateRequest()) {
return replicate(HttpMethod.PUT, requestProcessGroupEntity);
} else if (isDisconnectedFromCluster()) {
@ -543,68 +554,100 @@ public class ProcessGroupResource extends FlowUpdateResource<ProcessGroupImportE
}
// handle expects request (usually from the cluster manager)
final Revision requestRevision = getRevision(requestProcessGroupEntity, id);
final ParameterContextReferenceEntity requestParamContext = requestProcessGroupDTO.getParameterContext();
final String requestGroupId = requestProcessGroupDTO.getId();
final Map<ProcessGroupEntity, Revision> updatableProcessGroups = new HashMap<>();
updatableProcessGroups.put(requestProcessGroupEntity, getRevision(requestProcessGroupEntity, requestGroupId));
if (updateStrategy == ProcessGroupUpdateStrategy.CURRENT_GROUP_WITH_CHILDREN) {
for (ProcessGroupEntity processGroupEntity : serviceFacade.getProcessGroups(requestGroupId, updateStrategy)) {
final ProcessGroupDTO processGroupDTO = processGroupEntity.getComponent();
final String processGroupId = processGroupDTO == null ? processGroupEntity.getId() : processGroupDTO.getId();
if (processGroupDTO != null) {
processGroupDTO.setParameterContext(requestParamContext);
}
updatableProcessGroups.put(processGroupEntity, getRevision(processGroupEntity, processGroupId));
}
}
return withWriteLock(
serviceFacade,
requestProcessGroupEntity,
requestRevision,
new HashSet<>(updatableProcessGroups.values()),
lookup -> {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
Authorizable authorizable = lookup.getProcessGroup(id).getAuthorizable();
authorizable.authorize(authorizer, RequestAction.WRITE, user);
for (final ProcessGroupEntity updatableGroupEntity : updatableProcessGroups.keySet()) {
final ProcessGroupDTO updatableGroupDto = updatableGroupEntity.getComponent();
final String groupId = updatableGroupDto == null ? updatableGroupEntity.getId() : updatableGroupDto.getId();
// Ensure that user has READ permission on current Parameter Context (if any) because user is un-binding.
final ParameterContextReferenceEntity referencedParamContext = requestProcessGroupDTO.getParameterContext();
if (referencedParamContext != null) {
// Lookup the current Parameter Context and determine whether or not the Parameter Context is changing
final String groupId = requestProcessGroupDTO.getId();
final ProcessGroupEntity currentGroupEntity = serviceFacade.getProcessGroup(groupId);
final ProcessGroupDTO groupDto = currentGroupEntity.getComponent();
final ParameterContextReferenceEntity currentParamContext = groupDto.getParameterContext();
final String currentParamContextId = currentParamContext == null ? null : currentParamContext.getId();
final boolean parameterContextChanging = !Objects.equals(referencedParamContext.getId(), currentParamContextId);
Authorizable authorizable = lookup.getProcessGroup(groupId).getAuthorizable();
authorizable.authorize(authorizer, RequestAction.WRITE, user);
// If Parameter Context is changing...
if (parameterContextChanging) {
// In order to bind to a Parameter Context, the user must have the READ policy to that Parameter Context.
if (referencedParamContext.getId() != null) {
lookup.getParameterContext(referencedParamContext.getId()).authorize(authorizer, RequestAction.READ, user);
}
// Ensure that user has READ permission on current Parameter Context (if any) because user is un-binding.
final ParameterContextReferenceEntity referencedParamContext = updatableGroupDto.getParameterContext();
if (referencedParamContext != null) {
// Lookup the current Parameter Context and determine whether or not the Parameter Context is changing
final ProcessGroupEntity currentGroupEntity = serviceFacade.getProcessGroup(groupId);
final ProcessGroupDTO groupDto = currentGroupEntity.getComponent();
final ParameterContextReferenceEntity currentParamContext = groupDto.getParameterContext();
final String currentParamContextId = currentParamContext == null ? null : currentParamContext.getId();
final boolean parameterContextChanging = !Objects.equals(referencedParamContext.getId(), currentParamContextId);
// If currently referencing a Parameter Context, we must authorize that the user has READ permissions on the Parameter Context in order to un-bind to it.
if (currentParamContextId != null) {
lookup.getParameterContext(currentParamContextId).authorize(authorizer, RequestAction.READ, user);
}
// If Parameter Context is changing...
if (parameterContextChanging) {
// In order to bind to a Parameter Context, the user must have the READ policy to that Parameter Context.
if (referencedParamContext.getId() != null) {
lookup.getParameterContext(referencedParamContext.getId()).authorize(authorizer, RequestAction.READ, user);
}
// Because the user will be changing the behavior of any component in this group that is currently referencing any Parameter, we must ensure that the user has
// both READ and WRITE policies for each of those components.
for (final AffectedComponentEntity affectedComponentEntity : serviceFacade.getProcessorsReferencingParameter(groupId)) {
final Authorizable processorAuthorizable = lookup.getProcessor(affectedComponentEntity.getId()).getAuthorizable();
processorAuthorizable.authorize(authorizer, RequestAction.READ, user);
processorAuthorizable.authorize(authorizer, RequestAction.WRITE, user);
}
// If currently referencing a Parameter Context, we must authorize that the user has READ permissions on the Parameter Context in order to un-bind to it.
if (currentParamContextId != null) {
lookup.getParameterContext(currentParamContextId).authorize(authorizer, RequestAction.READ, user);
}
for (final AffectedComponentEntity affectedComponentEntity : serviceFacade.getControllerServicesReferencingParameter(groupId)) {
final Authorizable serviceAuthorizable = lookup.getControllerService(affectedComponentEntity.getId()).getAuthorizable();
serviceAuthorizable.authorize(authorizer, RequestAction.READ, user);
serviceAuthorizable.authorize(authorizer, RequestAction.WRITE, user);
// Because the user will be changing the behavior of any component in this group that is currently referencing any Parameter, we must ensure that the user has
// both READ and WRITE policies for each of those components.
for (final AffectedComponentEntity affectedComponentEntity : serviceFacade.getProcessorsReferencingParameter(groupId)) {
final Authorizable processorAuthorizable = lookup.getProcessor(affectedComponentEntity.getId()).getAuthorizable();
processorAuthorizable.authorize(authorizer, RequestAction.READ, user);
processorAuthorizable.authorize(authorizer, RequestAction.WRITE, user);
}
for (final AffectedComponentEntity affectedComponentEntity : serviceFacade.getControllerServicesReferencingParameter(groupId)) {
final Authorizable serviceAuthorizable = lookup.getControllerService(affectedComponentEntity.getId()).getAuthorizable();
serviceAuthorizable.authorize(authorizer, RequestAction.READ, user);
serviceAuthorizable.authorize(authorizer, RequestAction.WRITE, user);
}
}
}
}
},
() -> serviceFacade.verifyUpdateProcessGroup(requestProcessGroupDTO),
(revision, processGroupEntity) -> {
// update the process group
final ProcessGroupEntity entity = serviceFacade.updateProcessGroup(revision, processGroupEntity.getComponent());
populateRemainingProcessGroupEntityContent(entity);
// prune response as necessary
if (entity.getComponent() != null) {
entity.getComponent().setContents(null);
() -> {
for (final ProcessGroupEntity entity : updatableProcessGroups.keySet()) {
serviceFacade.verifyUpdateProcessGroup(entity.getComponent());
}
},
(revisions, entities) -> {
ProcessGroupEntity responseEntity = null;
for (Map.Entry<ProcessGroupEntity, Revision> entry : updatableProcessGroups.entrySet()) {
// update the process group
final Revision revision = entry.getValue();
final ProcessGroupDTO groupDTO = entry.getKey().getComponent();
final ProcessGroupEntity entity = serviceFacade.updateProcessGroup(revision, groupDTO);
return generateOkResponse(entity).build();
if (requestGroupId.equals(entity.getId())) {
responseEntity = entity;
populateRemainingProcessGroupEntityContent(responseEntity);
// prune response as necessary
if (responseEntity.getComponent() != null) {
responseEntity.getComponent().setContents(null);
}
}
}
return generateOkResponse(responseEntity).build();
}
);
}
@ -2175,7 +2218,7 @@ public class ProcessGroupResource extends FlowUpdateResource<ProcessGroupImportE
});
// get the process groups
final Set<ProcessGroupEntity> entities = serviceFacade.getProcessGroups(groupId);
final Set<ProcessGroupEntity> entities = serviceFacade.getProcessGroups(groupId, ProcessGroupUpdateStrategy.CURRENT_GROUP);
// always prune the contents
for (final ProcessGroupEntity entity : entities) {

View File

@ -24,6 +24,7 @@ import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.VariableRegistryDTO;
import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import java.util.Collection;
import java.util.Map;
@ -59,9 +60,10 @@ public interface ProcessGroupDAO {
* Gets all of the process groups.
*
* @param parentGroupId The parent group id
* @param processGroupUpdateStrategy if process groups with its child groups should be included
* @return The process groups
*/
Set<ProcessGroup> getProcessGroups(String parentGroupId);
Set<ProcessGroup> getProcessGroups(String parentGroupId, ProcessGroupUpdateStrategy processGroupUpdateStrategy);
/**
* Verifies the specified process group can be modified.

View File

@ -45,6 +45,7 @@ import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.VariableRegistryDTO;
import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import org.apache.nifi.web.api.entity.VariableEntity;
import org.apache.nifi.web.dao.ProcessGroupDAO;
@ -136,9 +137,13 @@ public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGrou
}
@Override
public Set<ProcessGroup> getProcessGroups(String parentGroupId) {
public Set<ProcessGroup> getProcessGroups(final String parentGroupId, final ProcessGroupUpdateStrategy processGroupUpdateStrategy) {
ProcessGroup group = locateProcessGroup(flowController, parentGroupId);
return group.getProcessGroups();
if (processGroupUpdateStrategy == ProcessGroupUpdateStrategy.CURRENT_GROUP_WITH_CHILDREN) {
return new HashSet<>(group.findAllProcessGroups());
} else {
return group.getProcessGroups();
}
}
@Override

View File

@ -16,10 +16,16 @@
*/
package org.apache.nifi.web.api;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupUpdateStrategy;
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.dto.TemplateDTO;
import org.apache.nifi.web.api.entity.TemplateEntity;
@ -41,10 +47,14 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyString;
import static org.junit.jupiter.api.Assertions.assertFalse;
@ExtendWith(MockitoExtension.class)
public class TestProcessGroupResource {
@ -110,4 +120,33 @@ public class TestProcessGroupResource {
assertFalse(Pattern.compile("<script.*>").matcher(response.getEntity().toString()).find());
});
}
@Test
public void testUpdateProcessGroupNotExecuted_WhenUserNotAuthorized(@Mock HttpServletRequest httpServletRequest, @Mock NiFiProperties properties) {
when(httpServletRequest.getHeader(any())).thenReturn(null);
when(properties.isNode()).thenReturn(Boolean.FALSE);
processGroupResource.properties = properties;
processGroupResource.serviceFacade = serviceFacade;
processGroupResource.httpServletRequest = httpServletRequest;
final ProcessGroupEntity processGroupEntity = new ProcessGroupEntity();
final ProcessGroupDTO groupDTO = new ProcessGroupDTO();
groupDTO.setId("id");
groupDTO.setName("name");
final RevisionDTO revisionDTO = new RevisionDTO();
revisionDTO.setClientId("clientId");
revisionDTO.setVersion(1L);
processGroupEntity.setRevision(revisionDTO);
processGroupEntity.setProcessGroupUpdateStrategy(ProcessGroupUpdateStrategy.CURRENT_GROUP.name());
processGroupEntity.setComponent(groupDTO);
doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
assertThrows(AccessDeniedException.class, () ->
processGroupResource.updateProcessGroup(httpServletRequest, "id", processGroupEntity));
verify(serviceFacade, never()).verifyUpdateProcessGroup(any());
verify(serviceFacade, never()).updateProcessGroup(any(), any());
}
}

View File

@ -741,6 +741,7 @@
<include>${staging.dir}/css/processor-configuration.css</include>
<include>${staging.dir}/css/processor-details.css</include>
<include>${staging.dir}/css/process-group-configuration.css</include>
<include>${staging.dir}/css/new-process-group-dialog.css</include>
<include>${staging.dir}/css/policy-management.css</include>
<include>${staging.dir}/css/remote-process-group-configuration.css</include>
<include>${staging.dir}/css/port-configuration.css</include>

View File

@ -28,6 +28,13 @@
</div>
<input id="new-process-group-name" type="text" placeholder="Enter a name or select a file to upload"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Parameter Context</div>
<div class="setting-field">
<div id="new-pg-parameter-context-combo"></div>
<div id="parameters-from-uploaded-flow">Parameters will be imported from the uploaded Flow Definition.</div>
</div>
</div>
<div id="file-cancel-button-container">
<button class="icon" id="file-cancel-button" aria-hidden="true" title="Cancel the selected file">
@ -45,10 +52,4 @@
</div>
</div>
</div>
<div class="setting">
<span id="import-process-group-link" class="link" title="Import a flow from a registry">
<i class="fa fa-cloud-download" aria-hidden="true" style="margin-left: 5px; margin-right: 5px;"></i>
Import from Registry...
</span>
</div>
</div>
</div>

View File

@ -40,6 +40,12 @@
<div class="setting-name">Process group parameter context</div>
<div class="editable setting-field">
<div id="process-group-parameter-context-combo"></div>
<div id="parameter-contexts-recursive-container">
<div id="parameter-contexts-recursive" class="nf-checkbox checkbox-unchecked"></div>
<div class="nf-checkbox-label">Apply recursively</div>
<div class="fa fa-question-circle" alt="Info" title="When checked Parameter Context will be applied to the Process Group and all the embedded Process Groups recursively, if the user has the proper permissions on all the respective components. If the user does not have the proper permissions on any embedded Process Group, then the Parameter Context will not be applied for any components."></div>
</div>
<div class="clear"></div>
</div>
<div class="read-only setting-field">
<span id="read-only-process-group-parameter-context" class="unset">Unauthorized</span>

View File

@ -37,6 +37,7 @@
@import url(new-reporting-task-dialog.css);
@import url(new-parameter-provider-dialog.css);
@import url(new-parameter-context-dialog.css);
@import url(new-process-group-dialog.css);
@import url(graph.css);
@import url(header.css);
@import url(main.css);

View File

@ -484,7 +484,7 @@ div.button-icon span {
div.button-icon {
float: left !important;
margin-left: 20px;
margin-left: 0;
font-size: 18px !important;
}

View File

@ -385,32 +385,6 @@ span.details-title {
width: 300px;
}
span#import-process-group-link.link:focus {
outline: none;
}
span#import-process-group-link.link {
position: relative;
top: 164px;
left: 20px;
color: #004849; /*link-color*/
font-weight: normal;
display: inline-block;
border-bottom: 1px solid #CCDADB;
font-size: 13px;
font-family: Roboto;
z-index: 1;
}
span#import-process-group-link.link-over {
border-bottom: 1px solid #004849;
}
span#import-process-group-link.link-bold {
font-weight: bold;
border-bottom: none;
}
#file-cancel-button {
position: absolute;
width: 0px;
@ -421,4 +395,4 @@ span#import-process-group-link.link-bold {
background-color: transparent;
padding: 0px;
margin-top: 16px;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.
*/
/*
New process group dialog.
*/
#new-process-group-dialog {
min-height:320px;
height: 320px;
width: 540px;
min-width: 500px;
}
div.fa-cloud-download.button-icon {
width: 160px;
}
#parameters-from-uploaded-flow {
color: #888;
font-weight: 400;
display: none;
}

View File

@ -83,6 +83,21 @@
#process-group-parameter-context-combo {
width: 328px;
float: left;
}
#parameter-contexts-recursive-container {
float: left;
margin-left: 10px;
}
#parameter-contexts-recursive-container > div {
float: left;
line-height: 30px;
}
#parameter-contexts-recursive {
margin-top: 8px;
}
#process-group-flowfile-concurrency-combo {
@ -119,8 +134,4 @@
#upload-process-group-link {
float: right;
}
#process-group-log-to-own-file-combo {
width: 328px;
}

View File

@ -55,7 +55,7 @@
.combo-arrow {
float: right;
width: 10px;
margin-top: 8px;
margin-top: 4px;
}
.combo-text {
@ -111,4 +111,4 @@ div.combo-nifi-tooltip {
div.combo-button-normal {
background-color: #eaeef0;
}
}

View File

@ -26,10 +26,11 @@
'nf.Graph',
'nf.CanvasUtils',
'nf.ErrorHandler',
'nf.FlowVersion',
'nf.Common',
'nf.Dialog'],
function ($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog) {
return (nf.ng.GroupComponent = factory($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog));
function ($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFlowVersion, nfCommon, nfDialog) {
return (nf.ng.GroupComponent = factory($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFlowVersion, nfCommon, nfDialog));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ng.GroupComponent =
@ -40,6 +41,7 @@
require('nf.Graph'),
require('nf.CanvasUtils'),
require('nf.ErrorHandler'),
require('nf.FlowVersion'),
require('nf.Common'),
require('nf.Dialog')));
} else {
@ -50,10 +52,11 @@
root.nf.Graph,
root.nf.CanvasUtils,
root.nf.ErrorHandler,
root.nf.FlowVersion,
root.nf.Common,
root.nf.Dialog);
}
}(this, function ($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog) {
}(this, function ($, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFlowVersion, nfCommon, nfDialog) {
'use strict';
return function (serviceProvider) {
@ -63,13 +66,19 @@
var cancelFileBtn = $('#file-cancel-button');
var submitFileContainer = $('#submit-file-container');
var noParameterContext = {
text: 'No parameter context',
value: null
};
/**
* Create the group and add to the graph.
*
* @argument {string} groupName The name of the group.
* @argument {object} pt The point that the group was dropped.
* @argument {string} parameterContextId Option parameter context reference
*/
var createGroup = function (groupName, pt) {
var createGroup = function (groupName, pt, parameterContextId) {
var processGroupEntity = {
'revision': nfClient.getRevision({
'revision': {
@ -86,6 +95,13 @@
}
};
// include the parameter context if specified
if (nf.Common.isDefinedAndNotNull(parameterContextId)) {
processGroupEntity.component.parameterContext = {
'id': parameterContextId
};
}
// create a new processor of the defined type
return $.ajax({
type: 'POST',
@ -127,6 +143,50 @@
return filepath.replace(/^.*[\\\/]/, '').replace(/\..*/, '');
};
var setParameterContextOptions = function () {
return new $.Deferred(function (deferred) {
$.ajax({
type: 'GET',
url: '../nifi-api/flow/parameter-contexts',
dataType: 'json'
}).done(function (response) {
var selectedOption = noParameterContext;
var parameterContextOptions = [noParameterContext];
var parentParameterContext = nf.CanvasUtils.getParameterContext();
// prepare the options
var sortedParameterContexts = nfCommon.sortParameterContextsAlphabeticallyBasedOnAuthorization(response.parameterContexts);
sortedParameterContexts.forEach(function (parameterContext) {
if (parameterContext.permissions.canRead) {
var option = {
text: parameterContext.component.name,
value: parameterContext.id,
description: parameterContext.component.description
};
if (nfCommon.isDefinedAndNotNull(parentParameterContext) && parentParameterContext.id === parameterContext.id) {
selectedOption = option;
}
parameterContextOptions.push(option);
}
});
// set the combo options
$('#new-pg-parameter-context-combo').combo({
selectedOption: {
value: selectedOption.value
},
options: parameterContextOptions
});
deferred.resolve();
}).fail(function (xhr, status, error) {
deferred.reject(xhr, status, error);
}).fail(nfErrorHandler.handleAjaxError);
}).promise();
};
function GroupComponent() {
this.icon = 'icon icon-group';
@ -170,8 +230,13 @@
selectedFilename.text('');
uploadFileField.val('');
self.fileToBeUploaded = null;
// reset the parameter context fields
$('#parameters-from-uploaded-flow').hide();
$('#new-pg-parameter-context-combo').show();
}
self.fileForm = $('#file-upload-form').ajaxForm({
url: '../nifi-api/process-groups/',
dataType: 'json',
@ -235,6 +300,10 @@
processGroupDialog.modal('refreshButtons');
}
// update the parameter context
$('#parameters-from-uploaded-flow').show();
$('#new-pg-parameter-context-combo').hide();
cancelFileBtn.show();
});
@ -354,6 +423,8 @@
// get the name of the group and clear the textfield
var groupName = $('#new-process-group-name').val();
var parameterContextId = $('#new-pg-parameter-context-combo').combo('getSelectedOption').value
// ensure the group name is specified
if (nfCommon.isBlank(groupName)) {
nfDialog.showOkDialog({
@ -437,7 +508,7 @@
self.modal.fileForm.submit();
} else {
// create the group and resolve the deferred accordingly
createGroup(groupName, pt).done(function (response) {
createGroup(groupName, pt, parameterContextId).done(function (response) {
deferred.resolve(response.component);
}).fail(function () {
deferred.reject();
@ -449,74 +520,88 @@
}
};
groupComponent.modal.update('setButtonModel', [{
buttonText: 'Add',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
disabled: function () {
if (nfCommon.isBlank($('#new-process-group-name').val())) {
return true;
} else {
return false;
}
},
handler: {
click: addGroup
}
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
// reject the deferred
deferred.reject();
// close the dialog
groupComponent.modal.hide();
// set the parameter context options and then proceed with showing the new pg dialog
setParameterContextOptions().done(function () {
var buttonModel = [{
buttonText: 'Add',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
disabled: function () {
if (nfCommon.isBlank($('#new-process-group-name').val())) {
return true;
} else {
return false;
}
},
handler: {
click: addGroup
}
}
}]);
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
// reject the deferred
deferred.reject();
// hide the selected file to upload title
submitFileContainer.hide();
// close the dialog
groupComponent.modal.hide();
}
}
}];
// hide file cancel button
cancelFileBtn.hide();
// determine if import from registry link should show
var importProcessGroupLink = $('#import-process-group-link');
if (showImportLink === true && nfCommon.canVersionFlows()) {
importProcessGroupLink.show();
} else {
importProcessGroupLink.hide();
}
// determine if Upload File button should show
if (showUploadFileButton === true) {
uploadFileBtn.show();
} else {
uploadFileBtn.hide();
}
// show the dialog
groupComponent.modal.storePt(pt);
groupComponent.modal.show();
// set up the focus and key handlers
$('#new-process-group-name').focus().off('keyup').on('keyup', function (e) {
var code = e.keyCode ? e.keyCode : e.which;
if (code === $.ui.keyCode.ENTER) {
addGroup();
if (showImportLink === true && nfCommon.canVersionFlows()) {
buttonModel.push({
buttonText: 'Import from Registry',
clazz: 'fa fa-cloud-download button-icon',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
nfFlowVersion.showImportFlowDialog();
}
}
});
}
// set the button model
groupComponent.modal.update('setButtonModel', buttonModel);
// hide the selected file to upload title
submitFileContainer.hide();
// hide file cancel button
cancelFileBtn.hide();
// determine if Upload File button should show
if (showUploadFileButton === true) {
uploadFileBtn.show();
} else {
uploadFileBtn.hide();
}
// show the dialog
groupComponent.modal.storePt(pt);
groupComponent.modal.show();
// set up the focus and key handlers
$('#new-process-group-name').focus().off('keyup').on('keyup', function (e) {
var code = e.keyCode ? e.keyCode : e.which;
if (code === $.ui.keyCode.ENTER) {
addGroup();
}
});
});
}).promise();
}
@ -525,4 +610,4 @@
var groupComponent = new GroupComponent();
return groupComponent;
};
}));
}));

View File

@ -1784,17 +1784,19 @@
}
});
// handle the click for the process group import
$('#import-process-group-link').on('click', function() {
showImportFlowVersionDialog();
});
// initialize the import flow version table
initImportFlowVersionTable();
initLocalChangesTable($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
initLocalChangesTable($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
},
/**
* Shows the import flow dialog.
*/
showImportFlowDialog: function () {
showImportFlowVersionDialog();
},
/**
* Shows the flow version dialog.
*

View File

@ -93,6 +93,13 @@
*/
var saveConfiguration = function (version, groupId) {
// build the entity
var updateStrategy;
if ($('#parameter-contexts-recursive').hasClass('checkbox-unchecked')) {
updateStrategy = 'CURRENT_GROUP';
} else {
updateStrategy = 'CURRENT_GROUP_WITH_CHILDREN';
};
var entity = {
'revision': nfClient.getRevision({
'revision': {
@ -100,6 +107,7 @@
}
}),
'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
'processGroupUpdateStrategy': updateStrategy,
'component': {
'id': groupId,
'name': $('#process-group-name').val(),
@ -375,37 +383,7 @@
value: null
}];
var authorizedParameterContexts = parameterContexts.filter(function (parameterContext) {
return parameterContext.permissions.canRead;
});
var unauthorizedParameterContexts = parameterContexts.filter(function (parameterContext) {
return !parameterContext.permissions.canRead;
});
//sort alphabetically
var sortedAuthorizedParameterContexts = authorizedParameterContexts.sort(function (a, b) {
if (a.component.name < b.component.name) {
return -1;
}
if (a.component.name > b.component.name) {
return 1;
}
return 0;
});
//sort alphabetically
var sortedUnauthorizedParameterContexts = unauthorizedParameterContexts.sort(function (a, b) {
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
return 0;
});
var sortedParameterContexts = sortedAuthorizedParameterContexts.concat(sortedUnauthorizedParameterContexts);
var sortedParameterContexts = nfCommon.sortParameterContextsAlphabeticallyBasedOnAuthorization(parameterContexts);
sortedParameterContexts.forEach(function (parameterContext) {
var option;
@ -517,6 +495,10 @@
// initialize the parameter context combo
$('#process-group-parameter-context-combo').combo('destroy').combo(comboOptions);
// initialize parameter context recursive checkbox
$('#parameter-contexts-recursive').removeClass().addClass('nf-checkbox checkbox-unchecked');
}).fail(nfErrorHandler.handleAjaxError);
};

View File

@ -1855,6 +1855,46 @@
return true;
}
return false;
},
/**
* Sorts Parameter Contexts alphabetically for authorized items first, following unauthorized items.
*
* @param Array
* @returns {Array}
*/
sortParameterContextsAlphabeticallyBasedOnAuthorization: function (parameterContexts) {
var authorizedParameterContexts = parameterContexts.filter(function (parameterContext) {
return parameterContext.permissions.canRead;
});
var unauthorizedParameterContexts = parameterContexts.filter(function (parameterContext) {
return !parameterContext.permissions.canRead;
});
// sort alphabetically
var sortedAuthorizedParameterContexts = authorizedParameterContexts.sort(function (a, b) {
if (a.component.name < b.component.name) {
return -1;
}
if (a.component.name > b.component.name) {
return 1;
}
return 0;
});
// sort alphabetically
var sortedUnauthorizedParameterContexts = unauthorizedParameterContexts.sort(function (a, b) {
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
return 0;
});
return sortedAuthorizedParameterContexts.concat(sortedUnauthorizedParameterContexts);
}
};