NIFI-4436:

- Adding support to save a version of a flow based on a selected Process Group.
- Adding support for revert changes back to the most recent version.
- Adding support to disconnect from version control.
- Moving the version control information out of the entity objects and into the dto's.
- Fixing checkstyle issues.
NIFI-4502:
- Updating the UI to allow for the user to register registry clients.
- Updating the version control menu item names.
This commit is contained in:
Matt Gilman 2017-10-12 12:29:05 -04:00 committed by Bryan Bende
parent 6a58d780d7
commit 7a0a900a0f
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
55 changed files with 2465 additions and 246 deletions

View File

@ -0,0 +1,69 @@
/*
* 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.dto;
import io.swagger.annotations.ApiModelProperty;
import javax.xml.bind.annotation.XmlType;
/**
* Details about a bucket in a registry.
*/
@XmlType(name = "bucket")
public class BucketDTO {
private String id;
private String name;
private String description;
private Long created;
@ApiModelProperty("The bucket identifier")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@ApiModelProperty("The bucket name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ApiModelProperty("The bucket description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@ApiModelProperty("The created timestamp of this bucket")
public Long getCreated() {
return created;
}
public void setCreated(Long created) {
this.created = created;
}
}

View File

@ -28,6 +28,7 @@ public class ControllerConfigurationDTO {
private Integer maxTimerDrivenThreadCount;
private Integer maxEventDrivenThreadCount;
private String registryUrl;
/**
* @return maximum number of timer driven threads this NiFi has available

View File

@ -32,6 +32,7 @@ public class FlowConfigurationDTO {
private Boolean supportsManagedAuthorizer;
private Boolean supportsConfigurableAuthorizer;
private Boolean supportsConfigurableUsersAndGroups;
private Boolean supportsFlowVersioning;
private Long autoRefreshIntervalSeconds;
private Date currentTime;
@ -127,4 +128,18 @@ public class FlowConfigurationDTO {
public void setTimeOffset(Integer timeOffset) {
this.timeOffset = timeOffset;
}
/**
* @return whether this NiFi is configured for support flow versioning
*/
@ApiModelProperty(
value = "Whether this NiFi supports flow versioning."
)
public Boolean getSupportsFlowVersioning() {
return supportsFlowVersioning;
}
public void setSupportsFlowVersioning(Boolean supportsFlowVersioning) {
this.supportsFlowVersioning = supportsFlowVersioning;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.dto;
import io.swagger.annotations.ApiModelProperty;
import javax.xml.bind.annotation.XmlType;
/**
* Details about a configured registry.
*/
@XmlType(name = "registry")
public class RegistryDTO {
private String id;
private String name;
private String description;
private String uri;
@ApiModelProperty("The registry identifier")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@ApiModelProperty("The registry name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ApiModelProperty("The registry description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@ApiModelProperty("The registry URI")
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}

View File

@ -28,6 +28,7 @@ public class VersionedFlowDTO {
private String flowId;
private String flowName;
private String description;
private String comments;
@ApiModelProperty("The ID of the registry that the flow is tracked to")
public String getRegistryId() {
@ -73,4 +74,13 @@ public class VersionedFlowDTO {
public void setDescription(String description) {
this.description = description;
}
@ApiModelProperty("Comments for the changeset")
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
}

View File

@ -17,6 +17,7 @@
package org.apache.nifi.web.api.dto.flow;
import io.swagger.annotations.ApiModelProperty;
import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
import javax.xml.bind.annotation.XmlType;
@ -28,6 +29,7 @@ public class FlowBreadcrumbDTO {
private String id;
private String name;
private VersionControlInformationDTO versionControlInformation;
/**
* The id for this group.
@ -60,4 +62,18 @@ public class FlowBreadcrumbDTO {
public void setName(final String name) {
this.name = name;
}
/**
* @return the process group version control information or null if not version controlled
*/
@ApiModelProperty(
value = "The process group version control information or null if not version controlled."
)
public VersionControlInformationDTO getVersionControlInformation() {
return versionControlInformation;
}
public void setVersionControlInformation(VersionControlInformationDTO versionControlInformation) {
this.versionControlInformation = versionControlInformation;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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;
import org.apache.nifi.web.api.dto.BucketDTO;
import javax.xml.bind.annotation.XmlRootElement;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a BucketDTO.
*/
@XmlRootElement(name = "bucketEntity")
public class BucketEntity extends Entity {
private BucketDTO bucket;
public BucketDTO getBucket() {
return bucket;
}
public void setBucket(BucketDTO bucket) {
this.bucket = bucket;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Set;
/**
* A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a set of BucketEntity's.
*/
@XmlRootElement(name = "bucketsEntity")
public class BucketsEntity extends Entity {
private Set<BucketEntity> buckets;
/**
* @return collection of BucketEntity's that are being serialized
*/
public Set<BucketEntity> getBuckets() {
return buckets;
}
public void setBuckets(Set<BucketEntity> buckets) {
this.buckets = buckets;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Set;
/**
* A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a set of RegistryEntity's.
*/
@XmlRootElement(name = "registriesEntity")
public class RegistriesEntity extends Entity {
private Set<RegistryEntity> registries;
/**
* @return collection of LabelEntity's that are being serialized
*/
public Set<RegistryEntity> getRegistries() {
return registries;
}
public void setRegistries(Set<RegistryEntity> registries) {
this.registries = registries;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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;
import org.apache.nifi.web.api.dto.RegistryDTO;
import javax.xml.bind.annotation.XmlRootElement;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a RegistryDTO.
*/
@XmlRootElement(name = "registryEntity")
public class RegistryEntity extends ComponentEntity {
private RegistryDTO component;
public RegistryDTO getComponent() {
return component;
}
public void setComponent(RegistryDTO component) {
this.component = component;
}
}

View File

@ -25,16 +25,16 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "versionControlInformationEntity")
public class VersionControlInformationEntity extends Entity {
private VersionControlInformationDTO versionControlDto;
private VersionControlInformationDTO versionControlInformation;
private RevisionDTO processGroupRevision;
@ApiModelProperty("The Version Control information")
public VersionControlInformationDTO getVersionControlInformation() {
return versionControlDto;
return versionControlInformation;
}
public void setVersionControlInformation(VersionControlInformationDTO versionControlDto) {
this.versionControlDto = versionControlDto;
this.versionControlInformation = versionControlDto;
}
@ApiModelProperty("The Revision for the Process Group")

View File

@ -16,7 +16,6 @@
*/
package org.apache.nifi.groups;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -42,7 +41,6 @@ import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.registry.flow.UnknownResourceException;
import org.apache.nifi.registry.flow.VersionControlInformation;
import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
import org.apache.nifi.remote.RemoteGroupPort;
@ -940,6 +938,11 @@ public interface ProcessGroup extends ComponentAuthorizable, Positionable, Versi
*/
void setVersionControlInformation(VersionControlInformation versionControlInformation, Map<String, String> versionedComponentIds);
/**
* Disconnects this Process Group from version control. If not currently under version control, this method does nothing.
*/
void disconnectVersionControl();
/**
* Synchronizes the Process Group with the given Flow Registry, determining whether or not the local flow
* is up to date with the newest version of the flow in the Registry and whether or not the local flow has been

View File

@ -17,7 +17,11 @@
package org.apache.nifi.registry.flow;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.registry.bucket.Bucket;
import java.io.IOException;
import java.util.Set;
public interface FlowRegistry {
@ -26,6 +30,19 @@ public interface FlowRegistry {
*/
String getURL();
/**
* @return the name of the Flow Registry
*/
String getName();
/**
* Gets the buckets for the specified user.
*
* @param user current user
* @return buckets for this user
*/
Set<Bucket> getBuckets(NiFiUser user) throws IOException;
/**
* Registers the given Versioned Flow with the Flow Registry
*

View File

@ -160,6 +160,11 @@
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-flow-diff</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>

View File

@ -16,30 +16,6 @@
*/
package org.apache.nifi.groups;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -133,6 +109,30 @@ import org.apache.nifi.web.api.dto.TemplateDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
public final class StandardProcessGroup implements ProcessGroup {
private final String id;
@ -2835,6 +2835,16 @@ public final class StandardProcessGroup implements ProcessGroup {
}
}
public void disconnectVersionControl() {
writeLock.lock();
try {
// TODO remove version component ids from each component (until another versioned PG is encountered)
this.versionControlInfo.set(null);
} finally {
writeLock.unlock();
}
}
private void updateVersionedComponentIds(final ProcessGroup processGroup, final Map<String, String> versionedComponentIds) {
if (versionedComponentIds == null || versionedComponentIds.isEmpty()) {
return;

View File

@ -17,6 +17,15 @@
package org.apache.nifi.registry.flow;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.registry.bucket.Bucket;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -35,13 +44,6 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.util.DefaultPrettyPrinter;
/**
* A simple file-based implementation of a Flow Registry Client. Rather than interacting
* with an actual Flow Registry, this implementation simply reads flows from disk and writes
@ -113,6 +115,35 @@ public class FileBasedFlowRegistryClient implements FlowRegistryClient, FlowRegi
return directory.toURI().toString();
}
@Override
public String getName() {
return "Local Registry";
}
@Override
public Set<Bucket> getBuckets(NiFiUser user) throws IOException {
final Set<Bucket> buckets = new HashSet<>();
final File[] bucketDirs = directory.listFiles();
if (bucketDirs == null) {
throw new IOException("Could not get listing of directory " + directory);
}
for (final File bucketDirectory : bucketDirs) {
final String bucketIdentifier = bucketDirectory.getName();
final long creation = bucketDirectory.lastModified();
final Bucket bucket = new Bucket();
bucket.setIdentifier(bucketIdentifier);
bucket.setName("Bucket '" + bucketIdentifier + "'");
bucket.setCreatedTimestamp(creation);
buckets.add(bucket);
}
return buckets;
}
@Override
public synchronized VersionedFlow registerVersionedFlow(final VersionedFlow flow) throws IOException, UnknownResourceException {
Objects.requireNonNull(flow);
@ -303,9 +334,9 @@ public class FileBasedFlowRegistryClient implements FlowRegistryClient, FlowRegi
final File contentsFile = new File(versionDir, "flow.xml");
final VersionedProcessGroup processGroup;
try (final JsonParser parser = jsonFactory.createJsonParser(contentsFile)) {
try (final JsonParser parser = jsonFactory.createParser(contentsFile)) {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
parser.setCodec(mapper);
processGroup = parser.readValueAs(VersionedProcessGroup.class);

View File

@ -17,16 +17,6 @@
package org.apache.nifi.controller.service.mock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.connectable.Connectable;
@ -52,6 +42,16 @@ import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
import org.apache.nifi.registry.variable.MutableVariableRegistry;
import org.apache.nifi.remote.RemoteGroupPort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class MockProcessGroup implements ProcessGroup {
private final Map<String, ControllerServiceNode> serviceMap = new HashMap<>();
private final Map<String, ProcessorNode> processorMap = new HashMap<>();
@ -661,4 +661,9 @@ public class MockProcessGroup implements ProcessGroup {
public void setVersionControlInformation(VersionControlInformation versionControlInformation, Map<String, String> versionedComponentIds) {
this.versionControlInfo = versionControlInformation;
}
@Override
public void disconnectVersionControl() {
this.versionControlInfo = null;
}
}

View File

@ -16,14 +16,6 @@
*/
package org.apache.nifi.web;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
@ -31,7 +23,6 @@ import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.repository.claim.ContentDirection;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.registry.flow.UnknownResourceException;
import org.apache.nifi.registry.flow.VersionedFlow;
import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
@ -62,6 +53,7 @@ import org.apache.nifi.web.api.dto.PortDTO;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RegistryDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
@ -104,6 +96,7 @@ import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorStatusEntity;
import org.apache.nifi.web.api.entity.RegistryEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
@ -119,6 +112,14 @@ import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity;
import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
import org.apache.nifi.web.api.entity.VersionedFlowEntity;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
/**
* Defines the NiFiServiceFacade interface.
*/
@ -1320,6 +1321,14 @@ public interface NiFiServiceFacade {
VersionControlInformationEntity setVersionControlInformation(Revision processGroupRevision, String processGroupId, VersionControlInformationDTO versionControlInfo,
Map<String, String> versionedComponentMapping);
/**
* Disconnects the specified Process Group from version control.
*
* @param revision revision
* @param processGroupId group id
* @return version control information prior to disconnecting
*/
VersionControlInformationEntity deleteVersionControl(final Revision revision, final String processGroupId);
/**
* Retrieves the Versioned Flow Snapshot for the coordinates provided by the given Version Control Information DTO
@ -1381,8 +1390,6 @@ public interface NiFiServiceFacade {
ProcessGroupEntity updateProcessGroup(NiFiUser user, Revision revision, String groupId, VersionControlInformationDTO versionControlInfo, VersionedFlowSnapshot snapshot, String componentIdSeed,
boolean verifyNotModified);
void setFlowRegistryClient(FlowRegistryClient flowRegistryClient);
// ----------------------------------------
// Component state methods
// ----------------------------------------
@ -1828,6 +1835,52 @@ public interface NiFiServiceFacade {
*/
void verifyDeleteReportingTask(String reportingTaskId);
// ----------------------------------------
// Registry methods
// ----------------------------------------
/**
* Creates a registry.
*
* @param revision revision
* @param registryDTO The registry DTO
* @return The reporting task DTO
*/
RegistryEntity createRegistry(Revision revision, RegistryDTO registryDTO);
/**
* Gets a registry with the specified id.
*
* @param registryId id
* @return entity
*/
RegistryEntity getRegistry(String registryId);
/**
* Gets all registries.
*
* @return registries
*/
Set<RegistryEntity> getRegistries();
/**
* Updates the specified registry using the specified revision.
*
* @param revision revision
* @param registryDTO the registry dto
* @return the updated registry registry entity
*/
RegistryEntity updateRegistry(Revision revision, RegistryDTO registryDTO);
/**
* Deletes the specified registry using the specified revision.
*
* @param revision revision
* @param registryId id
* @return the deleted registry entity
*/
RegistryEntity deleteRegistry(Revision revision, String registryId);
// ----------------------------------------
// History methods
// ----------------------------------------

View File

@ -98,6 +98,7 @@ public class NiFiWebApiResourceConfig extends ResourceConfig {
register(ctx.getBean("accessResource"));
register(ctx.getBean("accessPolicyResource"));
register(ctx.getBean("tenantsResource"));
register(ctx.getBean("versionsResource"));
// exception mappers
register(AccessDeniedExceptionMapper.class);

View File

@ -16,32 +16,7 @@
*/
package org.apache.nifi.web;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import com.google.common.collect.Sets;
import org.apache.nifi.action.Action;
import org.apache.nifi.action.Component;
import org.apache.nifi.action.FlowChangeAction;
@ -141,6 +116,7 @@ import org.apache.nifi.reporting.BulletinQuery;
import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.reporting.ComponentType;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.Tuple;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.dto.AccessPolicySummaryDTO;
import org.apache.nifi.web.api.dto.AffectedComponentDTO;
@ -179,6 +155,7 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.PropertyHistoryDTO;
import org.apache.nifi.web.api.dto.RegistryDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
@ -236,6 +213,7 @@ import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusSnapshotEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorStatusEntity;
import org.apache.nifi.web.api.entity.RegistryEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
@ -279,7 +257,30 @@ import org.apache.nifi.web.util.SnippetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Implementation of NiFiServiceFacade that performs revision checking.
@ -330,6 +331,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
private AuthorizableLookup authorizableLookup;
private Map<String, Tuple<Revision, RegistryDTO>> registryCache = new HashMap<>();
// -----------------------------------------
// Synchronization methods
// -----------------------------------------
@ -2257,6 +2260,45 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createControllerServiceEntity(snapshot, null, permissions, null);
}
private RegistryEntity createRegistryEntity(final Revision updatedRevision, final RegistryDTO registryDTO) {
final RegistryEntity entity = new RegistryEntity();
entity.setId(registryDTO.getId());
entity.setPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getController()));
entity.setRevision(dtoFactory.createRevisionDTO(updatedRevision));
entity.setComponent(registryDTO);
return entity;
}
@Override
public RegistryEntity createRegistry(Revision revision, RegistryDTO registryDTO) {
registryCache.put(registryDTO.getId(), new Tuple(revision, registryDTO));
return createRegistryEntity(revision, registryDTO);
}
@Override
public RegistryEntity getRegistry(String registryId) {
final Tuple<Revision, RegistryDTO> registry = registryCache.get(registryId);
return createRegistry(registry.getKey(), registry.getValue());
}
@Override
public Set<RegistryEntity> getRegistries() {
return registryCache.values().stream()
.map(registry -> createRegistry(registry.getKey(), registry.getValue()))
.collect(Collectors.toSet());
}
@Override
public RegistryEntity updateRegistry(Revision revision, RegistryDTO registryDTO) {
registryCache.put(registryDTO.getId(), new Tuple(revision, registryDTO));
return createRegistryEntity(revision, registryDTO);
}
@Override
public RegistryEntity deleteRegistry(Revision revision, String registryId) {
final Tuple<Revision, RegistryDTO> registry = registryCache.remove(registryId);
return createRegistryEntity(registry.getKey(), registry.getValue());
}
@Override
public ReportingTaskEntity createReportingTask(final Revision revision, final ReportingTaskDTO reportingTaskDTO) {
@ -3504,7 +3546,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return null;
}
final VersionControlInformationDTO versionControlDto = dtoFactory.createVersionControlInformationDto(versionControlInfo);
final VersionControlInformationDTO versionControlDto = dtoFactory.createVersionControlInformationDto(processGroup);
final RevisionDTO groupRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(groupId));
return entityFactory.createVersionControlInformationEntity(versionControlDto, groupRevision);
}
@ -3555,7 +3597,19 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final RevisionUpdate<VersionControlInformationDTO> snapshot = updateComponent(revision,
group,
() -> processGroupDAO.updateVersionControlInformation(versionControlInfo, versionedComponentMapping),
processGroup -> dtoFactory.createVersionControlInformationDto(processGroup.getVersionControlInformation()));
processGroup -> dtoFactory.createVersionControlInformationDto(processGroup));
return entityFactory.createVersionControlInformationEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()));
}
@Override
public VersionControlInformationEntity deleteVersionControl(final Revision revision, final String processGroupId) {
final ProcessGroup group = processGroupDAO.getProcessGroup(processGroupId);
final RevisionUpdate<VersionControlInformationDTO> snapshot = updateComponent(revision,
group,
() -> processGroupDAO.disconnectVersionControl(processGroupId),
processGroup -> dtoFactory.createVersionControlInformationDto(group));
return entityFactory.createVersionControlInformationEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()));
}
@ -3835,12 +3889,6 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createProcessGroupEntity(snapshot.getComponent(), updatedRevision, permissions, status, bulletinEntities);
}
@Override
public void setFlowRegistryClient(final FlowRegistryClient client) {
this.flowRegistryClient = client;
}
private AuthorizationResult authorizeAction(final Action action) {
final String sourceId = action.getSourceId();
final Component type = action.getSourceType();
@ -4194,4 +4242,28 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
public void setLeaderElectionManager(final LeaderElectionManager leaderElectionManager) {
this.leaderElectionManager = leaderElectionManager;
}
public void setFlowRegistryClient(FlowRegistryClient flowRegistryClient) {
this.flowRegistryClient = flowRegistryClient;
// temp code to load the registry client cache
final Set<String> registryIdentifiers = flowRegistryClient.getRegistryIdentifiers();
if (registryIdentifiers != null) {
for (final String registryIdentifier : registryIdentifiers) {
final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(registryIdentifier);
final RegistryDTO registry = new RegistryDTO();
registry.setId(registryIdentifier);
registry.setName(flowRegistry.getName());
registry.setUri(flowRegistry.getURL());
registry.setDescription("Default client for storing Flow Revisions to the local disk.");
final RegistryEntity registryEntity = new RegistryEntity();
registryEntity.setComponent(registry);
registryCache.put(registryIdentifier, new Tuple(new Revision(0L, null, registryIdentifier), registry));
}
}
}
}

View File

@ -37,6 +37,7 @@ import org.apache.nifi.web.api.dto.BulletinDTO;
import org.apache.nifi.web.api.dto.ClusterDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.NodeDTO;
import org.apache.nifi.web.api.dto.RegistryDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
import org.apache.nifi.web.api.entity.BulletinEntity;
import org.apache.nifi.web.api.entity.ClusterEntity;
@ -45,12 +46,16 @@ import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.Entity;
import org.apache.nifi.web.api.entity.HistoryEntity;
import org.apache.nifi.web.api.entity.NodeEntity;
import org.apache.nifi.web.api.entity.RegistryEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.DateTimeParameter;
import org.apache.nifi.web.api.request.LongParameter;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
@ -81,6 +86,17 @@ public class ControllerResource extends ApplicationResource {
private ReportingTaskResource reportingTaskResource;
private ControllerServiceResource controllerServiceResource;
/**
* Populate the uri's for the specified registry.
*
* @param registryEntity registry
* @return dtos
*/
public RegistryEntity populateRemainingRegistryEntityContent(final RegistryEntity registryEntity) {
registryEntity.setUri(generateResourceUri("controller", "registries", registryEntity.getId()));
return registryEntity;
}
/**
* Authorizes access to the flow.
*/
@ -290,6 +306,288 @@ public class ControllerResource extends ApplicationResource {
);
}
// ----------
// registries
// ----------
/**
* Creates a new Registry.
*
* @param httpServletRequest request
* @param requestRegistryEntity A registryEntity.
* @return A registryEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("registries")
@ApiOperation(
value = "Creates a new registry",
response = RegistryEntity.class,
authorizations = {
@Authorization(value = "Write - /controller")
}
)
@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 = 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 createRegistry(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The registry configuration details.",
required = true
) final RegistryEntity requestRegistryEntity) {
if (requestRegistryEntity == null || requestRegistryEntity.getComponent() == null) {
throw new IllegalArgumentException("Registry details must be specified.");
}
if (requestRegistryEntity.getRevision() == null || (requestRegistryEntity.getRevision().getVersion() == null || requestRegistryEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Registry.");
}
final RegistryDTO requestReportingTask = requestRegistryEntity.getComponent();
if (requestReportingTask.getId() != null) {
throw new IllegalArgumentException("Registry ID cannot be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestRegistryEntity);
}
return withWriteLock(
serviceFacade,
requestRegistryEntity,
lookup -> {
authorizeController(RequestAction.WRITE);
},
null,
(registryEntity) -> {
final RegistryDTO registry = registryEntity.getComponent();
// set the processor id as appropriate
registry.setId(generateUuid());
// create the reporting task and generate the json
final Revision revision = getRevision(registryEntity, registry.getId());
final RegistryEntity entity = serviceFacade.createRegistry(revision, registry);
populateRemainingRegistryEntityContent(entity);
// build the response
return generateCreatedResponse(URI.create(entity.getUri()), entity).build();
}
);
}
/**
* Retrieves the specified registry.
*
* @param id The id of the registry to retrieve
* @return A registryEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("/registries/{id}")
@ApiOperation(
value = "Gets a registry",
response = RegistryEntity.class,
authorizations = {
@Authorization(value = "Read - /controller")
}
)
@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 getRegistry(
@ApiParam(
value = "The registry id.",
required = true
)
@PathParam("id") final String id) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
authorizeController(RequestAction.READ);
// get the registry
final RegistryEntity entity = serviceFacade.getRegistry(id);
populateRemainingRegistryEntityContent(entity);
return generateOkResponse(entity).build();
}
/**
* Updates the specified registry.
*
* @param httpServletRequest request
* @param id The id of the controller service to update.
* @param requestRegsitryEntity A controllerServiceEntity.
* @return A controllerServiceEntity.
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/registries/{id}")
@ApiOperation(
value = "Updates a registry",
response = RegistryEntity.class,
authorizations = {
@Authorization(value = "Write - /controller")
}
)
@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 updateControllerService(
@Context HttpServletRequest httpServletRequest,
@ApiParam(
value = "The registry id.",
required = true
)
@PathParam("id") final String id,
@ApiParam(
value = "The registry configuration details.",
required = true
) final RegistryEntity requestRegsitryEntity) {
if (requestRegsitryEntity == null || requestRegsitryEntity.getComponent() == null) {
throw new IllegalArgumentException("Registry details must be specified.");
}
if (requestRegsitryEntity.getRevision() == null) {
throw new IllegalArgumentException("Revision must be specified.");
}
// ensure the ids are the same
final RegistryDTO requestRegistryDTO = requestRegsitryEntity.getComponent();
if (!id.equals(requestRegistryDTO.getId())) {
throw new IllegalArgumentException(String.format("The registry id (%s) in the request body does not equal the "
+ "registry id of the requested resource (%s).", requestRegistryDTO.getId(), id));
}
if (isReplicateRequest()) {
return replicate(HttpMethod.PUT, requestRegsitryEntity);
}
// handle expects request (usually from the cluster manager)
final Revision requestRevision = getRevision(requestRegsitryEntity, id);
return withWriteLock(
serviceFacade,
requestRegsitryEntity,
requestRevision,
lookup -> {
authorizeController(RequestAction.WRITE);
},
null,
(revision, registryEntity) -> {
final RegistryDTO registry = registryEntity.getComponent();
// update the controller service
final RegistryEntity entity = serviceFacade.updateRegistry(revision, registry);
populateRemainingRegistryEntityContent(entity);
return generateOkResponse(entity).build();
}
);
}
/**
* Removes the specified registry.
*
* @param httpServletRequest request
* @param version The revision is used to verify the client is working with
* the latest version of the flow.
* @param clientId Optional client id. If the client id is not specified, a
* new one will be generated. This value (whether specified or generated) is
* included in the response.
* @param id The id of the registry to remove.
* @return A entity containing the client id and an updated revision.
*/
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("/registries/{id}")
@ApiOperation(
value = "Deletes a reistry",
response = RegistryEntity.class,
authorizations = {
@Authorization(value = "Write - /controller")
}
)
@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 deleteRegistry(
@Context HttpServletRequest httpServletRequest,
@ApiParam(
value = "The revision is used to verify the client is working with the latest version of the flow.",
required = false
)
@QueryParam(VERSION) final LongParameter version,
@ApiParam(
value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
required = false
)
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
@ApiParam(
value = "The registry id.",
required = true
)
@PathParam("id") final String id) {
if (isReplicateRequest()) {
return replicate(HttpMethod.DELETE);
}
final RegistryEntity requestRegistryEntity = new RegistryEntity();
requestRegistryEntity.setId(id);
// handle expects request (usually from the cluster manager)
final Revision requestRevision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
return withWriteLock(
serviceFacade,
requestRegistryEntity,
requestRevision,
lookup -> {
authorizeController(RequestAction.WRITE);
},
null,
(revision, registryEntity) -> {
// delete the specified registry
final RegistryEntity entity = serviceFacade.deleteRegistry(revision, registryEntity.getId());
return generateOkResponse(entity).build();
}
);
}
/**
* Creates a Bulletin.
*

View File

@ -39,13 +39,18 @@ import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.nar.NarClassLoaders;
import org.apache.nifi.registry.bucket.Bucket;
import org.apache.nifi.registry.flow.FlowRegistry;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.IllegalClusterResourceRequestException;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.api.dto.AboutDTO;
import org.apache.nifi.web.api.dto.BannerDTO;
import org.apache.nifi.web.api.dto.BucketDTO;
import org.apache.nifi.web.api.dto.BulletinBoardDTO;
import org.apache.nifi.web.api.dto.BulletinQueryDTO;
import org.apache.nifi.web.api.dto.ClusterDTO;
@ -64,6 +69,8 @@ import org.apache.nifi.web.api.entity.AboutEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.BannerEntity;
import org.apache.nifi.web.api.entity.BucketEntity;
import org.apache.nifi.web.api.entity.BucketsEntity;
import org.apache.nifi.web.api.entity.BulletinBoardEntity;
import org.apache.nifi.web.api.entity.ClusteSummaryEntity;
import org.apache.nifi.web.api.entity.ClusterSearchResultsEntity;
@ -84,6 +91,8 @@ import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorTypesEntity;
import org.apache.nifi.web.api.entity.RegistriesEntity;
import org.apache.nifi.web.api.entity.RegistryEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity;
@ -112,6 +121,7 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
@ -148,8 +158,11 @@ public class FlowResource extends ApplicationResource {
private TemplateResource templateResource;
private ProcessGroupResource processGroupResource;
private ControllerServiceResource controllerServiceResource;
private ControllerResource controllerResource;
private ReportingTaskResource reportingTaskResource;
private FlowRegistryClient flowRegistryClient;
public FlowResource() {
super();
}
@ -1317,6 +1330,98 @@ public class FlowResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
// ----------
// registries
// ----------
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("registries")
@ApiOperation(value = "Gets the listing of available registries", response = RegistriesEntity.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 getRegistries() {
authorizeFlow();
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
final Set<RegistryEntity> registries = serviceFacade.getRegistries();
registries.forEach(registry -> controllerResource.populateRemainingRegistryEntityContent(registry));
final RegistriesEntity registryEntities = new RegistriesEntity();
registryEntities.setRegistries(registries);
return generateOkResponse(registryEntities).build();
}
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("registries/{id}/buckets")
@ApiOperation(value = "Gets the buckets from the specified registry for the current user", response = BucketsEntity.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 getBuckets(
@ApiParam(
value = "The registry id.",
required = true
)
@PathParam("id") String id) {
authorizeFlow();
try {
final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(id);
if (flowRegistry == null) {
throw new IllegalArgumentException("The specified registry id is unknown to this NiFi.");
}
final Set<Bucket> userBuckets = flowRegistry.getBuckets(NiFiUserUtils.getNiFiUser());
final BucketsEntity bucketsEntity = new BucketsEntity();
if (userBuckets != null) {
final Set<BucketEntity> bucketSet = new HashSet<>();
for (final Bucket userBucket : userBuckets) {
final BucketDTO bucket = new BucketDTO();
bucket.setId(userBucket.getIdentifier());
bucket.setName(userBucket.getName());
bucket.setDescription(userBucket.getDescription());
bucket.setCreated(userBucket.getCreatedTimestamp());
final BucketEntity bucketEntity = new BucketEntity();
bucketEntity.setBucket(bucket);
bucketSet.add(bucketEntity);
}
bucketsEntity.setBuckets(bucketSet);
}
return generateOkResponse(bucketsEntity).build();
} catch (final IOException ioe) {
throw new NiFiCoreException("Unable to obtain bucket listing: " + ioe.getMessage(), ioe);
}
}
// --------------
// bulletin board
// --------------
@ -2524,6 +2629,10 @@ public class FlowResource extends ApplicationResource {
this.processGroupResource = processGroupResource;
}
public void setControllerResource(ControllerResource controllerResource) {
this.controllerResource = controllerResource;
}
public void setControllerServiceResource(ControllerServiceResource controllerServiceResource) {
this.controllerServiceResource = controllerServiceResource;
}
@ -2535,4 +2644,8 @@ public class FlowResource extends ApplicationResource {
public void setAuthorizer(Authorizer authorizer) {
this.authorizer = authorizer;
}
public void setFlowRegistryClient(FlowRegistryClient flowRegistryClient) {
this.flowRegistryClient = flowRegistryClient;
}
}

View File

@ -17,8 +17,6 @@
package org.apache.nifi.web.api;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.core.ResourceContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@ -84,9 +82,9 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@ -109,8 +107,6 @@ import java.util.stream.Collectors;
public class VersionsResource extends ApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(VersionsResource.class);
@Context
private ResourceContext resourceContext;
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
private ComponentLifecycle clusterComponentLifecycle;
@ -128,12 +124,11 @@ public class VersionsResource extends ApplicationResource {
private ActiveRequest activeRequest = null;
private final Object activeRequestMonitor = new Object();
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("process-groups/{id}")
@ApiOperation(value = "Gets the Version Control information for a process group", response = VersionControlInformationEntity.class, authorizations = {
@ApiOperation(value = "Gets the Version Control information for a process group", response = VersionControlInformationEntity.class, notes = NON_GUARANTEED_ENDPOINT, authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}")
})
@ApiResponses(value = {
@ -144,6 +139,7 @@ public class VersionsResource extends ApplicationResource {
@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 getVersionInformation(@ApiParam(value = "The process group id.", required = true) @PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
@ -155,9 +151,11 @@ public class VersionsResource extends ApplicationResource {
});
// get the version control information for this process group
final VersionControlInformationEntity entity = serviceFacade.getVersionControlInformation(groupId);
VersionControlInformationEntity entity = serviceFacade.getVersionControlInformation(groupId);
if (entity == null) {
throw new ResourceNotFoundException("Process Group with ID " + groupId + " is not currently under Version Control");
final ProcessGroupEntity processGroup = serviceFacade.getProcessGroup(groupId);
entity = new VersionControlInformationEntity();
entity.setProcessGroupRevision(processGroup.getRevision());
}
return generateOkResponse(entity).build();
@ -168,7 +166,10 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("start-requests")
@ApiOperation(value = "Creates a request so that a Process Group can be placed under Version Control or have its Version Control configuration changed", response = VersionControlInformationEntity.class)
@ApiOperation(
value = "Creates a request so that a Process Group can be placed under Version Control or have its Version Control configuration changed",
response = VersionControlInformationEntity.class,
notes = NON_GUARANTEED_ENDPOINT)
@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."),
@ -186,6 +187,7 @@ public class VersionsResource extends ApplicationResource {
serviceFacade,
/* entity */ null,
lookup -> {
// TODO - pass in PG ID to authorize
},
/* verifier */ null,
requestEntity -> {
@ -213,7 +215,11 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("start-requests/{id}")
@ApiOperation(value = "Updates the request with the given ID", response = VersionControlInformationEntity.class, authorizations = {
@ApiOperation(
value = "Updates the request with the given ID",
response = VersionControlInformationEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}")
})
@ApiResponses(value = {
@ -301,7 +307,9 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("start-requests/{id}")
@ApiOperation(value = "Deletes the request with the given ID")
@ApiOperation(
value = "Deletes the request with the given ID",
notes = NON_GUARANTEED_ENDPOINT)
@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."),
@ -343,7 +351,11 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("process-groups/{id}")
@ApiOperation(value = "Begins version controlling the Process Group with the given ID", response = VersionControlInformationEntity.class, authorizations = {
@ApiOperation(
value = "Begins version controlling the Process Group with the given ID",
response = VersionControlInformationEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}")
})
@ApiResponses(value = {
@ -545,7 +557,11 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("process-groups/{id}")
@ApiOperation(value = "Stops version controlling the Process Group with the given ID", response = VersionControlInformationEntity.class, authorizations = {
@ApiOperation(
value = "Stops version controlling the Process Group with the given ID",
response = VersionControlInformationEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}"),
@Authorization(value = "Write - /process-groups/{uuid}"),
})
@ -557,10 +573,14 @@ public class VersionsResource extends ApplicationResource {
@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 stopVersionControl(
@ApiParam(value = "The version is used to verify the client is working with the latest version of the flow.", required = false) @QueryParam(VERSION) final LongParameter version,
@ApiParam(value = "If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.", required = false) @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
@ApiParam(
value = "The version is used to verify the client is working with the latest version of the flow.",
required = false)
@QueryParam(VERSION) final LongParameter version,
@ApiParam(
value = "If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.",
required = false)
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
@ApiParam("The process group id.") @PathParam("id") final String groupId) throws IOException {
if (isReplicateRequest()) {
@ -584,8 +604,8 @@ public class VersionsResource extends ApplicationResource {
}
},
(revision, groupEntity) -> {
// set the version control info to null
final VersionControlInformationEntity entity = serviceFacade.setVersionControlInformation(requestRevision, groupId, null, null);
// disconnect from version control
final VersionControlInformationEntity entity = serviceFacade.deleteVersionControl(requestRevision, groupId);
// generate the response
return generateOkResponse(entity).build();
@ -597,8 +617,10 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("process-groups/{id}")
@ApiOperation(value = "For a Process Group that is already under Version Control, this will update the version of the flow to a different version",
@ApiOperation(
value = "For a Process Group that is already under Version Control, this will update the version of the flow to a different version",
response = VersionControlInformationEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}"),
@Authorization(value = "Write - /process-groups/{uuid}")
@ -686,8 +708,10 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("update-requests/{id}")
@ApiOperation(value = "Returns the Update Request with the given ID",
@ApiOperation(
value = "Returns the Update Request with the given ID",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
})
@ApiResponses(value = {
@ -705,8 +729,10 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("revert-requests/{id}")
@ApiOperation(value = "Returns the Revert Request with the given ID",
@ApiOperation(
value = "Returns the Revert Request with the given ID",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
})
@ApiResponses(value = {
@ -750,7 +776,11 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("update-requests/{id}")
@ApiOperation(value = "Deletes the Update Request with the given ID", response = VersionedFlowUpdateRequestEntity.class, authorizations = {
@ApiOperation(
value = "Deletes the Update Request with the given ID",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
})
@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."),
@ -767,7 +797,11 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("revert-requests/{id}")
@ApiOperation(value = "Deletes the Revert Request with the given ID", response = VersionedFlowUpdateRequestEntity.class, authorizations = {
@ApiOperation(
value = "Deletes the Revert Request with the given ID",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
})
@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."),
@ -813,8 +847,12 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("update-requests/process-groups/{id}")
@ApiOperation(value = "For a Process Group that is already under Version Control, this will initiate the action of changing "
+ "from a specific version of the flow in the Flow Registry to a different version of the flow.", response = VersionedFlowUpdateRequestEntity.class, authorizations = {
@ApiOperation(
value = "For a Process Group that is already under Version Control, this will initiate the action of changing "
+ "from a specific version of the flow in the Flow Registry to a different version of the flow.",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}"),
@Authorization(value = "Write - /process-groups/{uuid}")
})
@ -970,9 +1008,13 @@ public class VersionsResource extends ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("revert-requests/process-groups/{id}")
@ApiOperation(value = "For a Process Group that is already under Version Control, this will initiate the action of reverting "
@ApiOperation(
value = "For a Process Group that is already under Version Control, this will initiate the action of reverting "
+ "any changes that have been made to the Process Group since it was last synchronized with the Flow Registry. This will result in the "
+ "flow matching the Versioned Flow that exists in the Flow Registry.", response = VersionedFlowUpdateRequestEntity.class, authorizations = {
+ "flow matching the Versioned Flow that exists in the Flow Registry.",
response = VersionedFlowUpdateRequestEntity.class,
notes = NON_GUARANTEED_ENDPOINT,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}"),
@Authorization(value = "Write - /process-groups/{uuid}")
})
@ -1250,7 +1292,7 @@ public class VersionsResource extends ApplicationResource {
private <T> T getResponseEntity(final NodeResponse nodeResponse, final Class<T> clazz) {
T entity = (T) nodeResponse.getUpdatedEntity();
if (entity == null) {
entity = nodeResponse.getClientResponse().getEntity(clazz);
entity = nodeResponse.getClientResponse().readEntity(clazz);
}
return entity;
}

View File

@ -17,10 +17,10 @@
package org.apache.nifi.web.api.concurrent;
import java.util.Date;
import org.apache.nifi.authorization.user.NiFiUser;
import java.util.Date;
public interface AsynchronousWebRequest<T> {
/**
@ -67,7 +67,7 @@ public interface AsynchronousWebRequest<T> {
/**
* Indicates the reason that the request failed, or <code>null</code> if the request has not failed
*
* @param explanation the reason that the request failed, or <code>null</code> if the request has not failed
* @return the reason that the request failed, or <code>null</code> if the request has not failed
*/
String getFailureReason();

View File

@ -16,33 +16,7 @@
*/
package org.apache.nifi.web.api.dto;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.action.Action;
@ -139,6 +113,7 @@ import org.apache.nifi.provenance.lineage.LineageEdge;
import org.apache.nifi.provenance.lineage.LineageNode;
import org.apache.nifi.provenance.lineage.ProvenanceEventLineageNode;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.registry.flow.VersionControlInformation;
import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedConnection;
import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedControllerService;
@ -209,6 +184,32 @@ import org.apache.nifi.web.api.entity.VariableEntity;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.revision.RevisionManager;
import javax.ws.rs.WebApplicationException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public final class DtoFactory {
@SuppressWarnings("rawtypes")
@ -225,6 +226,7 @@ public final class DtoFactory {
private EntityFactory entityFactory;
private Authorizer authorizer;
private NiFiProperties properties;
private FlowRegistryClient flowRegistryClient;
public ControllerConfigurationDTO createControllerConfigurationDto(final ControllerFacade controllerFacade) {
final ControllerConfigurationDTO dto = new ControllerConfigurationDTO();
@ -242,6 +244,7 @@ public final class DtoFactory {
dto.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer));
dto.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer));
dto.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer));
dto.setSupportsFlowVersioning(CollectionUtils.isNotEmpty(flowRegistryClient.getRegistryIdentifiers()));
final Date now = new Date();
dto.setTimeOffset(TimeZone.getDefault().getOffset(now.getTime()));
@ -1687,6 +1690,9 @@ public final class DtoFactory {
dto.setId(group.getIdentifier());
dto.setName(group.getName());
final VersionControlInformationDTO versionControlInformation = createVersionControlInformationDto(group);
dto.setVersionControlInformation(versionControlInformation);
return dto;
}
@ -2145,7 +2151,7 @@ public final class DtoFactory {
dto.setComments(group.getComments());
dto.setName(group.getName());
dto.setVersionedComponentId(group.getVersionedComponentId().orElse(null));
dto.setVersionControlInformation(createVersionControlInformationDto(group.getVersionControlInformation()));
dto.setVersionControlInformation(createVersionControlInformationDto(group));
final Map<String, String> variables = group.getVariableRegistry().getVariableMap().entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().getName(), entry -> entry.getValue()));
@ -2169,12 +2175,18 @@ public final class DtoFactory {
return dto;
}
public VersionControlInformationDTO createVersionControlInformationDto(final VersionControlInformation versionControlInfo) {
public VersionControlInformationDTO createVersionControlInformationDto(final ProcessGroup group) {
if (group == null) {
return null;
}
final VersionControlInformation versionControlInfo = group.getVersionControlInformation();
if (versionControlInfo == null) {
return null;
}
final VersionControlInformationDTO dto = new VersionControlInformationDTO();
dto.setGroupId(group.getIdentifier());
dto.setRegistryId(versionControlInfo.getRegistryIdentifier());
dto.setBucketId(versionControlInfo.getBucketIdentifier());
dto.setFlowId(versionControlInfo.getFlowIdentifier());
@ -3722,4 +3734,8 @@ public final class DtoFactory {
public void setProperties(final NiFiProperties properties) {
this.properties = properties;
}
public void setFlowRegistryClient(FlowRegistryClient flowRegistryClient) {
this.flowRegistryClient = flowRegistryClient;
}
}

View File

@ -213,6 +213,7 @@ public final class EntityFactory {
public ProcessGroupEntity createProcessGroupEntity(final ProcessGroupDTO dto, final RevisionDTO revision, final PermissionsDTO permissions,
final ProcessGroupStatusDTO status, final List<BulletinEntity> bulletins) {
final ProcessGroupEntity entity = new ProcessGroupEntity();
entity.setRevision(revision);
if (dto != null) {

View File

@ -16,10 +16,6 @@
*/
package org.apache.nifi.web.dao;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup;
@ -28,6 +24,10 @@ 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 java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
public interface ProcessGroupDAO {
/**
@ -112,7 +112,7 @@ public interface ProcessGroupDAO {
* @param groupId the ID of the process group
* @param proposedSnapshot Flow the new version of the flow
* @param versionControlInformation the new Version Control Information
* @param the seed value to use for generating ID's for new components
* @param componentIdSeed the seed value to use for generating ID's for new components
* @return the process group
*/
ProcessGroup updateProcessGroupFlow(String groupId, VersionedFlowSnapshot proposedSnapshot, VersionControlInformationDTO versionControlInformation, String componentIdSeed,
@ -127,6 +127,14 @@ public interface ProcessGroupDAO {
*/
ProcessGroup updateVersionControlInformation(VersionControlInformationDTO versionControlInformation, Map<String, String> versionedComponentMapping);
/**
* Disconnects the specified group from version control.
*
* @param groupId group id
* @return the corresponding Process Group
*/
ProcessGroup disconnectVersionControl(String groupId);
/**
* Updates the specified variable registry
*

View File

@ -16,13 +16,6 @@
*/
package org.apache.nifi.web.dao.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Port;
import org.apache.nifi.connectable.Position;
@ -43,6 +36,13 @@ import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
import org.apache.nifi.web.api.entity.VariableEntity;
import org.apache.nifi.web.dao.ProcessGroupDAO;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGroupDAO {
private FlowController flowController;
@ -246,6 +246,12 @@ public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGrou
return group;
}
public ProcessGroup disconnectVersionControl(final String groupId) {
final ProcessGroup group = locateProcessGroup(flowController, groupId);
group.disconnectVersionControl();
return group;
}
@Override
public ProcessGroup updateProcessGroupFlow(final String groupId, final VersionedFlowSnapshot proposedSnapshot, final VersionControlInformationDTO versionControlInformation,
final String componentIdSeed, final boolean verifyNotModified) {

View File

@ -17,19 +17,6 @@
package org.apache.nifi.web.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
@ -55,7 +42,18 @@ import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ClusterReplicationComponentLifecycle implements ComponentLifecycle {
private static final Logger logger = LoggerFactory.getLogger(ClusterReplicationComponentLifecycle.class);
@ -155,8 +153,9 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
* Periodically polls the process group with the given ID, waiting for all processors whose ID's are given to have the given Scheduled State.
*
* @param user the user making the request
* @param originalUri the original uri
* @param groupId the ID of the Process Group to poll
* @param processorIds the ID of all Processors whose state should be equal to the given desired state
* @param processors the Processors whose state should be equal to the given desired state
* @param desiredState the desired state for all processors with the ID's given
* @param pause the Pause that can be used to wait between polling
* @return <code>true</code> if successful, <code>false</code> if unable to wait for processors to reach the desired state
@ -172,7 +171,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
}
final Map<String, String> headers = new HashMap<>();
final MultivaluedMap<String, String> requestEntity = new MultivaluedMapImpl();
final MultivaluedMap<String, String> requestEntity = new MultivaluedHashMap<>();
boolean continuePolling = true;
while (continuePolling) {
@ -217,7 +216,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
private <T> T getResponseEntity(final NodeResponse nodeResponse, final Class<T> clazz) {
T entity = (T) nodeResponse.getUpdatedEntity();
if (entity == null) {
entity = nodeResponse.getClientResponse().getEntity(clazz);
entity = nodeResponse.getClientResponse().readEntity(clazz);
}
return entity;
}
@ -354,7 +353,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
}
final Map<String, String> headers = new HashMap<>();
final MultivaluedMap<String, String> requestEntity = new MultivaluedMapImpl();
final MultivaluedMap<String, String> requestEntity = new MultivaluedHashMap<>();
boolean continuePolling = true;
while (continuePolling) {
@ -405,7 +404,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
* Updates the affected controller services in the specified updateRequest with the serviceEntities.
*
* @param serviceEntities service entities
* @param updateRequest update request
* @param affectedServices affected services
*/
private void updateAffectedControllerServices(final Set<ControllerServiceEntity> serviceEntities, final Map<String, AffectedComponentEntity> affectedServices) {
// update the affected components

View File

@ -17,12 +17,6 @@
package org.apache.nifi.web.util;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceState;
@ -38,6 +32,12 @@ import org.apache.nifi.web.revision.RevisionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class LocalComponentLifecycle implements ComponentLifecycle {
private static final Logger logger = LoggerFactory.getLogger(LocalComponentLifecycle.class);
@ -232,7 +232,7 @@ public class LocalComponentLifecycle implements ComponentLifecycle {
* Periodically polls the process group with the given ID, waiting for all controller services whose ID's are given to have the given Controller Service State.
*
* @param groupId the ID of the Process Group to poll
* @param serviceIds the ID of all Controller Services whose state should be equal to the given desired state
* @param affectedServices all Controller Services whose state should be equal to the given desired state
* @param desiredState the desired state for all services with the ID's given
* @param pause the Pause that can be used to wait between polling
* @param user the user that is retrieving the controller services
@ -275,7 +275,7 @@ public class LocalComponentLifecycle implements ComponentLifecycle {
* Updates the affected controller services in the specified updateRequest with the serviceEntities.
*
* @param serviceEntities service entities
* @param updateRequest update request
* @param affectedServices all Controller Services whose state should be equal to the given desired state
*/
private void updateAffectedControllerServices(final Set<ControllerServiceEntity> serviceEntities, final Map<String, AffectedComponentEntity> affectedServices) {
// update the affected components

View File

@ -53,6 +53,7 @@
<property name="authorizer" ref="authorizer"/>
<property name="bulletinRepository" ref="bulletinRepository"/>
<property name="properties" ref="nifiProperties"/>
<property name="flowRegistryClient" ref="flowRegistryClient" />
</bean>
<!-- snippet utils -->
@ -208,6 +209,7 @@
<property name="remoteProcessGroupResource" ref="remoteProcessGroupResource"/>
<property name="connectionResource" ref="connectionResource"/>
<property name="templateResource" ref="templateResource"/>
<property name="controllerResource" ref="controllerResource"/>
<property name="controllerServiceResource" ref="controllerServiceResource"/>
<property name="reportingTaskResource" ref="reportingTaskResource"/>
<property name="processGroupResource" ref="processGroupResource"/>
@ -215,6 +217,7 @@
<property name="clusterCoordinator" ref="clusterCoordinator"/>
<property name="requestReplicator" ref="requestReplicator" />
<property name="flowController" ref="flowController" />
<property name="flowRegistryClient" ref="flowRegistryClient" />
</bean>
<bean id="resourceResource" class="org.apache.nifi.web.api.ResourceResource" scope="singleton">
<property name="serviceFacade" ref="serviceFacade"/>

View File

@ -475,6 +475,7 @@
<include>${staging.dir}/js/nf/canvas/nf-draggable.js</include>
<include>${staging.dir}/js/nf/canvas/nf-connectable.js</include>
<include>${staging.dir}/js/nf/canvas/nf-graph.js</include>
<include>${staging.dir}/js/nf/canvas/nf-flow-version.js</include>
<include>${staging.dir}/js/nf/nf-filtered-dialog-common.js</include>
<include>${staging.dir}/js/nf/nf-status-history.js</include>
<include>${staging.dir}/js/nf/canvas/nf-queue-listing.js</include>

View File

@ -57,6 +57,7 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-ng-bridge.js?
<script type="text/javascript" src="js/nf/canvas/nf-draggable.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/canvas/nf-connectable.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/canvas/nf-graph.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/canvas/nf-flow-version.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/nf-filtered-dialog-common.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/nf-status-history.js?${project.version}"></script>\n\
<script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\

View File

@ -115,6 +115,8 @@
<jsp:include page="/WEB-INF/partials/canvas/instantiate-template-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/fill-color-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/connections-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/save-flow-version-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/registry-configuration-dialog.jsp"/>
<div id="canvas-container" class="unselectable"></div>
<div id="canvas-tooltips">
<div id="processor-tooltips"></div>

View File

@ -22,56 +22,56 @@
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.processor}}"
id="processor-component"
class="component-button icon icon-processor"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.processorComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.inputPort}}"
id="port-in-component"
class="component-button icon icon-port-in"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.inputPortComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.outputPort}}"
id="port-out-component"
class="component-button icon icon-port-out"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.outputPortComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.processGroup}}"
id="group-component"
class="component-button icon icon-group"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.groupComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.remoteProcessGroup}}"
id="group-remote-component"
class="component-button icon icon-group-remote"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.remoteGroupComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.funnel}}"
id="funnel-component"
class="component-button icon icon-funnel"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.funnelComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.template}}"
id="template-component"
class="component-button icon icon-template"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.templateComponent);">
<span class="component-button-grip"></span>
</button>
<button title="{{appCtrl.serviceProvider.headerCtrl.toolboxCtrl.config.type.label}}"
id="label-component"
class="component-button icon icon-label"
ng-disabled="!appCtrl.nf.CanvasUtils.canWrite();"
ng-disabled="!appCtrl.nf.CanvasUtils.canWriteCurrentGroup();"
nf-draggable="appCtrl.serviceProvider.headerCtrl.toolboxCtrl.draggableComponentConfig(appCtrl.serviceProvider.headerCtrl.toolboxCtrl.labelComponent);">
<span class="component-button-grip"></span>
</button>

View File

@ -0,0 +1,40 @@
<%--
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.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
<div id="registry-configuration-dialog" layout="column" class="hidden medium-dialog">
<div class="dialog-content">
<div class="setting">
<div class="setting-name">Name</div>
<div class="setting-field">
<span id="registry-id" class="hidden"></span>
<input type="text" id="registry-name" class="setting-input"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Location</div>
<div class="setting-field">
<input type="text" id="registry-location" class="setting-input"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Description</div>
<div class="setting-field">
<textarea id="registry-description" class="setting-input"></textarea>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
<%--
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.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
<div id="save-flow-version-dialog" layout="column" class="hidden large-dialog">
<div class="dialog-content">
<div class="setting">
<div class="setting-name">Registry</div>
<div class="setting-field">
<div id="flow-version-registry-combo"></div>
<div id="flow-version-registry" class="hidden"></div>
</div>
</div>
<div class="setting">
<div class="setting-name">Location</div>
<div class="setting-field">
<div id="flow-version-bucket-combo"></div>
<div id="flow-version-bucket" class="hidden"></div>
</div>
</div>
<div class="setting">
<div class="setting-name">Name</div>
<div class="setting-field">
<span id="flow-version-process-group-id" class="hidden"></span>
<input type="text" id="flow-version-name" class="setting-input"/>
</div>
</div>
<div class="setting">
<div class="setting-name">Description</div>
<div class="setting-field">
<textarea id="flow-version-description" class="setting-input"></textarea>
</div>
</div>
<div class="setting">
<div class="setting-name">Change Comments</div>
<div class="setting-field">
<textarea id="flow-version-change-comments" class="setting-input"></textarea>
</div>
</div>
</div>
</div>

View File

@ -62,6 +62,9 @@
<div id="reporting-tasks-tab-content" class="configuration-tab controller-settings-table">
<div id="reporting-tasks-table" class="settings-table"></div>
</div>
<div id="registries-tab-content" class="configuration-tab controller-settings-table">
<div id="registries-table" class="settings-table"></div>
</div>
</div>
</div>
<div id="settings-refresh-container">

View File

@ -212,6 +212,14 @@ div.progress-label {
height: 575px;
}
/*
Flow Version
*/
#flow-version-description, #flow-version-change-comments {
height: 85px;
}
/*
Variable Registry
*/
@ -251,6 +259,14 @@ div.slick-cell div.overridden {
text-decoration: line-through;
}
/*
Registry configuration dialog
*/
#registry-description {
height: 85px;
}
/*
General dialog styles.
*/

View File

@ -422,6 +422,14 @@ text.process-group-name {
font-size: 14px;
}
text.version-control {
font-family: FontAwesome;
font-size: 18px;
fill: rgba(0, 255, 0, 0.65);
stroke: rgba(0, 0, 0, 0.65);
visibility: hidden;
}
text.process-group-contents-count {
fill: #775351;
font-size: 15px;

View File

@ -278,6 +278,11 @@ rect.birdseye-brush {
top: 8px;
}
span.breadcrumb-version-control {
color: #0f0;
text-shadow: 0px 0px 1px #000;
}
#breadcrumbs-left-border {
position: absolute;
left: 0;

View File

@ -74,6 +74,21 @@
}
},
/**
* Updates the version control information for the specified process group.
*
* @param processGroupId
* @param versionControlInformation
*/
updateVersionControlInformation: function (processGroupId, versionControlInformation) {
$.each(this.breadcrumbs, function (_, breadcrumbEntity) {
if (breadcrumbEntity.id === processGroupId) {
breadcrumbEntity.breadcrumb.versionControlInformation = versionControlInformation;
return false;
}
});
},
/**
* Reset the breadcrumbs.
*/

View File

@ -234,7 +234,7 @@
*/
getContextName: function () {
var selection = nfCanvasUtils.getSelection();
var canRead = nfCanvasUtils.canReadFromGroup();
var canRead = nfCanvasUtils.canReadCurrentGroup();
if (selection.empty()) {
if (canRead) {

View File

@ -33,6 +33,7 @@
'nf.Shell',
'nf.VariableRegistry',
'nf.ComponentState',
'nf.FlowVersion',
'nf.Draggable',
'nf.Birdseye',
'nf.Connection',
@ -55,8 +56,8 @@
'nf.ComponentVersion',
'nf.QueueListing',
'nf.StatusHistory'],
function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory) {
return (nf.Actions = factory($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory));
function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory) {
return (nf.Actions = factory($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.Actions =
@ -74,6 +75,7 @@
require('nf.Shell'),
require('nf.VariableRegistry'),
require('nf.ComponentState'),
require('nf.FlowVersion'),
require('nf.Draggable'),
require('nf.Birdseye'),
require('nf.Connection'),
@ -111,6 +113,7 @@
root.nf.Shell,
root.nf.VariableRegistry,
root.nf.ComponentState,
root.nf.FlowVersion,
root.nf.Draggable,
root.nf.Birdseye,
root.nf.Connection,
@ -134,7 +137,7 @@
root.nf.QueueListing,
root.nf.StatusHistory);
}
}(this, function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory) {
}(this, function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetails, nfPolicyManagement, nfRemoteProcessGroup, nfLabel, nfProcessor, nfRemoteProcessGroupPorts, nfComponentVersion, nfQueueListing, nfStatusHistory) {
'use strict';
var config = {
@ -1236,6 +1239,51 @@
nfComponentState.showState(processor, nfCanvasUtils.isConfigurable(selection));
},
/**
* Shows the flow version dialog.
*/
saveFlowVersion: function (selection) {
if (selection.empty()) {
nfFlowVersion.showFlowVersionDialog(nfCanvasUtils.getGroupId());
} else if (selection.size() === 1) {
var selectionData = selection.datum();
if (nfCanvasUtils.isProcessGroup(selection)) {
nfFlowVersion.showFlowVersionDialog(selectionData.id);
}
}
},
/**
* Reverts outstanding changes.
*/
revertFlowChanges: function (selection) {
if (selection.empty()) {
nfFlowVersion.revertFlowChanges(nfCanvasUtils.getGroupId());
} else if (selection.size() === 1) {
var selectionData = selection.datum();
nfFlowVersion.revertFlowChanges(selectionData.id);
}
},
/**
* Changes the flow version.
*/
changeFlowVersion: function (selection) {
},
/**
* Disconnects a Process Group from flow versioning.
*/
disconnectFlowVersioning: function (selection) {
if (selection.empty()) {
nfFlowVersion.disconnectFlowVersioning(nfCanvasUtils.getGroupId());
} else if (selection.size() === 1) {
var selectionData = selection.datum();
nfFlowVersion.disconnectFlowVersioning(selectionData.id);
}
},
/**
* Opens the variable registry for the specified selection of the current group if the selection is emtpy.
*

View File

@ -39,6 +39,7 @@
'nf.QueueListing',
'nf.VariableRegistry',
'nf.ComponentState',
'nf.FlowVersion',
'nf.ComponentVersion',
'nf.Draggable',
'nf.Connectable',
@ -82,8 +83,8 @@
'nf.ng.Canvas.OperateCtrl',
'nf.ng.BreadcrumbsDirective',
'nf.ng.DraggableDirective'],
function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective) {
return factory($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective);
function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective) {
return factory($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective);
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = factory(require('jquery'),
@ -106,6 +107,7 @@
require('nf.QueueListing'),
require('nf.VariableRegistry'),
require('nf.ComponentState'),
require('nf.FlowVersion'),
require('nf.ComponentVersion'),
require('nf.Draggable'),
require('nf.Connectable'),
@ -170,6 +172,7 @@
root.nf.QueueListing,
root.nf.VariableRegistry,
root.nf.ComponentState,
root.nf.FlowVersion,
root.nf.ComponentVersion,
root.nf.Draggable,
root.nf.Connectable,
@ -214,7 +217,7 @@
root.nf.ng.BreadcrumbsDirective,
root.nf.ng.DraggableDirective);
}
}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective) {
}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfClusterSummary, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfControllerServices, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupPorts, nfPortConfiguration, nfLabelConfiguration, nfProcessorDetails, nfPortDetails, nfConnectionDetails, nfRemoteProcessGroupDetails, nfGoto, nfNgBridge, appCtrl, appConfig, serviceProvider, breadcrumbsCtrl, headerCtrl, flowStatusCtrl, globalMenuCtrl, toolboxCtrl, processorComponent, inputPortComponent, outputPortComponent, processGroupComponent, remoteProcessGroupComponent, funnelComponent, templateComponent, labelComponent, graphControlsCtrl, navigateCtrl, operateCtrl, breadcrumbsDirective, draggableDirective) {
var config = {
urls: {
@ -334,6 +337,7 @@
nfCanvas.setManagedAuthorizer(configDetails.supportsManagedAuthorizer);
nfCanvas.setConfigurableAuthorizer(configDetails.supportsConfigurableAuthorizer);
nfCanvas.setConfigurableUsersAndGroups(configDetails.supportsConfigurableUsersAndGroups);
nfCanvas.setSupportsFlowVersioning(configDetails.supportsFlowVersioning);
// init nfStorage
nfStorage.init();
@ -352,6 +356,7 @@
nfQueueListing.init();
nfVariableRegistry.init();
nfComponentState.init();
nfFlowVersion.init();
nfComponentVersion.init(nfSettings);
// initialize the component behaviors

View File

@ -1819,6 +1819,13 @@
}
},
/**
* Returns whether this NiFi supports flow versioning.
*/
supportsFlowVersioning: function () {
return nfCanvas.supportsFlowVersioning();
},
/**
* Returns whether the authorizer is managed.
*/
@ -1884,7 +1891,7 @@
*
* @returns {boolean} can write
*/
canReadFromGroup: function () {
canReadCurrentGroup: function () {
return nfCanvas.canRead();
},
@ -1893,7 +1900,7 @@
*
* @returns {boolean} can write
*/
canWrite: function () {
canWriteCurrentGroup: function () {
return nfCanvas.canWrite();
},

View File

@ -85,6 +85,7 @@
var permissions = null;
var parentGroupId = null;
var managedAuthorizer = false;
var supportsFlowVersioning = false;
var configurableAuthorizer = false;
var configurableUsersAndGroups = false;
var svg = null;
@ -908,6 +909,23 @@
return managedAuthorizer;
},
/**
* Set whether this NiFi supports flow versioning.
*
* @param bool Whether this NiFi supports flow versioning
*/
setSupportsFlowVersioning: function (bool) {
supportsFlowVersioning = bool;
},
/**
*
* @returns {boolean}
*/
supportsFlowVersioning: function () {
return supportsFlowVersioning;
},
/**
* Set whether the authorizer is configurable.
*

View File

@ -230,7 +230,7 @@
applyFilter();
});
// initialize the processor configuration dialog
// initialize the component state dialog
$('#component-state-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Component State',

View File

@ -18,7 +18,7 @@
/* global nf */
/**
* Views state for a given component.
* Handles changing the version of a component bundle.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {

View File

@ -97,7 +97,7 @@
* @param {selection} selection The selection of currently selected components
*/
var canCreateTemplate = function (selection) {
return nfCanvasUtils.canWrite() && (selection.empty() || nfCanvasUtils.canRead(selection));
return nfCanvasUtils.canWriteCurrentGroup() && (selection.empty() || nfCanvasUtils.canRead(selection));
};
/**
@ -106,7 +106,7 @@
* @param {selection} selection The selection of currently selected components
*/
var canUploadTemplate = function (selection) {
return nfCanvasUtils.canWrite() && selection.empty();
return nfCanvasUtils.canWriteCurrentGroup() && selection.empty();
};
/**
@ -369,6 +369,89 @@
return nfCanvasUtils.isProcessGroup(selection);
};
/**
* Determines whether the current selection supports flow versioning.
*
* @param selection
*/
var supportsFlowVersioning = function (selection) {
if (nfCanvasUtils.supportsFlowVersioning() === false) {
return false;
}
if (selection.empty()) {
return nfCanvasUtils.canReadCurrentGroup() && nfCanvasUtils.canWriteCurrentGroup();
}
if (isProcessGroup(selection) === true) {
return nfCanvasUtils.canRead(selection) && nfCanvasUtils.canModify(selection);
}
return false;
};
/**
* Determines whether the current selection supports starting flow versioning.
*
* @param selection
*/
var supportsStartFlowVersioning = function (selection) {
// ensure this selection supports flow versioning above
if (supportsFlowVersioning(selection) === false) {
return false;
}
if (selection.empty()) {
// check bread crumbs for version control information in the current group
var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
if (breadcrumbEntities.length > 0) {
var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
if (breadcrumbEntity.permissions.canRead) {
return nfCommon.isUndefinedOrNull(breadcrumbEntity.breadcrumb.versionControlInformation);
} else {
return false;
}
} else {
return false;
}
}
// check the selection for version control information
var processGroupData = selection.datum();
return nfCommon.isUndefinedOrNull(processGroupData.component.versionControlInformation);
};
/**
* Determines whether the current selection supports stopping flow versioning.
*
* @param selection
*/
var supportsStopFlowVersioning = function (selection) {
// ensure this selection supports flow versioning above
if (supportsFlowVersioning(selection) === false) {
return false;
}
if (selection.empty()) {
// check bread crumbs for version control information in the current group
var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
if (breadcrumbEntities.length > 0) {
var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
if (breadcrumbEntity.permissions.canRead) {
return nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation);
} else {
return false;
}
} else {
return false;
}
}
// check the selection for version control information
var processGroupData = selection.datum();
return nfCommon.isDefinedAndNotNull(processGroupData.component.versionControlInformation);
};
/**
* Determines whether the current selection could have provenance.
*
@ -548,6 +631,16 @@
{id: 'show-details-menu-item', condition: hasDetails, menuItem: {clazz: 'fa fa-gear', text: 'View configuration', action: 'showDetails'}},
{id: 'variable-registry-menu-item', condition: hasVariables, menuItem: {clazz: 'fa', text: 'Variables', action: 'openVariableRegistry'}},
{separator: true},
{id: 'version-menu-item', groupMenuItem: {clazz: 'fa', text: 'Version'}, menuItems: [
{id: 'start-version-control-menu-item', condition: supportsStartFlowVersioning, menuItem: {clazz: 'fa fa-floppy-o', text: 'Start version control', action: 'saveFlowVersion'}},
{separator: true},
{id: 'commit-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa fa-floppy-o', text: 'Commit local changes', action: 'saveFlowVersion'}},
{id: 'revert-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Revert local changes', action: 'revertFlowChanges'}},
{id: 'change-version-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Change version', action: 'changeFlowVersion'}},
{separator: true},
{id: 'stop-version-control-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Stop version control', action: 'disconnectFlowVersioning'}}
]},
{separator: true},
{id: 'enter-group-menu-item', condition: isProcessGroup, menuItem: {clazz: 'fa fa-sign-in', text: 'Enter group', action: 'enterGroup'}},
{separator: true},
{id: 'start-menu-item', condition: isRunnable, menuItem: {clazz: 'fa fa-play', text: 'Start', action: 'start'}},

View File

@ -0,0 +1,476 @@
/*
* 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.
*/
/* global define, module, require, exports */
/**
* Handles versioning.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery',
'nf.ng.Bridge',
'nf.ErrorHandler',
'nf.Dialog',
'nf.Common',
'nf.Client',
'nf.CanvasUtils',
'nf.ProcessGroup',
'nf.Graph'],
function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph) {
return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.FlowVerison =
factory(require('jquery'),
require('nf.ng.Bridge'),
require('nf.ErrorHandler'),
require('nf.Dialog'),
require('nf.Common'),
require('nf.Client'),
require('nf.CanvasUtils'),
require('nf.ProcessGroup'),
require('nf.Graph')));
} else {
nf.FlowVersion = factory(root.$,
root.nf.ng.Bridge,
root.nf.ErrorHandler,
root.nf.Dialog,
root.nf.Common,
root.nf.Client,
root.nf.CanvasUtils,
root.nf.ProcessGroup,
root.nf.Graph);
}
}(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph) {
'use strict';
/**
* Reset the dialog.
*/
var resetDialog = function () {
$('#flow-version-registry-combo').combo('destroy').hide();
$('#flow-version-bucket-combo').combo('destroy').hide();
$('#flow-version-registry').text('').hide();
$('#flow-version-bucket').text('').hide();
$('#flow-version-name').val('');
$('#flow-version-description').val('');
$('#flow-version-change-comments').val('');
$('#flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
};
/**
* Loads the buckets for the specified registryIdentifier for the current user.
*
* @param registryIdentifier
* @returns {*}
*/
var loadBuckets = function (registryIdentifier) {
return $.ajax({
type: 'GET',
url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets',
dataType: 'json'
}).done(function (response) {
var buckets = [];
if (nfCommon.isDefinedAndNotNull(response.buckets) && response.buckets.length > 0) {
response.buckets.sort(function (a, b) {
return a.bucket.name > b.bucket.name;
});
$.each(response.buckets, function (_, bucketEntity) {
var bucket = bucketEntity.bucket;
buckets.push({
text: bucket.name,
value: bucket.id,
description: nfCommon.escapeHtml(bucket.description)
});
});
} else {
buckets.push({
text: 'No available buckets',
value: null,
optionClass: 'unset',
disabled: true
});
}
// load the buckets
$('#flow-version-bucket-combo').combo('destroy').combo({
options: buckets,
select: selectBucket
});
}).fail(function () {
$('#save-flow-version-dialog').modal('refreshButtons');
}).fail(nfErrorHandler.handleAjaxError);
};
/**
* Select handler for the registries combo.
*
* @param selectedOption
*/
var selectRegistry = function (selectedOption) {
if (selectedOption.disabled === true) {
$('#flow-version-bucket-combo').combo('destroy').combo({
options: [{
text: 'No available buckets',
value: null,
optionClass: 'unset',
disabled: true
}]
});
$('#save-flow-version-dialog').modal('refreshButtons');
} else {
loadBuckets(selectedOption.value);
}
};
/**
* Select handler for the buckets combo.
*
* @param selectedOption
*/
var selectBucket = function (selectedOption) {
$('#save-flow-version-dialog').modal('refreshButtons');
};
/**
* Saves a flow version.
*
* @returns {*}
*/
var saveFlowVersion = function () {
var processGroupId = $('#flow-version-process-group-id').text();
var processGroupRevision = $('#flow-version-process-group-id').data('revision');
var saveFlowVersionRequest = {
processGroupRevision: nfClient.getRevision({
revision: {
version: processGroupRevision.version
}
})
};
var versionControlInformation = $('#flow-version-process-group-id').data('versionControlInformation');
if (nfCommon.isDefinedAndNotNull(versionControlInformation)) {
saveFlowVersionRequest['versionedFlow'] = {
registryId: versionControlInformation.registryId,
bucketId: versionControlInformation.bucketId,
flowId: versionControlInformation.flowId,
flowName: $('#flow-version-name').val(),
description: $('#flow-version-description').val(),
comments: $('#flow-version-change-comments').val()
}
} else {
var selectedRegistry = $('#flow-version-registry-combo').combo('getSelectedOption');
var selectedBucket = $('#flow-version-bucket-combo').combo('getSelectedOption');
saveFlowVersionRequest['versionedFlow'] = {
registryId: selectedRegistry.value,
bucketId: selectedBucket.value,
flowName: $('#flow-version-name').val(),
description: $('#flow-version-description').val(),
comments: $('#flow-version-change-comments').val()
}
}
return $.ajax({
type: 'POST',
data: JSON.stringify(saveFlowVersionRequest),
url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json',
contentType: 'application/json'
}).fail(nfErrorHandler.handleAjaxError);
};
return {
init: function () {
// initialize the flow version dialog
$('#save-flow-version-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Save Flow Version',
buttons: [{
buttonText: 'Save',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
disabled: function () {
if ($('#flow-version-registry-combo').is(':visible')) {
var selectedRegistry = $('#flow-version-registry-combo').combo('getSelectedOption');
var selectedBucket = $('#flow-version-bucket-combo').combo('getSelectedOption');
if (nfCommon.isDefinedAndNotNull(selectedRegistry) && nfCommon.isDefinedAndNotNull(selectedBucket)) {
return selectedRegistry.disabled === true || selectedBucket.disabled === true;
} else {
return true;
}
} else {
return false;
}
},
handler: {
click: function () {
var processGroupId = $('#flow-version-process-group-id').text();
saveFlowVersion().done(function (response) {
// refresh either selected PG or bread crumb to reflect connected/tracking status
if (nfCanvasUtils.getGroupId() === processGroupId) {
nfNgBridge.injector.get('breadcrumbsCtrl').updateVersionControlInformation(processGroupId, response.versionControlInformation);
nfNgBridge.digest();
} else {
nfProcessGroup.reload(processGroupId);
}
// close the dialog
$('#save-flow-version-dialog').modal('hide');
});
}
}
}, {
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
$(this).modal('hide');
}
}
}],
handler: {
close: function () {
resetDialog();
}
}
});
},
/**
* Shows the flow version dialog.
*
* @param processGroupId
*/
showFlowVersionDialog: function (processGroupId) {
return $.Deferred(function (deferred) {
$.ajax({
type: 'GET',
url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json'
}).done(function (response) {
// record the revision
$('#flow-version-process-group-id').data('revision', response.processGroupRevision).text(processGroupId);
if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
var versionControlInformation = response.versionControlInformation;
// update the registry and bucket visibility
$('#flow-version-registry').text(versionControlInformation.registryId).show();
$('#flow-version-bucket').text(versionControlInformation.bucketId).show();
$('#flow-version-name').val('');
$('#flow-version-description').val('');
// record the versionControlInformation
$('#flow-version-process-group-id').data('versionControlInformation', versionControlInformation)
deferred.resolve();
} else {
// update the registry and bucket visibility
$('#flow-version-registry-combo').show();
$('#flow-version-bucket-combo').show();
$.ajax({
type: 'GET',
url: '../nifi-api/flow/registries',
dataType: 'json'
}).done(function (registriesResponse) {
var registries = [];
if (nfCommon.isDefinedAndNotNull(registriesResponse.registries) && registriesResponse.registries.length > 0) {
registriesResponse.registries.sort(function (a, b) {
return a.component.name > b.component.name;
});
$.each(registriesResponse.registries, function (_, registryEntity) {
var registry = registryEntity.component;
registries.push({
text: registry.name,
value: registry.id,
description: nfCommon.escapeHtml(registry.description)
});
});
} else {
registries.push({
text: 'No available registries',
value: null,
optionClass: 'unset',
disabled: true
});
}
// load the registries
$('#flow-version-registry-combo').combo({
options: registries,
select: selectRegistry
});
deferred.resolve();
}).fail(function () {
deferred.reject();
}).fail(nfErrorHandler.handleAjaxError);
}
}).fail(nfErrorHandler.handleAjaxError);
}).done(function () {
$('#save-flow-version-dialog').modal('show');
}).fail(function () {
$('#save-flow-version-dialog').modal('refreshButtons');
}).promise();
},
/**
* Reverts changes for the specified Process Group.
*
* @param processGroupId
*/
revertFlowChanges: function (processGroupId) {
// prompt the user before reverting
nfDialog.showYesNoDialog({
headerText: 'Revert Changes',
dialogContent: 'Are you sure you want to revert changes? All flow configuration changes will be reverted to the last version.',
noText: 'Cancel',
yesText: 'Revert',
yesHandler: function () {
$.ajax({
type: 'GET',
url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json'
}).done(function (response) {
if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
var revertFlowVersionRequest = {
processGroupRevision: nfClient.getRevision({
revision: {
version: response.processGroupRevision.version
}
}),
versionControlInformation: response.versionControlInformation
};
$.ajax({
type: 'POST',
data: JSON.stringify(revertFlowVersionRequest),
url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json',
contentType: 'application/json'
}).done(function (response) {
// TODO update multi step to show user the ramifications of reverting for confirmation
if (nfCanvasUtils.getGroupId() === processGroupId) {
// if reverting current PG... reload/refresh this group/canvas
// TODO consider implementing this differently
$.ajax({
type: 'GET',
url: '../nifi-api/flow/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json'
}).done(function (response) {
nfGraph.set(response.processGroupFlow.flow);
}).fail(nfErrorHandler.handleAjaxError);
} else {
// if reverting selected PG... reload selected PG to update counts, etc
nfProcessGroup.reload(processGroupId);
}
nfDialog.showOkDialog({
headerText: 'Revert Changes',
dialogContent: 'This Process Group has been reverted.'
});
}).fail(nfErrorHandler.handleAjaxError);
} else {
nfDialog.showOkDialog({
headerText: 'Revert Changes',
dialogContent: 'This Process Group is not currently under version control.'
});
}
}).fail(nfErrorHandler.handleAjaxError);
}
});
},
/**
* Disconnects the specified Process Group from flow versioning.
*
* @param processGroupId
*/
disconnectFlowVersioning: function (processGroupId) {
// prompt the user before disconnecting
nfDialog.showYesNoDialog({
headerText: 'Disconnect',
dialogContent: 'Are you sure you want to disconnect?',
noText: 'Cancel',
yesText: 'Disconnect',
yesHandler: function () {
$.ajax({
type: 'GET',
url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId),
dataType: 'json'
}).done(function (response) {
if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
var revision = nfClient.getRevision({
revision: {
version: response.processGroupRevision.version
}
});
$.ajax({
type: 'DELETE',
url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId) + '?' + $.param(revision),
dataType: 'json',
contentType: 'application/json'
}).done(function (response) {
// refresh either selected PG or bread crumb to reflect disconnected status
if (nfCanvasUtils.getGroupId() === processGroupId) {
nfNgBridge.injector.get('breadcrumbsCtrl').updateVersionControlInformation(processGroupId, undefined);
nfNgBridge.digest();
} else {
nfProcessGroup.reload(processGroupId);
}
nfDialog.showOkDialog({
headerText: 'Disconnect',
dialogContent: 'This Process Group has been disconnected.'
});
}).fail(nfErrorHandler.handleAjaxError);
} else {
nfDialog.showOkDialog({
headerText: 'Disconnect',
dialogContent: 'This Process Group is not currently under version control.'
})
}
}).fail(nfErrorHandler.handleAjaxError);
}
});
}
};
}));

View File

@ -215,7 +215,7 @@
$('#process-group-configuration').data('process-group', {
'permissions': {
canRead: false,
canWrite: nfCanvasUtils.canWrite()
canWrite: nfCanvasUtils.canWriteCurrentGroup()
}
});
} else {

View File

@ -180,6 +180,15 @@
'class': 'process-group-name'
});
// process group name
processGroup.append('text')
.attr({
'x': 3,
'y': 17,
'class': 'version-control'
})
.text('\uf00c');
// always support selecting and navigation
processGroup.on('dblclick', function (d) {
// enter this group on double click
@ -868,12 +877,18 @@
}).append('title').text(function (d) {
return d.component.name;
});
// update version control information
processGroup.select('text.version-control').style('visibility', nfCommon.isDefinedAndNotNull(processGroupData.component.versionControlInformation) ? 'visible' : 'hidden');
} else {
// clear the process group comments
details.select('text.process-group-comments').text(null);
// clear the process group name
processGroup.select('text.process-group-name').text(null);
// update version control information
processGroup.select('text.version-control').style('visibility', false);
}
// populate the stats

View File

@ -81,7 +81,9 @@
controllerConfig: '../nifi-api/controller/config',
reportingTaskTypes: '../nifi-api/flow/reporting-task-types',
createReportingTask: '../nifi-api/controller/reporting-tasks',
reportingTasks: '../nifi-api/flow/reporting-tasks'
reportingTasks: '../nifi-api/flow/reporting-tasks',
createRegistry: '../nifi-api/controller/registries',
registries: '../nifi-api/flow/registries'
}
};
@ -464,6 +466,94 @@
return addTask;
};
/**
* Adds the specified entity.
*/
var addRegistry = function () {
var registryEntity = {
'revision': nfClient.getRevision({
'revision': {
'version': 0
}
}),
'component': {
'name': $('#registry-name').val(),
'uri': $('#registry-location').val(),
'description': $('#registry-description').val()
}
};
// add the new registry
var addRegistry = $.ajax({
type: 'POST',
url: config.urls.createRegistry,
data: JSON.stringify(registryEntity),
dataType: 'json',
contentType: 'application/json'
}).done(function (registryEntity) {
// add the item
var registriesGrid = $('#registries-table').data('gridInstance');
var registriesData = registriesGrid.getData();
registriesData.addItem($.extend({
type: 'Registry'
}, registryEntity));
// resort
registriesData.reSort();
registriesGrid.invalidate();
// select the new reporting task
var row = registriesData.getRowById(registryEntity.id);
nfFilteredDialogCommon.choseRow(registriesGrid, row);
registriesGrid.scrollRowIntoView(row);
}).fail(nfErrorHandler.handleAjaxError);
// hide the dialog
$('#registry-configuration-dialog').modal('hide');
return addRegistry;
};
/**
* Updates the registry with the specified id.
*
* @param registryId
*/
var updateRegistry = function (registryId) {
var registriesGrid = $('#registries-table').data('gridInstance');
var registriesData = registriesGrid.getData();
var registryEntity = registriesData.getItemById(registryId);
var requestRegistryEntity = {
'revision': nfClient.getRevision(registryEntity),
'component': {
'id': registryId,
'name': $('#registry-name').val(),
'uri': $('#registry-location').val(),
'description': $('#registry-description').val()
}
};
// add the new reporting task
var updateRegistry = $.ajax({
type: 'PUT',
url: registryEntity.uri,
data: JSON.stringify(requestRegistryEntity),
dataType: 'json',
contentType: 'application/json'
}).done(function (registryEntity) {
// add the item
registriesData.updateItem(registryId, $.extend({
type: 'Registry'
}, registryEntity));
}).fail(nfErrorHandler.handleAjaxError);
// hide the dialog
$('#registry-configuration-dialog').modal('hide');
return updateRegistry;
};
/**
* Initializes the new reporting task dialog.
*/
@ -783,6 +873,20 @@
}
}
});
// initialize the registry configuration dialog
$('#registry-configuration-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Add Registry',
handler: {
close: function () {
$('#registry-id').text('');
$('#registry-name').val('');
$('#registry-location').val('');
$('#registry-description').val('');
}
}
});
};
/**
@ -1090,6 +1194,228 @@
});
};
var initRegistriesTable = function () {
var locationFormatter = function (row, cell, value, columnDef, dataContext) {
if (!dataContext.permissions.canRead) {
return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
}
return nfCommon.escapeHtml(dataContext.component.uri);
};
var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
if (!dataContext.permissions.canRead) {
return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
}
return nfCommon.escapeHtml(dataContext.component.description);
};
var registriesActionFormatter = function (row, cell, value, columnDef, dataContext) {
var markup = '';
if (nfCommon.canModifyController()) {
// edit registry
markup += '<div title="Edit" class="pointer edit-registry fa fa-pencil" style="margin-top: 2px; margin-right: 3px;" ></div>';
// remove registry
markup += '<div title="Remove" class="pointer remove-registry fa fa-trash" style="margin-top: 2px; margin-right: 3px;" ></div>';
}
return markup;
};
// define the column model for the reporting tasks table
var registriesColumnModel = [
{
id: 'name',
name: 'Name',
field: 'name',
formatter: nameFormatter,
sortable: true,
resizable: true
},
{
id: 'uri',
name: 'Location',
field: 'uri',
formatter: locationFormatter,
sortable: true,
resizable: true
},
{
id: 'description',
name: 'Description',
field: 'description',
formatter: descriptionFormatter,
sortable: true,
resizable: true
}
];
// action column should always be last
registriesColumnModel.push({
id: 'actions',
name: '&nbsp;',
resizable: false,
formatter: registriesActionFormatter,
sortable: false,
width: 90,
maxWidth: 90
});
// initialize the dataview
var registriesData = new Slick.Data.DataView({
inlineFilters: false
});
registriesData.setItems([]);
// initialize the sort
sort({
columnId: 'name',
sortAsc: true
}, registriesData);
// initialize the grid
var registriesGrid = new Slick.Grid('#registries-table', registriesData, registriesColumnModel, gridOptions);
registriesGrid.setSelectionModel(new Slick.RowSelectionModel());
registriesGrid.registerPlugin(new Slick.AutoTooltips());
registriesGrid.setSortColumn('name', true);
registriesGrid.onSort.subscribe(function (e, args) {
sort({
columnId: args.sortCol.id,
sortAsc: args.sortAsc
}, registriesData);
});
// configure a click listener
registriesGrid.onClick.subscribe(function (e, args) {
var target = $(e.target);
// get the service at this row
var registryEntity = registriesData.getItem(args.row);
// determine the desired action
if (registriesGrid.getColumns()[args.cell].id === 'actions') {
if (target.hasClass('edit-registry')) {
editRegistry(registryEntity);
} else if (target.hasClass('remove-registry')) {
promptToRemoveRegistry(registryEntity);
}
} else if (registriesGrid.getColumns()[args.cell].id === 'moreDetails') {
// if (target.hasClass('view-reporting-task')) {
// nfReportingTask.showDetails(reportingTaskEntity);
// } else if (target.hasClass('reporting-task-usage')) {
// // close the settings dialog
// $('#shell-close-button').click();
//
// // open the documentation for this reporting task
// nfShell.showPage('../nifi-docs/documentation?' + $.param({
// select: reportingTaskEntity.component.type,
// group: reportingTaskEntity.component.bundle.group,
// artifact: reportingTaskEntity.component.bundle.artifact,
// version: reportingTaskEntity.component.bundle.version
// })).done(function () {
// nfSettings.showSettings();
// });
// }
}
});
// wire up the dataview to the grid
registriesData.onRowCountChanged.subscribe(function (e, args) {
registriesGrid.updateRowCount();
registriesGrid.render();
});
registriesData.onRowsChanged.subscribe(function (e, args) {
registriesGrid.invalidateRows(args.rows);
registriesGrid.render();
});
registriesData.syncGridSelection(registriesGrid, true);
// hold onto an instance of the grid
$('#registries-table').data('gridInstance', registriesGrid);
};
/**
* Edits the specified registry entity.
*
* @param registryEntity
*/
var editRegistry = function (registryEntity) {
// populate the dialog
$('#registry-id').text(registryEntity.id);
$('#registry-name').val(registryEntity.component.name);
$('#registry-location').val(registryEntity.component.uri);
$('#registry-description').val(registryEntity.component.description);
// show the dialog
$('#registry-configuration-dialog').modal('setButtonModel', [{
buttonText: 'Update',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
updateRegistry(registryEntity.id);
}
}
}, {
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
$(this).modal('hide');
}
}
}]).modal('show');
};
/**
* Prompts the user before attempting to delete the specified registry.
*
* @param {object} registryEntity
*/
var promptToRemoveRegistry = function (registryEntity) {
// prompt for deletion
nfDialog.showYesNoDialog({
headerText: 'Delete Registry',
dialogContent: 'Delete registry \'' + nfCommon.escapeHtml(registryEntity.component.name) + '\'?',
yesHandler: function () {
removeRegistry(registryEntity);
}
});
};
/**
* Deletes the specified registry.
*
* @param {object} registryEntity
*/
var removeRegistry = function (registryEntity) {
var revision = nfClient.getRevision(registryEntity);
$.ajax({
type: 'DELETE',
url: registryEntity.uri + '?' + $.param({
version: revision.version,
clientId: revision.clientId
}),
dataType: 'json'
}).done(function (response) {
// remove the task
var registryGrid = $('#registries-table').data('gridInstance');
var registryData = registryGrid.getData();
registryData.deleteItem(registryEntity.id);
}).fail(nfErrorHandler.handleAjaxError);
};
/**
* Loads the settings.
*/
@ -1158,6 +1484,9 @@
// load the reporting tasks
var reportingTasks = loadReportingTasks();
// load the registries
var registries = loadRegistries();
// return a deferred for all parts of the settings
return $.when(settings, controllerServicesXhr, reportingTasks).done(function (settingsResult, controllerServicesResult) {
var controllerServicesResponse = controllerServicesResult[0];
@ -1198,6 +1527,32 @@
});
};
/**
* Loads the registries.
*/
var loadRegistries = function () {
return $.ajax({
type: 'GET',
url: config.urls.registries,
dataType: 'json'
}).done(function (response) {
var registries = [];
$.each(response.registries, function (_, registryEntity) {
registries.push($.extend({
type: 'Registry'
}, registryEntity));
});
var registriesGrid = $('#registries-table').data('gridInstance');
var registriesData = registriesGrid.getData();
// update the registries
registriesData.setItems(registries);
registriesData.reSort();
registriesGrid.invalidate();
});
};
/**
* Shows the process group configuration.
*/
@ -1241,6 +1596,9 @@
}, {
name: 'Reporting Tasks',
tabContentId: 'reporting-tasks-tab-content'
}, {
name: 'Registries',
tabContentId: 'registries-tab-content'
}],
select: function () {
var tab = $(this).text();
@ -1267,6 +1625,9 @@
} else if (tab === 'Reporting Tasks') {
$('#settings-save').hide();
return 'Create a new reporting task';
} else if (tab === 'Registries') {
$('#settings-save').hide();
return 'Register a new registry';
}
});
} else {
@ -1276,7 +1637,7 @@
if (tab === 'Reporting Task Controller Services') {
$('#controller-cs-availability').show();
} else if (tab === 'Reporting Tasks') {
} else if (tab === 'Reporting Tasks' || tab === 'Registries') {
$('#controller-cs-availability').hide();
}
@ -1315,6 +1676,32 @@
// set the initial focus
$('#reporting-task-type-filter').focus();
} else if (selectedTab === 'Registries') {
$('#registry-configuration-dialog').modal('setButtonModel', [{
buttonText: 'Add',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
addRegistry();
}
}
}, {
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
$(this).modal('hide');
}
}
}]).modal('show');
}
});
@ -1322,6 +1709,7 @@
initGeneral();
nfControllerServices.init(getControllerServicesTable(), nfSettings.showSettings);
initReportingTasks();
initRegistriesTable();
},
/**

View File

@ -22,6 +22,7 @@ limitations under the License.
<span ng-if="separatorFunc(crumb.parentBreadcrumb)" style="margin: 0 12px;">
&raquo;
</span>
<span ng-if="separatorFunc(crumb.breadcrumb.versionControlInformation)" class="breadcrumb-version-control fa fa-check" style="margin: 0 6px;"></span>
<span class="link"
ng-class="(highlightCrumbId === crumb.id) ? 'link-bold' : ''"
ng-click="clickFunc(crumb.id)">