diff options
author | Kevin Sawicki | 2011-05-16 19:44:31 +0000 |
---|---|---|
committer | Kevin Sawicki | 2011-05-16 19:44:31 +0000 |
commit | f196bf7bc2f5e6f61f86a0f41a4ee9ce061b8b31 (patch) | |
tree | 461bf222097a2764f21ac7fc91cc1e69e89b57bb /org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core | |
parent | 48fe56ce3a3bb048d1103afb4c5ceb168f79a961 (diff) | |
download | egit-github-f196bf7bc2f5e6f61f86a0f41a4ee9ce061b8b31.tar.gz egit-github-f196bf7bc2f5e6f61f86a0f41a4ee9ce061b8b31.tar.xz egit-github-f196bf7bc2f5e6f61f86a0f41a4ee9ce061b8b31.zip |
Refactor package and class names for issue connector.
Change-Id: Icfaf402936f0ebd2527ac9ba2fbbeb135a58e5c4
Signed-off-by: Kevin Sawicki <kevin@github.com>
Diffstat (limited to 'org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core')
15 files changed, 1655 insertions, 5 deletions
diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHub.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHub.java new file mode 100644 index 00000000..6206ad0f --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHub.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core; + +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.egit.github.core.Repository; + +/** + * GitHub class + */ +public class GitHub { + + /** BUNDLE_ID */ + public static final String BUNDLE_ID = "org.eclipse.mylyn.github.core"; + + /** CONNECTOR_KIND */ + public static final String CONNECTOR_KIND = "github"; + + /** HTTP_WWW_GITHUB_ORG */ + public static final String HTTP_WWW_GITHUB_ORG = "http://www.github.org"; + + /** HTTP_GITHUB_COM */ + public static final String HTTP_GITHUB_COM = "http://github.com"; + + /** URL_PATTERN */ + public static final Pattern URL_PATTERN = Pattern.compile("(?:" + + Pattern.quote(HTTP_WWW_GITHUB_ORG) + "|" + + Pattern.quote(HTTP_GITHUB_COM) + ")/([^/]+)/([^/]+)"); + + /** + * Create status of severity with message + * + * @param severity + * @param message + * @return status + */ + public static IStatus createStatus(int severity, String message) { + return new Status(severity, BUNDLE_ID, message); + } + + /** + * Create status of severity with message and throwable + * + * @param severity + * @param message + * @param e + * @return status + */ + public static IStatus createStatus(int severity, String message, Throwable e) { + return new Status(severity, BUNDLE_ID, message, e); + } + + /** + * Create error status from message + * + * @param message + * @return status + */ + public static IStatus createErrorStatus(String message) { + return createStatus(IStatus.ERROR, message); + } + + /** + * Create error status from message and throwable + * + * @param message + * @param t + * @return status + */ + public static IStatus createErrorStatus(String message, Throwable t) { + return createStatus(IStatus.ERROR, message, t); + } + + /** + * Create error status from throwable + * + * @param e + * @return status + */ + public static IStatus createErrorStatus(Throwable e) { + return createStatus(IStatus.ERROR, + "Unexpected error: " + e.getLocalizedMessage(), e); + } + + /** + * Get log + * + * @return log + */ + public static ILog getLog() { + return Platform.getLog(Platform.getBundle(BUNDLE_ID)); + } + + /** + * Log message and throwable as error status + * + * @param message + * @param t + */ + public static void logError(String message, Throwable t) { + getLog().log(createErrorStatus(message, t)); + } + + /** + * Log throwable as error status + * + * @param t + */ + public static void logError(Throwable t) { + getLog().log(createErrorStatus(t.getMessage(), t)); + } + + /** + * Get repository for url + * + * @param repositoryUrl + * @return repository or null if not present in url + */ + public static Repository getRepository(String repositoryUrl) { + return Repository.createFromUrl(repositoryUrl); + } + + /** + * Create url with github.com host + * + * @param user + * @param project + * @return url + * + * @see #createGitHubUrlAlternate(String, String) + */ + public static String createGitHubUrl(String user, String project) { + return HTTP_GITHUB_COM + '/' + user + '/' + project; + } + + /** + * Create url with github.org host + * + * @param user + * @param project + * @return url + * + * @see #createGitHubUrl(String, String) + */ + public static String createGitHubUrlAlternate(String user, String project) { + return HTTP_WWW_GITHUB_ORG + '/' + user + '/' + project; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHubException.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHubException.java new file mode 100644 index 00000000..a0a58cf7 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/GitHubException.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2011 GitHub Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kevin Sawicki (GitHub Inc.) - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.List; + +import org.eclipse.egit.github.core.FieldError; +import org.eclipse.egit.github.core.RequestError; +import org.eclipse.egit.github.core.client.RequestException; + +/** + * GitHub exception that wraps and formats a {@link RequestException} + */ +public class GitHubException extends IOException { + + /** + * + */ + private static final long serialVersionUID = -1456910662911777231L; + + /** + * Create GitHub exception from {@link RequestException} + * + * @param cause + */ + public GitHubException(RequestException cause) { + super(cause); + } + + public String getMessage() { + RequestError error = ((RequestException) getCause()).getError(); + StringBuilder message = new StringBuilder(error.getMessage()); + List<FieldError> errors = error.getErrors(); + if (errors != null && errors.size() > 0) { + message.append(':'); + for (FieldError fieldError : errors) + message.append(' ').append(format(fieldError)).append(','); + message.deleteCharAt(message.length() - 1); + } + return message.toString(); + } + + private String format(FieldError error) { + String code = error.getCode(); + String value = error.getValue(); + String field = error.getField(); + String resource = error.getResource(); + if (FieldError.CODE_INVALID.equals(code)) + if (value != null) + return MessageFormat + .format(Messages.FieldError_InvalidFieldWithValue, + value, field); + else + return MessageFormat.format(Messages.FieldError_InvalidField, + field, value); + else if (FieldError.CODE_MISSING_FIELD.equals(code)) + return MessageFormat + .format(Messages.FieldError_MissingField, field); + else + return MessageFormat.format(Messages.FieldError_ResourceError, + field, resource); + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/Messages.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/Messages.java new file mode 100644 index 00000000..0c8858bf --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/Messages.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2011 GitHub Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kevin Sawicki (GitHub Inc.) - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core; + +import org.eclipse.osgi.util.NLS; + +/** + * NLS for Mylyn GitHub Core + */ +public class Messages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.github.core.messages"; //$NON-NLS-1$ + + /** */ + public static String FieldError_InvalidField; + + /** */ + public static String FieldError_InvalidFieldWithValue; + + /** */ + public static String FieldError_MissingField; + + /** */ + public static String FieldError_ResourceError; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/QueryUtils.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/QueryUtils.java new file mode 100644 index 00000000..85728004 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/QueryUtils.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2011 GitHub Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kevin Sawicki (GitHub Inc.) - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; + +/** + * Utilities for working with {@link IRepositoryQuery} objects. + * + * @author Kevin Sawicki (kevin@github.com) + */ +public abstract class QueryUtils { + + /** + * DELIMITER + */ + public static final String DELIMITER = "::"; //$NON-NLS-1$ + + /** + * Set attribute + * + * @param key + * @param values + * @param query + */ + public static void setAttribute(String key, Collection<String> values, + IRepositoryQuery query) { + if (key == null || query == null) + return; + + if (values != null && !values.isEmpty()) { + StringBuilder value = new StringBuilder(); + for (String entry : values) + value.append(entry).append(DELIMITER); + query.setAttribute(key, value.toString()); + } else + query.setAttribute(key, null); + + } + + /** + * Get attribute + * + * @param key + * @param query + * @return non-null but possibly empty list + */ + public static List<String> getAttributes(String key, IRepositoryQuery query) { + if (key == null || query == null) + return Collections.emptyList(); + + String attribute = query.getAttribute(key); + if (attribute == null || attribute.length() == 0) + return Collections.emptyList(); + + List<String> attrs = new LinkedList<String>(); + String[] values = attribute.split(DELIMITER); + for (String value : values) + if (value.length() > 0) + attrs.add(value); + + return attrs; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistAttachmentHandler.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistAttachmentHandler.java index 172c9332..09609f7e 100644 --- a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistAttachmentHandler.java +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistAttachmentHandler.java @@ -24,7 +24,7 @@ import org.eclipse.egit.github.core.client.GitHubRequest; import org.eclipse.egit.github.core.service.GistService; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; -import org.eclipse.mylyn.github.internal.GitHub; +import org.eclipse.mylyn.internal.github.core.GitHub; import org.eclipse.mylyn.tasks.core.ITask; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistConnector.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistConnector.java index fe3760bc..f92c6d1a 100644 --- a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistConnector.java +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistConnector.java @@ -22,7 +22,7 @@ import org.eclipse.egit.github.core.client.GitHubClient; import org.eclipse.egit.github.core.service.GistService; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; -import org.eclipse.mylyn.github.internal.GitHub; +import org.eclipse.mylyn.internal.github.core.GitHub; import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; import org.eclipse.mylyn.tasks.core.IRepositoryQuery; import org.eclipse.mylyn.tasks.core.ITask; diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistTaskDataHandler.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistTaskDataHandler.java index a09539e9..e68a2900 100644 --- a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistTaskDataHandler.java +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/gist/GistTaskDataHandler.java @@ -27,8 +27,8 @@ import org.eclipse.egit.github.core.client.GitHubClient; import org.eclipse.egit.github.core.service.GistService; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; -import org.eclipse.mylyn.github.internal.GitHub; -import org.eclipse.mylyn.github.internal.GitHubTaskAttributeMapper; +import org.eclipse.mylyn.internal.github.core.GitHub; +import org.eclipse.mylyn.internal.github.core.issue.IssueAttributeMapper; import org.eclipse.mylyn.tasks.core.IRepositoryPerson; import org.eclipse.mylyn.tasks.core.ITaskMapping; import org.eclipse.mylyn.tasks.core.RepositoryResponse; @@ -274,7 +274,7 @@ public class GistTaskDataHandler extends AbstractTaskDataHandler { * @see org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler#getAttributeMapper(org.eclipse.mylyn.tasks.core.TaskRepository) */ public TaskAttributeMapper getAttributeMapper(TaskRepository taskRepository) { - return new GitHubTaskAttributeMapper(taskRepository); + return new IssueAttributeMapper(taskRepository); } } diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttribute.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttribute.java new file mode 100644 index 00000000..8c97c965 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttribute.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * GitHub issue task attributes + */ +public enum IssueAttribute { + + /** + * Issue key + */ + KEY(Messages.IssueAttribute_LabelKey, TaskAttribute.TASK_KEY, + TaskAttribute.TYPE_SHORT_TEXT, true, true), + + /** + * Issue title + */ + TITLE(Messages.IssueAttribute_LabekSummary, TaskAttribute.SUMMARY, + TaskAttribute.TYPE_SHORT_RICH_TEXT, false, true), + + /** + * Issue description + */ + BODY(Messages.IssueAttribute_LabelDescription, + TaskAttribute.DESCRIPTION, TaskAttribute.TYPE_LONG_RICH_TEXT, + false, true), + + /** + * Issue creation date + */ + CREATION_DATE(Messages.IssueAttribute_LabelCreated, + TaskAttribute.DATE_CREATION, TaskAttribute.TYPE_DATETIME, true, + false), + + /** + * Issue modification date + */ + MODIFICATION_DATE(Messages.IssueAttribute_LabelModified, + TaskAttribute.DATE_MODIFICATION, TaskAttribute.TYPE_DATETIME, true, + false), + + /** + * Issue closed date + */ + CLOSED_DATE(Messages.IssueAttribute_LabelClosed, + TaskAttribute.DATE_COMPLETION, TaskAttribute.TYPE_DATETIME, true, + false), + + /** + * Issue status + */ + STATUS(Messages.IssueAttribute_LabelStatus, TaskAttribute.STATUS, + TaskAttribute.TYPE_SHORT_TEXT, false, true), + + /** + * Issue reporter + */ + REPORTER(Messages.IssueAttribute_LabelReporter, + TaskAttribute.USER_REPORTER, TaskAttribute.TYPE_PERSON, true, false), + + /** + * Comment being added to issue + */ + COMMENT_NEW(Messages.IssueAttribute_LabelComment, + TaskAttribute.COMMENT_NEW, TaskAttribute.TYPE_LONG_RICH_TEXT, + false, false), + + /** + * Labels applied to issue + */ + LABELS(Messages.IssueAttribute_LabelLabels, "github.issue.labels", //$NON-NLS-1$ + TaskAttribute.TYPE_MULTI_SELECT, true, false), + + /** + * Issue assignee + */ + ASSIGNEE(Messages.IssueAttribute_LabelAssignee, + TaskAttribute.USER_ASSIGNED, TaskAttribute.TYPE_PERSON, false, true), + + /** + * Issue milestone + */ + MILESTONE(Messages.IssueAttribute_LabelMilestone, + "github.issue.milestone", TaskAttribute.TYPE_SINGLE_SELECT, //$NON-NLS-1$ + false, true), + + /** + * Issue assignee gravatar + */ + ASSIGNEE_GRAVATAR(Messages.IssueAttribute_LabelAssigneeGravatar, + "github.issue.assignee.gravatar", TaskAttribute.TYPE_URL, null, //$NON-NLS-1$ + true, false), + + /** + * Issue reporter gravatar + */ + REPORTER_GRAVATAR(Messages.IssueAttribute_LabelReporterGravatar, + "github.issue.reporter.gravatar", TaskAttribute.TYPE_URL, null, //$NON-NLS-1$ + true, false); + + private final String id; + private final String label; + private final String kind; + private final boolean readOnly; + private final boolean initTask; + private final String type; + + private IssueAttribute(String label, String id, String type, + boolean readOnly, boolean initTask) { + this(label, id, type, TaskAttribute.KIND_DEFAULT, readOnly, initTask); + } + + private IssueAttribute(String label, String id, String type, + String kind, boolean readOnly, boolean initTask) { + this.label = label; + this.id = id; + this.kind = kind; + this.type = type; + this.readOnly = readOnly; + this.initTask = initTask; + } + + /** + * Get attribute label + * + * @return label + */ + public String getLabel() { + return label; + } + + /** + * Get attribute id + * + * @return id + */ + public String getId() { + return id; + } + + /** + * Get attribute type + * + * @return type + */ + public String getType() { + return type; + } + + /** + * Is attribute read only? + * + * @return true if read only, false otherwise + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Get attribute kind + * + * @return kind + */ + public String getKind() { + return this.kind; + } + + /** + * Is this attribute created for new tasks? + * + * @return true if needed for new tasks, false otherwise + */ + public boolean isInitTask() { + return initTask; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttributeMapper.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttributeMapper.java new file mode 100644 index 00000000..e4038af7 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueAttributeMapper.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +import java.text.DateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; + +/** + * GitHub task attribute mapper class. + */ +public class IssueAttributeMapper extends TaskAttributeMapper { + + private DateFormat format = DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, DateFormat.SHORT); + + /** + * @param taskRepository + */ + public IssueAttributeMapper(TaskRepository taskRepository) { + super(taskRepository); + } + + /** + * @see org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper#getValueLabels(org.eclipse.mylyn.tasks.core.data.TaskAttribute) + */ + public List<String> getValueLabels(TaskAttribute taskAttribute) { + String type = taskAttribute.getMetaData().getType(); + if (TaskAttribute.TYPE_DATE.equals(type) + || TaskAttribute.TYPE_DATETIME.equals(type)) { + String value = taskAttribute.getValue(); + if (value.length() > 0) { + Date date = new Date(Long.parseLong(value)); + synchronized (this.format) { + value = this.format.format(date); + } + return Collections.singletonList(value); + } + } + return super.getValueLabels(taskAttribute); + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueConnector.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueConnector.java new file mode 100644 index 00000000..274c4bcc --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueConnector.java @@ -0,0 +1,413 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.egit.github.core.Comment; +import org.eclipse.egit.github.core.Issue; +import org.eclipse.egit.github.core.Label; +import org.eclipse.egit.github.core.Milestone; +import org.eclipse.egit.github.core.Repository; +import org.eclipse.egit.github.core.client.GitHubClient; +import org.eclipse.egit.github.core.client.RequestException; +import org.eclipse.egit.github.core.service.IssueService; +import org.eclipse.egit.github.core.service.LabelService; +import org.eclipse.egit.github.core.service.MilestoneService; +import org.eclipse.egit.github.core.util.LabelComparator; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.github.core.GitHub; +import org.eclipse.mylyn.internal.github.core.GitHubException; +import org.eclipse.mylyn.internal.github.core.QueryUtils; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; +import org.eclipse.mylyn.tasks.core.data.TaskMapper; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; + +/** + * GitHub connector. + */ +public class IssueConnector extends AbstractRepositoryConnector { + + /** + * GitHub kind. + */ + public static final String KIND = GitHub.CONNECTOR_KIND; + + /** + * Create client for repository + * + * @param repository + * @return client + */ + public static GitHubClient createClient(TaskRepository repository) { + GitHubClient client = new GitHubClient(); + AuthenticationCredentials credentials = repository + .getCredentials(AuthenticationType.REPOSITORY); + if (credentials != null) + client.setCredentials(credentials.getUserName(), + credentials.getPassword()); + return client; + } + + /** + * GitHub specific {@link AbstractTaskDataHandler}. + */ + private final IssueTaskDataHandler taskDataHandler; + + private final Map<TaskRepository, List<Label>> repositoryLabels = Collections + .synchronizedMap(new HashMap<TaskRepository, List<Label>>()); + + private final Map<TaskRepository, List<Milestone>> repositoryMilestones = Collections + .synchronizedMap(new HashMap<TaskRepository, List<Milestone>>()); + + /** + * Create GitHub issue repository connector + */ + public IssueConnector() { + taskDataHandler = new IssueTaskDataHandler(this); + } + + /** + * Refresh labels for repository + * + * @param repository + * @return labels + * @throws CoreException + */ + public List<Label> refreshLabels(TaskRepository repository) + throws CoreException { + Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$ + Repository repo = GitHub.getRepository(repository.getRepositoryUrl()); + GitHubClient client = createClient(repository); + LabelService service = new LabelService(client); + try { + List<Label> labels = service.getLabels(repo.getOwner(), + repo.getName()); + Collections.sort(labels, new LabelComparator()); + this.repositoryLabels.put(repository, labels); + return labels; + } catch (IOException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + } + + /** + * Get labels for task repository. + * + * @param repository + * @return non-null but possibly empty list of labels + */ + public List<Label> getLabels(TaskRepository repository) { + Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$ + List<Label> labels = new LinkedList<Label>(); + List<Label> cached = this.repositoryLabels.get(repository); + if (cached != null) { + labels.addAll(cached); + } + return labels; + } + + /** + * Are there cached labels for the specified task repository? + * + * @param repository + * @return true if contains labels, false otherwise + */ + public boolean hasCachedLabels(TaskRepository repository) { + return this.repositoryLabels.containsKey(repository); + } + + /** + * Refresh milestones for repository + * + * @param repository + * @return milestones + * @throws CoreException + */ + public List<Milestone> refreshMilestones(TaskRepository repository) + throws CoreException { + Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$ + Repository repo = GitHub.getRepository(repository.getRepositoryUrl()); + GitHubClient client = createClient(repository); + MilestoneService service = new MilestoneService(client); + try { + List<Milestone> milestones = new LinkedList<Milestone>(); + milestones.addAll(service.getMilestones(repo.getOwner(), + repo.getName(), IssueService.STATE_OPEN)); + milestones.addAll(service.getMilestones(repo.getOwner(), + repo.getName(), IssueService.STATE_CLOSED)); + this.repositoryMilestones.put(repository, milestones); + return milestones; + } catch (IOException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + } + + /** + * Get milestones for task repository. + * + * @param repository + * @return non-null but possibly empty list of milestones + */ + public List<Milestone> getMilestones(TaskRepository repository) { + Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$ + List<Milestone> milestones = new LinkedList<Milestone>(); + List<Milestone> cached = this.repositoryMilestones.get(repository); + if (cached != null) { + milestones.addAll(cached); + } + return milestones; + } + + /** + * Are there cached milestones for the specified task repository? + * + * @param repository + * @return true if contains milestones, false otherwise + */ + public boolean hasCachedMilestones(TaskRepository repository) { + return this.repositoryMilestones.containsKey(repository); + } + + /** + * {@inheritDoc} + * + * @return always {@code true} + */ + @Override + public boolean canCreateNewTask(TaskRepository repository) { + return true; + } + + /** + * {@inheritDoc} + * + * @return always {@code true} + */ + @Override + public boolean canCreateTaskFromKey(TaskRepository repository) { + return true; + } + + /** + * {@inheritDoc} + * + * @see #KIND + */ + @Override + public String getConnectorKind() { + return KIND; + } + + /** + * {@inheritDoc} + */ + @Override + public String getLabel() { + return Messages.IssueConnector_LabelConnector; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractTaskDataHandler getTaskDataHandler() { + return this.taskDataHandler; + } + + @Override + public IStatus performQuery(TaskRepository repository, + IRepositoryQuery query, TaskDataCollector collector, + ISynchronizationSession session, IProgressMonitor monitor) { + IStatus result = Status.OK_STATUS; + List<String> statuses = QueryUtils.getAttributes( + IssueService.FILTER_STATE, query); + + monitor.beginTask(Messages.IssueConector_TaskQuerying, + statuses.size()); + try { + Repository repo = GitHub.getRepository(repository + .getRepositoryUrl()); + + GitHubClient client = createClient(repository); + IssueService service = new IssueService(client); + + Map<String, String> filterData = new HashMap<String, String>(); + String mentions = query.getAttribute(IssueService.FILTER_MENTIONED); + if (mentions != null) + filterData.put(IssueService.FILTER_MENTIONED, mentions); + + String assignee = query.getAttribute(IssueService.FILTER_ASSIGNEE); + if (assignee != null) + filterData.put(IssueService.FILTER_ASSIGNEE, assignee); + + String milestone = query + .getAttribute(IssueService.FILTER_MILESTONE); + if (milestone != null) + filterData.put(IssueService.FILTER_MILESTONE, milestone); + + List<String> labels = QueryUtils.getAttributes( + IssueService.FILTER_LABELS, query); + if (!labels.isEmpty()) { + StringBuilder labelsQuery = new StringBuilder(); + for (String label : labels) + labelsQuery.append(label).append(','); + filterData.put(IssueService.FILTER_LABELS, + labelsQuery.toString()); + } + + for (String status : statuses) { + filterData.put(IssueService.FILTER_STATE, status); + List<Issue> issues = service.getIssues(repo.getOwner(), + repo.getName(), filterData); + + // collect task data + for (Issue issue : issues) { + TaskData taskData = taskDataHandler.createTaskData( + repository, monitor, repo.getOwner(), + repo.getName(), issue); + taskData.setPartial(true); + collector.accept(taskData); + } + monitor.worked(1); + } + + result = Status.OK_STATUS; + } catch (RequestException e) { + result = GitHub.createErrorStatus(new GitHubException(e)); + } catch (IOException e) { + result = GitHub.createErrorStatus(e); + } + + monitor.done(); + return result; + } + + @Override + public TaskData getTaskData(TaskRepository repository, String taskId, + IProgressMonitor monitor) throws CoreException { + Repository repo = GitHub.getRepository(repository.getRepositoryUrl()); + + try { + GitHubClient client = createClient(repository); + IssueService service = new IssueService(client); + Issue issue = service.getIssue(repo.getOwner(), repo.getName(), + taskId); + List<Comment> comments = null; + if (issue.getComments() > 0) { + comments = service.getComments(repo.getOwner(), repo.getName(), + taskId); + } + TaskData taskData = taskDataHandler.createTaskData(repository, + monitor, repo.getOwner(), repo.getName(), issue, comments); + + return taskData; + } catch (IOException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + } + + @Override + public String getRepositoryUrlFromTaskUrl(String taskFullUrl) { + if (taskFullUrl != null) { + Matcher matcher = Pattern.compile( + "(http://.+?)/issues/issue/([^/]+)").matcher(taskFullUrl); //$NON-NLS-1$ + if (matcher.matches()) { + return matcher.group(1); + } + } + return null; + } + + @Override + public String getTaskIdFromTaskUrl(String taskFullUrl) { + if (taskFullUrl != null) { + Matcher matcher = Pattern + .compile(".+?/issues/issue/([^/]+)").matcher(taskFullUrl); //$NON-NLS-1$ + if (matcher.matches()) { + return matcher.group(1); + } + } + return null; + } + + @Override + public String getTaskUrl(String repositoryUrl, String taskId) { + return repositoryUrl + "/issues/issue/" + taskId; //$NON-NLS-1$ + } + + @Override + public void updateRepositoryConfiguration(TaskRepository taskRepository, + IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + monitor.beginTask("", 2); //$NON-NLS-1$ + monitor.setTaskName(Messages.IssueConnector_TaskUpdatingLabels); + refreshLabels(taskRepository); + monitor.worked(1); + monitor.setTaskName(Messages.IssueConnector_TaskUpdatingMilestones); + refreshMilestones(taskRepository); + monitor.done(); + } + + @Override + public boolean hasTaskChanged(TaskRepository repository, ITask task, + TaskData taskData) { + TaskAttribute modAttribute = taskData.getRoot().getAttribute( + TaskAttribute.DATE_MODIFICATION); + if (modAttribute == null) + return false; + + boolean changed = true; + Date modDate = task.getModificationDate(); + if (modDate != null) { + Date updateDate = taskData.getAttributeMapper().getDateValue( + modAttribute); + if (updateDate != null) + changed = updateDate.after(modDate); + } + return changed; + } + + @Override + public void updateTaskFromTaskData(TaskRepository taskRepository, + ITask task, TaskData taskData) { + if (!taskData.isNew()) { + task.setUrl(getTaskUrl(taskRepository.getUrl(), + taskData.getTaskId())); + } + new TaskMapper(taskData).applyTo(task); + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueOperation.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueOperation.java new file mode 100644 index 00000000..d851919d --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueOperation.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +/** + * Enumeration of task operations + */ +public enum IssueOperation { + + /** + * LEAD + */ + LEAVE("Leave "), + + /** + * REOPEN + */ + REOPEN("Reopen"), + + /** + * CLOSE + */ + CLOSE("Close"); + + private final String label; + + private IssueOperation(String label) { + this.label = label; + } + + /** + * Get label + * + * @return label + */ + public String getLabel() { + return label; + } + + /** + * Get id + * + * @return id + */ + public String getId() { + return name(); + } + + /** + * get the operation by its id + * + * @param opId + * the id, or null + * @return the operation, or null if the id was null or did not match any + * operation + */ + public static IssueOperation fromId(String opId) { + for (IssueOperation op : values()) { + if (op.getId().equals(opId)) { + return op; + } + } + return null; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueTaskDataHandler.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueTaskDataHandler.java new file mode 100644 index 00000000..3cb43c81 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/IssueTaskDataHandler.java @@ -0,0 +1,449 @@ +/******************************************************************************* + * Copyright (c) 2011 Red Hat and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * David Green <david.green@tasktop.com> - initial contribution + * Christian Trutz <christian.trutz@gmail.com> - initial contribution + * Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +import java.io.IOException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.egit.github.core.Comment; +import org.eclipse.egit.github.core.Issue; +import org.eclipse.egit.github.core.Label; +import org.eclipse.egit.github.core.Milestone; +import org.eclipse.egit.github.core.Repository; +import org.eclipse.egit.github.core.User; +import org.eclipse.egit.github.core.client.GitHubClient; +import org.eclipse.egit.github.core.client.RequestException; +import org.eclipse.egit.github.core.service.IssueService; +import org.eclipse.egit.github.core.service.LabelService; +import org.eclipse.mylyn.internal.github.core.GitHub; +import org.eclipse.mylyn.internal.github.core.GitHubException; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITaskMapping; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; +import org.eclipse.mylyn.tasks.core.RepositoryResponse.ResponseKind; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMetaData; +import org.eclipse.mylyn.tasks.core.data.TaskCommentMapper; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskOperation; + +/** + * GitHub issue task data handler + */ +public class IssueTaskDataHandler extends AbstractTaskDataHandler { + + private static final String DATA_VERSION = "1"; //$NON-NLS-1$ + private static final String MILESTONE_NONE_KEY = "0"; //$NON-NLS-1$ + private IssueAttributeMapper taskAttributeMapper = null; + private final IssueConnector connector; + + /** + * Create GitHub issue task data handler for connector + * + * @param connector + */ + public IssueTaskDataHandler(IssueConnector connector) { + this.connector = connector; + } + + @Override + public TaskAttributeMapper getAttributeMapper(TaskRepository taskRepository) { + if (this.taskAttributeMapper == null) + this.taskAttributeMapper = new IssueAttributeMapper( + taskRepository); + return this.taskAttributeMapper; + } + + public TaskData createTaskData(TaskRepository repository, + IProgressMonitor monitor, String user, String project, Issue issue) { + + String key = Integer.toString(issue.getNumber()); + TaskData data = new TaskData(getAttributeMapper(repository), + IssueConnector.KIND, repository.getRepositoryUrl(), + key); + data.setVersion(DATA_VERSION); + + createOperations(data, issue); + + createAttribute(data, IssueAttribute.KEY, key); + createAttribute(data, IssueAttribute.TITLE, issue.getTitle()); + createAttribute(data, IssueAttribute.BODY, issue.getBody()); + createAttribute(data, IssueAttribute.STATUS, issue.getState()); + createAttribute(data, IssueAttribute.CREATION_DATE, + issue.getCreatedAt()); + createAttribute(data, IssueAttribute.MODIFICATION_DATE, + issue.getUpdatedAt()); + createAttribute(data, IssueAttribute.CLOSED_DATE, + issue.getClosedAt()); + + User reporter = issue.getUser(); + createAttribute(data, IssueAttribute.REPORTER, reporter, + repository); + String reporterGravatar = reporter != null ? reporter.getGravatarUrl() + : null; + createAttribute(data, IssueAttribute.REPORTER_GRAVATAR, + reporterGravatar); + + User assignee = issue.getAssignee(); + createAttribute(data, IssueAttribute.ASSIGNEE, assignee, + repository); + String assigneeGravatar = assignee != null ? assignee.getGravatarUrl() + : null; + createAttribute(data, IssueAttribute.ASSIGNEE_GRAVATAR, + assigneeGravatar); + + createAttribute(data, IssueAttribute.COMMENT_NEW); + + createLabels(repository, data, issue); + + createMilestone(repository, data, issue); + + return data; + } + + private void createMilestone(TaskRepository repository, TaskData data, + Issue issue) { + Milestone current = issue.getMilestone(); + String number = current != null ? Integer.toString(current.getNumber()) + : MILESTONE_NONE_KEY; + TaskAttribute milestoneAttribute = createAttribute(data, + IssueAttribute.MILESTONE, number); + + if (!this.connector.hasCachedMilestones(repository)) + try { + this.connector.refreshMilestones(repository); + } catch (CoreException ignore) { + // Ignored + } + + List<Milestone> cachedMilestones = this.connector + .getMilestones(repository); + milestoneAttribute.putOption(MILESTONE_NONE_KEY, + Messages.IssueAttribute_MilestoneNone); + for (Milestone milestone : cachedMilestones) + milestoneAttribute.putOption( + Integer.toString(milestone.getNumber()), + milestone.getTitle()); + } + + private void createLabels(TaskRepository repository, TaskData data, + Issue issue) { + TaskAttribute labels = createAttribute(data, + IssueAttribute.LABELS, issue.getLabels()); + + if (!this.connector.hasCachedLabels(repository)) + try { + this.connector.refreshLabels(repository); + } catch (CoreException ignore) { + // Ignored + } + + List<Label> cachedLabels = this.connector.getLabels(repository); + for (Label label : cachedLabels) + labels.putOption(label.getName(), label.getName()); + } + + private void createOperations(TaskData data, Issue issue) { + TaskAttribute operationAttribute = data.getRoot().createAttribute( + TaskAttribute.OPERATION); + operationAttribute.getMetaData().setType(TaskAttribute.TYPE_OPERATION); + + if (!data.isNew()) { + String state = issue.getState(); + if (state != null) { + addOperation(data, issue, IssueOperation.LEAVE, true); + if (state.equals(IssueService.STATE_OPEN)) + addOperation(data, issue, IssueOperation.CLOSE, false); + else if (state.equals(IssueService.STATE_CLOSED)) + addOperation(data, issue, IssueOperation.REOPEN, false); + } + } + } + + private void addOperation(TaskData data, Issue issue, + IssueOperation operation, boolean asDefault) { + TaskAttribute attribute = data.getRoot().createAttribute( + TaskAttribute.PREFIX_OPERATION + operation.getId()); + String label = createOperationLabel(issue, operation); + TaskOperation.applyTo(attribute, operation.getId(), label); + + if (asDefault) { + TaskAttribute operationAttribute = data.getRoot().getAttribute( + TaskAttribute.OPERATION); + TaskOperation.applyTo(operationAttribute, operation.getId(), label); + } + } + + private String createOperationLabel(Issue issue, + IssueOperation operation) { + return operation == IssueOperation.LEAVE ? operation.getLabel() + + issue.getState() : operation.getLabel(); + } + + public TaskData createTaskData(TaskRepository repository, + IProgressMonitor monitor, String user, String project, Issue issue, + List<Comment> comments) { + TaskData taskData = createTaskData(repository, monitor, user, project, + issue); + taskData.setPartial(false); + + if (comments != null && !comments.isEmpty()) { + int count = 1; + TaskAttribute root = taskData.getRoot(); + for (Comment comment : comments) { + TaskCommentMapper commentMapper = new TaskCommentMapper(); + User author = comment.getUser(); + IRepositoryPerson authorPerson = repository.createPerson(author + .getLogin()); + authorPerson.setName(author.getName()); + commentMapper.setAuthor(authorPerson); + commentMapper.setCreationDate(comment.getCreatedAt()); + commentMapper.setText(comment.getBody()); + commentMapper.setCommentId(comment.getUrl()); + commentMapper.setNumber(count); + + TaskAttribute attribute = root + .createAttribute(TaskAttribute.PREFIX_COMMENT + count); + commentMapper.applyTo(attribute); + count++; + } + } + + return taskData; + } + + private Issue createIssue(TaskData taskData) { + Issue issue = new Issue(); + if (!taskData.isNew()) { + issue.setNumber(Integer.parseInt(taskData.getTaskId())); + } + issue.setBody(getAttributeValue(taskData, IssueAttribute.BODY)); + issue.setTitle(getAttributeValue(taskData, IssueAttribute.TITLE)); + + String assigneeValue = getAttributeValue(taskData, + IssueAttribute.ASSIGNEE); + if (assigneeValue != null) { + if (assigneeValue.trim().length() == 0) + assigneeValue = null; + User assignee = new User().setName(assigneeValue); + issue.setAssignee(assignee); + } + + String milestoneValue = getAttributeValue(taskData, + IssueAttribute.MILESTONE); + if (milestoneValue != null) { + Milestone milestone = new Milestone(); + if (milestoneValue.length() > 0) + milestone.setNumber(Integer.parseInt(milestoneValue)); + issue.setMilestone(milestone); + } + return issue; + } + + private String getAttributeValue(TaskData taskData, + IssueAttribute attr) { + TaskAttribute attribute = taskData.getRoot().getAttribute(attr.getId()); + return attribute == null ? null : attribute.getValue(); + } + + private TaskAttribute createAttribute(TaskData data, + IssueAttribute attribute) { + TaskAttribute attr = data.getRoot().createAttribute(attribute.getId()); + TaskAttributeMetaData metaData = attr.getMetaData(); + metaData.defaults().setType(attribute.getType()) + .setKind(attribute.getKind()).setLabel(attribute.getLabel()) + .setReadOnly(attribute.isReadOnly()); + return attr; + } + + private TaskAttribute createAttribute(TaskData data, + IssueAttribute attribute, String value) { + TaskAttribute attr = createAttribute(data, attribute); + if (value != null) { + data.getAttributeMapper().setValue(attr, value); + } + return attr; + } + + private TaskAttribute createAttribute(TaskData data, + IssueAttribute attribute, Date value) { + TaskAttribute attr = createAttribute(data, attribute); + if (value != null) { + data.getAttributeMapper().setDateValue(attr, value); + } + return attr; + } + + private void createAttribute(TaskData data, IssueAttribute attribute, + User value, TaskRepository repository) { + TaskAttribute attr = createAttribute(data, attribute); + if (value != null) { + IRepositoryPerson person = repository + .createPerson(value.getLogin()); + person.setName(value.getName()); + data.getAttributeMapper().setRepositoryPerson(attr, person); + } + } + + private TaskAttribute createAttribute(TaskData data, + IssueAttribute attribute, List<Label> values) { + TaskAttribute attr = createAttribute(data, attribute); + if (values != null) { + List<String> labels = new LinkedList<String>(); + for (Label label : values) { + labels.add(label.getName()); + } + data.getAttributeMapper().setValues(attr, labels); + } + return attr; + } + + @Override + public boolean initializeTaskData(TaskRepository repository, TaskData data, + ITaskMapping initializationData, IProgressMonitor monitor) + throws CoreException { + + data.setVersion(DATA_VERSION); + + for (IssueAttribute attr : IssueAttribute.values()) { + if (attr.isInitTask()) { + createAttribute(data, attr, (String) null); + } + } + + return true; + } + + /** + * Update labels for issue + * + * @param user + * @param repo + * @param client + * @param repository + * @param data + * @param oldAttributes + * @throws IOException + */ + protected void updateLabels(String user, String repo, GitHubClient client, + TaskRepository repository, TaskData data, + Set<TaskAttribute> oldAttributes) throws IOException { + // Update labels if changed + TaskAttribute labelsAttribute = data.getRoot().getAttribute( + IssueAttribute.LABELS.getId()); + if (oldAttributes.contains(labelsAttribute)) { + LabelService labelService = new LabelService(client); + + if (!this.connector.hasCachedLabels(repository)) + try { + this.connector.refreshLabels(repository); + } catch (CoreException ignore) { + // Ignore + } + List<Label> currentLabels = this.connector.getLabels(repository); + List<Label> newLabels = new LinkedList<Label>(); + List<Label> labels = new LinkedList<Label>(); + for (String value : labelsAttribute.getValues()) { + Label label = new Label().setName(value); + if (!currentLabels.contains(label)) + newLabels.add(label); + labels.add(label); + } + for (Label label : newLabels) + try { + labelService.createLabel(user, repo, label); + } catch (IOException e) { + // TODO detect failure and handle label already created + } + + labelService.setLabels(user, repo, data.getTaskId(), labels); + + if (!newLabels.isEmpty()) + try { + this.connector.refreshLabels(repository); + } catch (CoreException ignore) { + // Ignore + } + + } + } + + @Override + public RepositoryResponse postTaskData(TaskRepository repository, + TaskData taskData, Set<TaskAttribute> oldAttributes, + IProgressMonitor monitor) throws CoreException { + String taskId = taskData.getTaskId(); + Issue issue = createIssue(taskData); + Repository repo = GitHub.getRepository(repository.getRepositoryUrl()); + try { + GitHubClient client = IssueConnector + .createClient(repository); + IssueService service = new IssueService(client); + if (taskData.isNew()) { + issue.setState(IssueService.STATE_OPEN); + issue = service.createIssue(repo.getOwner(), repo.getName(), + issue); + taskId = Integer.toString(issue.getNumber()); + } else { + + // Handle new comment + String comment = getAttributeValue(taskData, + IssueAttribute.COMMENT_NEW); + if (comment != null && comment.length() > 0) + service.createComment(repo.getOwner(), repo.getName(), + taskId, comment); + + updateLabels(repo.getOwner(), repo.getName(), client, + repository, taskData, oldAttributes); + + // Handle state change + TaskAttribute operationAttribute = taskData.getRoot() + .getAttribute(TaskAttribute.OPERATION); + if (operationAttribute != null) { + IssueOperation operation = IssueOperation + .fromId(operationAttribute.getValue()); + if (operation != IssueOperation.LEAVE) + switch (operation) { + case REOPEN: + issue.setState(IssueService.STATE_OPEN); + break; + case CLOSE: + issue.setState(IssueService.STATE_CLOSED); + break; + default: + break; + } + } + + service.editIssue(repo.getOwner(), repo.getName(), issue); + } + return new RepositoryResponse( + taskData.isNew() ? ResponseKind.TASK_CREATED + : ResponseKind.TASK_UPDATED, taskId); + } catch (RequestException e) { + throw new CoreException( + GitHub.createErrorStatus(new GitHubException(e))); + } catch (IOException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/Messages.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/Messages.java new file mode 100644 index 00000000..6b97a831 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/Messages.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2011 GitHub Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kevin Sawicki (GitHub Inc.) - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.github.core.issue; + +import org.eclipse.osgi.util.NLS; + +/** + * NLS for Mylyn GitHub Core + */ +public class Messages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.github.core.issue.messages"; //$NON-NLS-1$ + + /** */ + public static String IssueConnector_LabelConnector; + + /** */ + public static String IssueConector_TaskQuerying; + + /** */ + public static String IssueConnector_TaskUpdatingLabels; + + /** */ + public static String IssueConnector_TaskUpdatingMilestones; + + /** */ + public static String IssueAttribute_LabekSummary; + + /** */ + public static String IssueAttribute_LabelAssignee; + + /** */ + public static String IssueAttribute_LabelAssigneeGravatar; + + /** */ + public static String IssueAttribute_LabelClosed; + + /** */ + public static String IssueAttribute_LabelComment; + + /** */ + public static String IssueAttribute_LabelCreated; + + /** */ + public static String IssueAttribute_LabelDescription; + + /** */ + public static String IssueAttribute_LabelKey; + + /** */ + public static String IssueAttribute_LabelLabels; + + /** */ + public static String IssueAttribute_LabelMilestone; + + /** */ + public static String IssueAttribute_LabelModified; + + /** */ + public static String IssueAttribute_LabelReporter; + + /** */ + public static String IssueAttribute_LabelReporterGravatar; + + /** */ + public static String IssueAttribute_LabelStatus; + + /** */ + public static String IssueAttribute_MilestoneNone; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/messages.properties b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/messages.properties new file mode 100644 index 00000000..b64e1b8a --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/issue/messages.properties @@ -0,0 +1,19 @@ +IssueConnector_LabelConnector=GitHub Issues +IssueConector_TaskQuerying=Querying repository... +IssueConnector_TaskUpdatingLabels=Updating labels +IssueConnector_TaskUpdatingMilestones=Updating milestones +IssueAttribute_LabekSummary=Summary +IssueAttribute_LabelAssignee=Assignee: +IssueAttribute_LabelAssigneeGravatar=Assignee +IssueAttribute_LabelClosed=Closed: +IssueAttribute_LabelComment=Comment: +IssueAttribute_LabelCreated=Created: +IssueAttribute_LabelDescription=Description +IssueAttribute_LabelKey=Key +IssueAttribute_LabelLabels=Labels: +IssueAttribute_LabelMilestone=Milestone: +IssueAttribute_LabelModified=Modified: +IssueAttribute_LabelReporter=Reporter: +IssueAttribute_LabelReporterGravatar=Reporter +IssueAttribute_LabelStatus=Status: +IssueAttribute_MilestoneNone=None diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/messages.properties b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/messages.properties new file mode 100644 index 00000000..52d8a86a --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/internal/github/core/messages.properties @@ -0,0 +1,4 @@ +FieldError_InvalidField=Invalid value for field {1} +FieldError_InvalidFieldWithValue=Invalid value of {0} for field {1} +FieldError_MissingField=Missing required field {0} +FieldError_ResourceError=Error with field {0} in {1} resource |