Tags:
not added yet
Table of Contents
Add support for special JIRA custom fields to the JIRA synchronizationThe Synchronization of Trackers with Atlassian JIRA© by default only supports
JIRA custom fields defined by JIRA Add-Ons and Extensions cannot be synchronized by default, because in the field schema provided by the JIRA REST API, the custom field type is identified by an opaque identifier.
E.g. Checklist for Jira: "customfield_10201": { "required": false, "schema": { "type": "array", "items": "checklist-item", "custom": "com.okapya.jira.checklist:checklist", "customId": 10201 }, "name": "DoD", ... You need additional knowledge about each specific custom field, e.g. Modifying checklists using a REST API, that is not available via the JIRA REST API itself, in order to
Custom field extensionsIn codeBeamer 21.05 (Dorothy) and newer, you can add support for specific JIRA custom fields to the Synchronization of Trackers with Atlassian JIRA© via the @CustomField extension framework. Example CodeA fully featured example adding support for Checklist for Jira fields can be found at https://github.com/intland/cb-jira-checklist Development environmentDeveloping own/custom field extensions in Java requires a Java 8 or newer Standard Edition (SE) Java Development Kit (JDK). To setup the development environment you can adopt the Gradle build script published with the example and follow the instructions to configure your IDE. Alternatively you can setup the build manually by adding frameworks/libraries:
You also need cb.jar from the directory ~/CB-../tomcat/webapps/cb/WEB-INF/lib of the CodeBeamer 2021 or newer installation, you want to develop new @CustomField extensions for. @CustomFieldA Custom field extension is a Java class, that must be annotated as @CustomField and @Component.
If the JIRA custom field has a primitive type, e.g. string, that is compatible with the mapped codeBeamer field type, e.g. Text, then an empty class with only the annotations is already sufficient.
For example: Picker Color Custom Field: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.intland.codebeamer.controller.jira.CustomField; @Component("ru.andreymarkelov.atlas.plugins.colorfields:picker-color-custom-field") @CustomField(type = "Color") public class ColorPickerField { }
But with more complex field types, you may also need to add support for specific aspects of custom field schema and value handling:
For example: Checklist for Jira: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.intland.codebeamer.controller.jira.CustomField; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { ... }
All methods of @CustomField extensions must be
Context specific information required by a method, must be declared as parameters. Only the parameter type is relevant, not the parameter name or position.
@MetaDataIf you need to make adjustments to the JIRA custom field schema mapping, then your @CustomField extension must also provide a @CustomField.MetaData method.
For example: Checklist for Jira: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.node.ObjectNode; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.controller.jira.JiraImportController; import com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { @CustomField.MetaData public void setMetaData(TrackerLayoutLabelDto field, ObjectNode metaData, JiraImportController controller) { // In the JIRA REST-API schema, a checklist field is an array of checklist items, but in CB it's a single value WIKI field' field.setMultipleSelection(Boolean.FALSE); // The default value (options) of a checklist, are available in the "allowedValues" property. Only status ID is available with Meta API's. The status name is not.' field.setDefaultValue(importChecklist(null, metaData.remove("allowedValues"), controller)); // Place checklist on own row with full width field.setBreakRow(Boolean.TRUE); field.setColspan(Integer.valueOf(3)); } } @ImportFieldValueIn order to convert the JIRA custom field value, e.g. Array of checklist items, into the appropriate codeBeamer field value, e.g. Wiki markup, your @CustomField extension must provide a @CustomField.ImportFieldValue method.
For example: Checklist for Jira: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.node.JsonNode; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.controller.jira.JiraImportController; import com.intland.codebeamer.controller.jira.JiraTrackerSyncConfig; import com.intland.codebeamer.wiki.plugins.ChecklistPlugin; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { /** * Convert a <a href="https://okapya.atlassian.net/wiki/spaces/CHKDOC/pages/270172389/Modifying+Checklists+using+a+REST+API">Checklist for JIRA<a> * into a {@link ChecklistPlugin} body * @param tracker is the JIRA tracker sync configuration * @param checklist is the checklist as returned from Jira * @param controller to {@link JiraImportController#check4ByteChars(String)} * @return the checklist converted into a {@link ChecklistPlugin} body */ public JsonNode jira2cb(JiraTrackerSyncConfig tracker, JsonNode checklist, JiraImportController controller) { ... } @CustomField.ImportFieldValue public String importChecklist(JiraTrackerSyncConfig tracker, JsonNode checklist, JiraImportController controller) { return ChecklistPlugin.wrapChecklist(jira2cb(tracker, checklist, controller)); } } The Checklist for Jira extension converts the Array of checklist items into an array of Checklist Plugin items (via jira2cb()) and wraps them into Checklist Plugin markup. The Checklist Plugin is also a custom codeBeamer Wiki Plugin, whose example source code is attached. @ImportFieldHistoryValueIn order to also import the JIRA issue changelog, the JIRA changelog entries, must be converted into appropriate codeBeamer TrackerItemHistoryConfigurations. { "from" : "10000", "fromString" : "To Do", "to" : "3", "toString" : "In Progress" } the TrackerItemHistoryConfiguration's change op should be Set and the oldValue and newValue should be the old and new status.
For multiple (array) value fields, e.g. assignee: { "from" : null, "fromString" : null, "to" : "lzoltan", "toString" : "Luspai Zoltan" } JIRA typically provides multiple changelog entries for each
value. In this case, the resulting TrackerItemHistoryConfiguration's change op should be Add (the newValue) or Remove (the oldValue).
But each JIRA custom field is free to write into the JIRA changelog whatever it wants!
E.g. Checklist for Jira, although the JIRA field is defined to be an Array of checklist items, only provides a single changelog entry, but that can contain any number of checklist changes, in form of a multi-line string, where an x) at the start of a new line marks the begin of the x.th change. { "from" : null, "fromString": "1) [Checked] Deploy to staging server\n\r2) [Checked] Pull request", "to" : null, "toString" : "1) [Status changed, Unchecked] (In Progress) Deploy to staging server\n\r2) [Unchecked] Pull request" }
In order to decode the JIRA custom field changelog
a @CustomField extension can provide a @CustomField.ImportFieldHistoryValuemethod, that is called twice per JIRA changelog entry
If the Jira change log string value might contain 4-byte UTF-8 characters, it is necessary to pass such Strings through JiraImportController.check4ByteChars(string)!
The method has access to the following context information via parameters of the appropriate type:
For example: Checklist for Jira: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.controller.jira.JiraImportController; import com.intland.codebeamer.wiki.plugins.ChecklistPlugin; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { /** * Parse the information about JIRA checklist changes from a JIRA issue changelog <code>fromString</code> or <code>toString</code> * @param lines is a <code>fromString</code> or <code>toString</code>, that can contain multiple lines of checklist changes, one line per changed checklist item * @param controller to {@link JiraImportController#check4ByteChars(String)}, or null * @return a Map of the parsed checklist item changes */ @CustomField.ImportFieldHistoryValue public Map<Integer,Change> getItemChanges(String lines, JiraImportController controller) { Map<Integer,Change> result = ... return result; } } parses the changelog item string into a Map of Changes per Change ID. This code is quite complex and not shown here, but the example source code is attached.
Please note, that this would not be a valid oldValue or newValue for a codeBeamer Checklist history entry, because a Checklist field is a single value Wikitext field, so the oldValue and newValue must be Checklist Wiki markup!
@ImportFieldChangeIn order to convert an oldValueObject and a newValueObject (as provided by @ImportFieldHistoryValue) into an appropriate TrackerItemHistoryConfiguration, a @CustomField extension can provide a @CustomField.ImportFieldChange method.
The method should process the specified TrackerItemHistoryConfiguration,
For example: Checklist for Jira package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.node.JsonNode; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.controller.jira.JiraImportController; import com.intland.codebeamer.controller.jira.JiraTrackerSyncConfig; import com.intland.codebeamer.manager.util.ImportStatistics; import com.intland.codebeamer.manager.util.ImporterSupport; import com.intland.codebeamer.manager.util.TrackerItemHistoryConfiguration; import com.intland.codebeamer.persistence.dto.TrackerItemDto; import com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto; import com.intland.codebeamer.wiki.plugins.ChecklistPlugin; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { public static Map<Integer,Change> getChanges(Object value) { return value instanceof Map ? (Map)value : null; } /** * Convert the specified incremental checklist change into an appropriate Checklist Wikitext change * @param tracker is the tracker sync configuration * @param item is the tracker item to import * @param fieldChange is the incremental custom checklist field change * @param importer is the current import cache/support * @param statistic are the current import statistics */ @CustomField.ImportFieldChange public void buildTrackerItemHistoryConfiguration(JiraTrackerSyncConfig tracker, TrackerItemDto item, TrackerItemHistoryConfiguration fieldChange, ImporterSupport importer, ImportStatistics statistic) { TrackerLayoutLabelDto field; Map<Integer,Change> newValues; if (fieldChange != null && (field = fieldChange.getField()) != null && (newValues = getChanges(fieldChange.getNewValueObject())) != null && newValues.size() > 0) { Map<Integer,Change> oldValues = getChanges(fieldChange.getOldValueObject()); fieldChange.setOldValueObject(null); fieldChange.setNewValueObject(null); JsonNode oldItems = getChecklist(tracker, item, field, importer, statistic); Checklist modified = new Checklist(oldItems.deepCopy()); for (Map.Entry<Integer,Change> change : newValues.entrySet()) { Change newItem = change.getValue(); Change oldItem = oldValues.get(change.getKey()); if (newItem.wasAdded()) { newItem.apply(tracker, modified.addItem()); } else if (newItem.wasRemoved()) { modified.removeItem(StringUtils.defaultString(oldItem != null ? oldItem.getName() : null, newItem.getName())); } else if (newItem.wasReordered()) { modified.reorderItems(); } else { // We must NOT add the item here, if no such item exists (any more), because it was most probably removed locally and MUST remain removed ! ObjectNode node = modified.getItem(StringUtils.defaultString(newItem.wasRenamed() && oldItem != null ? oldItem.getName() : null, newItem.getName())); if (node != null) { if (!newItem.isHeader() && oldItem != null && oldItem.isHeader()) { node.remove(HEADER); } newItem.apply(tracker, node); } } } modified.applyOrder(getChecklist(tracker, item, field)); fieldChange.setOldValue(ChecklistPlugin.wrapChecklist(oldItems)); fieldChange.setNewValue(ChecklistPlugin.wrapChecklist(modified.getItems())); setChecklist(item, field, modified.getItems(), importer); } } } In order to convert incremental changes to the Checklist for Jira into the appropriate oldValue and newValue of the codeBeamer Checklist Wiki field before and after the change, the ChecklistForJira extension needs to maintain a cache of checklist values per field.
But since a custom field extension must be stateless, this cache must be stored in the import context, represented by the ImporterSupport instance:
The full example source code is attached).
@ResolveUpdateConflictsUpdate conflicts can only occur upon the import of changes for an already existing target item, if the same field value was modified in JIRA and an in codeBeamer concurrently.
If a @CustomField extension wants to get involved in/take control of conflict resolution, it can provide a @CustomField.ResolveUpdateConflictsmethod.
The method has access to the following context information via parameters of the appropriate type:
For example: Checklist for Jira does not @ResolveUpdateConflicts
@ImportFinishedIf a @CustomField extension wants to get called, after the import processing of an item is finished, it can provide a @CustomField.ImportFinished method.
Please note, that this method will get called once per imported item and per item field, where the @CustomField extension is associated with.
For example: Checklist for Jira package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.node.JsonNode; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.manager.util.ImporterSupport; import com.intland.codebeamer.persistence.dto.TrackerItemDto; import com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto; import com.intland.codebeamer.wiki.plugins.ChecklistPlugin; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { /** * Remove the cached checklist for the specified field of the specified item, after the item import is finished * @param item is the current tracker item to import * @param field is the checklist field * @param importer is the current import cache/support */ @CustomField.ImportFinished public void resetChecklist(TrackerItemDto item, TrackerLayoutLabelDto field, ImporterSupport importer) { Map<Integer,JsonNode> checklists = getChecklists(importer, false); if (checklists != null) { if (field != null) { checklists.remove(field.getId()); } else { checklists.clear(); } } } } The ChecklistForJira extension needs to reset its cache of checklist values per field after each item. @ExportFieldValueIn order to convert the codeBeamer field value, e.g. Wiki markup, into the appropriate JIRA custom field value, e.g. Array of checklist items, your @CustomField extension must provide a @CustomField.ExportFieldValue method.
For example: Checklist for Jira: package com.intland.codebeamer.controller.jira; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.node.JsonNode; import com.intland.codebeamer.controller.jira.CustomField; import com.intland.codebeamer.controller.jira.JiraImportController; import com.intland.codebeamer.controller.jira.JiraTrackerSyncConfig; import com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto; import com.intland.codebeamer.wiki.plugins.ChecklistPlugin; @Component("com.okapya.jira.checklist:checklist") @CustomField(type="WikiText", of="Checklist") public class ChecklistForJiraField { /** * Convert a {@link ChecklistPlugin} body into a * <a href="https://okapya.atlassian.net/wiki/spaces/CHKDOC/pages/270172389/Modifying+Checklists+using+a+REST+API">Checklist for JIRA<a> * @param tracker is the JIRA tracker sync configuration * @param checklist is the {@link ChecklistPlugin} body * @return the converted {@link ChecklistPlugin} body */ public JsonNode cb2jira(JiraTrackerSyncConfig tracker, JsonNode checklist) { ... } @CustomField.ExportFieldValue public JsonNode exportChecklist(JiraTrackerSyncConfig tracker, String markup) { return cb2jira(tracker, ChecklistPlugin.unwrap(markup)); } } Upon import, the Checklist for Jira extension converts the Array of checklist items into an array of Checklist Plugin items (via jira2cb()) and wraps them into Checklist Plugin markup. DeploymentTo deploy your custom field extension(s), the Java code must be compiled and the resulting *.class file(s) must be uploaded to the appropriate sub-directory under ~/CB-.../tomcat/webapps/cb/WEB-INF/classes/..., where the sub-directory is the equivalent of the extension's package name. Finally you have to restart CodeBeamer, for your newly deployed classes to get loaded. |
Fast Links
codebeamer Overview codebeamer Knowledge Base Services by Intland Software |
This website stores cookies on your computer. These cookies are used to improve your browsing experience, constantly optimize the functionality and content of our website, furthermore helps us to understand your interests and provide more personalized services to you, both on this website and through other media. With your permission we and our partners may use precise geolocation data and identification through device scanning. You may click accept to consent to our and our partners’ processing as described above. Please be aware that some processing of your personal data may not require your consent, but you have a right to object to such processing. By using our website, you acknowledge this notice of our cookie practices. By accepting and continuing to browse this site, you agree to this use. For more information about the cookies we use, please visit our Privacy Policy.Your preferences will apply to this website only.