storage

Embedding Javascript in JIRA with Custom Fields

Did you know you can embed Javascript in JIRA via custom fields? You may wonder, what’s the point? To you good sir, or madame, I say that the point is 75% because you can, and 75% because it enables customization of the JIRA interface without having to resort to writing JIRA plug-ins. And because JIRA ships with jQuery 1.8.3 in JIRA 6, it makes writing complex Javascript a breeze.

Just recently I needed to customize the appearance of the built-in Environment field in the Details section when viewing an issue. To reduce configuration complexity Atlassian actually embeds the look and feel of some of the default fields inside velocity templates which are themselves includes as resources in JIRA JAR files.

In other words, you cannot modify these settings through normal means.

So I created a custom field and added it to my default view and field configuration. I called the custom field "JS Custom Field". It is the one field into which I’ll embed all of my Javascript. I decided to use a single-field approach to centralize my changes as well as preserving the descriptions of the fields I may want to modify with the Javascript. Because that’s where you actually embed your Javascript — in the descriptions of the fields. Crazy, right?

Right out of the gate I wanted to accomplish two things:

  • Customize the look and feel of the Environment field in the Details section when viewing an issue.
  • Transform a multi-select custom field I created into an autocomplete text box.

Oh, and while the custom field that will contain my Javascript has to be added to the DOM in order for its Javascript to be loaded, I do not actually ever want any users seeing it. So the Javascript must also ensure that the custom field is never seen, and can never be made to be seen.

And thus this little bit of Javascript was born. It:

  • Customize the look and feel of the Environment field in the Details section when viewing an issue.
  • Transforms a multi-select custom field into an autocomplete box.
  • Prevents users from ever seeing or making visible the custom field that contains the Javascript.

Please note that my custom field IDs will be different than yours. You should use Chrome’s Developer Tools (or some other browser’s) to determine the IDs of your custom fields.

<script type="text/javascript">
    
    /**
     * The ID of the custom field that contains this javascript and that will
     * be hidden from the user.
     */
    JAVASCRIPT_FIELD_ID = '10313';

    /**
     * The common usage pattern for the javascript field ID.
     */ 
    JAVASCRIPT_CUSTOM_FIELD_STR = 'customfield_' + JAVASCRIPT_FIELD_ID;

    /**
     * The Environment val selector.
     */
    ENV_VAL_SEL = '#environment-val';

    /**
     * The Environment field selector.
     */
    ENV_FIELD_SEL = '#field-environment';

    /**
     * The selector to use to get the Environment field if it's been detwixed.
     */
    ENV_FIELD_DETWIXED_SEL = '#field-environment.user-content-block';

    /**
     * The vC Ops Version custom field ID.
     */
    VC_OPS_VER_FIELD_ID = '10305';

    /**
     * The vC Ops Version field selector.
     */
    VC_OPS_VER_FIELD_SEL = '#customfield_' + VC_OPS_VER_FIELD_ID;

    /**
     * The selector to get the "Edit Issue" button.
     */
    EDIT_ISSUE_BUTTON_SEL = '#edit-issue';

    /**
     * The selector to get the 'All' or 'Custom' links that change the view of 
     * the field picker popup.
     */
    FIELD_PICKER_VIEW_SEL = '#inline-dialog-field_picker_popup a[class*="configurable"]';

    /**
     * The selector to get the control in the field picker popup that toggles
     * the visibility of the custom field in the editor dialog.
     */
    FIELD_PICKER_TOGGLE_SEL = '[data-field-id="' + JAVASCRIPT_CUSTOM_FIELD_STR + '"]';

    /**
     * The selector to get the custom field on the editor dialog when the 
     * 'Custom' view is enabled.
     */
    EDIT_FIELD_CUSTOM_VIEW_SEL = '#qf-field-' + JAVASCRIPT_CUSTOM_FIELD_STR;

    /**
     * The selector to get the custom field on the editor dialog when the 
     * 'All' view is enabled.
     */
    EDIT_FIELD_ALL_VIEW_SEL = 'div.field-group:has(textarea#' + JAVASCRIPT_CUSTOM_FIELD_STR + ')';

    /**
     * The selector to get the editor popup.
     */
    EDIT_ISSUE_DIALOG_SEL = '#edit-issue-dialog';

    /**
     * The selector to get the "Configure Fields" button on the edit dialog.
     */
    CONFIG_FIELDS_BUTTON_SEL = '#qf-field-picker-trigger';

    /**
     * The selector to get the field picker popup.
     */
    FIELD_PICKER_POPUP_SEL = '#inline-dialog-field_picker_popup';

    /**
     * The field that stores the interval ID set when listening for the field
     * picker popup.
     */ 
    var fieldPickerPopupIntervalId = -1;

    /**
     * Converts a multiselect control into a multi-select text field with 
     * autocomplete capabilities.
     *
     * multiSelector The multiselect control's selector.
     */
    function convertMultiIntoAutoCompleteTextArea(multiSelector)
    {
        if ($(multiSelector+ "-textarea").length) return;
        new AJS.MultiSelect({
            element: $(multiSelector),
            itemAttrDisplayed: "label",
            errorMessage: AJS.params.multiselectComponentsError
        });
    }

    function removeMultiSelectNoneOption(multiSelector)
    {
        $(multiSelector + " option[value='-1']").remove();
    }

    /**
     * Removes the twixification of the Environment field on the issue's view
     * page inside the Details section. This makes the Environment field a 
     * normal text area, such as the Description field.
     */
    function deTwixifyEnvField()
    {
        // Modify the environment field in the issue's details view.
        div_env_val = $(ENV_FIELD_SEL);
        if (!div_env_val.length) return;

        div_env_val.attr('class', 
            'field-ignore-highlight editable-field inactive');

        div_env_field = $(ENV_FIELD_SEL);
        div_env_field.attr('class', 'user-content-block');
        div_env_field.find('div:first-child').removeClass();

        div_env_val.find('a:first-child').remove();
    }

    /**
     * Removes the custom field from the editor dialog.
     */
    function removeEditField()
    {
        target = $(EDIT_FIELD_CUSTOM_VIEW_SEL);
        if (!target.length) target = $(EDIT_FIELD_ALL_VIEW_SEL);
        target.remove();
    }

    /**
     * Removes the anchor that allows a user to enable the custom field on the
     * editor dialog.
     */
    function removeFieldPicker()
    {
        $(FIELD_PICKER_TOGGLE_SEL).remove();
    }

    function listenForEditIssueDialog()
    {
        // We only care about when the edit issue dialog is available.
        if (!$(EDIT_ISSUE_DIALOG_SEL).length) return true;
        
        // Get rid of this listener as quickly as possible, it's expensive.
        JIRA.unbind(JIRA.Events.NEW_CONTENT_ADDED, listenForEditIssueDialog);

        // Hide the javascript fields from the editor.
        removeEditField();

        // If the user decides to look at the field picker, don't show
        // the option to enable the javascript fields.
        $(CONFIG_FIELDS_BUTTON_SEL).one('click', registerFieldPickerPopupListener);
    }

    function registerFieldPickerPopupListener()
    {
        // Don't register it if it's currently registered.
        if (fieldPickerPopupIntervalId > -1) return;

        fieldPickerPopupIntervalId = 
            window.setInterval('listenForFieldPickerPopup()', 50);
    }

    function listenForFieldPickerPopup()
    {
        // We only care about when the field picker popup is available.
        if (!$(FIELD_PICKER_POPUP_SEL).length) return true;

        fieldPickerPopupIntervalId = -1;
        window.clearInterval(fieldPickerPopupIntervalId);
        
        // Get rid of this listener as quickly as possible, it's expensive.
        JIRA.unbind(JIRA.Events.NEW_CONTENT_ADDED, listenForFieldPickerPopup);

        // Hide the field picker links from the user.
        removeFieldPicker();

        // Hide the editor fields again as well in case the field picker
        // view changed. This can result in the editor fields being added
        // back to the editor popup.
        removeEditField();

        // If the user decides to change the field picker's view then we need
        // to prepare to remove them once again.
        $(FIELD_PICKER_VIEW_SEL).one('click', registerFieldPickerPopupListener);
    }

    function registerEditIssueDialogListener()
    {
        // This is kind of expensive, but necessary. That's why we're limiting
        // it to when the user clicks the Edit button instead of leaving it
        // enabled at all times.
        JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, listenForEditIssueDialog);
    }

    function listenForEnvironmentField()
    {
        if ($(ENV_FIELD_DETWIXED_SEL).length) return true;
        deTwixifyEnvField();
    }

    function listenForVcOpsVersionField()
    {
        if (!$(VC_OPS_VER_FIELD_SEL).length) return true;
        removeMultiSelectNoneOption(VC_OPS_VER_FIELD_SEL);
        convertMultiIntoAutoCompleteTextArea(VC_OPS_VER_FIELD_SEL);
    }

    function processVcOpsVersionField()
    {
        // Convert the vC Ops Version select box into an autocomplete text area.
        AJS.toInit(listenForVcOpsVersionField);
        JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, listenForVcOpsVersionField);
    }

    function processEnvironmentField()
    {
        // Silly Atlassian, twix are for kids!
        AJS.toInit(listenForEnvironmentField);
        JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, listenForEnvironmentField);
    }

    // When the 'Edit Issue' button is clicked we want to listen for the
    // appearance of the edit dialog.
    $(EDIT_ISSUE_BUTTON_SEL).click(registerEditIssueDialogListener);

    // Process the fields.
    processVcOpsVersionField();
    processEnvironmentField();

</script>
software development, storage, virtualization

EMC VSI 4.0

When I agreed to join EMC I was not sure what to expect. I’d worked in academia, a start-up, dabbled in reverse engineering, and contributed many projects to the open source community. However, EMC is unique with respect to all of the positions I’ve previously held or projects I’ve created. Higher education is a lot like open source, there is an implicit freedom — you get to play. A start-up is small and fast, and reverse engineering is all about figuring out how to gleam the rubik’s cube. EMC is the titan, the behemoth, the first IBM-like company that I decided to work for. Would I be consumed, or would I manage to stay relevant even as employee number 123456?

Eight months later, I have my answer. I’m tremendously proud to introduce to the world, in all its gory detail:

EMC VSI Icon

EMC Virtual Storage Integrator (VSI) 4.0 for VMware vSphere

Continue reading