diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BucketDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BucketDTO.java new file mode 100644 index 0000000000..3c004c7ddd --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BucketDTO.java @@ -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; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerConfigurationDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerConfigurationDTO.java index 61b037ffa9..597d700d5b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerConfigurationDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerConfigurationDTO.java @@ -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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java index 657d760468..03e1a7d0e3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java @@ -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; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RegistryDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RegistryDTO.java new file mode 100644 index 0000000000..f6304300cc --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/RegistryDTO.java @@ -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; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VersionedFlowDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VersionedFlowDTO.java index 27a83e68fc..1e1f5f579a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VersionedFlowDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VersionedFlowDTO.java @@ -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; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/flow/FlowBreadcrumbDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/flow/FlowBreadcrumbDTO.java index 170a30f7e5..60af3fa6f0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/flow/FlowBreadcrumbDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/flow/FlowBreadcrumbDTO.java @@ -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; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketEntity.java new file mode 100644 index 0000000000..3d99308dc7 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketEntity.java @@ -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; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketsEntity.java new file mode 100644 index 0000000000..830c1c256a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/BucketsEntity.java @@ -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 buckets; + + /** + * @return collection of BucketEntity's that are being serialized + */ + public Set getBuckets() { + return buckets; + } + + public void setBuckets(Set buckets) { + this.buckets = buckets; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistriesEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistriesEntity.java new file mode 100644 index 0000000000..6705c7a20a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistriesEntity.java @@ -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 registries; + + /** + * @return collection of LabelEntity's that are being serialized + */ + public Set getRegistries() { + return registries; + } + + public void setRegistries(Set registries) { + this.registries = registries; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistryEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistryEntity.java new file mode 100644 index 0000000000..59685797ec --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RegistryEntity.java @@ -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; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VersionControlInformationEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VersionControlInformationEntity.java index e8ec81fe7f..749a118645 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VersionControlInformationEntity.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VersionControlInformationEntity.java @@ -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") diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java index 8934788b39..d335461be5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java @@ -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 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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/FlowRegistry.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/FlowRegistry.java index a5bb73869b..962a9406eb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/FlowRegistry.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/FlowRegistry.java @@ -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 getBuckets(NiFiUser user) throws IOException; + /** * Registers the given Versioned Flow with the Flow Registry * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml index 09d032eb33..65480048c9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml @@ -160,6 +160,11 @@ org.apache.nifi.registry nifi-registry-flow-diff + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + org.apache.curator diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java index 282c50dd26..3b8117bb06 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java @@ -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 versionedComponentIds) { if (versionedComponentIds == null || versionedComponentIds.isEmpty()) { return; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/FileBasedFlowRegistryClient.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/FileBasedFlowRegistryClient.java index 22ba50b6af..2cc39c62d9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/FileBasedFlowRegistryClient.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/FileBasedFlowRegistryClient.java @@ -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 getBuckets(NiFiUser user) throws IOException { + final Set 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); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java index 27e167800d..18dc51b5c8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java @@ -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 serviceMap = new HashMap<>(); private final Map processorMap = new HashMap<>(); @@ -661,4 +661,9 @@ public class MockProcessGroup implements ProcessGroup { public void setVersionControlInformation(VersionControlInformation versionControlInformation, Map versionedComponentIds) { this.versionControlInfo = versionControlInformation; } + + @Override + public void disconnectVersionControl() { + this.versionControlInfo = null; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index 84e582cf1e..a68ad0c702 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -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 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 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 // ---------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java index 0881cdf0f0..065db86bfc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java @@ -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); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index d3a5fd0196..0105bf18cc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -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> 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 registry = registryCache.get(registryId); + return createRegistry(registry.getKey(), registry.getValue()); + } + + @Override + public Set 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 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 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 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 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)); + } + } + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java index b213755c71..959d06d7c9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java @@ -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. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java index 38e8891763..3e9be6d251 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java @@ -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 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 userBuckets = flowRegistry.getBuckets(NiFiUserUtils.getNiFiUser()); + + final BucketsEntity bucketsEntity = new BucketsEntity(); + + if (userBuckets != null) { + + final Set 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; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java index d3a11dca45..9fbd5e8373 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java @@ -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,9 +215,13 @@ 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 = { - @Authorization(value = "Write - /process-groups/{uuid}") - }) + @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 = { @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."), @@ -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,9 +351,13 @@ 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 = { - @Authorization(value = "Read - /process-groups/{uuid}") - }) + @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 = { @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."), @@ -545,10 +557,14 @@ 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 = { - @Authorization(value = "Read - /process-groups/{uuid}"), - @Authorization(value = "Write - /process-groups/{uuid}"), - }) + @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}"), + }) @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."), @@ -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,12 +617,14 @@ 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", - response = VersionControlInformationEntity.class, - authorizations = { - @Authorization(value = "Read - /process-groups/{uuid}"), - @Authorization(value = "Write - /process-groups/{uuid}") - }) + @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}") + }) @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."), @@ -686,10 +708,12 @@ 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", - response = VersionedFlowUpdateRequestEntity.class, - authorizations = { - }) + @ApiOperation( + value = "Returns 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."), @ApiResponse(code = 401, message = "Client could not be authenticated."), @@ -705,10 +729,12 @@ 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", - response = VersionedFlowUpdateRequestEntity.class, - authorizations = { - }) + @ApiOperation( + value = "Returns 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."), @ApiResponse(code = 401, message = "Client could not be authenticated."), @@ -750,8 +776,12 @@ 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."), @ApiResponse(code = 401, message = "Client could not be authenticated."), @@ -767,8 +797,12 @@ 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."), @ApiResponse(code = 401, message = "Client could not be authenticated."), @@ -813,11 +847,15 @@ 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 = { - @Authorization(value = "Read - /process-groups/{uuid}"), - @Authorization(value = "Write - /process-groups/{uuid}") - }) + @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}") + }) @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."), @@ -970,12 +1008,16 @@ 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 " - + "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 = { - @Authorization(value = "Read - /process-groups/{uuid}"), - @Authorization(value = "Write - /process-groups/{uuid}") - }) + @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, + notes = NON_GUARANTEED_ENDPOINT, + authorizations = { + @Authorization(value = "Read - /process-groups/{uuid}"), + @Authorization(value = "Write - /process-groups/{uuid}") + }) @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."), @@ -1250,7 +1292,7 @@ public class VersionsResource extends ApplicationResource { private T getResponseEntity(final NodeResponse nodeResponse, final Class clazz) { T entity = (T) nodeResponse.getUpdatedEntity(); if (entity == null) { - entity = nodeResponse.getClientResponse().getEntity(clazz); + entity = nodeResponse.getClientResponse().readEntity(clazz); } return entity; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsynchronousWebRequest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsynchronousWebRequest.java index d09f8959d2..2c14008f61 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsynchronousWebRequest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsynchronousWebRequest.java @@ -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 { /** @@ -67,7 +67,7 @@ public interface AsynchronousWebRequest { /** * Indicates the reason that the request failed, or null if the request has not failed * - * @param explanation the reason that the request failed, or null if the request has not failed + * @return the reason that the request failed, or null if the request has not failed */ String getFailureReason(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 489e5909ef..ae3fc56341 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -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 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; + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java index dd8d67f3d9..1e6167cdde 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java @@ -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 bulletins) { + final ProcessGroupEntity entity = new ProcessGroupEntity(); entity.setRevision(revision); if (dto != null) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessGroupDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessGroupDAO.java index 5f4dba53ce..806979f4f8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessGroupDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessGroupDAO.java @@ -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 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 * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java index 6fa316d061..f842a8c8ad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java @@ -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) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java index c41d13b316..3961be7f3f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java @@ -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 true if successful, false if unable to wait for processors to reach the desired state @@ -172,7 +171,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle } final Map headers = new HashMap<>(); - final MultivaluedMap requestEntity = new MultivaluedMapImpl(); + final MultivaluedMap requestEntity = new MultivaluedHashMap<>(); boolean continuePolling = true; while (continuePolling) { @@ -217,7 +216,7 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle private T getResponseEntity(final NodeResponse nodeResponse, final Class 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 headers = new HashMap<>(); - final MultivaluedMap requestEntity = new MultivaluedMapImpl(); + final MultivaluedMap 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 serviceEntities, final Map affectedServices) { // update the affected components diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/LocalComponentLifecycle.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/LocalComponentLifecycle.java index 22ffec75ef..e005d283f4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/LocalComponentLifecycle.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/LocalComponentLifecycle.java @@ -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 serviceEntities, final Map affectedServices) { // update the affected components diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml index 48db565794..99a4f1c7d7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml @@ -53,6 +53,7 @@ + @@ -208,6 +209,7 @@ + @@ -215,6 +217,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml index 4f2cc8ebce..a6f0385271 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml @@ -475,6 +475,7 @@ ${staging.dir}/js/nf/canvas/nf-draggable.js ${staging.dir}/js/nf/canvas/nf-connectable.js ${staging.dir}/js/nf/canvas/nf-graph.js + ${staging.dir}/js/nf/canvas/nf-flow-version.js ${staging.dir}/js/nf/nf-filtered-dialog-common.js ${staging.dir}/js/nf/nf-status-history.js ${staging.dir}/js/nf/canvas/nf-queue-listing.js diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties index 413c6c2f83..40a3b1ff37 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties @@ -57,6 +57,7 @@ nf.canvas.script.tags=\n\ \n\ \n\ +\n\ \n\ \n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp index 3c7d407bc8..c57b76f376 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp @@ -115,6 +115,8 @@ + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp index 13e214650a..7dd481fc76 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp @@ -22,56 +22,56 @@ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/registry-configuration-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/registry-configuration-dialog.jsp new file mode 100644 index 0000000000..7fa90f7eab --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/registry-configuration-dialog.jsp @@ -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" %> + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/save-flow-version-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/save-flow-version-dialog.jsp new file mode 100644 index 0000000000..dfed40911f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/save-flow-version-dialog.jsp @@ -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" %> + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp index ca22c04978..57f43c650c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/settings-content.jsp @@ -62,6 +62,9 @@
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css index cb5282ba81..1c44e717d0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css @@ -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. */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css index f5c1a6f4d7..caad5bd338 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css @@ -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; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css index 2439d229e1..fc930ebd4c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css @@ -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; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js index 72cd57ba0f..5bf70b15ff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js @@ -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. */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-graph-controls-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-graph-controls-controller.js index e661338194..3c9df0b6ae 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-graph-controls-controller.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-graph-controls-controller.js @@ -234,7 +234,7 @@ */ getContextName: function () { var selection = nfCanvasUtils.getSelection(); - var canRead = nfCanvasUtils.canReadFromGroup(); + var canRead = nfCanvasUtils.canReadCurrentGroup(); if (selection.empty()) { if (canRead) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 90a1140616..ff093309f9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -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. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js index e3ab5c966b..6e17b27ba7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js @@ -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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js index 68a918aa4a..9506fef137 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js @@ -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(); }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js index 49ded8cd69..d0ad4ee368 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js @@ -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. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js index 8fb73a8036..8f8bb6d0c4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js @@ -230,7 +230,7 @@ applyFilter(); }); - // initialize the processor configuration dialog + // initialize the component state dialog $('#component-state-dialog').modal({ scrollableContentStyle: 'scrollable', headerText: 'Component State', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js index 3110e64896..02abac12c5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-version.js @@ -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) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js index a32a47f80e..f12808627b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js @@ -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'}}, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js new file mode 100644 index 0000000000..6315d3032c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js @@ -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); + } + }); + } + }; +})); \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js index 498fbd754c..58402df6ca 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js @@ -215,7 +215,7 @@ $('#process-group-configuration').data('process-group', { 'permissions': { canRead: false, - canWrite: nfCanvasUtils.canWrite() + canWrite: nfCanvasUtils.canWriteCurrentGroup() } }); } else { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js index 13bbc70594..17bb069b42 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js @@ -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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js index 4da6fc65c3..0e5a2d796d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js @@ -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 '' + nfCommon.escapeHtml(dataContext.id) + ''; + } + + return nfCommon.escapeHtml(dataContext.component.uri); + }; + + var descriptionFormatter = function (row, cell, value, columnDef, dataContext) { + if (!dataContext.permissions.canRead) { + return '' + nfCommon.escapeHtml(dataContext.id) + ''; + } + + return nfCommon.escapeHtml(dataContext.component.description); + }; + + var registriesActionFormatter = function (row, cell, value, columnDef, dataContext) { + var markup = ''; + + if (nfCommon.canModifyController()) { + // edit registry + markup += '
'; + + // remove registry + markup += '
'; + } + + 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: ' ', + 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(); }, /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html index 10d36f1cbe..a8b85793c9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html @@ -22,6 +22,7 @@ limitations under the License. » +