IDEA-68253 Issue trackers integration. Basic implementation.

This commit is contained in:
Evgeny Zakrevsky
2012-10-09 01:28:22 +04:00
parent b173b7bc42
commit 2dcb0d6ae0
9 changed files with 408 additions and 30 deletions
@@ -19,6 +19,7 @@
*/
package com.intellij.util.ui;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -52,49 +53,46 @@ public class FormBuilder {
return new FormBuilder();
}
public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component) {
public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component) {
return addLabeledComponent(label, component, myVerticalGap, false);
}
public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, final int topInset) {
public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, final int topInset) {
return addLabeledComponent(label, component, topInset, false);
}
public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, boolean labelOnTop) {
public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, boolean labelOnTop) {
return addLabeledComponent(label, component, myVerticalGap, labelOnTop);
}
public FormBuilder addLabeledComponent(String labelText, JComponent component) {
public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component) {
return addLabeledComponent(labelText, component, myVerticalGap, false);
}
public FormBuilder addLabeledComponent(String labelText, JComponent component, final int topInset) {
public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, final int topInset) {
return addLabeledComponent(labelText, component, topInset, false);
}
public FormBuilder addLabeledComponent(String labelText, JComponent component, boolean labelOnTop) {
public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, boolean labelOnTop) {
return addLabeledComponent(labelText, component, myVerticalGap, labelOnTop);
}
public FormBuilder addLabeledComponent(String labelText, JComponent component, final int topInset, boolean labelOnTop) {
JLabel label = null;
if (labelText != null) {
label = new JLabel(UIUtil.removeMnemonic(labelText));
final int index = UIUtil.getDisplayMnemonicIndex(labelText);
if (index != -1) {
label.setDisplayedMnemonic(labelText.charAt(index + 1));
}
label.setLabelFor(component);
public FormBuilder addLabeledComponent(@NotNull String labelText, @NotNull JComponent component, final int topInset, boolean labelOnTop) {
JLabel label = new JLabel(UIUtil.removeMnemonic(labelText));
final int index = UIUtil.getDisplayMnemonicIndex(labelText);
if (index != -1) {
label.setDisplayedMnemonic(labelText.charAt(index + 1));
}
label.setLabelFor(component);
return addLabeledComponent(label, component, topInset, labelOnTop);
}
public FormBuilder addComponent(JComponent component) {
public FormBuilder addComponent(@NotNull JComponent component) {
return addLabeledComponent((JLabel)null, component, myVerticalGap, false);
}
public FormBuilder addComponent(JComponent component, final int topInset) {
public FormBuilder addComponent(@NotNull JComponent component, final int topInset) {
return addLabeledComponent((JLabel)null, component, topInset, false);
}
@@ -111,7 +109,7 @@ public class FormBuilder {
new Box.Filler(new Dimension(0, height), new Dimension(0, height), new Dimension(Short.MAX_VALUE, height)));
}
public FormBuilder addLabeledComponent(@Nullable JComponent label, JComponent component, int topInset, boolean labelOnTop) {
public FormBuilder addLabeledComponent(@Nullable JComponent label, @NotNull JComponent component, int topInset, boolean labelOnTop) {
GridBagConstraints c = new GridBagConstraints();
topInset = myLineCount > 0 ? topInset : 0;
@@ -388,9 +388,9 @@ public class UIUtil {
int defSize = getLabelFont().getSize();
switch (size) {
case SMALL:
return Math.max(defSize - 2f, 11f);
return Math.max(defSize - 2f, 10f);
case MINI:
return Math.max(defSize - 4f, 9f);
return Math.max(defSize - 4f, 8f);
default:
return defSize;
}
@@ -146,11 +146,13 @@
<properties/>
<border type="none"/>
<children>
<component id="ae3b0" class="javax.swing.JLabel" binding="myComment">
<component id="bb533" class="com.intellij.ui.components.JBLabel" binding="myComment">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<componentStyle value="SMALL"/>
<fontColor value="BRIGHTER"/>
<text value="Use..."/>
</properties>
</component>
@@ -62,7 +62,7 @@ public class BaseRepositoryEditor<T extends BaseRepository> extends TaskReposito
private JPanel myCustomPanel;
private JBCheckBox myAddCommitMessage;
private JLabel myComment;
private JBLabel myComment;
private JPanel myEditorPanel;
private boolean myApplying;
@@ -116,6 +116,7 @@
<tasks.repositoryType implementation="com.intellij.tasks.trac.TracRepositoryType"/>
<tasks.repositoryType implementation="com.intellij.tasks.fogbugz.FogBugzRepositoryType"/>
<tasks.repositoryType implementation="com.intellij.tasks.mantis.MantisRepositoryType"/>
<tasks.repositoryType implementation="com.intellij.tasks.connector.WebRepositoryType"/>
<checkinHandlerFactory implementation="com.intellij.tasks.impl.TaskCheckinHandlerFactory"/>
</extensions>
@@ -0,0 +1,252 @@
package com.intellij.tasks.connector;
import com.intellij.openapi.util.Comparing;
import com.intellij.tasks.Comment;
import com.intellij.tasks.Task;
import com.intellij.tasks.TaskRepositoryType;
import com.intellij.tasks.TaskType;
import com.intellij.tasks.impl.BaseRepository;
import com.intellij.tasks.impl.BaseRepositoryImpl;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.annotations.Tag;
import icons.TasksIcons;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* User: Evgeny.Zakrevsky
* Date: 10/4/12
*/
@Tag("Web")
public class WebRepository extends BaseRepositoryImpl {
private String myTasksListURL;
private String myTaskPattern;
private String myLoginURL;
final static String SERVER_URL_PLACEHOLDER = "{serverUrl}";
final static String USERNAME_PLACEHOLDER = "{username}";
final static String PASSWORD_PLACEHOLDER = "{password}";
final static String ID_PLACEHOLDER = "{id}";
final static String SUMMARY_PLACEHOLDER = "{summary}";
final static String DESCRIPTION_PLACEHOLDER = "{description}";
final static String PAGE_PLACEHOLDER = "{page}";
//final Map<String, String> myPlaceholder2Value = new HashMap<String, String>();
@SuppressWarnings({"UnusedDeclaration"})
public WebRepository() {
}
public WebRepository(final TaskRepositoryType type) {
super(type);
}
public WebRepository(final WebRepository other) {
super(other);
myTasksListURL = other.getTasksListURL();
myTaskPattern = other.getTaskPattern();
myLoginURL = other.getLoginURL();
}
@Override
public Task[] getIssues(@Nullable final String query, final int max, final long since) throws Exception {
final HttpClient httpClient = getHttpClient();
login(httpClient);
final GetMethod getMethod = new GetMethod(getFullTasksUrl());
httpClient.executeMethod(getMethod);
final String response = getMethod.getResponseBodyAsString(Integer.MAX_VALUE);
final String taskPatternWithoutPlaceholders = myTaskPattern.replaceAll("\\{.+?\\}", "");
Matcher matcher = Pattern
.compile(taskPatternWithoutPlaceholders,
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNICODE_CASE | Pattern.CANON_EQ)
.matcher(response);
final List<String> placeholders = getPlaceholders(myTaskPattern);
if (matcher.groupCount() != 2 || !placeholders.contains(ID_PLACEHOLDER) || !placeholders.contains(SUMMARY_PLACEHOLDER)) {
throw new Exception("Incorrect Task Pattern");
}
List<Task> tasks = new ArrayList<Task>();
while (matcher.find()) {
final String id = matcher.group(placeholders.indexOf(ID_PLACEHOLDER) + 1);
final String description = matcher.group(placeholders.indexOf(SUMMARY_PLACEHOLDER) + 1);
tasks.add(new Task() {
@NotNull
@Override
public String getId() {
return id;
}
@NotNull
@Override
public String getSummary() {
return description;
}
@Nullable
@Override
public String getDescription() {
return null;
}
@NotNull
@Override
public Comment[] getComments() {
return new Comment[0];
}
@Nullable
@Override
public Icon getIcon() {
return TasksIcons.Other;
}
@NotNull
@Override
public TaskType getType() {
return TaskType.OTHER;
}
@Nullable
@Override
public Date getUpdated() {
return null;
}
@Nullable
@Override
public Date getCreated() {
return null;
}
@Override
public boolean isClosed() {
return false;
}
@Override
public boolean isIssue() {
return true;
}
@Nullable
@Override
public String getIssueUrl() {
return null;
}
});
}
return tasks.toArray(new Task[tasks.size()]);
}
private void login(final HttpClient httpClient) throws IOException {
final GetMethod method = new GetMethod(getFullLoginUrl());
httpClient.executeMethod(method);
}
private static List<String> getPlaceholders(String value) {
if (value == null) {
return ContainerUtil.emptyList();
}
List<String> vars = new ArrayList<String>();
Matcher m = Pattern.compile("\\{(.+?)\\}").matcher(value);
while (m.find()) {
vars.add(m.group(0));
}
return vars;
}
private String getFullTasksUrl() {
return getTasksListURL()
.replaceAll(placeholder2regexp(SERVER_URL_PLACEHOLDER), getUrl());
}
private String getFullLoginUrl() {
return getLoginURL()
.replaceAll(placeholder2regexp(SERVER_URL_PLACEHOLDER), getUrl())
.replaceAll(placeholder2regexp(USERNAME_PLACEHOLDER), getUsername())
.replaceAll(placeholder2regexp(PASSWORD_PLACEHOLDER), getPassword());
}
private static String placeholder2regexp(String placeholder) {
return placeholder.replaceAll("\\{", "\\\\{");
}
@Nullable
@Override
public Task findTask(final String id) throws Exception {
return null;
}
@Override
public BaseRepository clone() {
return new WebRepository(this);
}
public String getTasksListURL() {
return myTasksListURL;
}
public void setTasksListURL(final String tasksListURL) {
myTasksListURL = tasksListURL;
}
public String getTaskPattern() {
return myTaskPattern;
}
public void setTaskPattern(final String taskPattern) {
myTaskPattern = taskPattern;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof WebRepository)) return false;
if (!super.equals(o)) return false;
WebRepository that = (WebRepository)o;
if (!Comparing.equal(getTasksListURL(), that.getTasksListURL())) return false;
if (!Comparing.equal(getTaskPattern(), that.getTaskPattern())) return false;
if (!Comparing.equal(getLoginURL(), that.getLoginURL())) return false;
return true;
}
@Nullable
@Override
public CancellableConnection createCancellableConnection() {
return new CancellableConnection() {
@Override
protected void doTest() throws Exception {
final Task[] issues = getIssues("", 0, 0);
if (issues.length == 0) throw new Exception("Tasks not found. Probably, you don't login.");
}
@Override
public void cancel() {
}
};
}
public String getLoginURL() {
return myLoginURL;
}
public void setLoginURL(final String loginURL) {
myLoginURL = loginURL;
}
}
@@ -0,0 +1,77 @@
package com.intellij.tasks.connector;
import com.intellij.openapi.project.Project;
import com.intellij.tasks.config.BaseRepositoryEditor;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.Consumer;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* User: Evgeny.Zakrevsky
* Date: 10/4/12
*/
public class WebRepositoryEditor extends BaseRepositoryEditor<WebRepository> {
private JBLabel myTasksListURLLabel;
private JBLabel myTaskPatternLabel;
private JTextField myTasksListURLText;
private JTextField myTaskPatternText;
private JBLabel myLoginURLLabel;
private JTextField myLoginURLText;
public WebRepositoryEditor(final Project project, final WebRepository repository, final Consumer<WebRepository> changeListener) {
super(project, repository, changeListener);
myUrlLabel.setText("{serverUrl}:");
myUsernameLabel.setText("{username}:");
myPasswordLabel.setText("{password}:");
//setAnchor(myUsernameLabel);
}
@Override
public void apply() {
myRepository.setTasksListURL(myTasksListURLText.getText());
myRepository.setTaskPattern(myTaskPatternText.getText());
myRepository.setLoginURL(myLoginURLText.getText());
super.apply();
}
@Nullable
@Override
protected JComponent createCustomPanel() {
myTasksListURLLabel = new JBLabel("Tasks List URL:", SwingConstants.RIGHT);
myTasksListURLText = new JTextField(myRepository.getTasksListURL());
installListener(myTasksListURLText);
myTaskPatternLabel = new JBLabel("Task Pattern:", SwingConstants.RIGHT);
myTaskPatternText = new JTextField(myRepository.getTaskPattern());
installListener(myTaskPatternText);
myLoginURLLabel = new JBLabel("Login URL:", SwingConstants.RIGHT);
myLoginURLText = new JTextField(myRepository.getLoginURL());
installListener(myLoginURLText);
return FormBuilder.createFormBuilder().setAlignLabelOnRight(true)
.addVerticalGap(8)
.addLabeledComponent(myTasksListURLLabel, myTasksListURLText)
.addLabeledComponent(new JLabel(), new JBLabel("Available placeholders: " + WebRepository.SERVER_URL_PLACEHOLDER, UIUtil.ComponentStyle.SMALL,
UIUtil.FontColor.BRIGHTER), 1)
.addLabeledComponent(myTaskPatternLabel, myTaskPatternText, 8)
.addLabeledComponent(new JLabel(), new JBLabel("Task pattern should be a regexp with two matching group: ({id}.+?) and ({summary}.+?)",
UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER), 1)
.addLabeledComponent(myLoginURLLabel, myLoginURLText, 8)
.addLabeledComponent(new JLabel(), new JBLabel("Available placeholders: " + WebRepository.SERVER_URL_PLACEHOLDER + ", " +
WebRepository.USERNAME_PLACEHOLDER + ", " + WebRepository.PASSWORD_PLACEHOLDER, UIUtil.ComponentStyle.SMALL,
UIUtil.FontColor.BRIGHTER), 1)
.addVerticalGap(8)
.getPanel();
}
@Override
public void setAnchor(@Nullable final JComponent anchor) {
super.setAnchor(anchor);
myTasksListURLLabel.setAnchor(anchor);
myTaskPatternLabel.setAnchor(anchor);
myLoginURLLabel.setAnchor(anchor);
}
}
@@ -0,0 +1,47 @@
package com.intellij.tasks.connector;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.project.Project;
import com.intellij.tasks.TaskRepository;
import com.intellij.tasks.config.TaskRepositoryEditor;
import com.intellij.tasks.impl.BaseRepositoryType;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* User: Evgeny.Zakrevsky
* Date: 10/4/12
*/
public class WebRepositoryType extends BaseRepositoryType<WebRepository> {
@NotNull
@Override
public String getName() {
return "Web Repository";
}
@Override
public Icon getIcon() {
return AllIcons.General.Web;
}
@NotNull
@Override
public TaskRepository createRepository() {
return new WebRepository(this);
}
@Override
public Class<WebRepository> getRepositoryClass() {
return WebRepository.class;
}
@NotNull
@Override
public TaskRepositoryEditor createEditor(final WebRepository repository,
final Project project,
final Consumer<WebRepository> changeListener) {
return new WebRepositoryEditor(project, repository, changeListener);
}
}
@@ -16,12 +16,19 @@ public class TasksIcons {
public static final Icon Bug = load("/icons/bug.png"); // 16x16
public static final Icon Exception = load("/icons/exception.png"); // 16x16
public static final Icon Feature = load("/icons/feature.png"); // 16x16
public static final Icon Other = load("/icons/other.png"); // 16x16
public static final Icon Unknown = load("/icons/unknown.png"); // 16x16
public static final Icon SavedContext = load("/icons/savedContext.png"); // 16x16
public static final Icon Fogbugz = load("/icons/fogbugz.png"); // 16x16
public static final Icon Github = load("/icons/github.png"); // 16x16
public static final Icon Lighthouse = load("/icons/lighthouse.gif"); // 16x16
public static final Icon Mantis = load("/icons/mantis.png"); // 16x16
public static final Icon Other = load("/icons/other.png"); // 16x16
public static final Icon Pivotal = load("/icons/pivotal.png"); // 16x16
public static final Icon Redmine = load("/icons/redmine.png"); // 16x16
public static final Icon Trac = load("/icons/trac.png"); // 16x16
public static final Icon Youtrack = load("/icons/youtrack.png"); // 16x16
public static class Pivotal {
public static final Icon Bug = load("/icons/pivotal/bug.png"); // 14x14
public static final Icon Chore = load("/icons/pivotal/chore.png"); // 14x14
@@ -29,10 +36,4 @@ public class TasksIcons {
public static final Icon Release = load("/icons/pivotal/release.png"); // 14x14
}
public static final Icon Pivotal = load("/icons/pivotal.png"); // 16x16
public static final Icon Redmine = load("/icons/redmine.png"); // 16x16
public static final Icon SavedContext = load("/icons/savedContext.png"); // 16x16
public static final Icon Trac = load("/icons/trac.png"); // 16x16
public static final Icon Unknown = load("/icons/unknown.png"); // 16x16
public static final Icon Youtrack = load("/icons/youtrack.png"); // 16x16
}