From d4a6ae921e38a73143530dc8798bf649fbd538b6 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 4 Dec 2014 21:43:36 +0300 Subject: [PATCH 01/20] Initial version of custom task states for JIRA >= 5.0 --- .../com/intellij/tasks/CustomTaskState.java | 14 ++++ .../com/intellij/tasks/TaskRepository.java | 26 ++++++- .../intellij/tasks/TaskRepositoryType.java | 4 +- .../src/com/intellij/tasks/TaskState.java | 33 +++++++-- .../intellij/tasks/jira/JiraRemoteApi.java | 9 ++- .../intellij/tasks/jira/JiraRepository.java | 17 ++--- .../intellij/tasks/jira/rest/JiraRestApi.java | 11 ++- .../tasks/jira/rest/api2/JiraRestApi2.java | 38 +++++++---- .../rest/api2/model/JiraCustomTaskState.java | 39 +++++++++++ .../api2/model/JiraTransitionsWrapper.java | 57 ++++++++++++++++ .../rest/api20alpha1/JiraRestApi20Alpha1.java | 35 +++++----- .../tasks/jira/soap/JiraLegacyApi.java | 12 +++- .../tasks/actions/OpenTaskDialog.form | 18 +++-- .../tasks/actions/OpenTaskDialog.java | 67 +++++++++++++++---- .../com/intellij/tasks/impl/TaskUiUtil.java | 13 ++-- 15 files changed, 311 insertions(+), 82 deletions(-) create mode 100644 plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java create mode 100644 plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java create mode 100644 plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java new file mode 100644 index 000000000000..2f8cf8de2c64 --- /dev/null +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java @@ -0,0 +1,14 @@ +package com.intellij.tasks; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Mikhail Golubev + */ +public interface CustomTaskState { + @NotNull + String getId(); + + @NotNull + String getPresentableName(); +} diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 416a7158eb26..0d971ab18b80 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Set; import java.util.concurrent.Callable; /** @@ -164,6 +165,17 @@ public abstract class TaskRepository { return getIssues(query, offset, limit, withClosed); } + /** + * Retrieve states available for task from server. One of these states will be passed later to {@link #setTaskState(Task, TaskState)}. + * @param task task to update + * @return set of available states + */ + @NotNull + public Set getPossibleStates(@NotNull Task task) throws Exception { + //noinspection unchecked + return getRepositoryType().getPossibleTaskStates(); + } + /** * @param id task ID. Don't forget to define {@link #extractId(String)}, if your server uses not PROJECT-123 format for task IDs. * @return found task or {@code null} otherwise. Basically you should return {@code null} on e.g. 404 error and throw exception with @@ -179,6 +191,15 @@ public abstract class TaskRepository { @Nullable public abstract String extractId(@NotNull String taskName); + + /** + * @deprecated Use {@link #setTaskState(Task, CustomTaskState)} instead + */ + @Deprecated + public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { + throw new UnsupportedOperationException("Setting task to state " + state + " is not supported"); + } + /** * Update state of the task on server. Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and * supported states in {@link TaskRepositoryType#getPossibleTaskStates()}. @@ -188,10 +209,11 @@ public abstract class TaskRepository { * @see com.intellij.tasks.TaskRepositoryType#getPossibleTaskStates() * @see com.intellij.tasks.TaskRepository#getFeatures() */ - public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { - throw new UnsupportedOperationException("Setting task to state " + state + " is not supported"); + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { + setTaskState(task, ((TaskState)state)); } + // for serialization public TaskRepository() { myType = null; diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java index f348f4264c22..fac6ad863524 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java @@ -67,8 +67,10 @@ public abstract class TaskRepositoryType implements Ta public abstract Class getRepositoryClass(); /** - * @return states that can be set by {@link TaskRepository#setTaskState(Task, TaskState)} + * @return states that can be set by {@link TaskRepository#setTaskState(Task, CustomTaskState)} + * @deprecated Use {@link com.intellij.tasks.TaskRepository#getPossibleStates(Task)} instead. */ + @Deprecated public EnumSet getPossibleTaskStates() { return EnumSet.noneOf(TaskState.class); } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java index c0c0a2628b9d..27b8fb87c473 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java @@ -15,16 +15,35 @@ */ package com.intellij.tasks; +import org.jetbrains.annotations.NotNull; + /** * @author Dmitry Avdeev */ -public enum TaskState { +public enum TaskState implements CustomTaskState { + SUBMITTED("Submitted"), + OPEN("Open"), + IN_PROGRESS("In Progress"), + REOPENED("Reopened"), + RESOLVED("Resolved"), - SUBMITTED, - OPEN, - IN_PROGRESS, - REOPENED, - RESOLVED, + OTHER("Other"); - OTHER + String myPresentableName; + + TaskState(@NotNull String presentableName) { + myPresentableName = presentableName; + } + + @NotNull + @Override + public String getId() { + return ""; + } + + @NotNull + @Override + public String getPresentableName() { + return myPresentableName; + } } diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java index 8ee9c97512a1..11efe39cc3de 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java @@ -1,8 +1,8 @@ package com.intellij.tasks.jira; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; import com.intellij.tasks.Task; -import com.intellij.tasks.TaskState; import com.intellij.tasks.jira.rest.api2.JiraRestApi2; import com.intellij.tasks.jira.rest.api20alpha1.JiraRestApi20Alpha1; import com.intellij.tasks.jira.soap.JiraLegacyApi; @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Set; /** * Because of the number of available remote interfaces in JIRA, {@link JiraRepository} delegates @@ -29,7 +30,10 @@ public abstract class JiraRemoteApi { @Nullable public abstract Task findTask(@NotNull String key) throws Exception; - public abstract void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception; + @NotNull + public abstract Set getPossibleStates(@NotNull Task task) throws Exception; + + public abstract void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception; public abstract void updateTimeSpend(@NotNull LocalTask task, @NotNull String timeSpent, String comment) throws Exception; @@ -80,6 +84,5 @@ public abstract class JiraRemoteApi { public String getVersionName() { return myVersionName; } - } } diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java index 820bb22426e0..cea2c21a4e75 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java @@ -22,10 +22,10 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.io.StreamUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; import com.intellij.tasks.Task; import com.intellij.tasks.TaskBundle; -import com.intellij.tasks.TaskState; import com.intellij.tasks.impl.BaseRepositoryImpl; import com.intellij.tasks.impl.gson.GsonUtil; import com.intellij.tasks.jira.rest.JiraRestApi; @@ -44,10 +44,7 @@ import org.jetbrains.annotations.Nullable; import java.io.InputStream; import java.net.URL; -import java.util.Collections; -import java.util.Hashtable; -import java.util.List; -import java.util.Vector; +import java.util.*; import java.util.regex.Pattern; /** @@ -352,10 +349,16 @@ public class JiraRepository extends BaseRepositoryImpl { } @Override - public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { myApiVersion.setTaskState(task, state); } + @NotNull + @Override + public Set getPossibleStates(@NotNull Task task) throws Exception { + return myApiVersion.getPossibleStates(task); + } + public void setSearchQuery(String searchQuery) { mySearchQuery = searchQuery; } @@ -371,8 +374,6 @@ public class JiraRepository extends BaseRepositoryImpl { /** * Used to preserve discovered API version for the next initialization. - * - * @return */ @SuppressWarnings("UnusedDeclaration") @Nullable diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java index 502eed083e74..447a5c58adbe 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java @@ -2,8 +2,8 @@ package com.intellij.tasks.jira.rest; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.Task; -import com.intellij.tasks.TaskState; import com.intellij.tasks.jira.JiraRemoteApi; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.JiraVersion; @@ -99,19 +99,16 @@ public abstract class JiraRestApi extends JiraRemoteApi { protected abstract JiraIssue parseIssue(String response); @Override - public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { String requestBody = getRequestForStateTransition(state); LOG.debug(String.format("Transition: %s -> %s, request: %s", task.getState(), state, requestBody)); - if (requestBody == null) { - return; - } PostMethod method = new PostMethod(myRepository.getRestUrl("issue", task.getId(), "transitions")); method.setRequestEntity(createJsonEntity(requestBody)); myRepository.executeMethod(method); } - @Nullable - protected abstract String getRequestForStateTransition(@NotNull TaskState state); + @NotNull + protected abstract String getRequestForStateTransition(@NotNull CustomTaskState state); protected static RequestEntity createJsonEntity(String requestBody) { try { diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java index 0c9b647ee720..3ec3b99d3e6a 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java @@ -3,11 +3,14 @@ package com.intellij.tasks.jira.rest.api2; import com.google.gson.reflect.TypeToken; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; -import com.intellij.tasks.TaskState; +import com.intellij.tasks.Task; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.rest.JiraRestApi; +import com.intellij.tasks.jira.rest.api2.model.JiraCustomTaskState; import com.intellij.tasks.jira.rest.api2.model.JiraIssueApi2; +import com.intellij.tasks.jira.rest.api2.model.JiraTransitionsWrapper; import com.intellij.tasks.jira.rest.model.JiraIssue; import com.intellij.tasks.jira.rest.model.JiraResponseWrapper; import org.apache.commons.httpclient.methods.GetMethod; @@ -18,6 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * This REST API version is used in JIRA 5.1.8 and above (including JIRA 6.x.x). @@ -63,21 +67,27 @@ public class JiraRestApi2 extends JiraRestApi { return JiraRepository.GSON.fromJson(response, JiraIssueApi2.class); } - @Nullable + @NotNull @Override - protected String getRequestForStateTransition(@NotNull TaskState state) { - // REST API 2.0 require double quotes both around field names and values (even numbers) - switch (state) { - case IN_PROGRESS: - return "{\"transition\": {\"id\": \"4\"}}"; - case RESOLVED: - // 5 for "Resolved", 2 for "Closed" - return "{\"transition\": {\"id\": \"5\"}, \"fields\": {\"resolution\": {\"name\": \"Fixed\"}}}"; - case REOPENED: - return "{\"transition\": {\"id\": \"3\"}}"; - default: - return null; + protected String getRequestForStateTransition(@NotNull CustomTaskState state) { + final JiraCustomTaskState transition = (JiraCustomTaskState)state; + assert StringUtil.isNotEmpty(state.getId()); + if (transition.hasResolutionId()) { + return "{\"transition\": {\"id\": \"" + state.getId() + "\"}, \"fields\": {\"resolution\": {\"id\": \"" + transition.getResolutionId() + "\"}}}"; } + else { + return "{\"transition\": {\"id\": \"" + state.getId() + "\"}}"; + } + } + + @NotNull + @Override + public Set getPossibleStates(@NotNull Task task) throws Exception { + final GetMethod method = new GetMethod(myRepository.getRestUrl("issue", task.getId(), "transitions")); + method.setQueryString("expand=transitions.fields"); + final String response = myRepository.executeMethod(method); + final JiraTransitionsWrapper wrapper = JiraRepository.GSON.fromJson(response, JiraTransitionsWrapper.class); + return wrapper.getTransitions(); } @Override diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java new file mode 100644 index 000000000000..81520d82f6b4 --- /dev/null +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java @@ -0,0 +1,39 @@ +package com.intellij.tasks.jira.rest.api2.model; + +import com.intellij.tasks.CustomTaskState; +import org.jetbrains.annotations.NotNull; + +/** + * @author Mikhail Golubev + */ +public class JiraCustomTaskState implements CustomTaskState { + private final int myTransitionId; + private final String myPresentableName; + private final int myResolutionId; + + public JiraCustomTaskState(int transitionId, @NotNull String presentableName, int resolutionId) { + myTransitionId = transitionId; + myPresentableName = presentableName; + myResolutionId = resolutionId; + } + + public boolean hasResolutionId() { + return myResolutionId != 0; + } + + public int getResolutionId() { + return myResolutionId; + } + + @NotNull + @Override + public String getId() { + return String.valueOf(myTransitionId); + } + + @NotNull + @Override + public String getPresentableName() { + return myPresentableName; + } +} diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java new file mode 100644 index 000000000000..1768307c0d09 --- /dev/null +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java @@ -0,0 +1,57 @@ +package com.intellij.tasks.jira.rest.api2.model; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.annotations.SerializedName; +import com.intellij.tasks.CustomTaskState; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author Mikhail Golubev + */ +public class JiraTransitionsWrapper { + private List transitions = Collections.emptyList(); + + static class JiraTransition { + private int id; + private String name; + @SerializedName("to") + private JiraTaskState target; + private JsonObject fields; + + static class JiraTaskState { + private int id; + private String name; + } + } + + @NotNull + public Set getTransitions() { + final Set result = new LinkedHashSet(); + for (JiraTransition transition : transitions) { + final int transitionId = transition.id; + final String transitionName = transition.target.name; + String fieldName = null; + if (transition.fields != null) { + for (Map.Entry field : transition.fields.entrySet()) { + fieldName = field.getKey(); + final JsonObject fieldInfo = field.getValue().getAsJsonObject(); + if (fieldName.equals("resolution") && fieldInfo.get("required").getAsBoolean()) { + for (JsonElement allowedValue : fieldInfo.getAsJsonArray("allowedValues")) { + final String resolutionName = allowedValue.getAsJsonObject().get("name").getAsString(); + final int resolutionId = allowedValue.getAsJsonObject().get("id").getAsInt(); + result.add(new JiraCustomTaskState(transitionId, transitionName + " (" + resolutionName + ")", resolutionId)); + } + break; + } + } + } + if (fieldName == null) { + result.add(new JiraCustomTaskState(transitionId, transitionName, 0)); + } + } + return result; + } +} diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java index 7de17945ac31..6ccfc6b1f526 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java @@ -2,9 +2,11 @@ package com.intellij.tasks.jira.rest.api20alpha1; import com.google.gson.reflect.TypeToken; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; +import com.intellij.tasks.Task; import com.intellij.tasks.TaskBundle; -import com.intellij.tasks.TaskState; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.rest.JiraRestApi; import com.intellij.tasks.jira.rest.JiraRestTask; @@ -12,24 +14,33 @@ import com.intellij.tasks.jira.rest.api20alpha1.model.JiraIssueApi20Alpha1; import com.intellij.tasks.jira.rest.model.JiraIssue; import com.intellij.tasks.jira.rest.model.JiraResponseWrapper; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * This REST API is used in JIRA versions from 4.2 to 4.4. + * * @author Mikhail Golubev */ public class JiraRestApi20Alpha1 extends JiraRestApi { private static final Logger LOG = Logger.getInstance(JiraRestApi20Alpha1.class); - private static final Type ISSUES_WRAPPER_TYPE = new TypeToken>() { /* empty */ }.getType(); + private static final Type ISSUES_WRAPPER_TYPE = new TypeToken>() { /* empty */ + }.getType(); public JiraRestApi20Alpha1(JiraRepository repository) { super(repository); } + @NotNull + @Override + public Set getPossibleStates(@NotNull Task task) throws Exception { + return Collections.emptySet(); + } + @Override protected JiraIssue parseIssue(String response) { return JiraRepository.GSON.fromJson(response, JiraIssueApi20Alpha1.class); @@ -55,20 +66,12 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { return updatedIssues; } - @Nullable + @NotNull @Override - protected String getRequestForStateTransition(@NotNull TaskState state) { - switch (state) { - case IN_PROGRESS: - return "{\"transition\": \"4\"}"; - case RESOLVED: - // 5 for "Resolved", 2 for "Closed" - return "{\"transition\": \"5\", \"resolution\": \"Fixed\"}"; - case REOPENED: - return "{\"transition\": \"3\"}"; - default: - return null; - } + protected String getRequestForStateTransition(@NotNull CustomTaskState state) { + final String stateId = state.getId(); + assert StringUtil.isNotEmpty(stateId); + return "{\"transition\": \"" + stateId + "\"}"; } @Override diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java index e8cbee842cda..857c6e11aa5e 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java @@ -1,10 +1,10 @@ package com.intellij.tasks.jira.soap; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; import com.intellij.tasks.Task; import com.intellij.tasks.TaskBundle; -import com.intellij.tasks.TaskState; import com.intellij.tasks.impl.TaskUtil; import com.intellij.tasks.jira.JiraRemoteApi; import com.intellij.tasks.jira.JiraRepository; @@ -19,7 +19,9 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * Legacy integration restored due to IDEA-120595. @@ -102,8 +104,14 @@ public class JiraLegacyApi extends JiraRemoteApi { return ApiType.LEGACY; } + @NotNull @Override - public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { + public Set getPossibleStates(@NotNull Task task) throws Exception { + return Collections.emptySet(); + } + + @Override + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { throw new Exception(TaskBundle.message("jira.failure.no.state.update")); } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index fb7ab62bdaa8..69fcfb993d05 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -8,7 +8,7 @@ - + @@ -18,7 +18,7 @@ - + @@ -31,13 +31,19 @@ - + - + + + + + + + - - + + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 2cc241e4d8a1..b1008d493f63 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -20,6 +20,7 @@ import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.binding.BindControl; import com.intellij.openapi.options.binding.ControlBinder; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; @@ -30,8 +31,10 @@ import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.VcsTaskHandler; import com.intellij.tasks.*; import com.intellij.tasks.impl.TaskManagerImpl; +import com.intellij.tasks.impl.TaskUiUtil.ComboBoxUpdater; import com.intellij.tasks.impl.TaskUtil; import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.util.containers.ContainerUtil; @@ -42,6 +45,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.Set; /** * @author Dmitry Avdeev @@ -50,10 +54,23 @@ public class OpenTaskDialog extends DialogWrapper { private final static Logger LOG = Logger.getInstance("#com.intellij.tasks.actions.SimpleOpenTaskDialog"); public static final String START_FROM_BRANCH = "start.from.branch"; + private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState() { + @NotNull + @Override + public String getId() { + return ""; + } + + @NotNull + @Override + public String getPresentableName() { + return "-- do not update --"; + } + }; + private JPanel myPanel; @BindControl(value = "clearContext", instant = true) private JCheckBox myClearContext; - private JCheckBox myMarkAsInProgressBox; private JLabel myTaskNameLabel; private JPanel myVcsPanel; private JTextField myBranchName; @@ -62,6 +79,7 @@ public class OpenTaskDialog extends DialogWrapper { private JBCheckBox myCreateChangelist; private JBLabel myFromLabel; private ComboBox myBranchFrom; + private ComboBox myStateComboBox; private final Project myProject; private final Task myTask; @@ -81,10 +99,35 @@ public class OpenTaskDialog extends DialogWrapper { binder.bindAnnotations(this); binder.reset(); - TaskRepository repository = task.getRepository(); - myMarkAsInProgressBox.setSelected(manager.getState().markAsInProgress); - if (!TaskUtil.isStateSupported(repository, TaskState.IN_PROGRESS)) { - myMarkAsInProgressBox.setVisible(false); + myStateComboBox.setRenderer(new ListCellRendererWrapper() { + @Override + public void customize(JList list, CustomTaskState value, int index, boolean selected, boolean hasFocus) { + if (value != null) { + setText(value.getPresentableName()); + } + else { + setText("-- no states available --"); + } + } + }); + + // Capture correct modality state + final TaskRepository repository = myTask.getRepository(); + if (repository != null) { + // Find out proper way to determine modality state here + new ComboBoxUpdater(myProject, "Fetching available task states...", myStateComboBox) { + @NotNull + @Override + protected Set fetch(@NotNull ProgressIndicator indicator) throws Exception { + return repository.getPossibleStates(myTask); + } + + @Nullable + @Override + public CustomTaskState getExtraItem() { + return DO_NOT_UPDATE_STATE; + } + }.queue(); } TaskManagerImpl.Config state = taskManager.getState(); @@ -186,14 +229,14 @@ public class OpenTaskDialog extends DialogWrapper { public void createTask() { final TaskManagerImpl taskManager = (TaskManagerImpl)TaskManager.getManager(myProject); - taskManager.getState().markAsInProgress = isMarkAsInProgress(); taskManager.getState().createChangelist = myCreateChangelist.isSelected(); taskManager.getState().createBranch = myCreateBranch.isSelected(); TaskRepository repository = myTask.getRepository(); - if (isMarkAsInProgress() && repository != null) { + final CustomTaskState selectedItem = (CustomTaskState)myStateComboBox.getSelectedItem(); + if (repository != null && selectedItem != null && selectedItem != DO_NOT_UPDATE_STATE) { try { - repository.setTaskState(myTask, TaskState.IN_PROGRESS); + repository.setTaskState(myTask, selectedItem); } catch (Exception ex) { Messages.showErrorDialog(myProject, ex.getMessage(), "Cannot Set State For Issue"); @@ -252,10 +295,6 @@ public class OpenTaskDialog extends DialogWrapper { return myClearContext.isSelected(); } - private boolean isMarkAsInProgress() { - return myMarkAsInProgressBox.isSelected() && myMarkAsInProgressBox.isVisible(); - } - @NonNls protected String getDimensionServiceKey() { return "SimpleOpenTaskDialog"; @@ -277,4 +316,8 @@ public class OpenTaskDialog extends DialogWrapper { protected JComponent createCenterPanel() { return myPanel; } + + private void createUIComponents() { + myStateComboBox = new ComboBox(300); + } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java index 9f28f8868ae7..b130f375cfb8 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java @@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.List; +import java.util.Collection; /** * @author Mikhail Golubev @@ -33,13 +33,18 @@ public class TaskUiUtil { public abstract static class RemoteFetchTask extends Task.Backgroundable { protected T myResult; protected Exception myException; - private final ModalityState myModalityState = ModalityState.current(); + private final ModalityState myModalityState; /** * Should be called only from EDT, so current modality state can be captured. */ protected RemoteFetchTask(@Nullable Project project, @NotNull String title) { + this(project, title, ModalityState.current()); + } + + protected RemoteFetchTask(@Nullable Project project, @NotNull String title, @NotNull ModalityState modalityState) { super(project, title); + myModalityState = modalityState; } @Override @@ -80,11 +85,11 @@ public class TaskUiUtil { * Auxiliary remote fetcher designed to simplify updating of combo boxes in repository editors, which is * indeed a rather common task. */ - public static abstract class ComboBoxUpdater extends RemoteFetchTask> { + public static abstract class ComboBoxUpdater extends RemoteFetchTask> { protected final ComboBox myComboBox; public ComboBoxUpdater(@Nullable Project project, @NotNull String title, @NotNull ComboBox comboBox) { - super(project, title); + super(project, title, ModalityState.any()); myComboBox = comboBox; } From 8d9456bcddd62ebc5ae62f77d855b1d33f2fd212 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 5 Dec 2014 18:00:04 +0300 Subject: [PATCH 02/20] Restore possibility to set one of the standard states for issues in JIRA 4.x --- .../com/intellij/tasks/TaskRepository.java | 8 +++-- .../intellij/tasks/jira/rest/JiraRestApi.java | 2 +- .../tasks/jira/rest/api2/JiraRestApi2.java | 11 ++++--- ...r.java => JiraTransitionsWrapperApi2.java} | 8 ++--- .../rest/api20alpha1/JiraRestApi20Alpha1.java | 33 ++++++++++++------- .../{api2 => }/model/JiraCustomTaskState.java | 16 ++++----- 6 files changed, 45 insertions(+), 33 deletions(-) rename plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/{JiraTransitionsWrapper.java => JiraTransitionsWrapperApi2.java} (83%) rename plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/{api2 => }/model/JiraCustomTaskState.java (63%) diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 0d971ab18b80..535ba131f985 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -193,7 +193,7 @@ public abstract class TaskRepository { /** - * @deprecated Use {@link #setTaskState(Task, CustomTaskState)} instead + * @deprecated Use {@link #setTaskState(Task, CustomTaskState)} instead. */ @Deprecated public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { @@ -201,8 +201,10 @@ public abstract class TaskRepository { } /** - * Update state of the task on server. Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and - * supported states in {@link TaskRepositoryType#getPossibleTaskStates()}. + * Update state of the task on server. It's guaranteed that only issues returned by {@link #getPossibleStates(Task)} + * will be passed here. + *

+ * Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and supported states in {@link #getPossibleStates(Task)}. * * @param task issue to update * @param state new state of the issue diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java index 447a5c58adbe..78919e390d5f 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/JiraRestApi.java @@ -107,7 +107,7 @@ public abstract class JiraRestApi extends JiraRemoteApi { myRepository.executeMethod(method); } - @NotNull + @Nullable protected abstract String getRequestForStateTransition(@NotNull CustomTaskState state); protected static RequestEntity createJsonEntity(String requestBody) { diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java index 3ec3b99d3e6a..72cdc8130960 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java @@ -8,9 +8,9 @@ import com.intellij.tasks.LocalTask; import com.intellij.tasks.Task; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.rest.JiraRestApi; -import com.intellij.tasks.jira.rest.api2.model.JiraCustomTaskState; import com.intellij.tasks.jira.rest.api2.model.JiraIssueApi2; -import com.intellij.tasks.jira.rest.api2.model.JiraTransitionsWrapper; +import com.intellij.tasks.jira.rest.api2.model.JiraTransitionsWrapperApi2; +import com.intellij.tasks.jira.rest.model.JiraCustomTaskState; import com.intellij.tasks.jira.rest.model.JiraIssue; import com.intellij.tasks.jira.rest.model.JiraResponseWrapper; import org.apache.commons.httpclient.methods.GetMethod; @@ -72,8 +72,9 @@ public class JiraRestApi2 extends JiraRestApi { protected String getRequestForStateTransition(@NotNull CustomTaskState state) { final JiraCustomTaskState transition = (JiraCustomTaskState)state; assert StringUtil.isNotEmpty(state.getId()); - if (transition.hasResolutionId()) { - return "{\"transition\": {\"id\": \"" + state.getId() + "\"}, \"fields\": {\"resolution\": {\"id\": \"" + transition.getResolutionId() + "\"}}}"; + final String resolutionName = transition.getResolutionName(); + if (resolutionName != null) { + return "{\"transition\": {\"id\": \"" + state.getId() + "\"}, \"fields\": {\"resolution\": {\"name\": \"" + resolutionName + "\"}}}"; } else { return "{\"transition\": {\"id\": \"" + state.getId() + "\"}}"; @@ -86,7 +87,7 @@ public class JiraRestApi2 extends JiraRestApi { final GetMethod method = new GetMethod(myRepository.getRestUrl("issue", task.getId(), "transitions")); method.setQueryString("expand=transitions.fields"); final String response = myRepository.executeMethod(method); - final JiraTransitionsWrapper wrapper = JiraRepository.GSON.fromJson(response, JiraTransitionsWrapper.class); + final JiraTransitionsWrapperApi2 wrapper = JiraRepository.GSON.fromJson(response, JiraTransitionsWrapperApi2.class); return wrapper.getTransitions(); } diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java similarity index 83% rename from plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java rename to plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java index 1768307c0d09..3c1df68b4d35 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapper.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java @@ -4,6 +4,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import com.intellij.tasks.CustomTaskState; +import com.intellij.tasks.jira.rest.model.JiraCustomTaskState; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -11,7 +12,7 @@ import java.util.*; /** * @author Mikhail Golubev */ -public class JiraTransitionsWrapper { +public class JiraTransitionsWrapperApi2 { private List transitions = Collections.emptyList(); static class JiraTransition { @@ -41,15 +42,14 @@ public class JiraTransitionsWrapper { if (fieldName.equals("resolution") && fieldInfo.get("required").getAsBoolean()) { for (JsonElement allowedValue : fieldInfo.getAsJsonArray("allowedValues")) { final String resolutionName = allowedValue.getAsJsonObject().get("name").getAsString(); - final int resolutionId = allowedValue.getAsJsonObject().get("id").getAsInt(); - result.add(new JiraCustomTaskState(transitionId, transitionName + " (" + resolutionName + ")", resolutionId)); + result.add(new JiraCustomTaskState(transitionName + " (" + resolutionName + ")", transitionId, resolutionName)); } break; } } } if (fieldName == null) { - result.add(new JiraCustomTaskState(transitionId, transitionName, 0)); + result.add(new JiraCustomTaskState(transitionName, transitionId, null)); } } return result; diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java index 6ccfc6b1f526..a87f6052d93b 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java @@ -2,11 +2,7 @@ package com.intellij.tasks.jira.rest.api20alpha1; import com.google.gson.reflect.TypeToken; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.tasks.CustomTaskState; -import com.intellij.tasks.LocalTask; -import com.intellij.tasks.Task; -import com.intellij.tasks.TaskBundle; +import com.intellij.tasks.*; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.rest.JiraRestApi; import com.intellij.tasks.jira.rest.JiraRestTask; @@ -14,10 +10,11 @@ import com.intellij.tasks.jira.rest.api20alpha1.model.JiraIssueApi20Alpha1; import com.intellij.tasks.jira.rest.model.JiraIssue; import com.intellij.tasks.jira.rest.model.JiraResponseWrapper; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -38,7 +35,11 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { @NotNull @Override public Set getPossibleStates(@NotNull Task task) throws Exception { - return Collections.emptySet(); + final HashSet result = new HashSet(); + result.add(TaskState.IN_PROGRESS); + result.add(TaskState.RESOLVED); + result.add(TaskState.REOPENED); + return result; } @Override @@ -66,12 +67,22 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { return updatedIssues; } - @NotNull + @Nullable @Override protected String getRequestForStateTransition(@NotNull CustomTaskState state) { - final String stateId = state.getId(); - assert StringUtil.isNotEmpty(stateId); - return "{\"transition\": \"" + stateId + "\"}"; + // REST API of JIRA 4.x for retrieving possible transitions is very limited: we can't fetch possible resolutions and + // names of transition destinations. So we have no other options than to hardcode them. + switch ((TaskState)state) { + case IN_PROGRESS: + return "{\"transition\": \"4\"}"; + case RESOLVED: + // 5 for "Resolved", 2 for "Closed" + return "{\"transition\": \"5\", \"resolution\": \"Fixed\"}"; + case REOPENED: + return "{\"transition\": \"3\"}"; + default: + return null; + } } @Override diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java similarity index 63% rename from plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java rename to plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java index 81520d82f6b4..96fafeb5fa66 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraCustomTaskState.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java @@ -1,7 +1,8 @@ -package com.intellij.tasks.jira.rest.api2.model; +package com.intellij.tasks.jira.rest.model; import com.intellij.tasks.CustomTaskState; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author Mikhail Golubev @@ -9,19 +10,16 @@ import org.jetbrains.annotations.NotNull; public class JiraCustomTaskState implements CustomTaskState { private final int myTransitionId; private final String myPresentableName; - private final int myResolutionId; + private final String myResolutionId; - public JiraCustomTaskState(int transitionId, @NotNull String presentableName, int resolutionId) { + public JiraCustomTaskState(@NotNull String presentableName, int transitionId, @Nullable String resolutionName) { myTransitionId = transitionId; myPresentableName = presentableName; - myResolutionId = resolutionId; + myResolutionId = resolutionName; } - public boolean hasResolutionId() { - return myResolutionId != 0; - } - - public int getResolutionId() { + @Nullable + public String getResolutionName() { return myResolutionId; } From 023536cba45ec4a059d8500ede742506a596ec97 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sun, 11 Jan 2015 19:46:54 +0300 Subject: [PATCH 03/20] Rename TaskRepository#getPossibleTaskStates to getAvailableTaskStates --- .../src/com/intellij/tasks/TaskRepository.java | 16 +++++++++------- .../com/intellij/tasks/TaskRepositoryType.java | 2 +- .../com/intellij/tasks/jira/JiraRemoteApi.java | 2 +- .../com/intellij/tasks/jira/JiraRepository.java | 4 ++-- .../tasks/jira/rest/api2/JiraRestApi2.java | 2 +- .../rest/api20alpha1/JiraRestApi20Alpha1.java | 2 +- .../intellij/tasks/jira/soap/JiraLegacyApi.java | 2 +- .../intellij/tasks/actions/OpenTaskDialog.java | 2 +- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 535ba131f985..1e745aed291d 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -18,6 +18,8 @@ package com.intellij.tasks; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.impl.CancellableRunnable; +import com.intellij.tasks.impl.BaseRepository; import com.intellij.util.xmlb.annotations.Attribute; import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.Transient; @@ -35,7 +37,7 @@ import java.util.concurrent.Callable; * * @author Dmitry Avdeev * @see TaskRepositoryType - * @see com.intellij.tasks.impl.BaseRepository + * @see BaseRepository */ @Tag("server") public abstract class TaskRepository { @@ -110,7 +112,7 @@ public abstract class TaskRepository { /** * Returns an object that can test connection. - * {@link com.intellij.openapi.vcs.impl.CancellableRunnable#cancel()} should cancel the process. + * {@link CancellableRunnable#cancel()} should cancel the process. * * @return null if not supported */ @@ -171,7 +173,7 @@ public abstract class TaskRepository { * @return set of available states */ @NotNull - public Set getPossibleStates(@NotNull Task task) throws Exception { + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { //noinspection unchecked return getRepositoryType().getPossibleTaskStates(); } @@ -201,15 +203,15 @@ public abstract class TaskRepository { } /** - * Update state of the task on server. It's guaranteed that only issues returned by {@link #getPossibleStates(Task)} + * Update state of the task on server. It's guaranteed that only issues returned by {@link #getAvailableTaskStates(Task)} * will be passed here. *

- * Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and supported states in {@link #getPossibleStates(Task)}. + * Don't forget to add {@link #STATE_UPDATING} in {@link #getFeatures()} and supported states in {@link #getAvailableTaskStates(Task)}. * * @param task issue to update * @param state new state of the issue - * @see com.intellij.tasks.TaskRepositoryType#getPossibleTaskStates() - * @see com.intellij.tasks.TaskRepository#getFeatures() + * @see TaskRepositoryType#getPossibleTaskStates() + * @see TaskRepository#getFeatures() */ public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { setTaskState(task, ((TaskState)state)); diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java index fac6ad863524..d4575cf3eb92 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepositoryType.java @@ -68,7 +68,7 @@ public abstract class TaskRepositoryType implements Ta /** * @return states that can be set by {@link TaskRepository#setTaskState(Task, CustomTaskState)} - * @deprecated Use {@link com.intellij.tasks.TaskRepository#getPossibleStates(Task)} instead. + * @deprecated Use {@link TaskRepository#getAvailableTaskStates(Task)} instead. */ @Deprecated public EnumSet getPossibleTaskStates() { diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java index 11efe39cc3de..7b760769a0e3 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRemoteApi.java @@ -31,7 +31,7 @@ public abstract class JiraRemoteApi { public abstract Task findTask(@NotNull String key) throws Exception; @NotNull - public abstract Set getPossibleStates(@NotNull Task task) throws Exception; + public abstract Set getAvailableTaskStates(@NotNull Task task) throws Exception; public abstract void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception; diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java index cea2c21a4e75..ec0e612f4e81 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java @@ -355,8 +355,8 @@ public class JiraRepository extends BaseRepositoryImpl { @NotNull @Override - public Set getPossibleStates(@NotNull Task task) throws Exception { - return myApiVersion.getPossibleStates(task); + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { + return myApiVersion.getAvailableTaskStates(task); } public void setSearchQuery(String searchQuery) { diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java index 72cdc8130960..8163b1cca166 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java @@ -83,7 +83,7 @@ public class JiraRestApi2 extends JiraRestApi { @NotNull @Override - public Set getPossibleStates(@NotNull Task task) throws Exception { + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { final GetMethod method = new GetMethod(myRepository.getRestUrl("issue", task.getId(), "transitions")); method.setQueryString("expand=transitions.fields"); final String response = myRepository.executeMethod(method); diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java index a87f6052d93b..aa95f0b432a6 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java @@ -34,7 +34,7 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { @NotNull @Override - public Set getPossibleStates(@NotNull Task task) throws Exception { + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { final HashSet result = new HashSet(); result.add(TaskState.IN_PROGRESS); result.add(TaskState.RESOLVED); diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java index 857c6e11aa5e..7f5ab27cc9a1 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/soap/JiraLegacyApi.java @@ -106,7 +106,7 @@ public class JiraLegacyApi extends JiraRemoteApi { @NotNull @Override - public Set getPossibleStates(@NotNull Task task) throws Exception { + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { return Collections.emptySet(); } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index b1008d493f63..1e19e7d73fd0 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -119,7 +119,7 @@ public class OpenTaskDialog extends DialogWrapper { @NotNull @Override protected Set fetch(@NotNull ProgressIndicator indicator) throws Exception { - return repository.getPossibleStates(myTask); + return repository.getAvailableTaskStates(myTask); } @Nullable From a1e8c465b828e68681a557bdb39278b0ebcfa3cc Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sun, 11 Jan 2015 19:47:48 +0300 Subject: [PATCH 04/20] Add SimpleTaskState class, use it instead of JiraCustomTaskState --- .../intellij/tasks/impl/SimpleTaskState.java | 49 +++++++++++++++++++ .../tasks/jira/rest/api2/JiraRestApi2.java | 14 +++--- .../model/JiraTransitionsWrapperApi2.java | 6 +-- .../jira/rest/model/JiraCustomTaskState.java | 37 -------------- 4 files changed, 60 insertions(+), 46 deletions(-) create mode 100644 plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java delete mode 100644 plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java new file mode 100644 index 000000000000..8aefd3986769 --- /dev/null +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java @@ -0,0 +1,49 @@ +package com.intellij.tasks.impl; + +import com.intellij.tasks.CustomTaskState; +import org.jetbrains.annotations.NotNull; + +/** + * @author Mikhail Golubev + */ +public class SimpleTaskState implements CustomTaskState { + private final String myId; + private final String myPresentableName; + + public SimpleTaskState(@NotNull String id, @NotNull String presentableName) { + myId = id; + myPresentableName = presentableName; + } + + @NotNull + @Override + public String getId() { + return myId; + } + + @NotNull + @Override + public String getPresentableName() { + return myPresentableName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SimpleTaskState)) return false; + + final SimpleTaskState state = (SimpleTaskState)o; + + if (!myId.equals(state.myId)) return false; + if (!myPresentableName.equals(state.myPresentableName)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myId.hashCode(); + result = 31 * result + myPresentableName.hashCode(); + return result; + } +} diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java index 8163b1cca166..b83607481fac 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/JiraRestApi2.java @@ -10,7 +10,6 @@ import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.rest.JiraRestApi; import com.intellij.tasks.jira.rest.api2.model.JiraIssueApi2; import com.intellij.tasks.jira.rest.api2.model.JiraTransitionsWrapperApi2; -import com.intellij.tasks.jira.rest.model.JiraCustomTaskState; import com.intellij.tasks.jira.rest.model.JiraIssue; import com.intellij.tasks.jira.rest.model.JiraResponseWrapper; import org.apache.commons.httpclient.methods.GetMethod; @@ -70,14 +69,17 @@ public class JiraRestApi2 extends JiraRestApi { @NotNull @Override protected String getRequestForStateTransition(@NotNull CustomTaskState state) { - final JiraCustomTaskState transition = (JiraCustomTaskState)state; assert StringUtil.isNotEmpty(state.getId()); - final String resolutionName = transition.getResolutionName(); - if (resolutionName != null) { - return "{\"transition\": {\"id\": \"" + state.getId() + "\"}, \"fields\": {\"resolution\": {\"name\": \"" + resolutionName + "\"}}}"; + final String stateId = state.getId(); + final int index = stateId.indexOf(':'); + if (index >= 0) { + return "{" + + " \"transition\": {\"id\": \"" + stateId.substring(0, index) + "\"}, " + + " \"fields\": {\"resolution\": {\"name\": \"" + stateId.substring(index + 1) + "\"}}" + + "}"; } else { - return "{\"transition\": {\"id\": \"" + state.getId() + "\"}}"; + return "{\"transition\": {\"id\": \"" + stateId + "\"}}"; } } diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java index 3c1df68b4d35..48dd231c9dea 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java @@ -4,7 +4,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import com.intellij.tasks.CustomTaskState; -import com.intellij.tasks.jira.rest.model.JiraCustomTaskState; +import com.intellij.tasks.impl.SimpleTaskState; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -42,14 +42,14 @@ public class JiraTransitionsWrapperApi2 { if (fieldName.equals("resolution") && fieldInfo.get("required").getAsBoolean()) { for (JsonElement allowedValue : fieldInfo.getAsJsonArray("allowedValues")) { final String resolutionName = allowedValue.getAsJsonObject().get("name").getAsString(); - result.add(new JiraCustomTaskState(transitionName + " (" + resolutionName + ")", transitionId, resolutionName)); + result.add(new SimpleTaskState(transitionName + " (" + resolutionName + ")", transitionId + resolutionName)); } break; } } } if (fieldName == null) { - result.add(new JiraCustomTaskState(transitionName, transitionId, null)); + result.add(new SimpleTaskState(transitionName, String.valueOf(transitionId))); } } return result; diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java deleted file mode 100644 index 96fafeb5fa66..000000000000 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/model/JiraCustomTaskState.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.intellij.tasks.jira.rest.model; - -import com.intellij.tasks.CustomTaskState; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author Mikhail Golubev - */ -public class JiraCustomTaskState implements CustomTaskState { - private final int myTransitionId; - private final String myPresentableName; - private final String myResolutionId; - - public JiraCustomTaskState(@NotNull String presentableName, int transitionId, @Nullable String resolutionName) { - myTransitionId = transitionId; - myPresentableName = presentableName; - myResolutionId = resolutionName; - } - - @Nullable - public String getResolutionName() { - return myResolutionId; - } - - @NotNull - @Override - public String getId() { - return String.valueOf(myTransitionId); - } - - @NotNull - @Override - public String getPresentableName() { - return myPresentableName; - } -} From 4037f034b08ef7900cb2897af23ecaa99b50ac14 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 00:27:55 +0300 Subject: [PATCH 05/20] TaskRepository remembers last state used when opening task --- .../com/intellij/tasks/CustomTaskState.java | 80 ++++++++++++++++++- .../com/intellij/tasks/TaskRepository.java | 32 +++++++- .../src/com/intellij/tasks/TaskState.java | 17 ++-- .../intellij/tasks/impl/BaseRepository.java | 13 +++ .../intellij/tasks/impl/SimpleTaskState.java | 49 ------------ .../model/JiraTransitionsWrapperApi2.java | 35 +++++--- .../rest/api20alpha1/JiraRestApi20Alpha1.java | 30 ++++--- .../tasks/actions/OpenTaskDialog.java | 29 +++---- .../com/intellij/tasks/impl/TaskUiUtil.java | 16 +++- .../tasks/trello/TrelloRepositoryEditor.java | 10 +++ 10 files changed, 201 insertions(+), 110 deletions(-) delete mode 100644 plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java index 2f8cf8de2c64..dbae96362d79 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java @@ -1,14 +1,86 @@ package com.intellij.tasks; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author Mikhail Golubev */ -public interface CustomTaskState { - @NotNull - String getId(); +public class CustomTaskState { + private String myId; + private String myPresentableName; + private boolean myPredefined; + + /** + * For serialization purposes only. + */ + public CustomTaskState() { + } + + public CustomTaskState(@NotNull String id, @NotNull String name) { + myId = id; + myPresentableName = name; + } @NotNull - String getPresentableName(); + public String getId() { + return myId; + } + + /** + * For serialization purposes only. + */ + public void setId(String id) { + myId = id; + } + + @NotNull + public String getPresentableName() { + return myPresentableName; + } + + /** + * For serialization purposes only. + */ + public void setPresentableName(@NotNull String name) { + myPresentableName = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CustomTaskState)) return false; + + final CustomTaskState state = (CustomTaskState)o; + + return myId.equals(state.myId); + } + + @Override + public int hashCode() { + return myId.hashCode(); + } + + @Nullable + public TaskState asPredefinedTaskState() { + if (isPredefined()) { + try { + return TaskState.valueOf(getId()); + } + catch (IllegalArgumentException ignored) { + } + } + return null; + } + + private boolean isPredefined() { + return myPredefined; + } + + /** + * For serialization purposes only. + */ + public void setPredefined(boolean predefined) { + myPredefined = predefined; + } } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 1e745aed291d..93c2f2dbf6ab 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -20,6 +20,8 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.impl.CancellableRunnable; import com.intellij.tasks.impl.BaseRepository; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.Attribute; import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.Transient; @@ -175,9 +177,27 @@ public abstract class TaskRepository { @NotNull public Set getAvailableTaskStates(@NotNull Task task) throws Exception { //noinspection unchecked - return getRepositoryType().getPossibleTaskStates(); + return ContainerUtil.map2Set(getRepositoryType().getPossibleTaskStates(), new Function() { + @Override + public CustomTaskState fun(TaskState state) { + return state.asCustomTaskState(); + } + }); } + /** + * Remember state used for opening of task. + * @param state preferred task state + */ + public abstract void setPreferredOpenTaskState(@Nullable CustomTaskState state); + + /** + * Task state used last time for opening task. + * @return preferred task state + */ + @Nullable + public abstract CustomTaskState getPreferredOpenTaskState(); + /** * @param id task ID. Don't forget to define {@link #extractId(String)}, if your server uses not PROJECT-123 format for task IDs. * @return found task or {@code null} otherwise. Basically you should return {@code null} on e.g. 404 error and throw exception with @@ -214,7 +234,15 @@ public abstract class TaskRepository { * @see TaskRepository#getFeatures() */ public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { - setTaskState(task, ((TaskState)state)); + TaskState predefinedState = null; + try { + predefinedState = TaskState.valueOf(state.getId()); + } + catch (IllegalArgumentException ignored) { + } + if (predefinedState != null) { + setTaskState(task, predefinedState); + } } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java index 27b8fb87c473..95691db49270 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java @@ -18,9 +18,11 @@ package com.intellij.tasks; import org.jetbrains.annotations.NotNull; /** + * Predefined common task states used before {@link CustomTaskState} was introduced. + * * @author Dmitry Avdeev */ -public enum TaskState implements CustomTaskState { +public enum TaskState { SUBMITTED("Submitted"), OPEN("Open"), IN_PROGRESS("In Progress"), @@ -36,14 +38,9 @@ public enum TaskState implements CustomTaskState { } @NotNull - @Override - public String getId() { - return ""; - } - - @NotNull - @Override - public String getPresentableName() { - return myPresentableName; + public CustomTaskState asCustomTaskState() { + final CustomTaskState result = new CustomTaskState(name(), myPresentableName); + result.setPredefined(true); + return result; } } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java index 8423736c8c10..49ad62e2d1aa 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java @@ -18,6 +18,7 @@ package com.intellij.tasks.impl; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.PasswordUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.TaskRepository; import com.intellij.tasks.TaskRepositoryType; import com.intellij.util.xmlb.annotations.Tag; @@ -40,6 +41,7 @@ public abstract class BaseRepository extends TaskRepository { protected boolean myUseProxy; protected boolean myUseHttpAuthentication; protected boolean myLoginAnonymously; + protected CustomTaskState myPreferredOpenTaskState; public BaseRepository(TaskRepositoryType type) { super(type); @@ -135,6 +137,17 @@ public abstract class BaseRepository extends TaskRepository { myLoginAnonymously = loginAnonymously; } + @Override + public void setPreferredOpenTaskState(@Nullable CustomTaskState state) { + myPreferredOpenTaskState = state; + } + + @Nullable + @Override + public CustomTaskState getPreferredOpenTaskState() { + return myPreferredOpenTaskState; + } + @Nullable public String extractId(@NotNull String taskName) { Matcher matcher = PATTERN.matcher(taskName); diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java deleted file mode 100644 index 8aefd3986769..000000000000 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/SimpleTaskState.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.intellij.tasks.impl; - -import com.intellij.tasks.CustomTaskState; -import org.jetbrains.annotations.NotNull; - -/** - * @author Mikhail Golubev - */ -public class SimpleTaskState implements CustomTaskState { - private final String myId; - private final String myPresentableName; - - public SimpleTaskState(@NotNull String id, @NotNull String presentableName) { - myId = id; - myPresentableName = presentableName; - } - - @NotNull - @Override - public String getId() { - return myId; - } - - @NotNull - @Override - public String getPresentableName() { - return myPresentableName; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SimpleTaskState)) return false; - - final SimpleTaskState state = (SimpleTaskState)o; - - if (!myId.equals(state.myId)) return false; - if (!myPresentableName.equals(state.myPresentableName)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myId.hashCode(); - result = 31 * result + myPresentableName.hashCode(); - return result; - } -} diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java index 48dd231c9dea..a1534ab81e8a 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api2/model/JiraTransitionsWrapperApi2.java @@ -3,8 +3,8 @@ package com.intellij.tasks.jira.rest.api2.model; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.tasks.CustomTaskState; -import com.intellij.tasks.impl.SimpleTaskState; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -12,7 +12,9 @@ import java.util.*; /** * @author Mikhail Golubev */ +@SuppressWarnings({"unused", "FieldMayBeFinal"}) public class JiraTransitionsWrapperApi2 { + private static final Logger LOG = Logger.getInstance(JiraTransitionsWrapperApi2.class); private List transitions = Collections.emptyList(); static class JiraTransition { @@ -31,25 +33,34 @@ public class JiraTransitionsWrapperApi2 { @NotNull public Set getTransitions() { final Set result = new LinkedHashSet(); + nextTransition: for (JiraTransition transition : transitions) { - final int transitionId = transition.id; - final String transitionName = transition.target.name; - String fieldName = null; + final String stateName = transition.target.name; + final List resolutions = new ArrayList(); if (transition.fields != null) { for (Map.Entry field : transition.fields.entrySet()) { - fieldName = field.getKey(); + final String fieldName = field.getKey(); final JsonObject fieldInfo = field.getValue().getAsJsonObject(); - if (fieldName.equals("resolution") && fieldInfo.get("required").getAsBoolean()) { - for (JsonElement allowedValue : fieldInfo.getAsJsonArray("allowedValues")) { - final String resolutionName = allowedValue.getAsJsonObject().get("name").getAsString(); - result.add(new SimpleTaskState(transitionName + " (" + resolutionName + ")", transitionId + resolutionName)); + if (fieldInfo.get("required").getAsBoolean()) { + if (fieldName.equals("resolution")) { + for (JsonElement allowedValue : fieldInfo.getAsJsonArray("allowedValues")) { + resolutions.add(allowedValue.getAsJsonObject().get("name").getAsString()); + } + } + else { + LOG.info("Unknown required field '" + fieldName + "' for transition '" + stateName + "'"); + continue nextTransition; } - break; } } } - if (fieldName == null) { - result.add(new SimpleTaskState(transitionName, String.valueOf(transitionId))); + if (resolutions.isEmpty()) { + result.add(new CustomTaskState(String.valueOf(transition.id), stateName)); + } + else { + for (String resolution : resolutions) { + result.add(new CustomTaskState(transition.id + ":" + resolution, stateName + " (" + resolution + ")")); + } } } return result; diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java index aa95f0b432a6..0108ef688f80 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java @@ -36,9 +36,9 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { @Override public Set getAvailableTaskStates(@NotNull Task task) throws Exception { final HashSet result = new HashSet(); - result.add(TaskState.IN_PROGRESS); - result.add(TaskState.RESOLVED); - result.add(TaskState.REOPENED); + result.add(TaskState.IN_PROGRESS.asCustomTaskState()); + result.add(TaskState.RESOLVED.asCustomTaskState()); + result.add(TaskState.REOPENED.asCustomTaskState()); return result; } @@ -72,17 +72,21 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { protected String getRequestForStateTransition(@NotNull CustomTaskState state) { // REST API of JIRA 4.x for retrieving possible transitions is very limited: we can't fetch possible resolutions and // names of transition destinations. So we have no other options than to hardcode them. - switch ((TaskState)state) { - case IN_PROGRESS: - return "{\"transition\": \"4\"}"; - case RESOLVED: - // 5 for "Resolved", 2 for "Closed" - return "{\"transition\": \"5\", \"resolution\": \"Fixed\"}"; - case REOPENED: - return "{\"transition\": \"3\"}"; - default: - return null; + final TaskState taskState = state.asPredefinedTaskState(); + if (taskState != null) { + switch (taskState) { + case IN_PROGRESS: + return "{\"transition\": \"4\"}"; + case RESOLVED: + // 5 for "Resolved", 2 for "Closed" + return "{\"transition\": \"5\", \"resolution\": \"Fixed\"}"; + case REOPENED: + return "{\"transition\": \"3\"}"; + default: + return null; + } } + return null; } @Override diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 1e19e7d73fd0..0efa5396b424 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -54,19 +54,7 @@ public class OpenTaskDialog extends DialogWrapper { private final static Logger LOG = Logger.getInstance("#com.intellij.tasks.actions.SimpleOpenTaskDialog"); public static final String START_FROM_BRANCH = "start.from.branch"; - private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState() { - @NotNull - @Override - public String getId() { - return ""; - } - - @NotNull - @Override - public String getPresentableName() { - return "-- do not update --"; - } - }; + private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); private JPanel myPanel; @BindControl(value = "clearContext", instant = true) @@ -122,6 +110,12 @@ public class OpenTaskDialog extends DialogWrapper { return repository.getAvailableTaskStates(myTask); } + @Nullable + @Override + public CustomTaskState getSelectedItem() { + return repository.getPreferredOpenTaskState(); + } + @Nullable @Override public CustomTaskState getExtraItem() { @@ -232,11 +226,12 @@ public class OpenTaskDialog extends DialogWrapper { taskManager.getState().createChangelist = myCreateChangelist.isSelected(); taskManager.getState().createBranch = myCreateBranch.isSelected(); - TaskRepository repository = myTask.getRepository(); - final CustomTaskState selectedItem = (CustomTaskState)myStateComboBox.getSelectedItem(); - if (repository != null && selectedItem != null && selectedItem != DO_NOT_UPDATE_STATE) { + final TaskRepository repository = myTask.getRepository(); + final CustomTaskState taskState = (CustomTaskState)myStateComboBox.getSelectedItem(); + if (repository != null && taskState != null && taskState != DO_NOT_UPDATE_STATE) { try { - repository.setTaskState(myTask, selectedItem); + repository.setTaskState(myTask, taskState); + repository.setPreferredOpenTaskState(taskState); } catch (Exception ex) { Messages.showErrorDialog(myProject, ex.getMessage(), "Cannot Set State For Issue"); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java index 38d7fe7f6c03..5bffedf3e903 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java @@ -7,6 +7,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; +import com.intellij.tasks.config.TaskRepositoryEditor; import com.intellij.ui.ListCellRendererWrapper; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; @@ -60,7 +61,7 @@ public class TaskUiUtil { /** * {@link #onSuccess()} can't be used for this purpose, because it doesn't consider current modality state - * which will prevent UI updating in modal dialog (e.g. in {@link com.intellij.tasks.config.TaskRepositoryEditor}). + * which will prevent UI updating in modal dialog (e.g. in {@link TaskRepositoryEditor}). */ @Nullable @Override @@ -104,14 +105,23 @@ public class TaskUiUtil { /** * Return item to select after every combo box update. Default implementation select item, returned by {@link #getExtraItem()}. + * If returned value is not present in the list it will be added depending on policy set by {@link #addSelectedItemIfMissing()}. * * @return selected combo box item + * @see #addSelectedItemIfMissing() */ @Nullable public T getSelectedItem() { return getExtraItem(); } + /** + * @return whether value returned by {@link #getSelectedItem()} should be forcibly added to the combo box. + */ + protected boolean addSelectedItemIfMissing() { + return false; + } + @SuppressWarnings("unchecked") @Override protected void updateUI() { @@ -127,7 +137,7 @@ public class TaskUiUtil { final T selected = getSelectedItem(); if (selected != null) { - if (!selected.equals(extra) && !myResult.contains(selected)) { + if (!selected.equals(extra) && !myResult.contains(selected) && addSelectedItemIfMissing()) { myComboBox.addItem(selected); } myComboBox.setSelectedItem(selected); @@ -147,7 +157,7 @@ public class TaskUiUtil { } /** - * Very simple wrapper around {@link com.intellij.ui.ListCellRendererWrapper} useful for + * Very simple wrapper around {@link ListCellRendererWrapper} useful for * combo boxes where each item has plain text representation with special message for * {@code null} value. */ diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryEditor.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryEditor.java index 0ea5905cbcb2..f7dcd54d3baa 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryEditor.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryEditor.java @@ -260,6 +260,11 @@ public class TrelloRepositoryEditor extends BaseRepositoryEditor { @@ -284,5 +289,10 @@ public class TrelloRepositoryEditor extends BaseRepositoryEditor Date: Mon, 12 Jan 2015 13:19:27 +0300 Subject: [PATCH 06/20] Field 'markAsInProgress' in configuration of TaskManager is no longer needed --- .../tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java index 11c09894b852..94f940af9c5a 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java @@ -1036,7 +1036,6 @@ public class TaskManagerImpl extends TaskManager implements ProjectComponent, Pe public boolean saveContextOnCommit = true; public boolean trackContextForNewChangelist = false; - public boolean markAsInProgress = false; public String changelistNameFormat = "{id} {summary}"; public String branchNameFormat = "{id}"; From 2bd87412882e9da83836fde5776d6bd611633fab Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 14:00:22 +0300 Subject: [PATCH 07/20] States discovery works in YouTrack repository --- .../tasks/youtrack/YouTrackRepository.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java index 5da18ef96d21..077f31cd493b 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java @@ -25,6 +25,7 @@ import com.intellij.tasks.impl.BaseRepository; import com.intellij.tasks.impl.BaseRepositoryImpl; import com.intellij.tasks.impl.LocalTaskImpl; import com.intellij.tasks.impl.TaskUtil; +import com.intellij.util.Function; import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.VersionComparatorUtil; @@ -47,10 +48,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.InputStream; import java.io.StringReader; -import java.util.Date; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author Dmitry Avdeev @@ -198,12 +196,23 @@ public class YouTrackRepository extends BaseRepositoryImpl { } @Override - public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception { - String s = myCustomStateNames.get(state); - if (StringUtil.isEmpty(s)) { - s = state.name(); - } - doREST("/rest/issue/execute/" + task.getId() + "?command=" + encodeUrl("state " + s), true); + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { + doREST("/rest/issue/execute/" + task.getId() + "?command=" + encodeUrl("state " + state.getId()), true); + } + + @NotNull + @Override + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { + final HttpMethod method = doREST("/rest/issue/" + task.getId() + "/execute/intellisense?command=" + encodeUrl("state "), false); + final InputStream stream = method.getResponseBodyAsStream(); + final Element element = new SAXBuilder(false).build(stream).getRootElement(); + return ContainerUtil.map2Set(element.getChild("suggest").getChildren("item"), new Function() { + @Override + public CustomTaskState fun(Element element) { + final String stateName = element.getChildText("option"); + return new CustomTaskState(stateName, stateName); + } + }); } @Nullable From f24b2e2a5e13d10ee76e9625a1e6fd9ca007e659 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 14:42:18 +0300 Subject: [PATCH 08/20] Remove the panel added in YouTrackRepositoryEditor because of IDEA-114203 --- .../tasks/youtrack/YouTrackOptionsTab.form | 84 ------------------- .../tasks/youtrack/YouTrackOptionsTab.java | 40 --------- .../tasks/youtrack/YouTrackRepository.java | 40 +-------- .../youtrack/YouTrackRepositoryEditor.java | 17 ---- 4 files changed, 4 insertions(+), 177 deletions(-) delete mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.form delete mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.java diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.form deleted file mode 100644 index 8dc52a33211a..000000000000 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.form +++ /dev/null @@ -1,84 +0,0 @@ - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.java deleted file mode 100644 index fc084d293d52..000000000000 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackOptionsTab.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.intellij.tasks.youtrack; - -import com.intellij.ui.components.JBLabel; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; - -/** - * @author Mikhail Golubev - */ -public class YouTrackOptionsTab { - public static final String MESSAGE = "" + - "This option should be used only when \"Name\" property for \"State\" field " + - "was changed in server settings. It's not related to localized names of states." + - ""; - - private JTextField myInProgressState; - private JTextField myResolvedState; - private JPanel myRootPanel; - private JBLabel myNoteText; - - public YouTrackOptionsTab() { - myNoteText.setComponentStyle(UIUtil.ComponentStyle.SMALL); - myNoteText.setText(MESSAGE); - } - - public JTextField getInProgressState() { - return myInProgressState; - } - - public JTextField getResolvedState() { - return myResolvedState; - } - - public JPanel getRootPanel() { - return myRootPanel; - } - - -} diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java index 077f31cd493b..bb1f0c320bcb 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java @@ -29,8 +29,6 @@ import com.intellij.util.Function; import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.VersionComparatorUtil; -import com.intellij.util.xmlb.annotations.MapAnnotation; -import com.intellij.util.xmlb.annotations.Property; import com.intellij.util.xmlb.annotations.Tag; import org.apache.axis.utils.XMLChar; import org.apache.commons.httpclient.HttpClient; @@ -48,7 +46,9 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.InputStream; import java.io.StringReader; -import java.util.*; +import java.util.Date; +import java.util.List; +import java.util.Set; /** * @author Dmitry Avdeev @@ -57,13 +57,6 @@ import java.util.*; public class YouTrackRepository extends BaseRepositoryImpl { private String myDefaultSearch = "Assignee: me sort by: updated #Unresolved"; - private Map myCustomStateNames = new EnumMap(TaskState.class); - - // Default names for supported issues states - { - myCustomStateNames.put(TaskState.IN_PROGRESS, "In Progress"); - myCustomStateNames.put(TaskState.RESOLVED, "Fixed"); - } /** * for serialization @@ -85,7 +78,6 @@ public class YouTrackRepository extends BaseRepositoryImpl { private YouTrackRepository(YouTrackRepository other) { super(other); myDefaultSearch = other.getDefaultSearch(); - myCustomStateNames = new EnumMap(other.getCustomStateNames()); } public Task[] getIssues(@Nullable String request, int max, long since) throws Exception { @@ -324,9 +316,7 @@ public class YouTrackRepository extends BaseRepositoryImpl { public boolean equals(Object o) { if (!super.equals(o)) return false; YouTrackRepository repository = (YouTrackRepository)o; - if (!Comparing.equal(repository.getDefaultSearch(), getDefaultSearch())) return false; - if (!Comparing.equal(repository.getCustomStateNames(), getCustomStateNames())) return false; - return true; + return Comparing.equal(repository.getDefaultSearch(), getDefaultSearch()); } private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.youtrack.YouTrackRepository"); @@ -356,26 +346,4 @@ public class YouTrackRepository extends BaseRepositoryImpl { protected int getFeatures() { return super.getFeatures() | TIME_MANAGEMENT | STATE_UPDATING; } - - public void setCustomStateNames(Map customStateNames) { - myCustomStateNames.putAll(customStateNames); - } - - @Tag("customStates") - @Property(surroundWithTag = false) - @MapAnnotation( - surroundWithTag = false, - keyAttributeName = "state", - valueAttributeName = "name", - surroundKeyWithTag = false, - surroundValueWithTag = false - ) - - public Map getCustomStateNames() { - return myCustomStateNames; - } - - public void setCustomStateName(TaskState state, String name) { - myCustomStateNames.put(state, name); - } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepositoryEditor.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepositoryEditor.java index db16ea5247bb..34ba0a773b5c 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepositoryEditor.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepositoryEditor.java @@ -3,10 +3,8 @@ package com.intellij.tasks.youtrack; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; -import com.intellij.tasks.TaskState; import com.intellij.tasks.config.BaseRepositoryEditor; import com.intellij.tasks.youtrack.lang.YouTrackLanguage; import com.intellij.ui.EditorTextField; @@ -17,14 +15,12 @@ import com.intellij.util.ui.FormBuilder; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.Map; /** * @author Dmitry Avdeev */ public class YouTrackRepositoryEditor extends BaseRepositoryEditor { private static final Logger LOG = Logger.getInstance(YouTrackRepository.class); - private final YouTrackOptionsTab myOptions; private EditorTextField myDefaultSearch; private JBLabel mySearchLabel; @@ -36,17 +32,6 @@ public class YouTrackRepositoryEditor extends BaseRepositoryEditor states = myRepository.getCustomStateNames(); - myOptions.getInProgressState().setText(StringUtil.notNullize(states.get(TaskState.IN_PROGRESS))); - myOptions.getResolvedState().setText(StringUtil.notNullize(states.get(TaskState.RESOLVED))); - - installListener(myOptions.getInProgressState()); - installListener(myOptions.getResolvedState()); - - myTabbedPane.add("Options", myOptions.getRootPanel()); } @Override @@ -61,8 +46,6 @@ public class YouTrackRepositoryEditor extends BaseRepositoryEditor Date: Mon, 12 Jan 2015 16:00:53 +0300 Subject: [PATCH 09/20] Allow to move Trello card to another list when opening it --- .../com/intellij/tasks/TaskBundle.properties | 2 + .../tasks/trello/TrelloRepository.java | 42 ++++++++++++++++++- .../tasks/trello/TrelloRepositoryType.java | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/TaskBundle.properties b/plugins/tasks/tasks-core/src/com/intellij/tasks/TaskBundle.properties index ce617cbf01ca..4d631b7c5d20 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/TaskBundle.properties +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/TaskBundle.properties @@ -23,3 +23,5 @@ youtrack.default.query=for: me sort by: updated #Unresolved bugzilla.failure.malformed.response=Cannot decode server response. Check that XML-RPC plugin is enabled. bugzilla.failure.no.version=Cannot find Bugzilla version. Check that URL ends with "xmlrpc.cgi". +## Trello +trello.failure.write.access.required=This action requires write access to your account. Please update authorization token in settings. diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java index 89d3e3d7d8f2..5a8a0c12b728 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java @@ -22,6 +22,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.Task; import com.intellij.tasks.TaskBundle; import com.intellij.tasks.TaskRepositoryType; @@ -41,16 +42,20 @@ import org.apache.http.*; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URIBuilder; import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -244,7 +249,12 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { if (myCurrentBoard == null || myCurrentBoard == UNSPECIFIED_BOARD) { throw new IllegalStateException("Board not set"); } - final URIBuilder url = new URIBuilder(getRestApiUrl("boards", myCurrentBoard.getId(), "lists")) + return fetchBoardLists(myCurrentBoard.getId()); + } + + @NotNull + private List fetchBoardLists(@NotNull String boardId) throws Exception { + final URIBuilder url = new URIBuilder(getRestApiUrl("boards", boardId, "lists")) .addParameter("fields", TrelloList.REQUIRED_FIELDS); return makeRequestAndDeserializeJsonResponse(url.build(), TrelloUtil.LIST_OF_LISTS_TYPE); } @@ -424,8 +434,36 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { return "https://api.trello.com"; } + @NotNull + @Override + public Set getAvailableTaskStates(@NotNull Task task) throws Exception { + final TrelloCard card = fetchCardById(task.getId()); + if (card != null) { + final List lists = fetchBoardLists(card.getIdBoard()); + final Set result = new HashSet(); + for (TrelloList list : lists) { + if (!list.getId().equals(card.getIdList())) { + result.add(new CustomTaskState(list.getId(), list.getName())); + } + } + return result; + } + return Collections.emptySet(); + } + + @Override + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { + final URI url = new URIBuilder(getRestApiUrl("cards", task.getId(), "idList")).addParameter("value", state.getId()).build(); + final HttpResponse response = getHttpClient().execute(new HttpPut(url)); + if (response.getStatusLine() != null && + response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED && + EntityUtils.toString(response.getEntity()).trim().equals("unauthorized card permission requested")) { + throw new Exception(TaskBundle.message("trello.failure.write.access.required")); + } + } + @Override protected int getFeatures() { - return super.getFeatures() & ~NATIVE_SEARCH; + return super.getFeatures() & ~NATIVE_SEARCH | STATE_UPDATING; } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryType.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryType.java index 86fca7a40be4..253a41f7bbca 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryType.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepositoryType.java @@ -33,7 +33,7 @@ import javax.swing.*; public class TrelloRepositoryType extends BaseRepositoryType { public static final String DEVELOPER_KEY = "d6ec3709f7141007e150de64d4701181"; public static final String CLIENT_AUTHORIZATION_URL = - "https://trello.com/1/authorize?key=" + DEVELOPER_KEY +"&name=JetBrains&expiration=never&response_type=token"; + "https://trello.com/1/authorize?key=" + DEVELOPER_KEY +"&name=JetBrains&expiration=never&response_type=token&scope=read,write"; @NotNull @Override From 8ed654a1300a5e38f915015f803ffc8bef15007b Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 17:07:43 +0300 Subject: [PATCH 10/20] Allow to set custom task state on close, TaskRepository remembers most recently used state --- .../com/intellij/tasks/TaskRepository.java | 17 ++++- .../intellij/tasks/impl/BaseRepository.java | 12 ++++ .../tasks/actions/CloseTaskAction.java | 6 +- .../tasks/actions/CloseTaskDialog.form | 40 ++++++++--- .../tasks/actions/CloseTaskDialog.java | 70 ++++++++++++++++--- .../tasks/actions/OpenTaskDialog.form | 2 +- .../tasks/actions/OpenTaskDialog.java | 7 +- .../intellij/tasks/impl/TaskManagerImpl.java | 1 - 8 files changed, 129 insertions(+), 26 deletions(-) diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 93c2f2dbf6ab..05ec1f0efbc4 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -186,18 +186,31 @@ public abstract class TaskRepository { } /** - * Remember state used for opening of task. + * Remember state used when opening task most recently. * @param state preferred task state */ public abstract void setPreferredOpenTaskState(@Nullable CustomTaskState state); /** - * Task state used last time for opening task. + * Task state that was used last time when opening task. * @return preferred task state */ @Nullable public abstract CustomTaskState getPreferredOpenTaskState(); + /** + * Remember state used when closing task most recently. + * @param state preferred task state + */ + public abstract void setPreferredCloseTaskState(@Nullable CustomTaskState state); + + /** + * Task state that was used last time when closing task. + * @return preferred task state + */ + @Nullable + public abstract CustomTaskState getPreferredCloseTaskState(); + /** * @param id task ID. Don't forget to define {@link #extractId(String)}, if your server uses not PROJECT-123 format for task IDs. * @return found task or {@code null} otherwise. Basically you should return {@code null} on e.g. 404 error and throw exception with diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java index 49ad62e2d1aa..0f67d4bae909 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/impl/BaseRepository.java @@ -42,6 +42,7 @@ public abstract class BaseRepository extends TaskRepository { protected boolean myUseHttpAuthentication; protected boolean myLoginAnonymously; protected CustomTaskState myPreferredOpenTaskState; + private CustomTaskState myPreferredCloseTaskState; public BaseRepository(TaskRepositoryType type) { super(type); @@ -148,6 +149,17 @@ public abstract class BaseRepository extends TaskRepository { return myPreferredOpenTaskState; } + @Override + public void setPreferredCloseTaskState(@Nullable CustomTaskState state) { + myPreferredCloseTaskState = state; + } + + @Nullable + @Override + public CustomTaskState getPreferredCloseTaskState() { + return myPreferredCloseTaskState; + } + @Nullable public String extractId(@NotNull String taskName) { Matcher matcher = PATTERN.matcher(taskName); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskAction.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskAction.java index 6863eef78510..7bf2afa94b2f 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskAction.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskAction.java @@ -41,11 +41,13 @@ public class CloseTaskAction extends BaseTaskAction { LocalTask task = taskManager.getActiveTask(); CloseTaskDialog dialog = new CloseTaskDialog(project, task); if (dialog.showAndGet()) { - if (dialog.isCloseIssue()) { + final CustomTaskState taskState = dialog.getCloseIssueState(); + if (taskState != null) { try { TaskRepository repository = task.getRepository(); assert repository != null; - repository.setTaskState(task, TaskState.RESOLVED); + repository.setTaskState(task, taskState); + repository.setPreferredCloseTaskState(taskState); } catch (Exception e1) { Messages.showErrorDialog(project, e1.getMessage(), "Cannot Resolve Issue"); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form index 0c8600a9785b..faa9841f61c6 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form @@ -3,7 +3,7 @@ - + @@ -52,14 +52,6 @@
- - - - - - - - @@ -73,6 +65,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java index fa9879c05fe0..d194a073fbd7 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java @@ -16,45 +16,90 @@ package com.intellij.tasks.actions; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vcs.VcsType; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.LocalTask; import com.intellij.tasks.TaskManager; import com.intellij.tasks.TaskRepository; -import com.intellij.tasks.TaskState; import com.intellij.tasks.impl.TaskManagerImpl; +import com.intellij.tasks.impl.TaskUiUtil; import com.intellij.tasks.impl.TaskUtil; +import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Set; /** * @author Dmitry Avdeev */ public class CloseTaskDialog extends DialogWrapper { + private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); private JCheckBox myCommitChanges; - private JCheckBox myCloseIssue; private JPanel myPanel; private JLabel myTaskLabel; private JBCheckBox myMergeBranches; private JPanel myVcsPanel; + private ComboBox myStateComboBox; + private JLabel myStateComboBoxLabel; private final TaskManagerImpl myTaskManager; - public CloseTaskDialog(Project project, LocalTask task) { + public CloseTaskDialog(Project project, final LocalTask task) { super(project, false); setTitle("Close Task"); myTaskLabel.setText(TaskUtil.getTrimmedSummary(task)); myTaskLabel.setIcon(task.getIcon()); - TaskRepository repository = task.getRepository(); - boolean visible = task.isIssue() && TaskUtil.isStateSupported(repository, TaskState.RESOLVED); - myCloseIssue.setVisible(visible); + final TaskRepository repository = task.getRepository(); + myStateComboBox.setRenderer(new ListCellRendererWrapper() { + @Override + public void customize(JList list, CustomTaskState value, int index, boolean selected, boolean hasFocus) { + if (value != null) { + setText(value.getPresentableName()); + } + else { + setText("-- no states available --"); + } + } + }); + + // Capture correct modality state + if (task.isIssue() && repository != null && repository.isSupported(TaskRepository.STATE_UPDATING)) { + // Find out proper way to determine modality state here + new TaskUiUtil.ComboBoxUpdater(project, "Fetching available task states...", myStateComboBox) { + @NotNull + @Override + protected Set fetch(@NotNull ProgressIndicator indicator) throws Exception { + return repository.getAvailableTaskStates(task); + } + + @Nullable + @Override + public CustomTaskState getSelectedItem() { + return repository.getPreferredCloseTaskState(); + } + + @Nullable + @Override + public CustomTaskState getExtraItem() { + return DO_NOT_UPDATE_STATE; + } + }.queue(); + } + else { + myStateComboBoxLabel.setVisible(false); + myStateComboBox.setVisible(false); + } myTaskManager = (TaskManagerImpl)TaskManager.getManager(project); - myCloseIssue.setSelected(visible && myTaskManager.getState().closeIssue); if (myTaskManager.isVcsEnabled()) { myCommitChanges.setEnabled(!task.getChangeLists().isEmpty()); @@ -78,8 +123,10 @@ public class CloseTaskDialog extends DialogWrapper { return myPanel; } - boolean isCloseIssue() { - return myCloseIssue.isSelected(); + @Nullable + CustomTaskState getCloseIssueState() { + final CustomTaskState selected = (CustomTaskState)myStateComboBox.getSelectedItem(); + return selected == null || selected == DO_NOT_UPDATE_STATE ? null : selected; } boolean isCommitChanges() { @@ -92,7 +139,6 @@ public class CloseTaskDialog extends DialogWrapper { @Override protected void doOKAction() { - myTaskManager.getState().closeIssue = isCloseIssue(); if (myCommitChanges.isEnabled()) { myTaskManager.getState().commitChanges = isCommitChanges(); } @@ -101,4 +147,8 @@ public class CloseTaskDialog extends DialogWrapper { } super.doOKAction(); } + + private void createUIComponents() { + myStateComboBox = new ComboBox(300); + } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index 69fcfb993d05..3f506464dc95 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -37,7 +37,7 @@ - + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 0efa5396b424..b88b296fd5a6 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -68,6 +68,7 @@ public class OpenTaskDialog extends DialogWrapper { private JBLabel myFromLabel; private ComboBox myBranchFrom; private ComboBox myStateComboBox; + private JLabel myStateComboBoxLabel; private final Project myProject; private final Task myTask; @@ -101,7 +102,7 @@ public class OpenTaskDialog extends DialogWrapper { // Capture correct modality state final TaskRepository repository = myTask.getRepository(); - if (repository != null) { + if (myTask.isIssue() && repository != null && repository.isSupported(TaskRepository.STATE_UPDATING)) { // Find out proper way to determine modality state here new ComboBoxUpdater(myProject, "Fetching available task states...", myStateComboBox) { @NotNull @@ -123,6 +124,10 @@ public class OpenTaskDialog extends DialogWrapper { } }.queue(); } + else { + myStateComboBoxLabel.setVisible(false); + myStateComboBox.setVisible(false); + } TaskManagerImpl.Config state = taskManager.getState(); myClearContext.setSelected(state.clearContext); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java index 94f940af9c5a..745e3bb93719 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskManagerImpl.java @@ -1030,7 +1030,6 @@ public class TaskManagerImpl extends TaskManager implements ProjectComponent, Pe public boolean createBranch = true; // close task options - public boolean closeIssue = true; public boolean commitChanges = true; public boolean mergeBranch = true; From 842d61aa3bcf87892b909e7eb64bef5d7b5c4ca9 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 17:51:12 +0300 Subject: [PATCH 11/20] Update JiraIntegrationTest#testSetTaskState to use CustomTaskStates --- .../tasks/integration/JiraIntegrationTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/JiraIntegrationTest.java b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/JiraIntegrationTest.java index a3d99c33228c..9eb47a02c0b3 100644 --- a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/JiraIntegrationTest.java +++ b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/JiraIntegrationTest.java @@ -18,18 +18,13 @@ package com.intellij.tasks.integration; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.intellij.tasks.Task; -import com.intellij.tasks.TaskBundle; -import com.intellij.tasks.TaskManagerTestCase; -import com.intellij.tasks.TaskState; +import com.intellij.tasks.*; import com.intellij.tasks.config.TaskSettings; import com.intellij.tasks.impl.LocalTaskImpl; import com.intellij.tasks.impl.TaskUtil; import com.intellij.tasks.jira.JiraRepository; import com.intellij.tasks.jira.JiraRepositoryType; import com.intellij.tasks.jira.JiraVersion; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.GetMethod; import org.jetbrains.annotations.NonNls; @@ -52,6 +47,8 @@ public class JiraIntegrationTest extends TaskManagerTestCase { * JIRA 5.0.6, REST API 2.0 */ @NonNls private static final String JIRA_5_TEST_SERVER_URL = "http://trackers-tests.labs.intellij.net:8015"; + public static final CustomTaskState REOPENED_CUSTOM_STATE = new CustomTaskState("3", "Reopened"); + public static final CustomTaskState RESOLVED_CUSTOM_STATE = new CustomTaskState("5:Fixed", "Fixed"); private JiraRepository myRepository; @@ -167,18 +164,18 @@ public class JiraIntegrationTest extends TaskManagerTestCase { assertNotNull("Test task not found", task); // set required initial state, if was left wrong if (task.getState() != TaskState.REOPENED) { - myRepository.setTaskState(task, TaskState.REOPENED); + myRepository.setTaskState(task, REOPENED_CUSTOM_STATE); } try { //assertEquals("Wrong initial state of test issue: " + key, TaskState.REOPENED, task.getState()); - myRepository.setTaskState(task, TaskState.RESOLVED); + myRepository.setTaskState(task, RESOLVED_CUSTOM_STATE); task = myRepository.findTask(key); assertEquals(task.getState(), TaskState.RESOLVED); } finally { try { // always attempt to restore original state of the issue - myRepository.setTaskState(task, TaskState.REOPENED); + myRepository.setTaskState(task, REOPENED_CUSTOM_STATE); } catch (Exception ignored) { // empty From eea3c34032cd7e917bd9e5e2ba3182517ba94b9b Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 12 Jan 2015 19:02:08 +0300 Subject: [PATCH 12/20] Add test for state updates in TrelloIntegrationTest --- .../live/TrelloIntegrationTest.java | 90 +++++++++++++++---- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/live/TrelloIntegrationTest.java b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/live/TrelloIntegrationTest.java index 654b51261d88..d9150d342ce2 100644 --- a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/live/TrelloIntegrationTest.java +++ b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/live/TrelloIntegrationTest.java @@ -2,8 +2,10 @@ package com.intellij.tasks.integration.live; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.CustomTaskState; import com.intellij.tasks.trello.TrelloRepository; import com.intellij.tasks.trello.TrelloRepositoryType; +import com.intellij.tasks.trello.TrelloTask; import com.intellij.tasks.trello.model.*; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; @@ -22,8 +24,9 @@ import static com.intellij.tasks.trello.model.TrelloLabel.LabelColor.*; */ public class TrelloIntegrationTest extends LiveIntegrationTestCase { - private static final String BOARD_1_NAME = "Board 1"; - private static final String BOARD_1_ID = "53c416a8a6e5a78753562043"; + // Basic functionality (searching, filtering, etc.) + private static final String BASIC_FUNCTIONALITY_BOARD_NAME = "Basic Functionality"; + private static final String BASIC_FUNCTIONALITY_BOARD_ID = "53c416a8a6e5a78753562043"; private static final String LIST_1_1_NAME = "List 1-1"; private static final String LIST_1_1_ID = "53c416a8a6e5a78753562044"; @@ -31,6 +34,17 @@ public class TrelloIntegrationTest extends LiveIntegrationTestCase boards = myRepository.fetchUserBoards(); - assertEquals(2, boards.size()); - assertObjectsNamed("All boards of the user should be included", boards, "Board 1", "Board 2"); + assertEquals(3, boards.size()); + assertObjectsNamed("All boards of the user should be included", boards, + BASIC_FUNCTIONALITY_BOARD_NAME, LABELS_AND_COLORS_BOARD_NAME, STATE_UPDATES_BOARD_NAME); } public void testFetchListsOfBoard() throws Exception { - TrelloBoard selectedBoard = myRepository.fetchBoardById(BOARD_1_ID); + TrelloBoard selectedBoard = myRepository.fetchBoardById(BASIC_FUNCTIONALITY_BOARD_ID); assertNotNull(selectedBoard); myRepository.setCurrentBoard(selectedBoard); List lists = myRepository.fetchBoardLists(); @@ -88,7 +103,7 @@ public class TrelloIntegrationTest extends LiveIntegrationTestCase fetchCards(@Nullable String boardId, @Nullable String listId, boolean withClosed) throws Exception { if (boardId != null) { - TrelloBoard selectedBoard = myRepository.fetchBoardById(BOARD_1_ID); + TrelloBoard selectedBoard = myRepository.fetchBoardById(BASIC_FUNCTIONALITY_BOARD_ID); assertNotNull(selectedBoard); myRepository.setCurrentBoard(selectedBoard); } @@ -108,24 +123,24 @@ public class TrelloIntegrationTest extends LiveIntegrationTestCase cards = fetchCards(BOARD_1_ID, null, true); + List cards = fetchCards(BASIC_FUNCTIONALITY_BOARD_ID, null, true); assertObjectsNamed("All cards of the board should be included", cards, "Card 1-1-1", "Card 1-1-2", "Card 1-2-1", "Card 1-3-1", "Archived Card"); } public void testCardsFilteringByMembership() throws Exception { myRepository.setIncludeAllCards(true); - List allCards = fetchCards(BOARD_1_ID, LIST_1_1_ID, true); + List allCards = fetchCards(BASIC_FUNCTIONALITY_BOARD_ID, LIST_1_1_ID, true); assertObjectsNamed("All cards of the list should be included", allCards, "Card 1-1-1", "Card 1-1-2", "Archived Card"); myRepository.setIncludeAllCards(false); - List assignedCards = fetchCards(BOARD_1_ID, LIST_1_1_ID, true); + List assignedCards = fetchCards(BASIC_FUNCTIONALITY_BOARD_ID, LIST_1_1_ID, true); assertObjectsNamed("Only cards of the list assigned to user should be included", assignedCards, "Card 1-1-1"); } public void testCardsFilteringByStatus() throws Exception { myRepository.setIncludeAllCards(true); - List allCards = fetchCards(BOARD_1_ID, LIST_1_1_NAME, true); + List allCards = fetchCards(BASIC_FUNCTIONALITY_BOARD_ID, LIST_1_1_NAME, true); assertObjectsNamed("All cards of the list should be included", allCards, "Card 1-1-1", "Card 1-1-2", "Archived Card"); TrelloCard card = ContainerUtil.find(allCards, new Condition() { @@ -139,8 +154,17 @@ public class TrelloIntegrationTest extends LiveIntegrationTestCase labels = card.getLabels(); @@ -156,13 +180,41 @@ public class TrelloIntegrationTest extends LiveIntegrationTestCase states = myRepository.getAvailableTaskStates(task); + assertEquals(1, states.size()); + final CustomTaskState inProgressState = states.iterator().next(); + assertEquals(IN_PROGRESS_LIST_ID, inProgressState.getId()); + assertEquals("In Progress", inProgressState.getPresentableName()); + + // Backlog -> In Progress + myRepository.setTaskState(task, inProgressState); + card = myRepository.fetchCardById(FEATURE_CARD_ID); + assertNotNull(card); + assertEquals(STATE_UPDATES_BOARD_ID, card.getIdBoard()); + assertEquals(IN_PROGRESS_LIST_ID, card.getIdList()); + + // Discover "Backlog" list + task = new TrelloTask(card, myRepository); + states = myRepository.getAvailableTaskStates(task); + assertEquals(1, states.size()); + final CustomTaskState backlogState = states.iterator().next(); + assertEquals(BACKLOG_LIST_ID, backlogState.getId()); + assertEquals("Backlog", backlogState.getPresentableName()); + + // In Progress -> Backlog + myRepository.setTaskState(task, backlogState); + card = myRepository.fetchCardById(FEATURE_CARD_ID); + assertNotNull(card); + assertEquals(STATE_UPDATES_BOARD_ID, card.getIdBoard()); + assertEquals(BACKLOG_LIST_ID, card.getIdList()); } static void assertObjectsNamed(@NotNull String message, @NotNull Collection objects, @NotNull String... names) { From 91beb1c0e3f2f5c909f28920b1dd8253554eb24b Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 13 Jan 2015 16:27:57 +0300 Subject: [PATCH 13/20] Add test for state updates in YouTrackRepository. Also it correctly releases connections now --- .../com/intellij/tasks/CustomTaskState.java | 5 + .../tasks/youtrack/YouTrackRepository.java | 148 +++++++++++------- .../integration/YouTrackIntegrationTest.java | 59 +++++++ 3 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/YouTrackIntegrationTest.java diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java index dbae96362d79..cd479e90aee7 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java @@ -83,4 +83,9 @@ public class CustomTaskState { public void setPredefined(boolean predefined) { myPredefined = predefined; } + + @Override + public String toString() { + return "CustomTaskState(id='" + myId + '\'' + ", name='" + myPresentableName + '\'' + ", myPredefined=" + myPredefined + ')'; + } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java index bb1f0c320bcb..c67fa6834368 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/youtrack/YouTrackRepository.java @@ -42,6 +42,7 @@ import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.io.InputStream; @@ -88,39 +89,44 @@ public class YouTrackRepository extends BaseRepositoryImpl { } String requestUrl = "/rest/project/issues/?filter=" + encodeUrl(query) + "&max=" + max + "&updatedAfter" + since; HttpMethod method = doREST(requestUrl, false); - InputStream stream = method.getResponseBodyAsStream(); - - // todo workaround for http://youtrack.jetbrains.net/issue/JT-7984 - String s = StreamUtil.readText(stream, CharsetToolkit.UTF8_CHARSET); - for (int i = 0; i < s.length(); i++) { - if (!XMLChar.isValid(s.charAt(i))) { - s = s.replace(s.charAt(i), ' '); - } - } - - Element element; try { - //InputSource source = new InputSource(stream); - //source.setEncoding("UTF-8"); - //element = new SAXBuilder(false).build(source).getRootElement(); - element = new SAXBuilder(false).build(new StringReader(s)).getRootElement(); - } - catch (JDOMException e) { - LOG.error("Can't parse YouTrack response for " + requestUrl, e); - throw e; - } - if ("error".equals(element.getName())) { - throw new Exception("Error from YouTrack for " + requestUrl + ": '" + element.getText() + "'"); - } + InputStream stream = method.getResponseBodyAsStream(); - List children = element.getChildren("issue"); - - final List tasks = ContainerUtil.mapNotNull(children, new NullableFunction() { - public Task fun(Element o) { - return createIssue(o); + // todo workaround for http://youtrack.jetbrains.net/issue/JT-7984 + String s = StreamUtil.readText(stream, CharsetToolkit.UTF8_CHARSET); + for (int i = 0; i < s.length(); i++) { + if (!XMLChar.isValid(s.charAt(i))) { + s = s.replace(s.charAt(i), ' '); + } } - }); - return tasks.toArray(new Task[tasks.size()]); + + Element element; + try { + //InputSource source = new InputSource(stream); + //source.setEncoding("UTF-8"); + //element = new SAXBuilder(false).build(source).getRootElement(); + element = new SAXBuilder(false).build(new StringReader(s)).getRootElement(); + } + catch (JDOMException e) { + LOG.error("Can't parse YouTrack response for " + requestUrl, e); + throw e; + } + if ("error".equals(element.getName())) { + throw new Exception("Error from YouTrack for " + requestUrl + ": '" + element.getText() + "'"); + } + + List children = element.getChildren("issue"); + + final List tasks = ContainerUtil.mapNotNull(children, new NullableFunction() { + public Task fun(Element o) { + return createIssue(o); + } + }); + return tasks.toArray(new Task[tasks.size()]); + } + finally { + method.releaseConnection(); + } } @Nullable @@ -143,10 +149,16 @@ public class YouTrackRepository extends BaseRepositoryImpl { method.addParameter("password", getPassword()); client.getParams().setContentCharset("UTF-8"); client.executeMethod(method); - if (method.getStatusCode() != 200) { - throw new Exception("Cannot login: HTTP status code " + method.getStatusCode()); + String response; + try { + if (method.getStatusCode() != 200) { + throw new Exception("Cannot login: HTTP status code " + method.getStatusCode()); + } + response = method.getResponseBodyAsString(1000); + } + finally { + method.releaseConnection(); } - String response = method.getResponseBodyAsString(1000); if (response == null) { throw new NullPointerException(); } @@ -163,12 +175,23 @@ public class YouTrackRepository extends BaseRepositoryImpl { @Nullable public Task findTask(@NotNull String id) throws Exception { - HttpMethod method = doREST("/rest/issue/byid/" + id, false); - InputStream stream = method.getResponseBodyAsStream(); - Element element = new SAXBuilder(false).build(stream).getRootElement(); + final Element element = fetchRequestAsElement(id); return element.getName().equals("issue") ? createIssue(element) : null; } + @TestOnly + @NotNull + public Element fetchRequestAsElement(@NotNull String id) throws Exception { + final HttpMethod method = doREST("/rest/issue/byid/" + id, false); + try { + final InputStream stream = method.getResponseBodyAsStream(); + return new SAXBuilder(false).build(stream).getRootElement(); + } + finally { + method.releaseConnection(); + } + } + HttpMethod doREST(String request, boolean post) throws Exception { HttpClient client = login(new PostMethod(getUrl() + "/rest/user/login")); @@ -189,22 +212,27 @@ public class YouTrackRepository extends BaseRepositoryImpl { @Override public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { - doREST("/rest/issue/execute/" + task.getId() + "?command=" + encodeUrl("state " + state.getId()), true); + doREST("/rest/issue/execute/" + task.getId() + "?command=" + encodeUrl("state " + state.getId()), true).releaseConnection(); } @NotNull @Override public Set getAvailableTaskStates(@NotNull Task task) throws Exception { final HttpMethod method = doREST("/rest/issue/" + task.getId() + "/execute/intellisense?command=" + encodeUrl("state "), false); - final InputStream stream = method.getResponseBodyAsStream(); - final Element element = new SAXBuilder(false).build(stream).getRootElement(); - return ContainerUtil.map2Set(element.getChild("suggest").getChildren("item"), new Function() { - @Override - public CustomTaskState fun(Element element) { - final String stateName = element.getChildText("option"); - return new CustomTaskState(stateName, stateName); - } - }); + try { + final InputStream stream = method.getResponseBodyAsStream(); + final Element element = new SAXBuilder(false).build(stream).getRootElement(); + return ContainerUtil.map2Set(element.getChild("suggest").getChildren("item"), new Function() { + @Override + public CustomTaskState fun(Element element) { + final String stateName = element.getChildText("option"); + return new CustomTaskState(stateName, stateName); + } + }); + } + finally { + method.releaseConnection(); + } } @Nullable @@ -325,20 +353,30 @@ public class YouTrackRepository extends BaseRepositoryImpl { public void updateTimeSpent(@NotNull LocalTask task, @NotNull String timeSpent, @NotNull String comment) throws Exception { checkVersion(); final HttpMethod method = doREST("/rest/issue/execute/" + task.getId() + "?command=work+Today+" + timeSpent.replaceAll(" ", "+") + "+" + comment, true); - if (method.getStatusCode() != 200) { - InputStream stream = method.getResponseBodyAsStream(); - String message = new SAXBuilder(false).build(stream).getRootElement().getText(); - throw new Exception(message); + try { + if (method.getStatusCode() != 200) { + InputStream stream = method.getResponseBodyAsStream(); + String message = new SAXBuilder(false).build(stream).getRootElement().getText(); + throw new Exception(message); + } + } + finally { + method.releaseConnection(); } } private void checkVersion() throws Exception { HttpMethod method = doREST("/rest/workflow/version", false); - InputStream stream = method.getResponseBodyAsStream(); - Element element = new SAXBuilder(false).build(stream).getRootElement(); - final boolean timeTrackingAvailable = element.getName().equals("version") && VersionComparatorUtil.compare(element.getChildText("version"), "4.1") >= 0; - if (!timeTrackingAvailable) { - throw new Exception("This version of Youtrack the time tracking is not supported"); + try { + InputStream stream = method.getResponseBodyAsStream(); + Element element = new SAXBuilder(false).build(stream).getRootElement(); + final boolean timeTrackingAvailable = element.getName().equals("version") && VersionComparatorUtil.compare(element.getChildText("version"), "4.1") >= 0; + if (!timeTrackingAvailable) { + throw new Exception("This version of Youtrack the time tracking is not supported"); + } + } + finally { + method.releaseConnection(); } } diff --git a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/YouTrackIntegrationTest.java b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/YouTrackIntegrationTest.java new file mode 100644 index 000000000000..f4577b8b1327 --- /dev/null +++ b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/YouTrackIntegrationTest.java @@ -0,0 +1,59 @@ +package com.intellij.tasks.integration; + +import com.intellij.tasks.CustomTaskState; +import com.intellij.tasks.Task; +import com.intellij.tasks.TaskManagerTestCase; +import com.intellij.tasks.youtrack.YouTrackRepository; +import com.intellij.tasks.youtrack.YouTrackRepositoryType; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jdom.Element; + +import java.util.List; +import java.util.Set; + +/** + * @author Mikhail Golubev + */ +public class YouTrackIntegrationTest extends TaskManagerTestCase { + private static final String YOUTRACK_4_TEST_SERVER_URL = "http://trackers-tests.labs.intellij.net:8067"; + + private static final String REQUEST_WITH_CUSTOM_STATES_ID = "YT4CS-1"; + private static final CustomTaskState NORTH_STATE = new CustomTaskState("North", "North"); + private static final CustomTaskState SUBMITTED_STATE = new CustomTaskState("Submitted", "Submitted"); + + private YouTrackRepository myRepository; + + public void testCustomTaskStates() throws Exception { + final Task task = myRepository.findTask(REQUEST_WITH_CUSTOM_STATES_ID); + assertNotNull(task); + + final Set states = myRepository.getAvailableTaskStates(task); + final List stateNames = ContainerUtil.map(states, new Function() { + @Override + public String fun(CustomTaskState state) { + return state.getPresentableName(); + } + }); + assertContainsElements(stateNames, "North", "South"); + + // ? -> North + myRepository.setTaskState(task, NORTH_STATE); + Element element = myRepository.fetchRequestAsElement(REQUEST_WITH_CUSTOM_STATES_ID); + assertEquals("North", element.getAttributeValue("state")); + + // North -> Submitted + myRepository.setTaskState(task, SUBMITTED_STATE); + element = myRepository.fetchRequestAsElement(REQUEST_WITH_CUSTOM_STATES_ID); + assertEquals("Submitted", element.getAttributeValue("state")); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myRepository = new YouTrackRepository(new YouTrackRepositoryType()); + myRepository.setUrl(YOUTRACK_4_TEST_SERVER_URL); + myRepository.setUsername("buildtest"); + myRepository.setPassword("buildtest"); + } +} From 0fffb1177671fadd0bb2bf9afb02c2270c3e9230 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 12 Feb 2015 21:01:20 +0300 Subject: [PATCH 14/20] Move TaskState#asCustomTaskState to CustomTaskState --- .../src/com/intellij/tasks/CustomTaskState.java | 9 ++++++++- .../src/com/intellij/tasks/TaskRepository.java | 2 +- .../tasks-api/src/com/intellij/tasks/TaskState.java | 10 ++++------ .../jira/rest/api20alpha1/JiraRestApi20Alpha1.java | 8 ++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java index cd479e90aee7..0fced6c7af52 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/CustomTaskState.java @@ -61,8 +61,15 @@ public class CustomTaskState { return myId.hashCode(); } + @NotNull + public static CustomTaskState fromPredefined(@NotNull TaskState state) { + final CustomTaskState result = new CustomTaskState(state.name(), state.getPresentableName()); + result.setPredefined(true); + return result; + } + @Nullable - public TaskState asPredefinedTaskState() { + public TaskState asPredefined() { if (isPredefined()) { try { return TaskState.valueOf(getId()); diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java index 05ec1f0efbc4..c6e270fdef4e 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -180,7 +180,7 @@ public abstract class TaskRepository { return ContainerUtil.map2Set(getRepositoryType().getPossibleTaskStates(), new Function() { @Override public CustomTaskState fun(TaskState state) { - return state.asCustomTaskState(); + return CustomTaskState.fromPredefined(state); } }); } diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java index 95691db49270..79c504a44a60 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskState.java @@ -18,7 +18,7 @@ package com.intellij.tasks; import org.jetbrains.annotations.NotNull; /** - * Predefined common task states used before {@link CustomTaskState} was introduced. + * Predefined common task states were used before {@link CustomTaskState} was introduced. * * @author Dmitry Avdeev */ @@ -31,16 +31,14 @@ public enum TaskState { OTHER("Other"); - String myPresentableName; + private String myPresentableName; TaskState(@NotNull String presentableName) { myPresentableName = presentableName; } @NotNull - public CustomTaskState asCustomTaskState() { - final CustomTaskState result = new CustomTaskState(name(), myPresentableName); - result.setPredefined(true); - return result; + public String getPresentableName() { + return myPresentableName; } } diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java index 0108ef688f80..77c42205f3f9 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/rest/api20alpha1/JiraRestApi20Alpha1.java @@ -36,9 +36,9 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { @Override public Set getAvailableTaskStates(@NotNull Task task) throws Exception { final HashSet result = new HashSet(); - result.add(TaskState.IN_PROGRESS.asCustomTaskState()); - result.add(TaskState.RESOLVED.asCustomTaskState()); - result.add(TaskState.REOPENED.asCustomTaskState()); + result.add(CustomTaskState.fromPredefined(TaskState.IN_PROGRESS)); + result.add(CustomTaskState.fromPredefined(TaskState.RESOLVED)); + result.add(CustomTaskState.fromPredefined(TaskState.REOPENED)); return result; } @@ -72,7 +72,7 @@ public class JiraRestApi20Alpha1 extends JiraRestApi { protected String getRequestForStateTransition(@NotNull CustomTaskState state) { // REST API of JIRA 4.x for retrieving possible transitions is very limited: we can't fetch possible resolutions and // names of transition destinations. So we have no other options than to hardcode them. - final TaskState taskState = state.asPredefinedTaskState(); + final TaskState taskState = state.asPredefined(); if (taskState != null) { switch (taskState) { case IN_PROGRESS: From 4e4a8d711bd5980fe0145e910a4312e53ecf3522 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 13 Feb 2015 18:24:09 +0300 Subject: [PATCH 15/20] First naive attempt to use TemplateKindCombo in OpenTaskDialog --- .../ide/actions/TemplateKindCombo.java | 2 +- .../tasks/actions/OpenTaskDialog.form | 71 +++++++++++----- .../tasks/actions/OpenTaskDialog.java | 83 +++++++++++++------ .../com/intellij/tasks/impl/TaskUiUtil.java | 5 +- 4 files changed, 110 insertions(+), 51 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/ide/actions/TemplateKindCombo.java b/platform/lang-impl/src/com/intellij/ide/actions/TemplateKindCombo.java index 5a76d4bd52c7..46521faff6c5 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/TemplateKindCombo.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/TemplateKindCombo.java @@ -99,8 +99,8 @@ public class TemplateKindCombo extends ComboboxWithBrowseButton { } private void scrollBy(int delta) { - if (delta == 0) return; final int size = getComboBox().getModel().getSize(); + if (delta == 0 || size == 0) return; int next = getComboBox().getSelectedIndex() + delta; if (next < 0 || next >= size) { if (!UISettings.getInstance().CYCLE_SCROLLING) { diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index 3f506464dc95..fd6f4f24a247 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -1,24 +1,24 @@
- + - + - + - + - + @@ -31,27 +31,12 @@ - - - - - - - - - - - - - - - - + @@ -82,7 +67,7 @@ - + @@ -130,7 +115,9 @@ - + + + @@ -143,6 +130,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index ab2eac4df5ad..1d69673faeca 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -16,6 +16,7 @@ package com.intellij.tasks.actions; +import com.intellij.ide.actions.TemplateKindCombo; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.binding.BindControl; @@ -27,6 +28,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Trinity; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.VcsTaskHandler; import com.intellij.tasks.*; @@ -34,18 +36,23 @@ import com.intellij.tasks.impl.TaskManagerImpl; import com.intellij.tasks.impl.TaskUiUtil.ComboBoxUpdater; import com.intellij.tasks.impl.TaskUtil; import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; +import com.intellij.util.Function; +import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Set; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.List; /** * @author Dmitry Avdeev @@ -67,8 +74,9 @@ public class OpenTaskDialog extends DialogWrapper { private JBCheckBox myCreateChangelist; private JBLabel myFromLabel; private ComboBox myBranchFrom; - private ComboBox myStateComboBox; - private JLabel myStateComboBoxLabel; + private TemplateKindCombo myStateCombo; + private JLabel myStateComboLabel; + private JBLabel myStateComboHint; private final Project myProject; private final Task myTask; @@ -88,45 +96,59 @@ public class OpenTaskDialog extends DialogWrapper { binder.bindAnnotations(this); binder.reset(); - myStateComboBox.setRenderer(new ListCellRendererWrapper() { + myStateComboLabel.setLabelFor(myStateCombo); + myStateComboHint.setIcon(PlatformIcons.UP_DOWN_ARROWS); + + final JComboBox comboBox = myStateCombo.getComboBox(); + comboBox.setPreferredSize(new Dimension(300, UIUtil.fixComboBoxHeight(comboBox.getPreferredSize().height))); + final ListCellRenderer defaultRenderer = comboBox.getRenderer(); + //noinspection GtkPreferredJComboBoxRenderer + comboBox.setRenderer(new ListCellRenderer() { @Override - public void customize(JList list, CustomTaskState value, int index, boolean selected, boolean hasFocus) { - if (value != null) { - setText(value.getPresentableName()); - } - else { - setText("-- no states available --"); + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value == null) { + return new JBLabel("-- no states available --"); } + return defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } }); + // Capture correct modality state final TaskRepository repository = myTask.getRepository(); if (myTask.isIssue() && repository != null && repository.isSupported(TaskRepository.STATE_UPDATING)) { // Find out proper way to determine modality state here - new ComboBoxUpdater(myProject, "Fetching available task states...", myStateComboBox) { + new ComboBoxUpdater(myProject, "Fetching available task states...", comboBox) { @NotNull @Override - protected Set fetch(@NotNull ProgressIndicator indicator) throws Exception { - return repository.getAvailableTaskStates(myTask); + protected List fetch(@NotNull ProgressIndicator indicator) throws Exception { + return ContainerUtil + .map(repository.getAvailableTaskStates(myTask), new Function() { + @Override + public CustomStateTrinityAdapter fun(CustomTaskState state) { + return new CustomStateTrinityAdapter(state); + } + }); } @Nullable @Override - public CustomTaskState getSelectedItem() { - return repository.getPreferredOpenTaskState(); + public CustomStateTrinityAdapter getSelectedItem() { + final CustomTaskState state = repository.getPreferredOpenTaskState(); + return state != null ? new CustomStateTrinityAdapter(state) : null; } @Nullable @Override - public CustomTaskState getExtraItem() { - return DO_NOT_UPDATE_STATE; + public CustomStateTrinityAdapter getExtraItem() { + return new CustomStateTrinityAdapter(DO_NOT_UPDATE_STATE); } }.queue(); } else { - myStateComboBoxLabel.setVisible(false); - myStateComboBox.setVisible(false); + myStateComboLabel.setVisible(false); + myStateComboHint.setVisible(false); + myStateCombo.setVisible(false); } TaskManagerImpl.Config state = taskManager.getState(); @@ -204,6 +226,14 @@ public class OpenTaskDialog extends DialogWrapper { myChangelistName.setText(taskManager.getChangelistName(task)); updateFields(true); } + final JComponent contentPanel = getContentPanel(); + contentPanel.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + super.keyPressed(e); + } + }); + myStateCombo.registerUpDownHint(getPreferredFocusedComponent()); init(); } @@ -232,7 +262,7 @@ public class OpenTaskDialog extends DialogWrapper { taskManager.getState().createBranch = myCreateBranch.isSelected(); final TaskRepository repository = myTask.getRepository(); - final CustomTaskState taskState = (CustomTaskState)myStateComboBox.getSelectedItem(); + final CustomTaskState taskState = ((CustomStateTrinityAdapter)myStateCombo.getComboBox().getSelectedItem()).myState; if (repository != null && taskState != null && taskState != DO_NOT_UPDATE_STATE) { try { repository.setTaskState(myTask, taskState); @@ -309,7 +339,7 @@ public class OpenTaskDialog extends DialogWrapper { return myChangelistName; } else { - return null; + return myStateCombo.getComboBox(); } } @@ -317,7 +347,12 @@ public class OpenTaskDialog extends DialogWrapper { return myPanel; } - private void createUIComponents() { - myStateComboBox = new ComboBox(300); + private static class CustomStateTrinityAdapter extends Trinity { + final CustomTaskState myState; + + public CustomStateTrinityAdapter(@NotNull CustomTaskState state) { + super(state.getPresentableName(), null, state.getId()); + myState = state; + } } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java index 5bffedf3e903..3425378cf423 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java @@ -6,7 +6,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.ComboBox; import com.intellij.tasks.config.TaskRepositoryEditor; import com.intellij.ui.ListCellRendererWrapper; import com.intellij.util.ArrayUtil; @@ -86,9 +85,9 @@ public class TaskUiUtil { * indeed a rather common task. */ public static abstract class ComboBoxUpdater extends RemoteFetchTask> { - protected final ComboBox myComboBox; + protected final JComboBox myComboBox; - public ComboBoxUpdater(@Nullable Project project, @NotNull String title, @NotNull ComboBox comboBox) { + public ComboBoxUpdater(@Nullable Project project, @NotNull String title, @NotNull JComboBox comboBox) { super(project, title, ModalityState.any()); myComboBox = comboBox; } From 142397e076267df935247c18aa074875b211221c Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 16 Feb 2015 13:36:19 +0300 Subject: [PATCH 16/20] Delegate to ListCellRendererWrapper instead of plain JBLabel in ListCellRenderer --- .../src/com/intellij/tasks/actions/OpenTaskDialog.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 1d69673faeca..15b3b5e18856 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -36,6 +36,7 @@ import com.intellij.tasks.impl.TaskManagerImpl; import com.intellij.tasks.impl.TaskUiUtil.ComboBoxUpdater; import com.intellij.tasks.impl.TaskUtil; import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.util.Function; @@ -107,7 +108,12 @@ public class OpenTaskDialog extends DialogWrapper { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value == null) { - return new JBLabel("-- no states available --"); + return new ListCellRendererWrapper() { + @Override + public void customize(JList list, CustomStateTrinityAdapter value, int index, boolean selected, boolean hasFocus) { + setText("-- no states available --"); + } + }.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus); } return defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } From f85550c4853fb5770f85bea8b72c927bc84c1dd3 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 16 Feb 2015 14:41:03 +0300 Subject: [PATCH 17/20] Extract updatable combo box for issue states into standalone component --- .../tasks/actions/OpenTaskDialog.form | 18 +-- .../tasks/actions/OpenTaskDialog.java | 110 ++----------- .../intellij/tasks/impl/TaskStateCombo.java | 145 ++++++++++++++++++ 3 files changed, 166 insertions(+), 107 deletions(-) create mode 100644 plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index fd6f4f24a247..6e0f0a5863ec 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -130,7 +130,7 @@
- + @@ -138,7 +138,7 @@ - + @@ -148,23 +148,13 @@ - + - + - - - - - - - - - - diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 15b3b5e18856..4e74c1d5d5ff 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -16,44 +16,33 @@ package com.intellij.tasks.actions; -import com.intellij.ide.actions.TemplateKindCombo; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.binding.BindControl; import com.intellij.openapi.options.binding.ControlBinder; -import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Trinity; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.VcsTaskHandler; import com.intellij.tasks.*; import com.intellij.tasks.impl.TaskManagerImpl; -import com.intellij.tasks.impl.TaskUiUtil.ComboBoxUpdater; +import com.intellij.tasks.impl.TaskStateCombo; import com.intellij.tasks.impl.TaskUtil; import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; -import com.intellij.util.Function; -import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.List; /** * @author Dmitry Avdeev @@ -62,8 +51,6 @@ public class OpenTaskDialog extends DialogWrapper { private final static Logger LOG = Logger.getInstance("#com.intellij.tasks.actions.SimpleOpenTaskDialog"); private static final String START_FROM_BRANCH = "start.from.branch"; - private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); - private JPanel myPanel; @BindControl(value = "clearContext", instant = true) private JCheckBox myClearContext; @@ -75,9 +62,8 @@ public class OpenTaskDialog extends DialogWrapper { private JBCheckBox myCreateChangelist; private JBLabel myFromLabel; private ComboBox myBranchFrom; - private TemplateKindCombo myStateCombo; - private JLabel myStateComboLabel; - private JBLabel myStateComboHint; + private JLabel myTaskStateLabel; + private TaskStateCombo myTaskStateCombo; private final Project myProject; private final Task myTask; @@ -97,64 +83,10 @@ public class OpenTaskDialog extends DialogWrapper { binder.bindAnnotations(this); binder.reset(); - myStateComboLabel.setLabelFor(myStateCombo); - myStateComboHint.setIcon(PlatformIcons.UP_DOWN_ARROWS); - - final JComboBox comboBox = myStateCombo.getComboBox(); - comboBox.setPreferredSize(new Dimension(300, UIUtil.fixComboBoxHeight(comboBox.getPreferredSize().height))); - final ListCellRenderer defaultRenderer = comboBox.getRenderer(); - //noinspection GtkPreferredJComboBoxRenderer - comboBox.setRenderer(new ListCellRenderer() { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - if (value == null) { - return new ListCellRendererWrapper() { - @Override - public void customize(JList list, CustomStateTrinityAdapter value, int index, boolean selected, boolean hasFocus) { - setText("-- no states available --"); - } - }.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus); - } - return defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - } - }); - - - // Capture correct modality state - final TaskRepository repository = myTask.getRepository(); - if (myTask.isIssue() && repository != null && repository.isSupported(TaskRepository.STATE_UPDATING)) { - // Find out proper way to determine modality state here - new ComboBoxUpdater(myProject, "Fetching available task states...", comboBox) { - @NotNull - @Override - protected List fetch(@NotNull ProgressIndicator indicator) throws Exception { - return ContainerUtil - .map(repository.getAvailableTaskStates(myTask), new Function() { - @Override - public CustomStateTrinityAdapter fun(CustomTaskState state) { - return new CustomStateTrinityAdapter(state); - } - }); - } - - @Nullable - @Override - public CustomStateTrinityAdapter getSelectedItem() { - final CustomTaskState state = repository.getPreferredOpenTaskState(); - return state != null ? new CustomStateTrinityAdapter(state) : null; - } - - @Nullable - @Override - public CustomStateTrinityAdapter getExtraItem() { - return new CustomStateTrinityAdapter(DO_NOT_UPDATE_STATE); - } - }.queue(); - } - else { - myStateComboLabel.setVisible(false); - myStateComboHint.setVisible(false); - myStateCombo.setVisible(false); + myTaskStateLabel.setLabelFor(myTaskStateCombo); + if (!TaskStateCombo.taskStateSupported(task)) { + myTaskStateLabel.setVisible(false); + myTaskStateCombo.setVisible(false); } TaskManagerImpl.Config state = taskManager.getState(); @@ -232,14 +164,11 @@ public class OpenTaskDialog extends DialogWrapper { myChangelistName.setText(taskManager.getChangelistName(task)); updateFields(true); } - final JComponent contentPanel = getContentPanel(); - contentPanel.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - super.keyPressed(e); - } - }); - myStateCombo.registerUpDownHint(getPreferredFocusedComponent()); + final JComponent preferredFocusedComponent = getPreferredFocusedComponent(); + if (preferredFocusedComponent != null) { + myTaskStateCombo.registerUpDownAction(preferredFocusedComponent); + } + myTaskStateCombo.scheduleUpdate(); init(); } @@ -267,9 +196,9 @@ public class OpenTaskDialog extends DialogWrapper { taskManager.getState().createChangelist = myCreateChangelist.isSelected(); taskManager.getState().createBranch = myCreateBranch.isSelected(); + final CustomTaskState taskState = myTaskStateCombo.getSelectedState(); final TaskRepository repository = myTask.getRepository(); - final CustomTaskState taskState = ((CustomStateTrinityAdapter)myStateCombo.getComboBox().getSelectedItem()).myState; - if (repository != null && taskState != null && taskState != DO_NOT_UPDATE_STATE) { + if (repository != null && taskState != null) { try { repository.setTaskState(myTask, taskState); repository.setPreferredOpenTaskState(taskState); @@ -345,7 +274,7 @@ public class OpenTaskDialog extends DialogWrapper { return myChangelistName; } else { - return myStateCombo.getComboBox(); + return myTaskStateCombo.getComboBox(); } } @@ -353,12 +282,7 @@ public class OpenTaskDialog extends DialogWrapper { return myPanel; } - private static class CustomStateTrinityAdapter extends Trinity { - final CustomTaskState myState; - - public CustomStateTrinityAdapter(@NotNull CustomTaskState state) { - super(state.getPresentableName(), null, state.getId()); - myState = state; - } + private void createUIComponents() { + myTaskStateCombo = new TaskStateCombo(myProject, myTask); } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java new file mode 100644 index 000000000000..44976c179f58 --- /dev/null +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java @@ -0,0 +1,145 @@ +package com.intellij.tasks.impl; + +import com.intellij.ide.actions.TemplateKindCombo; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Trinity; +import com.intellij.tasks.CustomTaskState; +import com.intellij.tasks.Task; +import com.intellij.tasks.TaskRepository; +import com.intellij.tasks.impl.TaskUiUtil.ComboBoxUpdater; +import com.intellij.ui.ListCellRendererWrapper; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.Function; +import com.intellij.util.PlatformIcons; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Mikhail Golubev + */ +public class TaskStateCombo extends JPanel { + private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); + + public static boolean taskStateSupported(@Nullable Task task) { + if (task == null || !task.isIssue()) { + return false; + } + final TaskRepository repository = task.getRepository(); + return repository != null && repository.isSupported(TaskRepository.STATE_UPDATING); + } + + private final Project myProject; + private final Task myTask; + private final TemplateKindCombo myKindCombo = new TemplateKindCombo(); + + // For designer only + @SuppressWarnings("unused") + public TaskStateCombo() { + this(null, null); + } + + @SuppressWarnings({"GtkPreferredJComboBoxRenderer", "unchecked"}) + public TaskStateCombo(Project project, Task task) { + myProject = project; + myTask = task; + + final JBLabel hintButton = new JBLabel(); + hintButton.setIcon(PlatformIcons.UP_DOWN_ARROWS); + hintButton.setToolTipText("Pressing Up or Down arrows while in editor changes the state"); + final JComboBox comboBox = myKindCombo.getComboBox(); + comboBox.setPreferredSize(new Dimension(300, UIUtil.fixComboBoxHeight(comboBox.getPreferredSize().height))); + final ListCellRenderer defaultRenderer = comboBox.getRenderer(); + comboBox.setRenderer(new ListCellRenderer() { + @SuppressWarnings({"unchecked", "GtkPreferredJComboBoxRenderer"}) + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value == null) { + return new ListCellRendererWrapper() { + @Override + public void customize(JList list, CustomStateTrinityAdapter value, int index, boolean selected, boolean hasFocus) { + setText("-- no states available --"); + } + }.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus); + } + return defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + }); + + setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); + add(myKindCombo); + add(hintButton); + } + + public boolean scheduleUpdate() { + if (myProject != null && taskStateSupported(myTask)) { + final JComboBox comboBox = myKindCombo.getComboBox(); + final TaskRepository repository = myTask.getRepository(); + assert repository != null; + new ComboBoxUpdater(myProject, "Fetching available task states...", comboBox) { + @NotNull + @Override + protected java.util.List fetch(@NotNull ProgressIndicator indicator) throws Exception { + return ContainerUtil + .map(repository.getAvailableTaskStates(myTask), new Function() { + @Override + public CustomStateTrinityAdapter fun(CustomTaskState state) { + return new CustomStateTrinityAdapter(state); + } + }); + } + + @Nullable + @Override + public CustomStateTrinityAdapter getSelectedItem() { + final CustomTaskState state = repository.getPreferredOpenTaskState(); + return state != null ? new CustomStateTrinityAdapter(state) : null; + } + + @Nullable + @Override + public CustomStateTrinityAdapter getExtraItem() { + return new CustomStateTrinityAdapter(DO_NOT_UPDATE_STATE); + } + }.queue(); + return true; + } + return false; + } + + /** + * @return {@code null} if no state is available at the moment or special "do not update" state was selected + */ + @Nullable + public CustomTaskState getSelectedState() { + final CustomStateTrinityAdapter item = (CustomStateTrinityAdapter)myKindCombo.getComboBox().getSelectedItem(); + if (item == null) { + return null; + } + final CustomTaskState state = item.myState; + return state == DO_NOT_UPDATE_STATE ? null : state; + } + + public void registerUpDownAction(@NotNull JComponent focusable) { + myKindCombo.registerUpDownHint(focusable); + } + + @NotNull + public JComboBox getComboBox() { + return myKindCombo.getComboBox(); + } + + private static class CustomStateTrinityAdapter extends Trinity { + final CustomTaskState myState; + + public CustomStateTrinityAdapter(@NotNull CustomTaskState state) { + super(state.getPresentableName(), null, state.getId()); + myState = state; + } + } +} From 60a8b566a2b623d70e701d078459d74fe6c12fe5 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 16 Feb 2015 16:13:27 +0300 Subject: [PATCH 18/20] Add debug logging in Trello to figure out the cause of NFE --- .../tasks/impl/httpclient/ResponseUtil.java | 18 +++++++++++++++--- .../tasks/trello/TrelloRepository.java | 14 ++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/ResponseUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/ResponseUtil.java index e512ff04a15b..c3fc81ec27e7 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/ResponseUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/ResponseUtil.java @@ -134,14 +134,20 @@ public class ResponseUtil { if (LOG.isDebugEnabled()) { String content = getResponseContentAsString(response); TaskUtil.prettyFormatJsonToLog(LOG, content); - return myGson.fromJson(content, myClass); + return myGson.fromJson(content, myClass); + } + else { + return myGson.fromJson(getResponseContentAsReader(response), myClass); } - return myGson.fromJson(getResponseContentAsReader(response), myClass); } catch (JsonSyntaxException e) { LOG.warn("Malformed server response", e); return null; } + catch (NumberFormatException e) { + LOG.error("NFE in response: " + getResponseContentAsString(response), e); + throw new RequestFailedException("Malformed response"); + } } } @@ -175,12 +181,18 @@ public class ResponseUtil { TaskUtil.prettyFormatJsonToLog(LOG, content); return myGson.fromJson(content, myTypeToken.getType()); } - return myGson.fromJson(getResponseContentAsReader(response), myTypeToken.getType()); + else { + return myGson.fromJson(getResponseContentAsReader(response), myTypeToken.getType()); + } } catch (JsonSyntaxException e) { LOG.warn("Malformed server response", e); return Collections.emptyList(); } + catch (NumberFormatException e) { + LOG.error("NFE in response: " + getResponseContentAsString(response), e); + throw new RequestFailedException("Malformed response"); + } } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java index 5a8a0c12b728..e2cc5b078150 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/trello/TrelloRepository.java @@ -36,6 +36,7 @@ import com.intellij.tasks.trello.model.TrelloCard; import com.intellij.tasks.trello.model.TrelloList; import com.intellij.tasks.trello.model.TrelloUser; import com.intellij.util.Function; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.Tag; import org.apache.http.*; @@ -207,7 +208,7 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { try { final URIBuilder url = new URIBuilder(getRestApiUrl("members", "me")) .addParameter("fields", TrelloUser.REQUIRED_FIELDS); - return makeRequestAndDeserializeJsonResponse(url.build(), TrelloUser.class); + return ObjectUtils.assertNotNull(makeRequestAndDeserializeJsonResponse(url.build(), TrelloUser.class)); } catch (Exception e) { LOG.warn("Error while fetching initial user info", e); @@ -223,7 +224,7 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { final URIBuilder url = new URIBuilder(getRestApiUrl("boards", id)) .addParameter("fields", TrelloBoard.REQUIRED_FIELDS); try { - return makeRequestAndDeserializeJsonResponse(url.build(), TrelloBoard.class); + return ObjectUtils.assertNotNull(makeRequestAndDeserializeJsonResponse(url.build(), TrelloBoard.class)); } catch (Exception e) { LOG.warn("Error while fetching initial board info", e); @@ -236,7 +237,7 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { final URIBuilder url = new URIBuilder(getRestApiUrl("lists", id)) .addParameter("fields", TrelloList.REQUIRED_FIELDS); try { - return makeRequestAndDeserializeJsonResponse(url.build(), TrelloList.class); + return ObjectUtils.assertNotNull(makeRequestAndDeserializeJsonResponse(url.build(), TrelloList.class)); } catch (Exception e) { LOG.warn("Error while fetching initial list info" + id, e); @@ -337,7 +338,7 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { return cards; } - @NotNull + @Nullable private T executeMethod(@NotNull HttpUriRequest method, @NotNull ResponseHandler handler) throws Exception { final HttpClient client = getHttpClient(); final HttpResponse response = client.execute(method); @@ -355,10 +356,11 @@ public final class TrelloRepository extends NewBaseRepositoryImpl { @NotNull private List makeRequestAndDeserializeJsonResponse(@NotNull URI url, @NotNull TypeToken> type) throws Exception { - return executeMethod(new HttpGet(url), new GsonMultipleObjectsDeserializer(TrelloUtil.GSON, type)); + final List result = executeMethod(new HttpGet(url), new GsonMultipleObjectsDeserializer(TrelloUtil.GSON, type)); + return ObjectUtils.assertNotNull(result); } - @NotNull + @Nullable private T makeRequestAndDeserializeJsonResponse(@NotNull URI url, @NotNull Class cls) throws Exception { return executeMethod(new HttpGet(url), new GsonSingleObjectDeserializer(TrelloUtil.GSON, cls)); } From 654a2884b0eabfeae24cb13664b71eabca775fb1 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 16 Feb 2015 16:55:43 +0300 Subject: [PATCH 19/20] Use TaskStateCombo in "Close Task" dialog as well --- .../tasks/actions/CloseTaskDialog.form | 5 +- .../tasks/actions/CloseTaskDialog.java | 78 +++++++------------ .../tasks/actions/OpenTaskDialog.form | 1 + .../tasks/actions/OpenTaskDialog.java | 11 ++- .../intellij/tasks/impl/TaskStateCombo.java | 49 +++++++++--- .../com/intellij/tasks/impl/TaskUiUtil.java | 24 ++++-- 6 files changed, 97 insertions(+), 71 deletions(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form index faa9841f61c6..7508f5a7dc34 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.form @@ -78,7 +78,6 @@ -
@@ -87,9 +86,9 @@ - + - + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java index d194a073fbd7..73d14d23435a 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/CloseTaskDialog.java @@ -16,9 +16,7 @@ package com.intellij.tasks.actions; -import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vcs.VcsType; import com.intellij.tasks.CustomTaskState; @@ -26,77 +24,43 @@ import com.intellij.tasks.LocalTask; import com.intellij.tasks.TaskManager; import com.intellij.tasks.TaskRepository; import com.intellij.tasks.impl.TaskManagerImpl; -import com.intellij.tasks.impl.TaskUiUtil; +import com.intellij.tasks.impl.TaskStateCombo; import com.intellij.tasks.impl.TaskUtil; -import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.Set; +import java.util.Collection; /** * @author Dmitry Avdeev */ public class CloseTaskDialog extends DialogWrapper { - private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); - + private final Project myProject; + private final LocalTask myTask; private JCheckBox myCommitChanges; private JPanel myPanel; private JLabel myTaskLabel; private JBCheckBox myMergeBranches; private JPanel myVcsPanel; - private ComboBox myStateComboBox; private JLabel myStateComboBoxLabel; + private TaskStateCombo myStateCombo; private final TaskManagerImpl myTaskManager; public CloseTaskDialog(Project project, final LocalTask task) { super(project, false); + myProject = project; + myTask = task; setTitle("Close Task"); myTaskLabel.setText(TaskUtil.getTrimmedSummary(task)); myTaskLabel.setIcon(task.getIcon()); - final TaskRepository repository = task.getRepository(); - myStateComboBox.setRenderer(new ListCellRendererWrapper() { - @Override - public void customize(JList list, CustomTaskState value, int index, boolean selected, boolean hasFocus) { - if (value != null) { - setText(value.getPresentableName()); - } - else { - setText("-- no states available --"); - } - } - }); - - // Capture correct modality state - if (task.isIssue() && repository != null && repository.isSupported(TaskRepository.STATE_UPDATING)) { - // Find out proper way to determine modality state here - new TaskUiUtil.ComboBoxUpdater(project, "Fetching available task states...", myStateComboBox) { - @NotNull - @Override - protected Set fetch(@NotNull ProgressIndicator indicator) throws Exception { - return repository.getAvailableTaskStates(task); - } - - @Nullable - @Override - public CustomTaskState getSelectedItem() { - return repository.getPreferredCloseTaskState(); - } - - @Nullable - @Override - public CustomTaskState getExtraItem() { - return DO_NOT_UPDATE_STATE; - } - }.queue(); - } - else { + myStateComboBoxLabel.setLabelFor(myStateCombo); + if (!TaskStateCombo.isStateSupportedFor(task)) { myStateComboBoxLabel.setVisible(false); - myStateComboBox.setVisible(false); + myStateCombo.setVisible(false); } myTaskManager = (TaskManagerImpl)TaskManager.getManager(project); @@ -116,6 +80,11 @@ public class CloseTaskDialog extends DialogWrapper { else { myVcsPanel.setVisible(false); } + final JComponent preferredFocusedComponent = getPreferredFocusedComponent(); + if (preferredFocusedComponent != null) { + myStateCombo.registerUpDownAction(preferredFocusedComponent); + } + myStateCombo.scheduleUpdate(); init(); } @@ -123,10 +92,15 @@ public class CloseTaskDialog extends DialogWrapper { return myPanel; } + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myStateCombo.getComboBox(); + } + @Nullable CustomTaskState getCloseIssueState() { - final CustomTaskState selected = (CustomTaskState)myStateComboBox.getSelectedItem(); - return selected == null || selected == DO_NOT_UPDATE_STATE ? null : selected; + return myStateCombo.getSelectedState(); } boolean isCommitChanges() { @@ -149,6 +123,12 @@ public class CloseTaskDialog extends DialogWrapper { } private void createUIComponents() { - myStateComboBox = new ComboBox(300); + myStateCombo = new TaskStateCombo(myProject, myTask) { + @Nullable + @Override + protected CustomTaskState getPreferredState(@NotNull TaskRepository repository, @NotNull Collection available) { + return repository.getPreferredCloseTaskState(); + } + }; } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index 6e0f0a5863ec..d141d4e05fc4 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -155,6 +155,7 @@ + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java index 4e74c1d5d5ff..485161687d68 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.java @@ -43,6 +43,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.Collection; /** * @author Dmitry Avdeev @@ -84,7 +85,7 @@ public class OpenTaskDialog extends DialogWrapper { binder.reset(); myTaskStateLabel.setLabelFor(myTaskStateCombo); - if (!TaskStateCombo.taskStateSupported(task)) { + if (!TaskStateCombo.isStateSupportedFor(task)) { myTaskStateLabel.setVisible(false); myTaskStateCombo.setVisible(false); } @@ -283,6 +284,12 @@ public class OpenTaskDialog extends DialogWrapper { } private void createUIComponents() { - myTaskStateCombo = new TaskStateCombo(myProject, myTask); + myTaskStateCombo = new TaskStateCombo(myProject, myTask) { + @Nullable + @Override + protected CustomTaskState getPreferredState(@NotNull TaskRepository repository, @NotNull Collection available) { + return repository.getPreferredOpenTaskState(); + } + }; } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java index 44976c179f58..2126eece9902 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskStateCombo.java @@ -19,14 +19,16 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.util.Collection; +import java.util.List; /** * @author Mikhail Golubev */ -public class TaskStateCombo extends JPanel { +public abstract class TaskStateCombo extends JPanel { private static final CustomTaskState DO_NOT_UPDATE_STATE = new CustomTaskState("", "-- do not update --"); - public static boolean taskStateSupported(@Nullable Task task) { + public static boolean isStateSupportedFor(@Nullable Task task) { if (task == null || !task.isIssue()) { return false; } @@ -77,27 +79,21 @@ public class TaskStateCombo extends JPanel { } public boolean scheduleUpdate() { - if (myProject != null && taskStateSupported(myTask)) { + if (myProject != null && isStateSupportedFor(myTask)) { final JComboBox comboBox = myKindCombo.getComboBox(); final TaskRepository repository = myTask.getRepository(); assert repository != null; new ComboBoxUpdater(myProject, "Fetching available task states...", comboBox) { @NotNull @Override - protected java.util.List fetch(@NotNull ProgressIndicator indicator) throws Exception { - return ContainerUtil - .map(repository.getAvailableTaskStates(myTask), new Function() { - @Override - public CustomStateTrinityAdapter fun(CustomTaskState state) { - return new CustomStateTrinityAdapter(state); - } - }); + protected List fetch(@NotNull ProgressIndicator indicator) throws Exception { + return CustomStateTrinityAdapter.wrapList(repository.getAvailableTaskStates(myTask)); } @Nullable @Override public CustomStateTrinityAdapter getSelectedItem() { - final CustomTaskState state = repository.getPreferredOpenTaskState(); + final CustomTaskState state = getPreferredState(repository, CustomStateTrinityAdapter.unwrapList(myResult)); return state != null ? new CustomStateTrinityAdapter(state) : null; } @@ -134,6 +130,15 @@ public class TaskStateCombo extends JPanel { return myKindCombo.getComboBox(); } + /** + * Determine what state should be initially selected in the list. + * @param repository task repository to communicate with + * @param available tasks states already downloaded from the repository + * @return task state to select + */ + @Nullable + protected abstract CustomTaskState getPreferredState(@NotNull TaskRepository repository, @NotNull Collection available); + private static class CustomStateTrinityAdapter extends Trinity { final CustomTaskState myState; @@ -141,5 +146,25 @@ public class TaskStateCombo extends JPanel { super(state.getPresentableName(), null, state.getId()); myState = state; } + + @NotNull + static List wrapList(@NotNull Collection states) { + return ContainerUtil.map(states, new Function() { + @Override + public CustomStateTrinityAdapter fun(CustomTaskState state) { + return new CustomStateTrinityAdapter(state); + } + }); + } + + @NotNull + static List unwrapList(@NotNull Collection wrapped) { + return ContainerUtil.map(wrapped, new Function() { + @Override + public CustomTaskState fun(CustomStateTrinityAdapter adapter) { + return adapter.myState; + } + }); + } } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java index 3425378cf423..0f8e571a9e87 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUiUtil.java @@ -136,13 +136,21 @@ public class TaskUiUtil { final T selected = getSelectedItem(); if (selected != null) { - if (!selected.equals(extra) && !myResult.contains(selected) && addSelectedItemIfMissing()) { - myComboBox.addItem(selected); + if (!selected.equals(extra) && !myResult.contains(selected)) { + if (addSelectedItemIfMissing()) { + myComboBox.addItem(selected); + myComboBox.setSelectedItem(selected); + } + else { + selectFirstItem(); + } + } + else { + myComboBox.setSelectedItem(selected); } - myComboBox.setSelectedItem(selected); } - else if (myComboBox.getItemCount() > 0) { - myComboBox.setSelectedIndex(0); + else { + selectFirstItem(); } } else { @@ -150,6 +158,12 @@ public class TaskUiUtil { } } + private void selectFirstItem() { + if (myComboBox.getItemCount() > 0) { + myComboBox.setSelectedIndex(0); + } + } + protected void handleError() { myComboBox.removeAllItems(); } From f62860483c230e689966253cdfe419c386115508 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 16 Feb 2015 17:05:55 +0300 Subject: [PATCH 20/20] In "Open Task" dialog combo box for state is above checkbox "Clear current context" --- .../src/com/intellij/tasks/actions/OpenTaskDialog.form | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form index d141d4e05fc4..64b57bd9d93b 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/OpenTaskDialog.form @@ -3,7 +3,7 @@ - + @@ -11,7 +11,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -155,7 +155,6 @@ -