diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/Task.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/Task.java index 6b7717fd73f1..5ce47198299c 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/Task.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/Task.java @@ -33,15 +33,29 @@ public abstract class Task { /** * Global unique task identifier, e.g. IDEA-00001. It's important that its format is consistent with * {@link TaskRepository#extractId(String)}, because otherwise task won't be updated on its activation. + * Note that this ID is used to find issues and to compare them, so (ideally) it has to be unique. + * + * In some cases task server doesn't offer such global ID (but, for instance, pair (project-name, per-project-id) instead) or it's not + * what users want to see in UI (e.g. notorious id and iid in Gitlab). In this case you should generate artificial ID + * for internal usage and implement {@link #getPresentableId()}. * * @return unique global ID as described * - * @see com.intellij.tasks.TaskRepository#extractId(String) - * @see com.intellij.tasks.TaskManager#activateTask(Task, boolean) + * @see #getPresentableId() + * @see TaskRepository#extractId(String) + * @see TaskManager#activateTask(Task, boolean) */ @NotNull public abstract String getId(); + + /** + * @return ID in the form that is suitable for commit messages, dialogs, completion items, etc. + */ + @NotNull + public String getPresentableId() { + return getId(); + } /** * Short task description. * @return description @@ -100,7 +114,7 @@ public abstract class Task { public final String toString() { String text; if (isIssue()) { - text = getId() + ": " + getSummary(); + text = getPresentableId() + ": " + getSummary(); } else { text = getSummary(); } 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 8051403ded0f..8c4384b574df 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/TaskRepository.java @@ -352,7 +352,7 @@ public abstract class TaskRepository { @Nullable public String getTaskComment(@NotNull Task task) { return isShouldFormatCommitMessage() - ? myCommitMessageFormat.replace("{id}", task.getId()).replace("{summary}", task.getSummary()) + ? myCommitMessageFormat.replace("{id}", task.getPresentableId()).replace("{summary}", task.getSummary()) : null; } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/EditTaskDialog.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/EditTaskDialog.java index a25adf3907a7..4d5daa9bc9af 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/EditTaskDialog.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/EditTaskDialog.java @@ -59,7 +59,7 @@ public class EditTaskDialog extends DialogWrapper { protected EditTaskDialog(Project project, LocalTaskImpl task) { super(project); myTask = task; - setTitle("Edit Task " + (task.isIssue() ? task.getId() : "")); + setTitle("Edit Task " + (task.isIssue() ? task.getPresentableId() : "")); // mySummary.putClientProperty(DialogWrapperPeer.HAVE_INITIAL_SELECTION, ""); mySummary.setText(task.getSummary()); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskAutoCompletionListProvider.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskAutoCompletionListProvider.java index 6d2598d2a023..9fa080f1cc45 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskAutoCompletionListProvider.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskAutoCompletionListProvider.java @@ -103,7 +103,7 @@ public class TaskAutoCompletionListProvider extends TextFieldWithAutoCompletionL @NotNull @Override protected String getLookupString(@NotNull final Task task) { - return task.getId(); + return task.getPresentableId(); } @Override diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskSearchSupport.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskSearchSupport.java index 6fb815e0edfc..ab3f60a045d2 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskSearchSupport.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/TaskSearchSupport.java @@ -56,7 +56,7 @@ public class TaskSearchSupport { final Matcher matcher = getMatcher(pattern); return ContainerUtil.mapNotNull(tasks, new NullableFunction() { public Task fun(Task task) { - return matcher.matches(task.getId()) || matcher.matches(task.getSummary()) ? task : null; + return matcher.matches(task.getPresentableId()) || matcher.matches(task.getSummary()) ? task : null; } }); } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/doc/TaskDocumentationProvider.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/doc/TaskDocumentationProvider.java index 843a6fc7f7e2..87d980bc21e1 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/doc/TaskDocumentationProvider.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/doc/TaskDocumentationProvider.java @@ -51,7 +51,7 @@ public class TaskDocumentationProvider extends AbstractDocumentationProvider imp final Task task = ((TaskPsiElement)element).getTask(); final StringBuilder builder = new StringBuilder(); builder.append("Summary: ").append(task.getSummary()).append("
"); - builder.append("Id: ").append(task.getId()).append("
"); + builder.append("Id: ").append(task.getPresentableId()).append("
"); if (task.getCreated() != null) { builder.append("Created at: ").append(task.getCreated()).append("
"); } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java index 40c42a4df2a3..8b850c2f76a6 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java @@ -42,14 +42,17 @@ import static com.intellij.tasks.impl.httpclient.TaskResponseUtil.GsonSingleObje public class GitlabRepository extends NewBaseRepositoryImpl { @NonNls public static final String REST_API_PATH_PREFIX = "/api/v3/"; - private static final Pattern ID_PATTERN = Pattern.compile("\\d+"); + @NonNls private static final String TOKEN_HEADER = "PRIVATE-TOKEN"; - public static final Gson GSON = TaskGsonUtil.createDefaultBuilder().create(); - public static final TypeToken> LIST_OF_PROJECTS_TYPE = new TypeToken>() { - }; - public static final TypeToken> LIST_OF_ISSUES_TYPE = new TypeToken>() { - }; - public static final GitlabProject UNSPECIFIED_PROJECT = new GitlabProject() { + private static final Pattern ID_PATTERN = Pattern.compile("\\d+"); + private static final Gson GSON = TaskGsonUtil.createDefaultBuilder().create(); + + // @formatter:off + private static final TypeToken> LIST_OF_PROJECTS_TYPE = new TypeToken>() {}; + private static final TypeToken> LIST_OF_ISSUES_TYPE = new TypeToken>() {}; + // @formatter:on + + static final GitlabProject UNSPECIFIED_PROJECT = new GitlabProject() { @Override public String getName() { return "-- all issues created by you --"; @@ -88,7 +91,7 @@ public class GitlabRepository extends NewBaseRepositoryImpl { @Override public boolean equals(Object o) { if (!super.equals(o)) return false; - GitlabRepository repository = (GitlabRepository)o; + final GitlabRepository repository = (GitlabRepository)o; if (!Comparing.equal(myCurrentProject, repository.myCurrentProject)) return false; return true; } @@ -114,9 +117,7 @@ public class GitlabRepository extends NewBaseRepositoryImpl { @Override public Task findTask(@NotNull String id) throws Exception { // doesn't work now, because Gitlab's REST API doesn't provide endpoint to find task - // by its global ID, only by project ID and task's local ID (iid). - //GitlabIssue issue = fetchIssue(Integer.parseInt(id)); - //return issue == null ? null : new GitlabTask(this, issue); + // using only its global ID, it requires both task's global ID AND task's project ID return null; } @@ -155,7 +156,7 @@ public class GitlabRepository extends NewBaseRepositoryImpl { @SuppressWarnings("UnusedDeclaration") @NotNull public GitlabProject fetchProject(int id) throws Exception { - HttpGet request = new HttpGet(getRestApiUrl("project", id)); + final HttpGet request = new HttpGet(getRestApiUrl("project", id)); return getHttpClient().execute(request, new GsonSingleObjectDeserializer(GSON, GitlabProject.class)); } @@ -182,12 +183,14 @@ public class GitlabRepository extends NewBaseRepositoryImpl { return getRestApiUrl("issues"); } - @SuppressWarnings("UnusedDeclaration") + /** + * @param issueId global issue's ID (id field, not iid) + */ @Nullable - public GitlabIssue fetchIssue(int id) throws Exception { + public GitlabIssue fetchIssue(int projectId, int issueId) throws Exception { ensureProjectsDiscovered(); - HttpGet request = new HttpGet(getRestApiUrl("issues", id)); - ResponseHandler handler = new GsonSingleObjectDeserializer(GSON, GitlabIssue.class, true); + final HttpGet request = new HttpGet(getRestApiUrl("projects", projectId, "issues", issueId)); + final ResponseHandler handler = new GsonSingleObjectDeserializer(GSON, GitlabIssue.class, true); return getHttpClient().execute(request, handler); } @@ -223,7 +226,7 @@ public class GitlabRepository extends NewBaseRepositoryImpl { return new HttpRequestInterceptor() { @Override public void process(HttpRequest request, HttpContext context) throws HttpException, IOException { - request.addHeader("PRIVATE-TOKEN", myPassword); + request.addHeader(TOKEN_HEADER, myPassword); //request.addHeader("Accept", "application/json"); } }; diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabTask.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabTask.java index 335cd9514eb8..a6b04356df59 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabTask.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabTask.java @@ -37,9 +37,17 @@ public class GitlabTask extends Task { @NotNull @Override public String getId() { + // Will be in form : + //return myIssue.getProjectId() + ":" + myIssue.getId(); return String.valueOf(myIssue.getId()); } + @NotNull + @Override + public String getPresentableId() { + return "#" + myIssue.getLocalId(); + } + @NotNull @Override public String getSummary() { diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java index 50018ce09dea..0f96aed5c2f8 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java @@ -57,6 +57,7 @@ public class LocalTaskImpl extends LocalTask { private String myProject = null; private String myNumber = ""; + private String myPresentableId = ""; private boolean myIssue = false; private TaskRepository myRepository = null; @@ -161,6 +162,7 @@ public class LocalTaskImpl extends LocalTask { myProject = issue.getProject(); myNumber = issue.getNumber(); + myPresentableId = issue.getPresentableId(); } public void setId(String id) { @@ -394,4 +396,15 @@ public class LocalTaskImpl extends LocalTask { public void setProject(@Nullable String project) { myProject = project; } + + public void setPresentableId(@NotNull String presentableId) { + myPresentableId = presentableId; + } + + @NotNull + @Override + public String getPresentableId() { + // Use global ID for compatibility + return StringUtil.isEmpty(myPresentableId) ? getId() : myPresentableId; + } } 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 bc0ccb8e886c..0e4dcbb39761 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 @@ -497,7 +497,7 @@ public class TaskManagerImpl extends TaskManager implements ProjectComponent, Pe if (task.isIssue()) { StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() { public void run() { - ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(myProject, "Updating " + task.getId()) { + ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(myProject, "Updating " + task.getPresentableId()) { public void run(@NotNull ProgressIndicator indicator) { updateIssue(task.getId()); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUtil.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUtil.java index b7db9b9a9f68..74bc704393ed 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUtil.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/TaskUtil.java @@ -72,7 +72,7 @@ public class TaskUtil { public static String formatTask(@NotNull Task task, String format) { return format - .replace("{id}", task.getId()) + .replace("{id}", task.getPresentableId()) .replace("{number}", task.getNumber()) .replace("{project}", StringUtil.notNullize(task.getProject())) .replace("{summary}", task.getSummary()); @@ -90,7 +90,7 @@ public class TaskUtil { public static String getTrimmedSummary(Task task) { String text; if (task.isIssue()) { - text = task.getId() + ": " + task.getSummary(); + text = task.getPresentableId() + ": " + task.getSummary(); } else { text = task.getSummary(); diff --git a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/GitlabIntegrationTest.java b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/GitlabIntegrationTest.java index 3e5af94f2f53..a593989060f6 100644 --- a/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/GitlabIntegrationTest.java +++ b/plugins/tasks/tasks-tests/test/com/intellij/tasks/integration/GitlabIntegrationTest.java @@ -15,7 +15,6 @@ import com.intellij.util.containers.ContainerUtil; import java.util.Collections; /** - * TODO: install Gitlab on server and add more functional tests * @author Mikhail Golubev */ public class GitlabIntegrationTest extends TaskManagerTestCase { @@ -48,14 +47,13 @@ public class GitlabIntegrationTest extends TaskManagerTestCase { LocalTaskImpl localTask = new LocalTaskImpl(new GitlabTask(myRepository, issue)); String changeListComment = TaskUtil.getChangeListComment(localTask); - assertEquals("project-1 2 1 Sample title", changeListComment); + assertEquals("project-1 2 #2 Sample title", changeListComment); myRepository.setProjects(Collections.emptyList()); localTask = new LocalTaskImpl(new GitlabTask(myRepository, issue)); changeListComment = TaskUtil.getChangeListComment(localTask); // Project is unknown, so "" is substituted instead - assertEquals(" 2 1 Sample title", changeListComment); - + assertEquals(" 2 #2 Sample title", changeListComment); } public void testIssueFilteringByState() throws Exception { @@ -74,6 +72,25 @@ public class GitlabIntegrationTest extends TaskManagerTestCase { assertEquals("Opened issue #1", openedIssues[0].getSummary()); } + // IDEA-136499 + public void testPresentableId() throws Exception { + final GitlabIssue issue = myRepository.fetchIssue(5 /* ID Formatting Tests */, 10); + assertNotNull(issue); + assertEquals(10, issue.getId()); + assertEquals(1, issue.getLocalId()); + assertEquals(5, issue.getProjectId()); + + final GitlabTask task = new GitlabTask(myRepository, issue); + assertEquals("#1", task.getPresentableId()); + assertEquals("1", task.getNumber()); + assertEquals("ID Formatting Tests", task.getProject()); + assertEquals("10", task.getId()); + assertEquals("#1: First issue with iid = 1", task.toString()); + myRepository.setShouldFormatCommitMessage(true); + assertEquals("#1 First issue with iid = 1", myRepository.getTaskComment(task)); + } + + @Override public void setUp() throws Exception { super.setUp();