The plugin will be published on the Atlassian Marketplace. If it is not published yet, then the API is not ready.
mvn clean install
(or mvn install
when recompiling).mvn amps:debug
.In atlassian-plugin.xml
, we’ve defined a requirementyogi-extension
that points to our descriptor:
<requirementyogi-extension key="descriptor" class="com.playsql.extensions.reqif.ReqifDescriptor" />
This descriptor provides metadata for Requirement Yogi to display information to the users and build URLs to your requirements.
com.playsql.requirementyogi.api.documentimporter.Descriptor
by default,com.playsql.requirementyogi.api.documentimporter.VersioningDescriptor
if you are able to keep separate versions of your
requirements and freeze old documents.The descriptor is the central point of extension:
public class ReqifDescriptor implements Descriptor {
public static final String DESCRIPTOR_KEY = "reqif";
private final ContentEntityManager ceoManager;
private final AttachmentManager attachmentManager;
private final ApplicationProperties applicationProperties;
public ReqifDescriptor(ContentEntityManager ceoManager, AttachmentManager attachmentManager, ApplicationProperties applicationProperties) {
this.ceoManager = ceoManager;
this.attachmentManager = attachmentManager;
this.applicationProperties = applicationProperties;
}
@Override
public String getKey() {
return DESCRIPTOR_KEY;
}
@Override
public String getDisplayName() {
return "ReqIF 1.0";
}
@Override
public String getDocumentationURL() {
return null; // Optional, used in the administration, for information only.
}
@Override
public String getAdminURL() {
return null; // Optional, used in the administration, for information only.
}
@Override
public int getVersion() {
return 1;
}
/** Build the URL to a requirement */
@Override
public void fillDocumentDetails(Requirement bean, ImportedRef importedRef, Map<String, Object> stash) {
String baseUrl = (String) stash.get("base-url");
if (baseUrl == null) {
baseUrl = applicationProperties.getBaseUrl(UrlMode.ABSOLUTE);
stash.put("base-url", baseUrl);
}
ContentEntityObject attachment = (ContentEntityObject) stash.get("ceo-" + importedRef.getDocumentId());
if (attachment == null) {
attachment = ceoManager.getById(Long.parseLong(importedRef.getDocumentId()));
stash.put("ceo-" + importedRef.getDocumentId(), attachment);
}
String externalUrl = baseUrl + "/" + importedRef.getDescriptorKey() + "/view.action?key=" + bean.getSpaceKey() +
"&id=" + importedRef.getDocumentId() +
"&focus=" + importedRef.getMarkerInDocument();
importedRef.set(attachment != null ? attachment.getDisplayTitle() : "ReqIF file", externalUrl);
}
/** Get the name of a document, since only the documentId is stored by Requirement Yogi. In our situation,
* the document is an Attachment of a Confluence page. */
@Override
public Document getDocumentMetadata(@Nonnull DocumentId documentId) {
Attachment attachment = attachmentManager.getAttachment(Long.parseLong(documentId.getId()));
if (attachment != null) {
return new Document(documentId, attachment.getDisplayTitle(), applicationProperties.getBaseUrl(UrlMode.ABSOLUTE) + attachment.getUrlPath());
} else {
return new Document(documentId, "Attachment " + documentId.getId(), null);
}
}
}
In atlassian-plugin.xml
, we’ve defined a web-item
to define a new tab in Requirement Yogi:
<web-item key="ry-nav-import" name="Import tab in the nav" section="requirementyogi/space-nav-bar" weight="110">
<label>Import from ReqIF</label>
<link linkId="reqif-form">${req.contextPath}/reqif/view.action?key=${action.getSpaceKey()}</link>
</web-item>
In ReqifAction
, we’ve implemented this new tab:
list()
lists ReqIF files, and uses reqif-list.vm
to display the tab,config()
lets users add/update/delete files,In ReqifDocumentManager
, we’ve gathered the core functionality of the plugin:
importDocument()
parses the XML file then calls ExternalAPI.importDocument()
with the appropriate data,@EventListener on(AttachmentRemoveEvent)
is triggered by Confluence when a document is deleted, and in turn
tells Requirement Yogi to delete associated requirements. public ImportResults importDocument(Attachment attachment, ReqifConfig reqifConfig, ConfluenceUser user) throws ParseException, PermissionException {
InputStream attachmentData = attachmentManager.getAttachmentData(attachment);
try {
String documentTitle = attachment.getDisplayTitle();
String documentId = attachment.getIdAsString();
String spaceKey = attachment.getSpaceKey();
UIReqifDocument reqifDocument = parse(attachmentData);
return externalAPI.importDocument(new DocumentId(ReqifDescriptor.DESCRIPTOR_KEY, spaceKey, documentId), documentTitle, user, (api) -> {
for (UIRequirement uiRequirement : reqifDocument.getRequirements()) {
com.playsql.requirementyogi.ao.Requirement requirement = new com.playsql.requirementyogi.ao.Requirement();
requirement.setSpaceKey(spaceKey);
requirement.setKey(uiRequirement.getKey());
requirement.setHtmlExcerpt(uiRequirement.getHtml());
requirement.setProperties(uiRequirement.getProperties());
requirement.setOrigin(new ImportedRef(ReqifDescriptor.DESCRIPTOR_KEY, documentId, null, uiRequirement.getIdentifier()));
api.saveRequirement(requirement);
}
});
} finally {
ReqifUtils.closeSilently(attachmentData);
}
}
As shown, the RY API doesn’t require that you use actual files, and our ReqIF example relies on attachments.
You could imagine a plugin which receives requirements from a network or from a database. As long as importDocument()
can be called with the list of requirements, you will be
able to manage them.
There are many examples in the ReqIF plugin that should help you. Please specifically look at how we’ve resolved the following problems.
Of course! Just don’t publish the ReqIF extension without modifications, since we are already on it… Atlassian has regular guidelines for plugins on the Marketplace.