#161 - Add GitHub LabelConfiguration.
Also added GitHub command to create and update labels.
This commit is contained in:
@@ -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<Project> {
|
||||
|
||||
/*
|
||||
* (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<Completion> completions, Class<?> targetType, String existingData,
|
||||
String optionContext, MethodTarget target) {
|
||||
|
||||
for (Project project : Projects.all()) {
|
||||
completions.add(new Completion(project.getName()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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<List<GitHubIssue>> ISSUES_TYPE = new ParameterizedTypeReference<List<GitHubIssue>>() {};
|
||||
private static final ParameterizedTypeReference<GitHubIssue> ISSUE_TYPE = new ParameterizedTypeReference<GitHubIssue>() {};
|
||||
|
||||
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 <T>
|
||||
*/
|
||||
private <T> void doWithPaging(String endpointUri, HttpMethod method, Map<String, Object> parameters,
|
||||
HttpEntity<?> entity, ParameterizedTypeReference<T> type, Predicate<T> callbackContinue) {
|
||||
|
||||
ResponseEntity<T> 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<String> 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)
|
||||
|
||||
@@ -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<IssueTracker, Project> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<List<Label>> LABELS_TYPE = new ParameterizedTypeReference<List<Label>>() {};
|
||||
|
||||
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<String, Object> parameters = Collections.singletonMap("repoName", GitProject.of(project).getRepositoryName());
|
||||
|
||||
List<Label> existsOnGitHub = getLabelsFromGitHub(parameters);
|
||||
LabelConfiguration configuration = ProjectLabelConfiguration.forProject(project);
|
||||
List<Label> newLabels = configuration.getNewLabels(existsOnGitHub);
|
||||
List<Label> existingLabels = configuration.getExistingLabels(existsOnGitHub);
|
||||
List<Label> additionalLabels = configuration.getAdditionalLabels(existsOnGitHub);
|
||||
|
||||
if (!newLabels.isEmpty()) {
|
||||
|
||||
logger.log(project, "Creating new labels (%d)…", newLabels.size());
|
||||
newLabels.forEach(it -> {
|
||||
operations.postForObject(LABELS_URI, it, JsonNode.class, parameters);
|
||||
});
|
||||
}
|
||||
|
||||
newLabels.forEach(it -> {
|
||||
operations.postForObject(LABELS_URI, it, String.class, parameters);
|
||||
});
|
||||
|
||||
existingLabels.stream().filter(it -> {
|
||||
|
||||
Label existing = existsOnGitHub.get(existsOnGitHub.indexOf(it));
|
||||
|
||||
return existing.requiresUpdate(it);
|
||||
}).forEach(it -> {
|
||||
|
||||
logger.log(project, "Updating label %s…", it.getName());
|
||||
Map<String, Object> updateLabel = new HashMap<>(parameters);
|
||||
updateLabel.put("label", it.getName());
|
||||
|
||||
operations.postForObject(LABEL_URI, it, JsonNode.class, updateLabel);
|
||||
});
|
||||
|
||||
if (!additionalLabels.isEmpty()) {
|
||||
logger.log(project, "Found additional labels: %s",
|
||||
additionalLabels.stream().map(Label::getName).collect(Collectors.joining(", ")));
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Label> getLabelsFromGitHub(Map<String, Object> parameters) {
|
||||
|
||||
List<Label> existsOnGitHub = new ArrayList<>();
|
||||
|
||||
doWithPaging(LABELS_URI, HttpMethod.GET, parameters, new HttpEntity<>(new HttpHeaders()), LABELS_TYPE, labels -> {
|
||||
existsOnGitHub.addAll(labels);
|
||||
return true;
|
||||
});
|
||||
|
||||
return existsOnGitHub;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* Support class for GitHub operations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class GitHubSupport {
|
||||
|
||||
final RestOperations operations;
|
||||
|
||||
static RestOperations createOperations(RestTemplateBuilder templateBuilder, GitHubProperties properties) {
|
||||
return templateBuilder.uriTemplateHandler(new DefaultUriBuilderFactory(properties.getApiUrl())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T>
|
||||
*/
|
||||
<T> void doWithPaging(String endpointUri, HttpMethod method, Map<String, Object> parameters, HttpEntity<?> entity,
|
||||
ParameterizedTypeReference<T> type, Predicate<T> callbackContinue) {
|
||||
|
||||
ResponseEntity<T> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* GitHub label.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@EqualsAndHashCode(of = "name")
|
||||
public class Label {
|
||||
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
String color;
|
||||
|
||||
public boolean requiresUpdate(Label other) {
|
||||
|
||||
if (!other.getName().equals(getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!other.getDescription().equals(getDescription())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!other.getColor().equals(getColor())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.issues.github.LabelFactories.LabelFactory;
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
/**
|
||||
* GitHub label configurations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class LabelConfiguration implements Streamable<Label> {
|
||||
|
||||
private final Set<Label> labels;
|
||||
|
||||
private LabelConfiguration(Consumer<LabelConfigurer> configurerConsumer) {
|
||||
|
||||
LabelConfigurer configurer = new LabelConfigurer();
|
||||
configurerConsumer.accept(configurer);
|
||||
this.labels = configurer.labels;
|
||||
}
|
||||
|
||||
private LabelConfiguration(Set<Label> labels) {
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty {@link LabelConfiguration} without any labels.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static LabelConfiguration empty() {
|
||||
return new LabelConfiguration(Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LabelConfiguration} by merging this and the outcome of {@code consumerConfigurer}.
|
||||
*
|
||||
* @param configurerConsumer
|
||||
* @return
|
||||
*/
|
||||
public LabelConfiguration mergeWith(Consumer<LabelConfigurer> configurerConsumer) {
|
||||
return mergeWith(LabelConfiguration.of(configurerConsumer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LabelConfiguration} by merging this and {@link LabelConfiguration other configuration}.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public LabelConfiguration mergeWith(LabelConfiguration other) {
|
||||
|
||||
Set<Label> merged = new HashSet<>(this.labels);
|
||||
merged.addAll(other.labels);
|
||||
|
||||
return new LabelConfiguration(merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LabelConfiguration} applying {@code configurerConsumer}.
|
||||
*
|
||||
* @param configurerConsumer
|
||||
* @return
|
||||
*/
|
||||
public static LabelConfiguration of(Consumer<LabelConfigurer> configurerConsumer) {
|
||||
return new LabelConfiguration(configurerConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common label configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static LabelConfiguration commonLabels() {
|
||||
|
||||
LabelConfiguration configuration = LabelConfiguration.of(configurer -> {
|
||||
|
||||
configurer.register(LabelFactories.FOR_LABEL, "external-project",
|
||||
"For an external project and not something we can fix");
|
||||
configurer.register(LabelFactories.FOR_LABEL, "stackoverflow",
|
||||
"A question that's better suited to stackoverflow.com");
|
||||
configurer.register(LabelFactories.FOR_LABEL, "team-attention",
|
||||
"An issue we need to discuss as a team to make progress");
|
||||
configurer.register(LabelFactories.FOR_LABEL, "external-project", "An issue to discuss face-to-face");
|
||||
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "blocked",
|
||||
"An issue that's blocked on an external project change");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "declined",
|
||||
"A suggestion or change that we don't feel we should currently apply");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "duplicate", "A duplicate of another issue");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "feedback-provided", "Feedback has been provided");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "feedback-reminder",
|
||||
"We've sent a reminder that we need additional information before we can continue");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "first-timers-only",
|
||||
"An issue that can only be worked on by brand new contributors");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "ideal-for-contribution",
|
||||
"An issue that a contributor can help us with");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "invalid", "An issue that we don't feel is valid");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "on-hold", "We cannot start working on this issue yet");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "pending-design-work",
|
||||
"Needs design work before any code can be developed");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "superseded", "An issue that has been superseded by another");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "waiting-for-feedback",
|
||||
"We need additional information before we can continue");
|
||||
configurer.register(LabelFactories.STATUS_LABEL, "waiting-for-triage", "An issue we've not yet triaged");
|
||||
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "blocker", "An issue that is blocking us from releasing");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "bug", "A general bug");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "dependency-upgrade", "A dependency upgrade");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "documentation", "A documentation update");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "enhancement", "A general enhancement");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "regression", "A regression from a previous release");
|
||||
configurer.register(LabelFactories.TYPE_LABEL, "task", "A general task");
|
||||
});
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a required {@link Label}.
|
||||
*
|
||||
* @param labelFactory
|
||||
* @param name
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the label was not found.
|
||||
*/
|
||||
public Label getRequiredLabel(LabelFactory labelFactory, String name) {
|
||||
|
||||
Label toFind = labelFactory.apply(name, "");
|
||||
|
||||
return this.labels.stream().filter(it -> it.equals(toFind)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("Cannot find label %s", toFind.getName())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Label> iterator() {
|
||||
return labels.iterator();
|
||||
}
|
||||
|
||||
public List<Label> getNewLabels(Collection<Label> existingLabels) {
|
||||
return labels.stream().filter(o -> !existingLabels.contains(o)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Label> getExistingLabels(List<Label> existingLabels) {
|
||||
return labels.stream().filter(existingLabels::contains).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Label> getAdditionalLabels(List<Label> existingLabels) {
|
||||
|
||||
List<Label> result = new ArrayList<>(existingLabels);
|
||||
|
||||
result.removeAll(getNewLabels(existingLabels));
|
||||
result.removeAll(getExistingLabels(existingLabels));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration utility to register labels for a {@link LabelFactory}.
|
||||
*/
|
||||
class LabelConfigurer {
|
||||
|
||||
private final Set<Label> labels = new HashSet<>();
|
||||
|
||||
public void register(LabelFactory factory, String name, String description) {
|
||||
|
||||
Label label = factory.apply(name, description);
|
||||
|
||||
labels.add(label);
|
||||
}
|
||||
|
||||
public void register(Label label) {
|
||||
labels.add(label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.function.BiFunction;
|
||||
|
||||
public class LabelFactories {
|
||||
|
||||
public static final LabelFactory FOR_LABEL = (name, description) -> create("for:", name, description, "c5def5");
|
||||
|
||||
public static final LabelFactory STATUS_LABEL = (name, description) -> create("status:", name, description, "fef2c0");
|
||||
|
||||
public static final LabelFactory IN_LABEL = (name, description) -> create("in:", name, description, "e8f9de");
|
||||
|
||||
public static final LabelFactory TYPE_LABEL = (name, description) -> create("type:", name, description, "e3d9fc");
|
||||
|
||||
public static final LabelFactory HAS_LABEL = (name, description) -> create("has:", name, description, "dfdfdf");
|
||||
|
||||
private static Label create(String prefix, String labelName, String description, String color) {
|
||||
return new Label(String.format("%s %s", prefix, labelName), description, color);
|
||||
}
|
||||
|
||||
interface LabelFactory extends BiFunction<String, String, Label> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
|
||||
/**
|
||||
* {@link Project-specifc} Label Configuration.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ProjectLabelConfiguration {
|
||||
|
||||
static final Map<Project, LabelConfiguration> labelConfigurations = new HashMap<>();
|
||||
|
||||
static {
|
||||
|
||||
LabelConfiguration commonLabels = LabelConfiguration.commonLabels();
|
||||
|
||||
LabelConfiguration coreMappingRepository = LabelConfiguration.of(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "mapping", "Mapping and conversion infrastructure");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
});
|
||||
|
||||
labelConfigurations.put(Projects.BOM, LabelConfiguration.empty().mergeWith(config -> {
|
||||
|
||||
config.register(commonLabels.getRequiredLabel(LabelFactories.TYPE_LABEL, "task"));
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.BUILD, LabelConfiguration.empty().mergeWith(config -> {
|
||||
|
||||
config.register(commonLabels.getRequiredLabel(LabelFactories.TYPE_LABEL, "dependency-upgrade"));
|
||||
config.register(commonLabels.getRequiredLabel(LabelFactories.TYPE_LABEL, "task"));
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.CASSANDRA, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "map", "");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
config.register(LabelFactories.IN_LABEL, "kotlin", "Kotlin support");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.COMMONS, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "web", "Integration with Spring MVC");
|
||||
config.register(LabelFactories.IN_LABEL, "kotlin", "Kotlin support");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.COUCHBASE, commonLabels.mergeWith(coreMappingRepository));
|
||||
|
||||
labelConfigurations.put(Projects.ELASTICSEARCH, commonLabels.mergeWith(coreMappingRepository));
|
||||
|
||||
labelConfigurations.put(Projects.ENVERS, commonLabels.mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.GEODE, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "map", "");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.GEMFIRE, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "map", "");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.JDBC, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "statement-builder", "");
|
||||
config.register(LabelFactories.IN_LABEL, "relational", "");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.JPA, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "query-parser", "Everything related to parsing JPQL or SQL");
|
||||
config.register(LabelFactories.IN_LABEL, "querydsl", "Querydsl integration");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.KEY_VALUE, commonLabels.mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "map", "");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.LDAP, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "core", "Issues in core support");
|
||||
config.register(LabelFactories.IN_LABEL, "repository", "Repositories abstraction");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.NEO4J, commonLabels.mergeWith(coreMappingRepository));
|
||||
|
||||
labelConfigurations.put(Projects.MONGO_DB, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "aggregation-framework", "Aggregation framework support");
|
||||
config.register(LabelFactories.IN_LABEL, "gridfs", "");
|
||||
config.register(LabelFactories.IN_LABEL, "kotlin", "Kotlin support");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.R2DBC, commonLabels.mergeWith(coreMappingRepository));
|
||||
|
||||
labelConfigurations.put(Projects.REST, commonLabels.mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "api-documentation", "");
|
||||
config.register(LabelFactories.IN_LABEL, "content-negotiation", "");
|
||||
config.register(LabelFactories.IN_LABEL, "query-parser", "Everything related to parsing JPQL or SQL");
|
||||
config.register(LabelFactories.IN_LABEL, "querydsl", "Querydsl integration");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.REDIS, commonLabels.mergeWith(coreMappingRepository).mergeWith(config -> {
|
||||
|
||||
config.register(LabelFactories.IN_LABEL, "cache", "RedisCache and CacheManager");
|
||||
config.register(LabelFactories.IN_LABEL, "lettuce", "Lettuce driver");
|
||||
config.register(LabelFactories.IN_LABEL, "jedis", "Jedis driver");
|
||||
config.register(LabelFactories.IN_LABEL, "kotlin", "Kotlin support");
|
||||
}));
|
||||
|
||||
labelConfigurations.put(Projects.SOLR, commonLabels.mergeWith(coreMappingRepository));
|
||||
|
||||
verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the {@link LabelConfiguration} for a {@link Project}.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static LabelConfiguration forProject(Project project) {
|
||||
return labelConfigurations.get(project);
|
||||
}
|
||||
|
||||
static void verify() {
|
||||
|
||||
for (Project project : Projects.all()) {
|
||||
|
||||
if (!ProjectLabelConfiguration.labelConfigurations.containsKey(project)) {
|
||||
throw new IllegalStateException(String.format("No LabelConfiguration for %s", project.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||
import org.jgrapht.graph.DefaultEdge;
|
||||
@@ -140,4 +141,9 @@ public class Projects {
|
||||
return byName(name).//
|
||||
orElseThrow(() -> new IllegalArgumentException(String.format("No project named %s available!", name)));
|
||||
}
|
||||
|
||||
public static List<Project> all() {
|
||||
return PROJECTS.stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user