diff --git a/src/main/java/org/springframework/data/release/cli/ProjectConverter.java b/src/main/java/org/springframework/data/release/cli/ProjectConverter.java new file mode 100644 index 0000000..29e7d6c --- /dev/null +++ b/src/main/java/org/springframework/data/release/cli/ProjectConverter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.data.release.cli; + +import java.util.List; + +import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.Projects; +import org.springframework.shell.core.Completion; +import org.springframework.shell.core.Converter; +import org.springframework.shell.core.MethodTarget; +import org.springframework.stereotype.Component; + +/** + * @author Mark Paluch + */ +@Component +public class ProjectConverter implements Converter { + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String) + */ + @Override + public boolean supports(Class type, String optionContext) { + return Project.class.isAssignableFrom(type); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String) + */ + @Override + public Project convertFromText(String value, Class targetType, String optionContext) { + return Projects.requiredByName(value); + } + + /* + * (non-Javadoc) + * @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget) + */ + @Override + public boolean getAllPossibleValues(List completions, Class targetType, String existingData, + String optionContext, MethodTarget target) { + + for (Project project : Projects.all()) { + completions.add(new Completion(project.getName())); + } + + return true; + } +} diff --git a/src/main/java/org/springframework/data/release/issues/github/GitHub.java b/src/main/java/org/springframework/data/release/issues/github/GitHub.java index 7cceea3..2d291ec 100644 --- a/src/main/java/org/springframework/data/release/issues/github/GitHub.java +++ b/src/main/java/org/springframework/data/release/issues/github/GitHub.java @@ -25,9 +25,6 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,19 +51,16 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestOperations; -import org.springframework.web.util.DefaultUriBuilderFactory; /** * @author Oliver Gierke * @author Mark Paluch */ @Component -class GitHub implements IssueTracker { +class GitHub extends GitHubSupport implements IssueTracker { private static final String MILESTONE_URI = "/repos/spring-projects/{repoName}/milestones?state={state}"; private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}"; @@ -80,7 +74,6 @@ class GitHub implements IssueTracker { private static final ParameterizedTypeReference> ISSUES_TYPE = new ParameterizedTypeReference>() {}; private static final ParameterizedTypeReference ISSUE_TYPE = new ParameterizedTypeReference() {}; - private final RestOperations operations; private final Logger logger; private final GitHubProperties properties; private final ExecutorService executorService; @@ -88,7 +81,7 @@ class GitHub implements IssueTracker { public GitHub(@Qualifier("tracker") RestTemplateBuilder templateBuilder, Logger logger, GitHubProperties properties, ExecutorService executorService) { - this.operations = templateBuilder.uriTemplateHandler(new DefaultUriBuilderFactory(properties.getApiUrl())).build(); + super(createOperations(templateBuilder, properties)); this.logger = logger; this.properties = properties; this.executorService = executorService; @@ -390,58 +383,6 @@ class GitHub implements IssueTracker { return Optional.ofNullable(milestoneRef.get()); } - /** - * Apply a {@link Predicate callback} with GitHub paging starting at {@code endpointUri}. The given - * {@link Predicate#test(Object)} outcome controls whether paging continues by returning {@literal true} or stops. - * - * @param endpointUri - * @param method - * @param parameters - * @param entity - * @param type - * @param callbackContinue - * @param - */ - private void doWithPaging(String endpointUri, HttpMethod method, Map parameters, - HttpEntity entity, ParameterizedTypeReference type, Predicate callbackContinue) { - - ResponseEntity exchange = operations.exchange(endpointUri, method, entity, type, parameters); - - Pattern pattern = Pattern.compile("<([^ ]*)>; rel=\"(\\w+)\""); - - while (true) { - - if (!callbackContinue.test(exchange.getBody())) { - return; - } - - HttpHeaders responseHeaders = exchange.getHeaders(); - List links = responseHeaders.getValuesAsList("Link"); - - if (links.isEmpty()) { - return; - } - - String nextLink = null; - for (String link : links) { - - Matcher matcher = pattern.matcher(link); - if (matcher.find()) { - if (matcher.group(2).equals("next")) { - nextLink = matcher.group(1); - break; - } - } - } - - if (nextLink == null) { - return; - } - - exchange = operations.exchange(nextLink, method, entity, type, parameters); - } - } - /* * (non-Javadoc) * @see org.springframework.data.release.issues.IssueTracker#closeIteration(org.springframework.data.release.model.ModuleIteration) diff --git a/src/main/java/org/springframework/data/release/issues/github/GitHubCommands.java b/src/main/java/org/springframework/data/release/issues/github/GitHubCommands.java new file mode 100644 index 0000000..0cf0898 --- /dev/null +++ b/src/main/java/org/springframework/data/release/issues/github/GitHubCommands.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.data.release.issues.github; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.util.concurrent.Executor; + +import org.springframework.data.release.CliComponent; +import org.springframework.data.release.TimedCommand; +import org.springframework.data.release.issues.IssueTracker; +import org.springframework.data.release.model.Project; +import org.springframework.plugin.core.PluginRegistry; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; + +/** + * @author Mark Paluch + */ +@CliComponent +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +class GitHubCommands extends TimedCommand { + + @NonNull PluginRegistry tracker; + @NonNull GitHubLabels gitHubLabels; + @NonNull Executor executor; + + @CliCommand(value = "github update labels") + public void createOrUpdateLabels(@CliOption(key = "", mandatory = false) Project project) { + + if (project == null) { + // Projects.all().forEach(gitHubLabels::createOrUpdateLabels); + } else { + gitHubLabels.createOrUpdateLabels(project); + } + } + +} diff --git a/src/main/java/org/springframework/data/release/issues/github/GitHubLabels.java b/src/main/java/org/springframework/data/release/issues/github/GitHubLabels.java new file mode 100644 index 0000000..454dff8 --- /dev/null +++ b/src/main/java/org/springframework/data/release/issues/github/GitHubLabels.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed 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.springframework.data.release.issues.github; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.release.git.GitProject; +import org.springframework.data.release.model.Project; +import org.springframework.data.release.utils.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Methods to interact with GitHub labels. + * + * @author Mark Paluch + */ +@Component +public class GitHubLabels extends GitHubSupport { + + private static final String LABELS_URI = "/repos/spring-projects/{repoName}/labels"; + private static final String LABEL_URI = "/repos/spring-projects/{repoName}/labels/{label}"; + + private static final ParameterizedTypeReference> LABELS_TYPE = new ParameterizedTypeReference>() {}; + + private final Logger logger; + + public GitHubLabels(@Qualifier("tracker") RestTemplateBuilder templateBuilder, Logger logger, + GitHubProperties properties) { + + super(createOperations(templateBuilder, properties)); + this.logger = logger; + } + + /** + * Creates or updates GitHub labels for a {@link Project}. The actual {@link LabelConfiguration} is obtained from + * {@link ProjectLabelConfiguration}. + * + * @param project the project to process. + */ + public void createOrUpdateLabels(Project project) { + + logger.log(project, "Obtaining labels…"); + Map parameters = Collections.singletonMap("repoName", GitProject.of(project).getRepositoryName()); + + List