diff --git a/.gitignore b/.gitignore index a1c2a23..726fc30 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +.idea/ +out/ diff --git a/README.md b/README.md index 407a72f..504af82 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# intellij-gitea-plugin \ No newline at end of file +# Gitea issue tracker integration plugin (http://gitea.io) for Jetbrains IDE platform. \ No newline at end of file diff --git a/gitea_plugin.iml b/gitea_plugin.iml new file mode 100644 index 0000000..e025b20 --- /dev/null +++ b/gitea_plugin.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml new file mode 100644 index 0000000..836df08 --- /dev/null +++ b/resources/META-INF/plugin.xml @@ -0,0 +1,28 @@ + + biz.elfuego.idea.issues.gitea + Gitea issues plugin + 1.0 + elfuego.biz + + + + + ]]> + + + + + com.intellij.modules.lang + com.intellij.tasks + + + + + + + + + diff --git a/resources/resources/gitea.png b/resources/resources/gitea.png new file mode 100644 index 0000000..a4080c1 Binary files /dev/null and b/resources/resources/gitea.png differ diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java new file mode 100644 index 0000000..8edfc59 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java @@ -0,0 +1,346 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea; + +import biz.elfuego.idea.issues.gitea.model.GiteaProject; +import biz.elfuego.idea.issues.gitea.model.GiteaTask; +import biz.elfuego.idea.issues.gitea.util.Consts; +import biz.elfuego.idea.issues.gitea.util.Consts.CommentFields; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.tasks.Comment; +import com.intellij.tasks.CustomTaskState; +import com.intellij.tasks.Task; +import com.intellij.tasks.impl.BaseRepositoryImpl; +import com.intellij.tasks.impl.SimpleComment; +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Tag; +import com.intellij.util.xmlb.annotations.Transient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.auth.AuthPolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStreamReader; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static biz.elfuego.idea.issues.gitea.util.Utils.*; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +@Tag("Gitea") +class GiteaRepository extends BaseRepositoryImpl { + private String userId = null; + private List projects = new ArrayList<>(); + private GiteaProject selectedProject = null; + + @SuppressWarnings("UnusedDeclaration") + public GiteaRepository() { + super(); + } + + @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"}) + public GiteaRepository(GiteaRepositoryType type) { + super(type); + setUseHttpAuthentication(true); + setUrl(Consts.Url.DEFAULT); + } + + @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"}) + public GiteaRepository(GiteaRepository other) { + super(other); + userId = other.userId; + projects = other.projects; + selectedProject = other.selectedProject; + } + + @Nullable + @Override + public Task findTask(@NotNull String s) throws Exception { + // TODO + return null; + } + + @Override + public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed, @NotNull ProgressIndicator cancelled) throws Exception { + return getIssues(); + } + + @NotNull + @Override + @SuppressWarnings("CloneDoesntCallSuperClone") + public GiteaRepository clone() { + return new GiteaRepository(this); + } + + @Nullable + @Override + public CancellableConnection createCancellableConnection() { + return new CancellableConnection() { + @Override + protected void doTest() throws Exception { + GiteaRepository.this.doTest(); + } + + @Override + public void cancel() { + //Jetbrains left this method blank in their generic task repo as well. Just let it time out? + } + }; + } + + @Nullable + @Override + public String extractId(@NotNull String taskName) { + Matcher matcher = Pattern.compile("(d+)").matcher(taskName); + return matcher.find() ? matcher.group(1) : null; + } + + @NotNull + @Override + public Set getAvailableTaskStates(@NotNull Task task) { + Set result = new HashSet<>(); + for (Consts.States state : Consts.States.values()) { + String name = state.name().toLowerCase(); + result.add(new CustomTaskState(name, name)); + } + return result; + } + + @Override + public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception { + GiteaTaskImpl giteaTask = null; + if (task instanceof GiteaTaskImpl) { + giteaTask = (GiteaTaskImpl) task; + } else { + Task[] tasks = getIssues(); + for (Task t : tasks) { + if (task.getId().equals(t.getId())) { + giteaTask = (GiteaTaskImpl) t; + } + } + } + + if (giteaTask == null) { + throw new Exception("Task not found"); + } + + giteaTask.task.setState(state.getId()); + JsonObject jsonData = new JsonObject(); + jsonData.addProperty("state", state.getId()); + StringRequestEntity data = new StringRequestEntity( + jsonData.toString(), + "application/json", + "UTF-8" + ); + HttpMethod patchTask = getPatchMethod(getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName() + + Consts.EndPoint.ISSUES + "/" + giteaTask.getId(), data); + executeMethod(patchTask); + } + + @Override + protected int getFeatures() { + return NATIVE_SEARCH | STATE_UPDATING; + } + + private void doTest() throws Exception { + userId = null; + checkSetup(); + JsonElement response = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.ME)); + final JsonObject obj = getObject(response); + if (obj.has("id") && obj.has("login")) + return; + throw new Exception(Consts.ERROR); + } + + @NotNull + private String getApiUrl() { + return getUrl() + Consts.EndPoint.API; + } + + @Override + public boolean isConfigured() { + boolean result = true; + if (!super.isConfigured()) { + result = false; + } + if (result && StringUtil.isEmpty(this.getUrl())) { + result = false; + } + if (result && StringUtil.isEmpty(this.getUsername())) { + result = false; + } + if (result && StringUtil.isEmpty(this.getPassword())) { + result = false; + } + return result; + } + + private void checkSetup() throws Exception { + String result = ""; + int errors = 0; + if (StringUtil.isEmpty(getUrl())) { + result += "Server"; + errors++; + } + if (StringUtil.isEmpty(getUsername())) { + result += !StringUtils.isEmpty(result) ? " & " : ""; + result += "Username"; + errors++; + } + if (StringUtil.isEmpty(getPassword())) { + result += !StringUtils.isEmpty(result) ? " & " : ""; + result += "Password"; + errors++; + } + if (!result.isEmpty()) { + throw new Exception(result + ((errors > 1) ? " are required" : " is required")); + } + } + + private Task[] getIssues() throws Exception { + if (ifNoSelectedProj()) return new Task[]{}; + ensureUserId(); + List result = new ArrayList<>(); + + final String url = getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName() + Consts.EndPoint.ISSUES; + final JsonElement response = executeMethod(new GetMethod(url)); + JsonArray tasks = getArray(response); + for (int i = 0; i < tasks.size(); i++) { + JsonObject current = tasks.get(i).getAsJsonObject(); + GiteaTask raw = new GiteaTask(selectedProject, current); + if (!raw.isValid()) { + continue; + } + GiteaTaskImpl mapped = new GiteaTaskImpl(this, raw); + result.add(mapped); + } + Collections.sort(result); + Task[] primArray = new Task[result.size()]; + return result.toArray(primArray); + } + + private boolean ifNoSelectedProj() { + return selectedProject == null || selectedProject.getId().equals("-1"); + } + + public Comment[] getComments(GiteaTaskImpl task) throws Exception { + if (ifNoSelectedProj()) return new Comment[]{}; + ensureUserId(); + List result = new ArrayList<>(); + + final String url = getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName() + Consts.EndPoint.ISSUES + + "/" + task.getId() + Consts.EndPoint.COMMENTS; + final JsonElement response = executeMethod(new GetMethod(url)); + JsonArray comments = getArray(response); + for (int i = 0; i < comments.size(); i++) { + JsonObject current = comments.get(i).getAsJsonObject(); + Date date = getDate(current, CommentFields.DATE); + String text = getString(current, CommentFields.TEXT, ""); + JsonObject user = getObject(current, CommentFields.USER); + String author = getString(user, CommentFields.FULLNAME, ""); + if (author.isEmpty()) + author = getString(user, CommentFields.USERNAME, ""); + result.add(new SimpleComment(date, author, text)); + } + Comment[] primArray = new Comment[result.size()]; + return result.toArray(primArray); + } + + private JsonElement executeMethod(@NotNull HttpMethod method) throws Exception { + method.addRequestHeader("Content-type", "application/json"); + List authPrefs = Collections.singletonList(AuthPolicy.BASIC); + method.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); + getHttpClient().executeMethod(method); + + if (method.getStatusCode() != HttpStatus.SC_OK && method.getStatusCode() != HttpStatus.SC_CREATED) { + throw new Exception("Request failed with HTTP error: " + method.getStatusText()); + } + + return new JsonParser().parse(new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8")); + } + + private HttpMethod getPatchMethod(String url, StringRequestEntity data) { + PostMethod patchMethod = new PostMethod(url) { + @Override + public String getName() { + return "PATCH"; + } + }; + patchMethod.setRequestEntity(data); + return patchMethod; + } + + private void ensureUserId() throws Exception { + if (userId == null || userId.isEmpty()) { + JsonElement result = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.ME)); + userId = result.getAsJsonObject().get("login").getAsJsonPrimitive().getAsString(); + } + } + + @Transient + List getProjectList() throws Exception { + ensureUserId(); + if (projects == null || projects.isEmpty()) { + JsonElement response = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.REPOS_SEARCH + userId)); + JsonArray query = getOkData(response); + List result = new ArrayList<>(); + for (int i = 0; i < query.size(); i++) { + JsonObject current = getObject(query.get(i)); + GiteaProject project = new GiteaProject().setId(getString(current, "id", "")) + .setName(getString(current, "full_name", "")); + if (!project.isValid()) { + continue; + } + result.add(project); + } + projects = result; + } + return projects; + } + + @SuppressWarnings("WeakerAccess") + public GiteaProject getSelectedProject() { + return selectedProject; + } + + @SuppressWarnings("WeakerAccess") + public void setSelectedProject(GiteaProject selectedProject) { + this.selectedProject = selectedProject; + } + + @SuppressWarnings("UnusedDeclaration") + public String getUserId() { + return userId; + } + + @SuppressWarnings("UnusedDeclaration") + public void setUserId(String userId) { + this.userId = userId; + } + + @SuppressWarnings("UnusedDeclaration") + @AbstractCollection(surroundWithTag = false, elementTag = "GiteaProject", elementTypes = GiteaProject.class) + public List getProjects() { + return projects; + } + + @SuppressWarnings("UnusedDeclaration") + public void setProjects(List projects) { + this.projects = projects; + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java new file mode 100644 index 0000000..dfcbd35 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea; + +import biz.elfuego.idea.issues.gitea.model.GiteaProject; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.tasks.config.BaseRepositoryEditor; +import com.intellij.tasks.impl.TaskUiUtil; +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.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.List; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class GiteaRepositoryEditor extends BaseRepositoryEditor { + private JBLabel projectLabel; + private ComboBox projectBox; + + GiteaRepositoryEditor(GiteaRepository repository, Project project, Consumer consumer) { + super(project, repository, consumer); + + installListener(projectBox); + + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + initialize(); + } + }); + } + + private void initialize() { + if (myRepository.isConfigured()) { + new FetchProjectsTask().queue(); + } + } + + @Nullable + @Override + protected JComponent createCustomPanel() { + projectBox = new ComboBox(300); + projectBox.setRenderer(new TaskUiUtil.SimpleComboBoxRenderer("Set URL, username, and password")); + projectLabel = new JBLabel("Project:", SwingConstants.RIGHT); + projectLabel.setLabelFor(projectBox); + + return new FormBuilder().setAlignLabelOnRight(true) + .addLabeledComponent(projectLabel, projectBox) + .getPanel(); + } + + @Override + public void setAnchor(@Nullable JComponent anchor) { + super.setAnchor(anchor); + projectLabel.setAnchor(anchor); + } + + @Override + protected void afterTestConnection(boolean connectionSuccessful) { + if (connectionSuccessful) { + new FetchProjectsTask().queue(); + } + } + + @Override + public void apply() { + super.apply(); + myRepository.setSelectedProject((GiteaProject) projectBox.getSelectedItem()); + myTestButton.setEnabled(myRepository.isConfigured()); + } + + private class FetchProjectsTask extends TaskUiUtil.ComboBoxUpdater { + private FetchProjectsTask() { + super(GiteaRepositoryEditor.this.myProject, "Downloading Gitea projects...", projectBox); + } + + @Override + public GiteaProject getExtraItem() { + return GiteaProject.UNSPECIFIED_PROJECT; + } + + @Nullable + @Override + public GiteaProject getSelectedItem() { + return myRepository.getSelectedProject(); + } + + @NotNull + @Override + protected List fetch(@NotNull ProgressIndicator indicator) throws Exception { + return myRepository.getProjectList(); + } + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java new file mode 100644 index 0000000..7479661 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java @@ -0,0 +1,49 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +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.*; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class GiteaRepositoryType extends BaseRepositoryType { + @NotNull + @Override + public String getName() { + return "Gitea"; + } + + @NotNull + @Override + public Icon getIcon() { + return IconLoader.getIcon("/resources/gitea.png"); + } + + @NotNull + @Override + public TaskRepositoryEditor createEditor(GiteaRepository repository, Project project, Consumer consumer) { + return new GiteaRepositoryEditor(repository, project, consumer); + } + + @NotNull + @Override + public TaskRepository createRepository() { + return new GiteaRepository(this); + } + + @Override + public Class getRepositoryClass() { + return GiteaRepository.class; + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java b/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java new file mode 100644 index 0000000..5118a19 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea; + +import biz.elfuego.idea.issues.gitea.model.GiteaProject; +import biz.elfuego.idea.issues.gitea.model.GiteaTask; +import biz.elfuego.idea.issues.gitea.util.Consts; +import com.intellij.openapi.util.IconLoader; +import com.intellij.tasks.Comment; +import com.intellij.tasks.Task; +import com.intellij.tasks.TaskRepository; +import com.intellij.tasks.TaskType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Date; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class GiteaTaskImpl extends Task implements Comparable { + private GiteaProject project; + private GiteaRepository repository; + private Comment[] comments; + GiteaTask task; + + GiteaTaskImpl(@NotNull GiteaRepository repository, @NotNull GiteaTask task) { + this.repository = repository; + this.task = task; + this.project = task.getProject(); + } + + @NotNull + @Override + public String getId() { + return task.getId(); + } + + @NotNull + @Override + public String getSummary() { + return task.getTitle(); + } + + @Nullable + @Override + public String getDescription() { + return task.getDescription(); + } + + @NotNull + @Override + public Comment[] getComments() { + if (comments == null) { + try { + comments = repository.getComments(this); + } catch(Exception ignored) {} + if (comments == null) + comments = new Comment[0]; + } + return comments; + } + + @NotNull + @Override + public Icon getIcon() { + return IconLoader.getIcon("/resources/gitea.png"); + } + + @NotNull + @Override + public TaskType getType() { + return TaskType.OTHER; + } + + @Nullable + @Override + public Date getUpdated() { + return task.getUpdatedAt(); + } + + @Nullable + @Override + public Date getCreated() { + return task.getCreatedAt(); + } + + @Override + public boolean isClosed() { + return Consts.States.CLOSED.name().toLowerCase().equals(task.getState()); + } + + @Override + public boolean isIssue() { + return true; + } + + @Nullable + @Override + public TaskRepository getRepository() { + return repository; + } + + @Nullable + @Override + public String getIssueUrl() { + return repository.getUrl() + "/" + project.getName() + Consts.EndPoint.ISSUES + "/" + task.getId(); + } + + @Override + public int compareTo(@NotNull GiteaTaskImpl o) { + int me = Integer.parseInt(this.task.getId()); + int them = Integer.parseInt(o.task.getId()); + + return Integer.compare(me, them); + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java b/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java new file mode 100644 index 0000000..801606d --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java @@ -0,0 +1,71 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea.model; + +import biz.elfuego.idea.issues.gitea.util.Consts; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class GiteaProject { + private String id; + private String name; + + public String getId() { + return id; + } + + public GiteaProject setId(String mProjectId) { + this.id = mProjectId; + return this; + } + + public String getName() { + return name; + } + + public GiteaProject setName(String mProjectTitle) { + this.name = mProjectTitle; + return this; + } + + public boolean isValid() { + return !(id.equals("") || name.equals("")); + } + + @Override + public String toString() { + return name != null ? name : super.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + GiteaProject that = (GiteaProject) o; + + return (id != null ? id.equals(that.id) : that.id == null) && (name != null ? name.equals(that.name) : that.name == null); + } + + public static final GiteaProject UNSPECIFIED_PROJECT = new GiteaProject() { + @Override + public String getName() { + return "-- Select A Project (Required) --"; + } + + @Override + public String getId() { + return Consts.UNSPEC_PROJ_ID; + } + + @Override + public String toString() { + return getName(); + } + }; +} diff --git a/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java b/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java new file mode 100644 index 0000000..1de86d1 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java @@ -0,0 +1,136 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea.model; + +import biz.elfuego.idea.issues.gitea.util.Consts.TaskFields; +import com.google.gson.JsonObject; +import org.apache.http.util.TextUtils; + +import java.util.Date; + +import static biz.elfuego.idea.issues.gitea.util.Utils.getDate; +import static biz.elfuego.idea.issues.gitea.util.Utils.getString; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class GiteaTask { + private GiteaProject project; + private String id; + private String title; + private String description; + private Date createdAt; + private Date updatedAt; + private String state; + private String assignee; + + public GiteaTask(GiteaProject project, JsonObject json) { + this.project = project; + this.fromJson(json); + } + + public GiteaProject getProject() { + return project; + } + + public void setProject(GiteaProject project) { + this.project = project; + } + + public String getId() { + return id; + } + + @SuppressWarnings("WeakerAccess") + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + @SuppressWarnings("WeakerAccess") + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + @SuppressWarnings("WeakerAccess") + public void setDescription(String description) { + this.description = description; + } + + public Date getCreatedAt() { + return createdAt; + } + + @SuppressWarnings("WeakerAccess") + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + @SuppressWarnings("WeakerAccess") + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public String getState() { + return state; + } + + @SuppressWarnings("WeakerAccess") + public void setState(String state) { + this.state = state; + } + + public String getAssignee() { + return assignee; + } + + @SuppressWarnings("WeakerAccess") + public void setAssignee(String assignee) { + this.assignee = assignee; + } + + public boolean isValid() { + return !(TextUtils.isEmpty(id) || + TextUtils.isEmpty(title) || + (createdAt == null) || + (updatedAt == null) || + TextUtils.isEmpty(state)); + } + + private void fromJson(JsonObject current) { + if (current.has(TaskFields.ID)) { + this.setId(getString(current, TaskFields.ID, "")); + } + if (current.has(TaskFields.TITLE)) { + this.setTitle(getString(current, TaskFields.TITLE, "")); + } + if (current.has(TaskFields.DESCRIPTION)) { + this.setDescription(getString(current, TaskFields.DESCRIPTION, "")); + } + if (current.has(TaskFields.CREATEDAT)) { + this.setCreatedAt(getDate(current, TaskFields.CREATEDAT)); + } + if (current.has(TaskFields.UPDATEDAT)) { + this.setUpdatedAt(getDate(current, TaskFields.UPDATEDAT)); + } + if (current.has(TaskFields.STATE)) { + this.setState(getString(current, TaskFields.STATE, "")); + } + if (current.has(TaskFields.ASSIGNEE)) { + this.setAssignee(getString(current, TaskFields.ASSIGNEE, "")); + } + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/util/Consts.java b/src/biz/elfuego/idea/issues/gitea/util/Consts.java new file mode 100644 index 0000000..5794642 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/util/Consts.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea.util; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class Consts { + public static final String ERROR = "Error communicating to the server"; + public static final String UNSPEC_PROJ_ID = "--"; + + public interface Url { + String DEFAULT = "https://git2.elfuego.biz:444"; + } + + public interface EndPoint { + String API = "/api/v1"; + String ME = "/user"; + String REPOS = "/repos/"; + String ISSUES = "/issues"; + String COMMENTS = "/comments"; + String REPOS_SEARCH = REPOS + "search?uid="; + } + + public enum States { + OPEN, CLOSED; + } + + public interface TaskFields { + static final String ID = "id"; + static final String TITLE = "title"; + static final String DESCRIPTION = "body"; + static final String CREATEDAT = "created_at"; + static final String UPDATEDAT = "updated_at"; + static final String STATE = "state"; + static final String ASSIGNEE = "assignee"; + } + + public interface CommentFields { + static final String DATE = "updated_at"; + static final String TEXT = "body"; + static final String USER = "user"; + static final String FULLNAME = "full_name"; + static final String USERNAME = "username"; + } +} diff --git a/src/biz/elfuego/idea/issues/gitea/util/Utils.java b/src/biz/elfuego/idea/issues/gitea/util/Utils.java new file mode 100644 index 0000000..e8451e3 --- /dev/null +++ b/src/biz/elfuego/idea/issues/gitea/util/Utils.java @@ -0,0 +1,90 @@ +/* + * Copyright © 2018 by elfuego.biz + */ +package biz.elfuego.idea.issues.gitea.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author Roman Pedchenko + * @date 2018.06.30 + */ +public class Utils { + private static final String OK = "ok"; + private static final String DATA = "data"; + + public static String getString(JsonObject object, String fieldName, String defaultValue) { + String result = defaultValue; + if (object.has(fieldName) && object.get(fieldName).isJsonPrimitive()) { + result = object.get(fieldName).getAsJsonPrimitive().getAsString(); + } + + return result; + } + + public static Date getDate(JsonObject object, String fieldName) { + Date result = null; + if (object.has(fieldName) && object.get(fieldName).isJsonPrimitive()) { + result = parseDateISO8601(object.get(fieldName).getAsJsonPrimitive().getAsString()); + } + return result; + } + + private static Date parseDateISO8601(String input) { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); + if (input.endsWith("Z")) { + input = input.substring(0, input.length() - 1) + "GMT-00:00"; + } else { + int inset = 6; + + String s0 = input.substring(0, input.length() - inset); + String s1 = input.substring(input.length() - inset, input.length()); + + input = s0 + "GMT" + s1; + } + try { + return df.parse(input); + } catch (ParseException e) { + e.printStackTrace(); + } + return null; + } + + public static JsonObject getObject(JsonElement el) throws Exception { + if (el.isJsonObject()) { + return el.getAsJsonObject(); + } + throw new Exception(Consts.ERROR + ", getObject: " + el.toString()); + } + + public static JsonObject getObject(JsonObject object, String fieldName) throws Exception { + if (object.has(fieldName) && object.get(fieldName).isJsonObject()) { + return object.get(fieldName).getAsJsonObject(); + } + throw new Exception(Consts.ERROR + ", getObject2: " + object.toString()); + } + + public static JsonArray getArray(JsonElement el) throws Exception { + if (el.isJsonArray()) { + return el.getAsJsonArray(); + } + throw new Exception(Consts.ERROR + ", getArray: " + el.toString()); + } + + public static JsonArray getOkData(JsonElement el) throws Exception { + if (el.isJsonObject()) { + JsonObject obj = el.getAsJsonObject(); + if (obj.has(OK) && obj.get(OK).isJsonPrimitive() && obj.get(OK).getAsBoolean()) { + if (obj.has(DATA) && obj.get(DATA).isJsonArray()) + return obj.get(DATA).getAsJsonArray(); + } + } + throw new Exception(Consts.ERROR + ", getOkData: " + el.toString()); + } +}