From 17d047ed8d43a431054d548860ec72d8d74b051d Mon Sep 17 00:00:00 2001 From: Anja Kleebaum Date: Fri, 4 Feb 2022 21:50:15 +0100 Subject: [PATCH] CONCONDEC-32: Fix that freezing the history and manual changing of imported elements is not working (#77) * Remove macroId and pageId attributes from KnowledgeElement class * Remove macroId from REST API because it is not needed anymore * Rename REST API to better match conventions * Update Confluence version * Use ObjectMapper to serialize and deserialize knowledge elements * Serialize with alphabetical sorting * Update version to v0.0.5 --- README.md | 12 ++ doc/macro_configuration_possibilities.png | Bin 0 -> 13167 bytes doc/macro_json_edit_dialog.png | Bin 0 -> 66206 bytes pom.xml | 15 +- .../macro/DecisionKnowledgeImportMacro.java | 26 +-- .../confluence/model/KnowledgeElement.java | 154 +++++++----------- .../KnowledgePersistenceManager.java | 51 +++--- .../confluence/rest/KnowledgeRest.java | 55 ++----- src/main/resources/js/condec.api.js | 16 +- .../resources/js/condec.knowledge.import.js | 64 ++++---- src/main/resources/templates/standUpTable.vm | 2 +- .../confluence/mocks/MockApplicationLink.java | 4 +- .../mocks/MockPageBuilderService.java | 67 +------- .../model/TestCreateJsonFromObject.java | 36 ++++ .../model/TestCreateObjectFromJsonString.java | 4 +- .../model/TestKnowledgeElement.java | 36 ++-- .../TestKnowledgePersistenceManager.java | 33 ++-- .../rest/TestGetStoredKnowledgeElements.java | 4 +- .../rest/TestStoreKnowledgeElements.java | 15 +- 19 files changed, 249 insertions(+), 345 deletions(-) create mode 100644 doc/macro_configuration_possibilities.png create mode 100644 doc/macro_json_edit_dialog.png create mode 100644 src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateJsonFromObject.java diff --git a/README.md b/README.md index 8faa966..dd59a18 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,14 @@ The authentication type needs to be **OAuth (impersonation)**. The *Decision Knowledge Import Macro* can be used to create a stand-up table in meetings. ![Decision Knowledge Import Macro](doc/macro_edit_dialog.png) + *Macro to import decision knowledge from Jira* The stand-up table lists open and solved decision problems, decisions, and other decision knowledge elements for a certain time frame. The list of decision knowledge elements supports the developers in discussing recently made decisions and open decision problems during meetings. ![Decision Knowledge List](doc/imported_decision_knowledge.png) + *List of decision knowledge elements as part of a meeting agenda/protocol (used as stand-up table)* ConDec's decision knowledge import macro is different to the Jira issue import macro in the following ways: @@ -58,9 +60,19 @@ code comments, commit messages, and entire Jira issues. The Jira issue import macro would only enable to import decision knowledge elements documented as entire Jira issues. - It enables to **freeze** the imported elements so that changes made in the decision knowledge documentation after the meeting are not shown in the meeting protocol of a former meeting. That means that ConDec's decision knowledge import macro enables to **preserve the history**. +- It enables to manually input a JSON String exported from Jira, e.g. if there are no application links, and to **manually edit the JSON String**. - The unresolved decision problems (issues) are highlighted using red text color to **nudge the developers to collaboratively make and document a decision**. +![Configuration Possibilities](doc/macro_configuration_possibilities.png) + +*Configuration possibilities for the decision knowledge import macro when editing a Confluence page. +The macro enables to manually edit the imported knowledge elements.* + +![Dialog to Manually Edit JSON](doc/macro_json_edit_dialog.png) + +*Dialog to manually paste a JSON String exported from Jira or to manually edit the imported knowledge elements.* + ### Design Details The decision knowledge is imported from [ConDec Jira](https://github.com/cures-hub/cures-condec-jira) via the REST API. To access ConDec Jira's REST API, the application link between Confluence and Jira is used in the [JiraClient class](src/main/java/de/uhd/ifi/se/decision/management/confluence/oauth). \ No newline at end of file diff --git a/doc/macro_configuration_possibilities.png b/doc/macro_configuration_possibilities.png new file mode 100644 index 0000000000000000000000000000000000000000..bdc9d9dff443884c40860434fe7618a0b8c2886c GIT binary patch literal 13167 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 S$pn9Q0a27yktvfh3i@9k>QWB? literal 0 HcmV?d00001 diff --git a/doc/macro_json_edit_dialog.png b/doc/macro_json_edit_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..25cb1bf892426c2766128942f637fdd560d75ccd GIT binary patch literal 66206 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

@|T;OYNRO@tq;B-) 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⋙o+E>R8S8-L}L3y>YrUyuh1S|em* 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�>t#aKI``k4Txv~ORrcy?G^OE^}L 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 JZ#WWm<)2G9C0PIf literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index f49dd87..adec659 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,11 @@ - 4.0.0 de.uhd.ifi.se.decision management.confluence - 0.0.4 + 0.0.5 Software Engineering Research Group, Heidelberg University @@ -167,6 +166,11 @@ 20211205 provided + + com.fasterxml.jackson.core + jackson-databind + 2.13.1 + javax.xml.bind jaxb-api @@ -313,13 +317,12 @@ - 7.14.1-xpln-1903 + 7.16.0 8.3.0-128eead6d 2.0.2 2.2.3 0.8.7 - + ${project.groupId}.${project.artifactId} UTF-8 11 diff --git a/src/main/java/de/uhd/ifi/se/decision/management/confluence/macro/DecisionKnowledgeImportMacro.java b/src/main/java/de/uhd/ifi/se/decision/management/confluence/macro/DecisionKnowledgeImportMacro.java index cce8ee0..b505cf3 100644 --- a/src/main/java/de/uhd/ifi/se/decision/management/confluence/macro/DecisionKnowledgeImportMacro.java +++ b/src/main/java/de/uhd/ifi/se/decision/management/confluence/macro/DecisionKnowledgeImportMacro.java @@ -13,13 +13,10 @@ import org.slf4j.LoggerFactory; import com.atlassian.confluence.content.render.xhtml.ConversionContext; -import com.atlassian.confluence.content.render.xhtml.storage.macro.MacroId; import com.atlassian.confluence.macro.Macro; import com.atlassian.confluence.macro.MacroExecutionException; import com.atlassian.confluence.renderer.radeox.macros.MacroUtils; import com.atlassian.confluence.util.velocity.VelocityUtils; -import com.atlassian.confluence.xhtml.api.MacroDefinition; -import com.atlassian.fugue.Option; import de.uhd.ifi.se.decision.management.confluence.model.KnowledgeElement; import de.uhd.ifi.se.decision.management.confluence.oauth.JiraClient; @@ -33,9 +30,8 @@ public class DecisionKnowledgeImportMacro implements Macro { public String execute(Map map, String s, ConversionContext conversionContext) throws MacroExecutionException { int pageId = Integer.parseInt(conversionContext.getEntity().getIdAsString()); - String macroId = getMacroId(conversionContext); - List knowledgeElements = KnowledgePersistenceManager.getElements(pageId, macroId); + List knowledgeElements = KnowledgePersistenceManager.getElements(pageId); LOGGER.info("Number of elements in database:" + knowledgeElements.size()); boolean freeze = "true".equals(map.get("freeze")); @@ -102,13 +98,9 @@ public String execute(Map map, String s, ConversionContext conve endDate, knowledgeTypes, status); LOGGER.info("Number of elements imported from Jira:" + knowledgeElements.size()); - KnowledgePersistenceManager.removeKnowledgeElements(pageId, macroId); + KnowledgePersistenceManager.removeKnowledgeElements(pageId); knowledgeElements.sort(Comparator.comparing(KnowledgeElement::getKey)); - for (KnowledgeElement element : knowledgeElements) { - element.setPageId(pageId); - element.setMacroId(macroId); - KnowledgePersistenceManager.addKnowledgeElement(element); - } + KnowledgePersistenceManager.addKnowledgeElements(knowledgeElements, pageId); } } @@ -142,18 +134,6 @@ private long convertToUnixTimeStamp(String dateString) { return unixTimeStamp; } - private String getMacroId(ConversionContext conversionContext) { - String macroId = "0"; - try { - MacroDefinition macroDefinition = (MacroDefinition) conversionContext.getProperty("macroDefinition"); - Option option = macroDefinition.getMacroId(); - macroId = option.get().getId(); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - return macroId; - } - @Override public BodyType getBodyType() { return BodyType.NONE; diff --git a/src/main/java/de/uhd/ifi/se/decision/management/confluence/model/KnowledgeElement.java b/src/main/java/de/uhd/ifi/se/decision/management/confluence/model/KnowledgeElement.java index 20363ca..b7f9095 100644 --- a/src/main/java/de/uhd/ifi/se/decision/management/confluence/model/KnowledgeElement.java +++ b/src/main/java/de/uhd/ifi/se/decision/management/confluence/model/KnowledgeElement.java @@ -3,24 +3,24 @@ import java.io.Serializable; import java.net.URLDecoder; import java.nio.charset.Charset; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; -import javax.xml.bind.annotation.XmlElement; - import org.apache.commons.lang3.StringUtils; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.JsonMappingException; -import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonAlias; -import com.google.gson.Gson; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; @JsonIgnoreProperties(ignoreUnknown = true) public class KnowledgeElement implements Serializable { @@ -30,15 +30,13 @@ public class KnowledgeElement implements Serializable { private static final Logger LOGGER = LoggerFactory.getLogger(KnowledgeElement.class); private String link; - private int pageId; private String summary; private String type; private String key; private String id; - private String macroId; private String description; private String creator; - private String updatingDate; + private String latestUpdatingDate; private String status; private List groups; private String latestAuthor; @@ -47,66 +45,47 @@ public class KnowledgeElement implements Serializable { * @issue How can we convert a JSON string into a list of KnowledgeElement * objects? * @decision Convert a JSON string into a list of KnowledgeElement objects - * manually using the GSON library! + * manually using an ObjectMapper! * @con This is not very elegant and hard to understand. There might be an * easier way of mapping JSON Strings into objects. + * @alternative We could use GSON for (de)serialization. + * @con GSON only (de)serializes attributes, it is hard to add further getters + * and setters. */ public static List parseJsonString(String jsonString) { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.writerWithDefaultPrettyPrinter(); - + ObjectMapper objectMapper = createObjectMapper(); List elements = new ArrayList(); - try { - elements = objectMapper.readValue(jsonString, - objectMapper.getTypeFactory().constructCollectionType(List.class, KnowledgeElement.class)); - } catch (JsonMappingException e) { - try { - Gson g = new Gson(); - KnowledgeElement[] myelements = g.fromJson(jsonString.substring(1, jsonString.length() - 1), - KnowledgeElement[].class); - elements = Arrays.asList(myelements); - } catch (Exception e1) { - LOGGER.error(e1.getMessage()); - } + elements = objectMapper.readValue(jsonString, new TypeReference>() { + }); } catch (Exception e) { LOGGER.error(e.getMessage()); } - return elements; } - public KnowledgeElement(String link, int pageId, String summary, String type, String key, String description, - String macroId) { - this.pageId = pageId; - this.summary = summary; - this.type = type; - this.key = key; - this.link = link; - this.description = description; - this.macroId = macroId; - - // generate unique id - Date dNow = new Date(); - SimpleDateFormat format = new SimpleDateFormat("yyMMddhhmmssMs"); - String datetime = format.format(dNow); - this.id = datetime + key; - } - - public KnowledgeElement() { - } - - @XmlElement - public int getPageId() { - return pageId; + public static String toJsonString(List elements) { + ObjectMapper objectMapper = createObjectMapper(); + String jsonString = ""; + try { + jsonString = objectMapper.writeValueAsString(elements); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + return jsonString; } - @JsonProperty - public void setPageId(int pageId) { - this.pageId = pageId; + private static ObjectMapper createObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + objectMapper.enable(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES); + objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); + objectMapper.setSerializationInclusion(Include.NON_NULL); + objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); + objectMapper.writerWithDefaultPrettyPrinter(); + return objectMapper; } - @XmlElement public String getId() { return id; } @@ -116,37 +95,30 @@ public void setId(String id) { this.id = id; } - @XmlElement public String getSummary() { return summary; } - @JsonProperty public void setSummary(String summary) { this.summary = summary; } - @XmlElement public String getType() { return type; } - @JsonProperty public void setType(String type) { this.type = type; } - @XmlElement public String getKey() { return key; } - @JsonProperty public void setKey(String key) { this.key = key; } - @XmlElement public String getLink() { if (link != null) { return URLDecoder.decode(link, Charset.defaultCharset()); @@ -155,26 +127,11 @@ public String getLink() { } @JsonProperty + @JsonAlias("url") public void setLink(String link) { this.link = link; } - @JsonProperty - public void setUrl(String link) { - setLink(link); - } - - @XmlElement - public String getMacroId() { - return macroId; - } - - @JsonProperty - public void setMacroId(String macroId) { - this.macroId = macroId; - } - - @XmlElement public String getDescription() { return description != null ? description : getSummary(); } @@ -184,7 +141,6 @@ public void setDescription(String description) { this.description = description; } - @XmlElement public String getCreator() { return creator; } @@ -194,19 +150,28 @@ public void setCreator(String name) { this.creator = name; } - @XmlElement public String getUpdatingDate() { - return updatingDate; + Date date = new Date(Long.parseLong(latestUpdatingDate)); + return new SimpleDateFormat("yyyy-MM-dd").format(date); } - @JsonProperty("latestUpdatingDate") - @JsonAlias("updatingDate") - public void setUpdatingDate(String epochTime) { - Date date = new Date(Long.parseLong(epochTime)); - this.updatingDate = new SimpleDateFormat("yyyy-MM-dd").format(date); + @JsonProperty + public void setLatestUpdatingDate(String epochTime) { + this.latestUpdatingDate = epochTime; + } + + @JsonProperty + public void setUpdatingDate(String formatedDate) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date date = new Date(); + try { + date = format.parse(formatedDate); + } catch (ParseException e) { + LOGGER.error(e.getMessage()); + } + this.latestUpdatingDate = date.getTime() + ""; } - @XmlElement public String getLatestAuthor() { return latestAuthor; } @@ -216,7 +181,6 @@ public void setLatestAuthor(String latestAuthor) { this.latestAuthor = latestAuthor; } - @XmlElement public String getStatus() { if (status == null) { return "undefined"; @@ -245,11 +209,15 @@ public void setGroups(List groups) { this.groups = groups; } - @XmlElement - public String getGroups() { - if (groups == null) { - groups = new ArrayList<>(); - } + public List getGroups() { + return groups; + } + + public String getGroupsAsString() { return StringUtils.join(groups, ", "); } + + public boolean equals(Object object) { + return ((KnowledgeElement) object).getSummary().equals(getSummary()); + } } \ No newline at end of file diff --git a/src/main/java/de/uhd/ifi/se/decision/management/confluence/persistence/KnowledgePersistenceManager.java b/src/main/java/de/uhd/ifi/se/decision/management/confluence/persistence/KnowledgePersistenceManager.java index 6ea6682..fab1954 100644 --- a/src/main/java/de/uhd/ifi/se/decision/management/confluence/persistence/KnowledgePersistenceManager.java +++ b/src/main/java/de/uhd/ifi/se/decision/management/confluence/persistence/KnowledgePersistenceManager.java @@ -1,11 +1,13 @@ package de.uhd.ifi.se.decision.management.confluence.persistence; -import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.atlassian.bandana.BandanaContext; import com.atlassian.bandana.BandanaManager; import com.atlassian.confluence.setup.bandana.ConfluenceBandanaContext; @@ -21,6 +23,8 @@ @Named public class KnowledgePersistenceManager { + private static final Logger LOGGER = LoggerFactory.getLogger(KnowledgePersistenceManager.class); + @ConfluenceImport private static BandanaManager bandanaManager; private static BandanaContext bandanaContext; @@ -31,40 +35,37 @@ public KnowledgePersistenceManager(BandanaManager bandanaManager) { setBandanaContext(new ConfluenceBandanaContext("knowledge")); } - public static void addKnowledgeElement(KnowledgeElement knowledgeElement) { - bandanaManager.setValue(bandanaContext, knowledgeElement.getId(), knowledgeElement); + public static void addKnowledgeElements(List elements, int pageId) { + String jsonString = KnowledgeElement.toJsonString(elements); + addKnowledgeElements(jsonString, pageId); } - public static void removeKnowledgeElement(String id) { - bandanaManager.removeValue(bandanaContext, id); + public static void addKnowledgeElements(String jsonString, int pageId) { + bandanaManager.setValue(bandanaContext, pageId + "", jsonString); } - public static List getElements(int pageId, String macroId) { - List elements = new ArrayList(); - if (pageId == 0 || macroId == null) { - return elements; - } + public static void removeKnowledgeElements(int pageId) { + bandanaManager.removeValue(bandanaContext, pageId + ""); + } + + public static List getElements(int pageId) { + return KnowledgeElement.parseJsonString(getElementsAsJsonString(pageId)); + } + + public static String getElementsAsJsonString(int pageId) { + String jsonString = ""; + LOGGER.error("keys: " + bandanaManager.getKeys(bandanaContext).toString()); for (String id : bandanaManager.getKeys(bandanaContext)) { - Object storedObject = bandanaManager.getValue(bandanaContext, id); - if (!(storedObject instanceof KnowledgeElement)) { + if (!id.equals(pageId + "")) { continue; } - KnowledgeElement knowledgeElement = (KnowledgeElement) storedObject; - // add only if the page id and Macro id corresponds - // if macro id is null return all from page - if (knowledgeElement.getPageId() == pageId && knowledgeElement.getMacroId() != null - && (knowledgeElement.getMacroId().equals(macroId)) || macroId == null) { - elements.add(knowledgeElement); + Object storedObject = bandanaManager.getValue(bandanaContext, id); + if (storedObject instanceof String) { + jsonString = (String) storedObject; } } - return elements; - } - - public static void removeKnowledgeElements(int pageId, String macroId) { - for (KnowledgeElement element : getElements(pageId, macroId)) { - removeKnowledgeElement(element.getId()); - } + return jsonString; } public static void setBandanaManager(BandanaManager bandanaManager) { diff --git a/src/main/java/de/uhd/ifi/se/decision/management/confluence/rest/KnowledgeRest.java b/src/main/java/de/uhd/ifi/se/decision/management/confluence/rest/KnowledgeRest.java index 8e2008c..ec251f0 100644 --- a/src/main/java/de/uhd/ifi/se/decision/management/confluence/rest/KnowledgeRest.java +++ b/src/main/java/de/uhd/ifi/se/decision/management/confluence/rest/KnowledgeRest.java @@ -1,74 +1,49 @@ package de.uhd.ifi.se.decision.management.confluence.rest; -import java.util.List; - import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.uhd.ifi.se.decision.management.confluence.model.KnowledgeElement; import de.uhd.ifi.se.decision.management.confluence.oauth.JiraClient; import de.uhd.ifi.se.decision.management.confluence.persistence.KnowledgePersistenceManager; /** - * REST resource: Enables importing decision knowledge from Jira and storing it. + * REST resource: Enables importing decision knowledge from Jira and storing it + * using the {@link KnowledgePersistenceManager}. */ @Path("/knowledge") public class KnowledgeRest { - private static final Logger LOGGER = LoggerFactory.getLogger(KnowledgeRest.class); - - @Path("/storeKnowledgeElements") + @Path("/storeKnowledgeElements/{pageId}") @POST - @Consumes({ MediaType.APPLICATION_JSON }) - public Response storeKnowledgeElements(@Context HttpServletRequest request, @QueryParam("pageId") int pageId, - @QueryParam("macroId") String macroId, String jsonString) { - - if (pageId == 0 || macroId == null || macroId.isEmpty() || jsonString == null) { + public Response storeKnowledgeElements(@Context HttpServletRequest request, @PathParam("pageId") int pageId, + String jsonString) { + if (pageId == 0 || jsonString == null) { return Response.status(Status.BAD_REQUEST).build(); } - List elements = KnowledgeElement.parseJsonString(jsonString); - // first remove issues from this page - if (elements.size() > 0) { - KnowledgePersistenceManager.removeKnowledgeElements(pageId, macroId); - } - for (KnowledgeElement element : elements) { - element.setPageId(pageId); - element.setMacroId(macroId); - KnowledgePersistenceManager.addKnowledgeElement(element); - } - LOGGER.info(elements.size() + " knowledge elements were stored in database"); + KnowledgePersistenceManager.removeKnowledgeElements(pageId); + KnowledgePersistenceManager.addKnowledgeElements(jsonString, pageId); return Response.ok().build(); } - @Path("/getStoredKnowledgeElements") + @Path("/storedKnowledgeElements/{pageId}") @GET - @Produces({ MediaType.APPLICATION_JSON }) - public Response getStoredKnowledgeElements(@QueryParam("pageId") int pageId, - @QueryParam("macroId") String macroId) { - if (pageId == 0 || macroId == null || macroId.isEmpty()) { + public Response getStoredKnowledgeElements(@PathParam("pageId") int pageId) { + if (pageId == 0) { return Response.status(Status.BAD_REQUEST).build(); } - List storedElements = KnowledgePersistenceManager.getElements(pageId, macroId); - return Response.status(Response.Status.OK).entity(storedElements).build(); + return Response.ok(KnowledgePersistenceManager.getElementsAsJsonString(pageId)).build(); } - @Path("/getProjectsFromJira") + @Path("/projectsFromJira") @GET - @Produces({ MediaType.APPLICATION_JSON }) public Response getProjectsFromJira() { String jiraProjectsJsonResponse = JiraClient.instance.getJiraProjectsAsJson(); - return Response.status(Response.Status.OK).entity(jiraProjectsJsonResponse).build(); + return Response.ok(jiraProjectsJsonResponse).build(); } } \ No newline at end of file diff --git a/src/main/resources/js/condec.api.js b/src/main/resources/js/condec.api.js index 4064773..68be60b 100644 --- a/src/main/resources/js/condec.api.js +++ b/src/main/resources/js/condec.api.js @@ -15,12 +15,12 @@ this.restPrefix = AJS.Data.get("context-path") + "/rest/condec/latest"; }; - /* + /** * external references: condec.knowledge.import */ ConDecAPI.prototype.getProjectsFromJira = function getProjectsFromJira(callback) { console.log("conDecApi getProjectsFromJira"); - var url = this.restPrefix + "/knowledge/getProjectsFromJira"; + var url = this.restPrefix + "/knowledge/projectsFromJira"; getJSON(url, function(error, projects) { if (error === null && !checkForError(projects)) { callback(projects); @@ -28,13 +28,13 @@ }); }; - /* + /** * Used to get the JSON string that can be manually edited by the user. * * external references: condec.knowledge.import */ - ConDecAPI.prototype.getStoredKnowledgeElements = function getStoredKnowledgeElements(pageId, macroId, callback) { - var url = this.restPrefix + "/knowledge/getStoredKnowledgeElements?pageId=" + pageId + "¯oId=" + macroId; + ConDecAPI.prototype.getStoredKnowledgeElements = function getStoredKnowledgeElements(pageId, callback) { + var url = this.restPrefix + "/knowledge/storedKnowledgeElements/" + pageId; getJSON(url, function(error, elements) { if (error === null && !checkForError(elements)) { callback(elements); @@ -42,18 +42,18 @@ }); }; - /* + /** * Used to store the JSON string that was manually edited/imported from Jira by the user. * external references: condec.knowledge.import */ - ConDecAPI.prototype.storeKnowledgeElements = function storeKnowledgeElements(userInput, pageId, macroId) { + ConDecAPI.prototype.storeKnowledgeElements = function storeKnowledgeElements(userInput, pageId) { var jsonArray = ""; try { jsonArray = JSON.parse(userInput); } catch (e) { showFlag("error", "Your input could not be parsed. " + e); } - var url = this.restPrefix + "/knowledge/storeKnowledgeElements?pageId=" + pageId + "¯oId=" + macroId; + var url = this.restPrefix + "/knowledge/storeKnowledgeElements/" + pageId; postJSON(url, jsonArray, function(error, result) { if (error === null) { showFlag("success", "The stand-up table was successfully updated."); diff --git a/src/main/resources/js/condec.knowledge.import.js b/src/main/resources/js/condec.knowledge.import.js index 3cba85f..81e5b0f 100644 --- a/src/main/resources/js/condec.knowledge.import.js +++ b/src/main/resources/js/condec.knowledge.import.js @@ -1,20 +1,17 @@ -/* -* -* This module implements the dialog, which can be used for importing decision knowledge -* from Jira and was manually implemented. -* It's not sure, if it will be further developed. -* The main dialog für importing decision knowledge from Jira -* is implemented in atlassian-plugin.xml -* -* */ +/** + * This module implements the dialog, which can be used for manual importing decision knowledge + * from Jira via a JSON string and to change the imported elements. + * The main dialog for importing decision knowledge from Jira + * is implemented in atlassian-plugin.xml + */ AJS.bind("init.rte", function() { - + var macroName = "decision-knowledge-import-macro"; var jsOverrides = { - "fields" : { - "enum" : { - "project" : function(params, options) { + "fields": { + "enum": { + "project": function(params, options) { var field = AJS.MacroBrowser.ParameterFields["enum"](params, options); conDecAPI.getProjectsFromJira(function(projects) { var options = ""; @@ -34,34 +31,34 @@ AJS.bind("init.rte", function() { } AJS.MacroBrowser.setMacroJsOverride(macroName, jsOverrides); - - var updateMacro = function (macroId) { + + var updateMacro = function() { var dialog = $("#json-dialog"); if (dialog !== null) { dialog.remove(); } - + // Standard sizes are 400, 600, 800 and 960 pixels dialog = new AJS.Dialog({ - width : 960, - height : 800, - id : "json-dialog", - closeOnOutsideClick : true + width: 960, + height: 800, + id: "json-dialog", + closeOnOutsideClick: true }); - + dialog.addPanel( "Edit JSON String", - "

Paste a JSON String exported from Jira or manually edit the existing one. " + "

Paste a JSON String exported from Jira or manually edit the existing one. " + "Make sure you enabled the 'freeze' option! Otherwise changes will not be saved!

" + "
", - "panel-body"); - + "panel-body"); + // get knowledge elements from backend via REST var pageId = parseInt(AJS.params.pageId, 10); - conDecAPI.getStoredKnowledgeElements(pageId, macroId, function(elements) { - $("#jsonTextArea").val("[" + JSON.stringify(elements, undefined, "\t") + "]"); + conDecAPI.getStoredKnowledgeElements(pageId, function(elements) { + $("#jsonTextArea").val(JSON.stringify(elements, undefined, "\t")); }); - + dialog.addLink("Cancel", function(dialog) { dialog.hide(); }, "#"); @@ -70,19 +67,14 @@ AJS.bind("init.rte", function() { dialog.addButton("Update Knowledge", function(dialog) { var userInput = $("#jsonTextArea").val(); - conDecAPI.storeKnowledgeElements(userInput, pageId, macroId); + conDecAPI.storeKnowledgeElements(userInput, pageId); dialog.hide(); - }); + }); dialog.show(); }; - + AJS.Confluence.PropertyPanel.Macro.registerButtonHandler("updateButton", function(e, macroNode) { - var macroId = macroNode.getAttribute("data-macro-id"); - if (macroId && macroId !== "") { - updateMacro(macroId); - } else { - conDecAPI.showFlag("error", "Please save the page first before manually updating the JSON string."); - } + updateMacro(); }); }); \ No newline at end of file diff --git a/src/main/resources/templates/standUpTable.vm b/src/main/resources/templates/standUpTable.vm index c3de951..4c32751 100644 --- a/src/main/resources/templates/standUpTable.vm +++ b/src/main/resources/templates/standUpTable.vm @@ -20,7 +20,7 @@ $webResourceManager.requireResource("de.uhd.ifi.se.decision.management.confluenc $knowledgeElement.getSummary() $knowledgeElement.getLatestAuthor() $knowledgeElement.getUpdatingDate() - $knowledgeElement.getGroups() + $knowledgeElement.getGroupsAsString()
$knowledgeElement.getKey() #end diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockApplicationLink.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockApplicationLink.java index a9c7efa..21d5f14 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockApplicationLink.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockApplicationLink.java @@ -31,9 +31,9 @@ public String mockResponseByUrl(String url) { return "[ { 'key' : 'CONDEC' } ]"; } if (IS_DOCUMENTATION_COMPLETE) { - return "[[{'type':'issue'}, {'type':'decision'}]]"; + return "[{'type':'issue'}, {'type':'decision'}]"; } - return "[[{'key' : 'CONDEC-1', 'type':'issue'}]]"; + return "[{'key' : 'CONDEC-1', 'type':'issue'}]"; } @Override diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockPageBuilderService.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockPageBuilderService.java index b9b3b82..6ee8911 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockPageBuilderService.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/mocks/MockPageBuilderService.java @@ -1,76 +1,19 @@ package de.uhd.ifi.se.decision.management.confluence.mocks; -import java.util.Set; - -import com.atlassian.webresource.api.assembler.AssembledResources; import com.atlassian.webresource.api.assembler.PageBuilderService; -import com.atlassian.webresource.api.assembler.RequiredData; -import com.atlassian.webresource.api.assembler.RequiredResources; import com.atlassian.webresource.api.assembler.WebResourceAssembler; public class MockPageBuilderService implements PageBuilderService { @Override - public WebResourceAssembler assembler() { - return new WebResourceAssembler() { - - @Override - public RequiredResources resources() { - return new RequiredResources() { - - @Override - public RequiredResources requireWebResource(String moduleCompleteKey) { - return this; - } - - @Override - public RequiredResources requirePage(String key) { - // TODO Auto-generated method stub - return null; - } - - @Override - public RequiredResources requireModule(String name) { - // TODO Auto-generated method stub - return null; - } - - @Override - public RequiredResources requireContext(String context) { - // TODO Auto-generated method stub - return null; - } - - @Override - public RequiredResources exclude(Set webResources, Set contexts) { - // TODO Auto-generated method stub - return null; - } - }; - } - - @Override - public RequiredData data() { - // TODO Auto-generated method stub - return null; - } - - @Override - public WebResourceAssembler copy() { - // TODO Auto-generated method stub - return null; - } - - @Override - public AssembledResources assembled() { - // TODO Auto-generated method stub - return null; - } - }; + public void seed(WebResourceAssembler webResourceAssembler) { + // TODO Auto-generated method stub } @Override - public void seed(WebResourceAssembler webResourceAssembler) { + public WebResourceAssembler assembler() { // TODO Auto-generated method stub + return null; } + } \ No newline at end of file diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateJsonFromObject.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateJsonFromObject.java new file mode 100644 index 0000000..a115b28 --- /dev/null +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateJsonFromObject.java @@ -0,0 +1,36 @@ +package de.uhd.ifi.se.decision.management.confluence.model; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class TestCreateJsonFromObject { + + @Test + public void testToJsonStringWithValidElements() { + KnowledgeElement element = new KnowledgeElement(); + element.setSummary("Link new created type to different devices inside the pop up field."); + element.setLink("https://jira-se.ifi.uni-heidelberg.de/browse/ISE2019-103"); + element.setUpdatingDate("2042-01-01"); + String jsonString = "[{\"description\":\"Link new created type to different devices inside the pop up field.\"," + + "\"link\":\"https://jira-se.ifi.uni-heidelberg.de/browse/ISE2019-103\"," + + "\"status\":\"undefined\",\"statusColor\":\"black\"," + + "\"summary\":\"Link new created type to different devices inside the pop up field.\"," + + "\"updatingDate\":\"2042-01-01\"}]"; + + List elements = new ArrayList<>(); + elements.add(element); + assertEquals(jsonString, KnowledgeElement.toJsonString(elements)); + } + + @Test + public void testToJsonStringWithInvalidElements() { + KnowledgeElement element = new KnowledgeElement(); + List elements = new ArrayList<>(); + elements.add(element); + assertEquals("", KnowledgeElement.toJsonString(elements)); + } +} \ No newline at end of file diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateObjectFromJsonString.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateObjectFromJsonString.java index fd12e0b..f3d2a9a 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateObjectFromJsonString.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestCreateObjectFromJsonString.java @@ -18,7 +18,6 @@ public void testParseJsonString() { List elements = KnowledgeElement.parseJsonString(jsonString); KnowledgeElement element = elements.get(0); assertEquals("link type to devices", element.getSummary()); - assertEquals(122191874, element.getPageId()); assertEquals("ISE2019-103", element.getKey()); assertEquals("https://jira-se.ifi.uni-heidelberg.de/browse/ISE2019-103", element.getLink()); } @@ -33,14 +32,13 @@ public void testParseJsonStringArray() { List elements = KnowledgeElement.parseJsonString(jsonString); KnowledgeElement element = elements.get(0); assertEquals("WI: Implement jump-to methods for nodes in knowledge graph", element.getSummary()); - assertEquals(0, element.getPageId()); assertEquals("ECONDEC-20", element.getKey()); assertEquals("https://jira-se.ifi.uni-heidelberg.de/browse/ECONDEC-20", element.getLink()); } @Test public void testParseTypes() { - String jsonString = "[[{'type':'issue'}, {'type':'decision'}]]"; + String jsonString = "[{'type':'issue'}, {'type':'decision'}]"; List elements = KnowledgeElement.parseJsonString(jsonString); assertEquals("issue", elements.get(0).getType()); assertEquals("decision", elements.get(1).getType()); diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestKnowledgeElement.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestKnowledgeElement.java index 3697b40..c93fe1c 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestKnowledgeElement.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/model/TestKnowledgeElement.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.List; @@ -18,25 +19,17 @@ public void createElement() { element.setId("myId"); element.setKey("myKey"); element.setLink("myLink"); - element.setMacroId("myMacroId"); - element.setPageId(1); element.setSummary("mySummary"); element.setType("myType"); element.setCreator("myAuthor"); element.setLatestAuthor("myLatestAuthor"); - element.setUpdatingDate("0"); + element.setLatestUpdatingDate("0"); List groups = new ArrayList<>(); groups.add("High Level"); groups.add("Git"); element.setGroups(groups); } - @Test - public void testConstructor() { - KnowledgeElement element = new KnowledgeElement("", 0, "", "", "", "", ""); - assertNotNull(element); - } - @Test public void testDescription() { // if desription is null the summary is returned @@ -63,16 +56,6 @@ public void testLink() { assertEquals("", element.getLink()); } - @Test - public void testMacroId() { - assertEquals("myMacroId", element.getMacroId()); - } - - @Test - public void testPageId() { - assertEquals(1, element.getPageId()); - } - @Test public void testSummary() { assertEquals("mySummary", element.getSummary()); @@ -110,10 +93,23 @@ public void testLatestAuthor() { @Test public void testUpdatingDate() { assertEquals("1970-01-01", element.getUpdatingDate()); + element.setUpdatingDate("2042-01-01"); + assertEquals("2042-01-01", element.getUpdatingDate()); + + element.setUpdatingDate("unknown format"); + assertNotNull(element.getUpdatingDate()); } @Test public void testGroups() { - assertEquals("High Level, Git", element.getGroups()); + assertEquals(List.of("High Level", "Git"), element.getGroups()); + assertEquals("High Level, Git", element.getGroupsAsString()); + } + + @Test + public void testEquals() { + KnowledgeElement secondElement = new KnowledgeElement(); + secondElement.setSummary(element.getSummary()); + assertTrue(element.equals(secondElement)); } } \ No newline at end of file diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/persistence/TestKnowledgePersistenceManager.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/persistence/TestKnowledgePersistenceManager.java index 09c2d18..6a23bda 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/persistence/TestKnowledgePersistenceManager.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/persistence/TestKnowledgePersistenceManager.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.ArrayList; import java.util.List; import org.junit.Before; @@ -17,15 +18,14 @@ public class TestKnowledgePersistenceManager { private KnowledgeElement element; + private String jsonString; @Before public void setUp() { ComponentLocator.setComponentLocator(new MockComponentLocator()); - String jsonString = "[{\"link\":\"https://jira-se.ifi.uni-heidelberg.de/browse/ISE2019-103\",\"pageId\":122191874,\"summary\":\"link type to devices\",\"type\":\"Sub-task\",\"key\":\"ISE2019-103\",\"id\":\"1911281118291129ISE2019-103\",\"macroId\":\"28e02c88-0754-40de-9df1-ac403833dd38\",\"description\":\"Link new created type to different devices inside the pop up field.\\r\\n{issue}Should the user be able to add the new masterdata type to other devices while creating it?{issue}\\r\\n{pro}Most of the time, masterdata types aren`t created just for one single device. Adjusting it during creation is intuitive.{pro}\"}]"; + jsonString = "[{\"link\":\"https://jira-se.ifi.uni-heidelberg.de/browse/ISE2019-103\",\"pageId\":122191874,\"summary\":\"link type to devices\",\"type\":\"Sub-task\",\"key\":\"ISE2019-103\",\"id\":\"1911281118291129ISE2019-103\",\"macroId\":\"28e02c88-0754-40de-9df1-ac403833dd38\",\"description\":\"Link new created type to different devices inside the pop up field.\\r\\n{issue}Should the user be able to add the new masterdata type to other devices while creating it?{issue}\\r\\n{pro}Most of the time, masterdata types aren`t created just for one single device. Adjusting it during creation is intuitive.{pro}\"}]"; List elements = KnowledgeElement.parseJsonString(jsonString); element = elements.get(0); - element.setMacroId("42"); - element.setPageId(23); KnowledgePersistenceManager.setBandanaManager(new MockBandanaManager()); } @@ -36,37 +36,32 @@ public void testConstructor() { } @Test - public void testAddElement() { - KnowledgePersistenceManager.addKnowledgeElement(element); - List storedElements = KnowledgePersistenceManager.getElements(23, "42"); + public void testAddElementsViaJson() { + KnowledgePersistenceManager.addKnowledgeElements(jsonString, 23); + List storedElements = KnowledgePersistenceManager.getElements(23); assertEquals(element, storedElements.get(0)); } @Test public void testGetElementsInvalidPageId() { - List storedElements = KnowledgePersistenceManager.getElements(0, "42"); + KnowledgePersistenceManager.addKnowledgeElements(jsonString, 23); + List storedElements = KnowledgePersistenceManager.getElements(0); assertEquals(0, storedElements.size()); } @Test public void testGetElementsInvalidMacroId() { - List storedElements = KnowledgePersistenceManager.getElements(42, null); - assertEquals(0, storedElements.size()); - } - - @Test - public void testRemoveElement() { - KnowledgePersistenceManager.addKnowledgeElement(element); - KnowledgePersistenceManager.removeKnowledgeElement(element.getId()); - List storedElements = KnowledgePersistenceManager.getElements(23, "42"); + List storedElements = KnowledgePersistenceManager.getElements(42); assertEquals(0, storedElements.size()); } @Test public void testRemoveElements() { - KnowledgePersistenceManager.addKnowledgeElement(element); - KnowledgePersistenceManager.removeKnowledgeElements(23, "42"); - List storedElements = KnowledgePersistenceManager.getElements(23, "42"); + List elements = new ArrayList<>(); + elements.add(element); + KnowledgePersistenceManager.addKnowledgeElements(elements, 23); + KnowledgePersistenceManager.removeKnowledgeElements(23); + List storedElements = KnowledgePersistenceManager.getElements(23); assertEquals(0, storedElements.size()); } diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestGetStoredKnowledgeElements.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestGetStoredKnowledgeElements.java index bdee348..e2bf053 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestGetStoredKnowledgeElements.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestGetStoredKnowledgeElements.java @@ -24,11 +24,11 @@ public void setUp() { @Test public void testInvalid() { - assertEquals(Status.BAD_REQUEST.getStatusCode(), knowledgeRest.getStoredKnowledgeElements(0, "").getStatus()); + assertEquals(Status.BAD_REQUEST.getStatusCode(), knowledgeRest.getStoredKnowledgeElements(0).getStatus()); } @Test public void testValid() { - assertEquals(Status.OK.getStatusCode(), knowledgeRest.getStoredKnowledgeElements(1, "1").getStatus()); + assertEquals(Status.OK.getStatusCode(), knowledgeRest.getStoredKnowledgeElements(1).getStatus()); } } diff --git a/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestStoreKnowledgeElements.java b/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestStoreKnowledgeElements.java index 167867e..b266b7c 100644 --- a/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestStoreKnowledgeElements.java +++ b/src/test/java/de/uhd/ifi/se/decision/management/confluence/rest/TestStoreKnowledgeElements.java @@ -23,19 +23,24 @@ public void setUp() { } @Test - public void testInvalidRequest() { + public void testInvalidRequestPageIdZero() { + assertEquals(Status.BAD_REQUEST.getStatusCode(), knowledgeRest.storeKnowledgeElements(null, 0, "").getStatus()); + } + + @Test + public void testInvalidRequestJsonStringNull() { assertEquals(Status.BAD_REQUEST.getStatusCode(), - knowledgeRest.storeKnowledgeElements(null, 1, "", "").getStatus()); + knowledgeRest.storeKnowledgeElements(null, 42, null).getStatus()); } @Test public void testValidRequestJsonStringEmpty() { - assertEquals(Status.OK.getStatusCode(), knowledgeRest.storeKnowledgeElements(null, 1, "1", "").getStatus()); + assertEquals(Status.OK.getStatusCode(), knowledgeRest.storeKnowledgeElements(null, 1, "").getStatus()); } @Test public void testValidRequest() { - assertEquals(Status.OK.getStatusCode(), knowledgeRest - .storeKnowledgeElements(null, 1, "1", "[[{'key' : 'CONDEC-1', 'type':'issue'}]]").getStatus()); + assertEquals(Status.OK.getStatusCode(), + knowledgeRest.storeKnowledgeElements(null, 1, "[{'key' : 'CONDEC-1', 'type':'issue'}]").getStatus()); } }