/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2014 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2014 Sun Microsystems, Inc. */ package com.junichi11.netbeans.modules.github.issues.issue; import com.junichi11.netbeans.modules.github.issues.GitHubIssueState; import com.junichi11.netbeans.modules.github.issues.GitHubIssues; import com.junichi11.netbeans.modules.github.issues.GitHubIssuesConfig; import com.junichi11.netbeans.modules.github.issues.repository.GitHubRepository; import com.junichi11.netbeans.modules.github.issues.utils.DateUtils; import com.junichi11.netbeans.modules.github.issues.utils.UiUtils; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JTable; import org.eclipse.egit.github.core.Comment; import org.eclipse.egit.github.core.Issue; import org.eclipse.egit.github.core.Milestone; import org.eclipse.egit.github.core.PullRequest; import org.eclipse.egit.github.core.User; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.modules.bugtracking.commons.UIUtils; import org.netbeans.modules.bugtracking.issuetable.ColumnDescriptor; import org.netbeans.modules.bugtracking.issuetable.IssueNode; import org.netbeans.modules.bugtracking.spi.IssueController; import org.netbeans.modules.bugtracking.spi.IssueProvider; import org.netbeans.modules.bugtracking.spi.IssueScheduleInfo; import org.netbeans.modules.bugtracking.spi.IssueScheduleProvider; import org.netbeans.modules.bugtracking.spi.IssueStatusProvider; import org.netbeans.modules.bugtracking.spi.IssueStatusProvider.Status; import org.openide.util.NbBundle; import org.pegdown.PegDownProcessor; /** * * @author junichi11 */ public final class GitHubIssue { private final GitHubRepository repository; private Issue issue; private IssueNode node; private GitHubIssueController controller; private IssueScheduleInfo scheduleInfo; private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public static final String LABEL_NAME_ID = "github.issue.id"; // NOI18N public static final String LABEL_NAME_STATUS = "github.issue.status"; // NOI18N public static final String LABEL_NAME_CREATED = "github.issue.created"; // NOI18N public static final String LABEL_NAME_UPDATED = "github.issue.updated"; // NOI18N public static final String LABEL_NAME_CREATED_BY = "github.issue.created.by"; // NOI18N public static final String LABEL_NAME_ASSIGNEE = "github.issue.assignee"; // NOI18N private static final Logger LOGGER = Logger.getLogger(GitHubIssue.class.getName()); public GitHubIssue(GitHubRepository repository) { this(repository, null); } public GitHubIssue(GitHubRepository repository, Issue issue) { this.repository = repository; this.issue = issue; } public GitHubRepository getRepository() { return repository; } public void setIssue(Issue issue) { this.issue = issue; } public Issue getIssue() { return issue; } @NbBundle.Messages({ "GitHubIssue.new.issue.displayName=New Issue" }) public String getDisplayName() { if (isNew()) { return Bundle.GitHubIssue_new_issue_displayName(); } return String.format("%s - %s", issue.getNumber(), issue.getTitle()); // NOI18N } @NbBundle.Messages({ "GitHubIssue.LBL.assignee=Assignee", "GitHubIssue.LBL.created=Created", "GitHubIssue.LBL.createdBy=Created by", "GitHubIssue.LBL.dueDate=Due date", "GitHubIssue.LBL.milestone=Milestone" }) public String getTooltip() { // XXX improve StringBuilder sb = new StringBuilder(); String title = String.format("%s [%s]", getDisplayName(), repository.getFullName()); // NOI18N sb.append("<html>"); // NOI18N sb.append("<b>").append(title).append("</b>"); // NOI18N sb.append("<hr>"); // NOI18N Date created = getCreated(); Date dueDate = getDueDate(); User assignee = getAssignee(); User createdUser = getCreatedUser(); Milestone milestone = getMilestone(); if (created != null) { sb.append(Bundle.GitHubIssue_LBL_created()).append(" : ") // NOI18N .append(DateUtils.DEFAULT_DATE_FORMAT.format(created)).append("<br>"); // NOI18N } if (dueDate != null) { sb.append(Bundle.GitHubIssue_LBL_dueDate()).append(" : ") // NOI18N .append(DateUtils.DEFAULT_DATE_FORMAT.format(dueDate)).append("<br>"); // NOI18N } if (createdUser != null) { sb.append(Bundle.GitHubIssue_LBL_createdBy()).append(" : ") // NOI18N .append(createdUser.getLogin()).append("<br>"); // NOI18N } if (assignee != null) { sb.append(Bundle.GitHubIssue_LBL_assignee()).append(" : ") // NOI18N .append(assignee.getLogin()).append("<br>"); // NOI18N } if (milestone != null) { sb.append(Bundle.GitHubIssue_LBL_milestone()).append(" : ") // NOI18N .append(milestone.getTitle()).append("<br>"); // NOI18N } sb.append("</html>"); // NOI18N return sb.toString(); } public Status getIssueStatus() { return GitHubIssuesConfig.getInstance().getStatus(this); } public void setIssueStatus(Status status) { GitHubIssuesConfig.getInstance().setStatus(this, status); fireStatusChange(); } public boolean isNew() { return issue == null; } public String getID() { if (issue != null) { return String.valueOf(issue.getNumber()); } return null; } public Collection<String> getSubtasks() { return Collections.emptyList(); } public String getSummary() { if (issue != null) { return issue.getTitle(); } return null; } public String getStatus() { if (issue != null) { return issue.getState(); } return null; } public User getAssignee() { if (issue != null) { return issue.getAssignee(); } return null; } public User getCreatedUser() { if (issue != null) { return issue.getUser(); } return null; } public Date getUpdated() { if (issue != null) { return issue.getUpdatedAt(); } return null; } public Date getCreated() { if (issue != null) { return issue.getCreatedAt(); } return null; } public Date getClosed() { if (issue != null) { return issue.getClosedAt(); } return null; } public Milestone getMilestone() { if (issue != null) { return issue.getMilestone(); } return null; } public boolean isFinished() { if (issue == null) { return false; } return "closed".equals(issue.getState()); // NOI18N } public boolean refresh() { return true; } public void refreshIssue() { getRepository().refresh(this); fireStatusChange(); } public void addComment(String comment, boolean resolveAsFixed) { if (resolveAsFixed) { // close an issue Issue i = getIssue(); if (i != null) { GitHubIssueState state = GitHubIssueState.toEnum(i.getState()); if (state != GitHubIssueState.OPEN) { LOGGER.log(Level.INFO, "This issue({0} #{1}) state is already closed.", new Object[]{i.getTitle(), i.getNumber()}); // NOI18N return; } GitHubIssueSupport.toggleState(this); } } } public void attachFile(File file, String string, boolean bln) { // TODO } public GitHubIssueController getController() { if (controller == null) { controller = new GitHubIssueController(this); } return controller; } public Issue submitNewIssue(CreateIssueParams params) { Issue newIssue = repository.submitNewIssue(params); setNewIssue(newIssue); return newIssue; } /** * Set a new issue. Add GitHubIssue to the issue cache. * * @param newIssue a new Issue */ private void setNewIssue(Issue newIssue) { if (newIssue != null) { setIssue(newIssue); // add to cache repository.addIssue(this); scheduleInfo = createScheduleInfo(); fireChange(); fireDataChange(); fireScheduleChange(); setIssueStatus(Status.SEEN); } } public Issue editIssue(CreateIssueParams params) { Issue editIssue = repository.editIssue(this, params); if (editIssue != null) { setIssue(editIssue); scheduleInfo = createScheduleInfo(); fireChange(); fireDataChange(); fireScheduleChange(); } else { repository.refresh(this); } return editIssue; } public Comment editComment(Comment comment, String editedBody) { if (editedBody != null) { String originalBody = comment.getBody(); if (issue != null) { comment.setBody(editedBody); Comment editComment = GitHubIssueSupport.editComment(getRepository(), comment); if (editComment != null) { PegDownProcessor processor = GitHubIssues.getInstance().getPegDownProcessor(); String body = editComment.getBody(); String bodyHtml = processor.markdownToHtml(body); comment.setBodyHtml(String.format("<html>%s</html>", bodyHtml)); // NOI18N comment.setBody(body); comment.setUpdatedAt(editComment.getUpdatedAt()); } else { comment.setBody(originalBody); } return editComment; } } return null; } /** * Create a pull request from an existing issue. * * @param head head username:branch * @param base base branch name * @return PullRequest if it was created successfully, otherwise * {@code null} * @throws IOException */ @CheckForNull public PullRequest createPullRequest(String head, String base) throws IOException { GitHubRepository repo = getRepository(); PullRequest pullRequest = repo.createPullRequest(getIssue().getNumber(), head, base); return pullRequest; } /** * Create a new pull request. Title, body, base and head can be set. * * @param pullRequest PullRequest * @return PullRequest if new pull request has been created, otherwise * {@code null} * @throws IOException */ @CheckForNull public PullRequest createPullRequest(PullRequest pullRequest) throws IOException { GitHubRepository repo = getRepository(); PullRequest newPullRequest = repo.createPullRequest(pullRequest); if (newPullRequest != null) { Issue newIssue = repo.getIssue(newPullRequest.getNumber()); setNewIssue(newIssue); } return newPullRequest; } public boolean isCreatedUser() { if (issue == null) { return false; } User user = issue.getUser(); return user.getLogin().equals(repository.getUserName()); } public List<Comment> getComments() { if (isNew()) { return Collections.emptyList(); } return repository.getComments(issue.getNumber()); } // schedule @NbBundle.Messages({ "GitHubIssue.MSG.setSchedule=Set a due date to your milestone" }) public void setSchedule(IssueScheduleInfo scheduleInfo) { UiUtils.showPlainDialog(Bundle.GitHubIssue_MSG_setSchedule()); } public Date getDueDate() { String status = getStatus(); if (status == null || GitHubIssueState.toEnum(status) == GitHubIssueState.CLOSED) { return null; } Milestone milestone = getMilestone(); if (milestone != null) { return milestone.getDueOn(); } return null; } public IssueScheduleInfo getSchedule() { String status = getStatus(); if (status == null || GitHubIssueState.toEnum(status) == GitHubIssueState.CLOSED) { return null; } if (scheduleInfo == null) { scheduleInfo = createScheduleInfo(); } return scheduleInfo; } private IssueScheduleInfo createScheduleInfo() { Milestone milestone = getMilestone(); if (milestone != null) { Date dueDate = milestone.getDueOn(); if (dueDate != null) { return new IssueScheduleInfo(dueDate, 1); } } return null; } public long getLastUpdatedTime() { Date updated = this.getUpdated(); if (updated != null) { long time = updated.getTime(); return time; } return -1L; } @NbBundle.Messages({ "GitHubIssue.column.descriptor.issueType.displayName=Issue Type", "GitHubIssue.column.descriptor.summary.displayName=Summary", "GitHubIssue.column.descriptor.priority.displayName=Priority", "GitHubIssue.column.descriptor.created.displayName=Created", "GitHubIssue.column.descriptor.dueDate.displayName=Due Date", "GitHubIssue.column.descriptor.updated.displayName=Updated", "GitHubIssue.column.descriptor.createdBy.displayName=Created by", "GitHubIssue.column.descriptor.assignee.displayName=Assignee", "GitHubIssue.column.descriptor.status.displayName=Status", "GitHubIssue.column.descriptor.attachment.displayName=Attachment", "GitHubIssue.column.descriptor.sharedFile.displayName=Shared File" }) public static ColumnDescriptor<String>[] getColumnDescriptors() { List<ColumnDescriptor<String>> descriptors = new LinkedList<>(); JTable table = new JTable(); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_ID, String.class, "ID", "ID", UIUtils.getColumnWidthInPixels(6, table))); descriptors.add(new ColumnDescriptor<>(IssueNode.LABEL_NAME_SUMMARY, String.class, Bundle.GitHubIssue_column_descriptor_summary_displayName(), Bundle.GitHubIssue_column_descriptor_summary_displayName())); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_CREATED, String.class, Bundle.GitHubIssue_column_descriptor_created_displayName(), Bundle.GitHubIssue_column_descriptor_created_displayName())); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_UPDATED, String.class, Bundle.GitHubIssue_column_descriptor_updated_displayName(), Bundle.GitHubIssue_column_descriptor_updated_displayName())); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_CREATED_BY, String.class, Bundle.GitHubIssue_column_descriptor_createdBy_displayName(), Bundle.GitHubIssue_column_descriptor_createdBy_displayName())); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_ASSIGNEE, String.class, Bundle.GitHubIssue_column_descriptor_assignee_displayName(), Bundle.GitHubIssue_column_descriptor_assignee_displayName())); descriptors.add(new ColumnDescriptor<>(LABEL_NAME_STATUS, String.class, Bundle.GitHubIssue_column_descriptor_status_displayName(), Bundle.GitHubIssue_column_descriptor_status_displayName())); return descriptors.toArray(new ColumnDescriptor[descriptors.size()]); } /** * Get issue node. Use an issue node to add to an issue table. * * @return issue node */ public IssueNode getIssueNode() { if (node == null) { node = createIssueNode(); } return node; } /** * Get recent changes. * * @return changes */ public String getRecentChanges() { return ""; // NOI18N } private IssueNode createIssueNode() { return new GitHubIssueNode(this); } public boolean isEditableUser() { return isCreatedUser() || repository.isCollaborator(); } void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } void fireChange() { propertyChangeSupport.firePropertyChange(IssueController.PROP_CHANGED, null, null); } void fireDataChange() { propertyChangeSupport.firePropertyChange(IssueProvider.EVENT_ISSUE_DATA_CHANGED, null, null); } void fireStatusChange() { propertyChangeSupport.firePropertyChange(IssueStatusProvider.EVENT_STATUS_CHANGED, null, null); } void fireScheduleChange() { propertyChangeSupport.firePropertyChange(IssueScheduleProvider.EVENT_ISSUE_SCHEDULE_CHANGED, null, null); } }