Coding paradigm: line by line all my opinions are belong to me

Jira - How to Include JSS and CSS resources on every page

Hi all,

Disclaimer 1: The post and approach is 99% based on Jira Behaviors Plugin source code.
Disclaimer 2: The approach described here should be considered a hack/workaround and does not inline with Atlassian Jira Plugin structture and maintainability. 
Update: See the comments section. I would recommend better approach using Servlet Filter. 

You may find yourself requiring to either show/hide some part of Jira UI elements, or extend JIRA built-in edit/search capabilities. If it’s custom field we are talking about - you might consider to implement Jira Custom Field (See Quick Custom Field Types Primer on render options and capabilities).

For some other cases - like Jamie Eclin awesome Behaviors Plugin, or like in my example - hiding some always visible JIRA UI built-in  elements - you might consider different approach to implement. Your options basically are:

  1. Hack and modify velocity/JSP file whatever does render the element in context. It even might be system level plugin - like system Issue operations. In some cases it might require JIRA instance restart and even re-compilation and re-deployment. And thus we will increase maintainability technical debt a lot.
  2. We could manually configure Jira Announcement Banner to include required resources on every page. Thus it will be one-time configuration option and it will be our responsibility to do so and maintain that piece of JSS/CSS snippet. In case our product consists of several plugins - we will endup with long list of configuration items + 1 more unrelated item - which is kind of implementation details only item. So, not so good afterwards (good enough for internal stuff, not so good for giving away).
So, after reconsidering those options - I've chosen the second one + basically the Jamie Eclin approach on Behaviors Plugin. The main reasons to do so were:
  1. Not really a complete hack - could be implemented as valid Atlassian Plugin v2 plugin
  2. Could be automated - no additional configuration needed
  3. Source code for Behaviors Plugin is provided :)
  4. See pt 3.
Implementation 1. Component Simple interface with the only method - void setup(); Serves only as an entry point for resource injection during Jira startup procedure. 2. ComponentImpl The actual component implementation. The purpose is to check whether Jira announcement banner already contains required JSS/CSS and it doesn't the inject them to the banner contents.

public class ComponentImpl implements Component, Startable {
    private static final String HIDE_CREATE_ISSUE_MARKER = "XYZ Plugin Marker - Do not modify!";
    private static final String CREATE_ISSUE_MARKER_START = "<!-- --Start--[" + HIDE_CREATE_ISSUE_MARKER + "]--Start--";
    private static final String CREATE_ISSUE_MARKER_END = "<!-- --End--[" + HIDE_CREATE_ISSUE_MARKER + "]--End-- -->";

    private final ApplicationProperties applicationProperties;

    public ComponentImpl(ApplicationProperties applicationProperties) {
        this.applicationProperties = applicationProperties;
    }

    @Override
    public void setup() throws RuntimeException {
        String alertHeader = applicationProperties.getDefaultBackedText(APKeys.JIRA_ALERT_HEADER);
        String injectedAlertHeader = installHideCreateIssue(alertHeader);
        if (!StringUtils.equals(alertHeader, injectedAlertHeader)) {
            applicationProperties.setText(APKeys.JIRA_ALERT_HEADER, injectedAlertHeader);
            //applicationProperties.setString(APKeys.JIRA_ALERT_HEADER_VISIBILITY, EditAnnouncementBanner.PUBLIC_BANNER);
        }
    }

    @Override
    public void start() throws Exception {
        setup();
    }

    
    private boolean isIncludesCreateIssueMarker(String input) {
        return StringUtils.contains(input, CREATE_ISSUE_MARKER_START)
                && StringUtils.contains(input, CREATE_ISSUE_MARKER_END);
    }

    String getInjection(String comment, String cssDefinitions, String javascript) {
        StringBuilder injection = new StringBuilder(CREATE_ISSUE_MARKER_START).append(comment).append(" -->");

        if (StringUtils.isNotBlank(cssDefinitions)) {
            injection = injection.append("\n<style type=\"text/css\">\n")
                    .append(prepareCleanInject(cssDefinitions))
                    .append("\n</style>");
        }

        if (StringUtils.isNotBlank(javascript)) {
            injection = injection.append("\n<script type=\"text/javascript\">\n")
                    .append(prepareCleanInject(javascript))
                    .append("\n</script>");
        }

        injection = injection.append(CREATE_ISSUE_MARKER_END);

        return injection.toString();
    }

    public String installHideCreateIssue(String alertHeader) throws RuntimeException {
        if (!isIncludesCreateIssueMarker(alertHeader)) {
            alertHeader = new StringBuilder()
                    // PROOF OF concept Items - it really should be done with Permission Schemes, not this hacky way
                    .append(getInjection("", "div#create-issue, div#createItem, ul#opsbar-edit li a#editIssue { display : none; }", ""))

                    .append("\n")
                    .append(StringUtils.defaultString(alertHeader)).toString();
        }

        return alertHeader;
    }
}
3. atlassian-plugin.xml snippet Component and ComponentImpl registration snippet

<component key="myJSSCSSInjectionSnippet" name="My CSS/JSS injection snippet"
    class="com.xyz.ComponentImpl">
    <interface>com.xyz.Component</interface>
</component>
and Voile, we are done :)

NB. If you've found typos or errors, please suggest a correction or edit on github.
comments powered by Disqus