NOW LOADING

Integrating with another system

See it in action

The plugin will be published on the Atlassian Marketplace. If it is not published yet, then the API is not ready.

Download and compile the ReqIF Extension plugin

The descriptor

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.

  • Use com.playsql.requirementyogi.api.documentimporter.Descriptor by default,
  • Use 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);
        }
    }
}

Build the UI for your users

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,

Calling the API

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.

Can I publish a public extension to Requirement Yogi?

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.