Integrate Jira Cloud issues with merge requests and push events in GitLab


Get Started

Not the template you're looking for? Browse more.

About the template


Seamless synchronisation of merge requests and push events with Jira Cloud. tickets. Get started to learn more.

About ScriptRunner Connect


What is ScriptRunner Connect?

ScriptRunner Connect is an AI assisted code-first (JavaScript/TypeScript) integration platform (iPaaS) for building complex integrations and automations.

Can I try it out for free?

Yes. ScriptRunner Connect comes with a forever free tier.

Can I customize the integration logic?

Absolutely. The main value proposition of ScriptRunner Connect is that you'll get full access to the code that is powering the integration, which means you can make any changes to the the integration logic yourself.

Can I change the integration to communicate with additional apps?

Yes. Since ScriptRunner Connect specializes in enabling complex integrations, you can easily change the integration logic to connect to as many additional apps as you need, no limitations.

What if I don't feel comfortable making changes to the code?

First you can try out our AI assistant which can help you understand what the code does, and also help you make changes to the code. Alternatively you can hire our professionals to make the changes you need or build new integrations from scratch.

Do I have to host it myself?

No. ScriptRunner Connect is a fully managed SaaS (Software-as-a-Service) product.

What about security?

ScriptRunner Connect is ISO 27001 and SOC 2 certified. Learn more about our security.

Template Content


README

Scripts

TypeScriptOnGitLabMergeRequestEvents
Merge request events

README


📋 Overview

This integration between GitLab and Jira Cloud ensures merge requests are aligned with project management protocols. It listens to merge request events from GitLab and checks the status of the associated Jira ticket. If the ticket is not approved, the merge request is blocked. Additionally, the script listens to push events from GitLab and updates the corresponding Jira ticket with the branch name, ensuring seamless synchronization between code changes and project tracking.

🖊️ Setup

  • Setup the API connection with the Gitlab and Jira Cloud connectors.

  • Create a custom field in Jira Cloud in order to store the Gitlab branch name.

    • Open your Jira Cloud instance and click on the cog icon next to your avatar.
    • Select Issues, then navigate to Custom fields and click on Create custom field.
    • Select the short text (plain text only) option.
    • Enter Branch name into the Name input. If you want to use another name, you need to also specify it under parameters.
    • Check the issue screens that are relevant to the issue type you wish to sync.
    • Click on Update.
  • In ScriptRunner Connect, navigate to Parameters and ensure the custom field name matches what was setup in the previous step.

  • Following the event listener setup instructions to create the webhooks in Gitlab.

🚀 Usage

Push a change into a Gitlab repository or create a merge request to trigger the scripts. Please note that the branch name is expected to be in the format: JIRA-TICKET/BRANCH-NAME. If you wish to use a different format, update the getJiraTicketKey function in the Utils script.

API Connections


TypeScriptOnGitLabMergeRequestEvents

import { GetIssueResponseOK } from '@managed-api/jira-cloud-v3-core/types/issue';
import GitLab from './api/gitlab';
import JiraCloud from './api/jira/cloud';
import { MergeRequestEvent } from '@sr-connect/gitlab/events';
import { createParagraphInADF, getJiraTicketKey } from './Utils';

/**
 * Entry point to Merge request events event
 *
 * @param event Object that holds Merge request events event data
 * @param context Object that holds function invocation context data
 */
export default async function(event: MergeRequestEvent, context: Context<EV>): Promise<void> {
    if (context.triggerType === 'MANUAL') {
        console.error('This script is designed to be triggered externally. If you need to trigger the script manually, consider emulating the event with a test payload instead.');
        return;
    }

    //Add a Jira comment with the merge request description on a successful merge
    if (event.object_attributes.action === 'merge' && event.object_attributes.state === 'merged') {
        const { description,  source_branch } = event.object_attributes;

        const jiraTicket = getJiraTicketKey(source_branch);
        console.log(`Going to add a comment on the Jira ticket ${jiraTicket} with the merge description: ${description}`);
        const issue = await JiraCloud.Issue.getIssue({
            issueIdOrKey: jiraTicket,
        });
        await JiraCloud.Issue.Comment.addComment({
            issueIdOrKey: issue.key,
            body: {
                body: createParagraphInADF(description)
            }
        });
        return;
    }

    //When a merge request is opened or updated, block the merge if certain conditions are not met
    if (event.object_attributes.state === 'opened') {
        const { source_branch } = event.object_attributes;
        const jiraTicket = getJiraTicketKey(source_branch);

        let issue: GetIssueResponseOK;
        try {
            issue = await JiraCloud.Issue.getIssue({
                issueIdOrKey: jiraTicket,
            });             
        } catch(error) {
            console.error('Error whilst fetching ticket', error);
            throw error;
        }
        const status = issue ? issue.fields.status.name : undefined;

        //Block the merge request if the Jira ticket is not approved or not found
        if (!status || status !== 'Approved') {
            console.warn('Ticket is not approved so will create a thread: ', { ticket: jiraTicket, merge_request_id: event.object_attributes.iid })
            await GitLab.fetch(`/api/v4/projects/${event.project.id}/merge_requests/${event.object_attributes.iid}/discussions`, {
                method: 'POST',
                body: JSON.stringify({
                    body: issue ? `The Jira ticket ${jiraTicket} is not approved.` : `${jiraTicket} not found in Jira.`
                })
            })
            return;
        }
    } else {
        console.warn('Ignoring the merge request event because status is not opened');
    }
}
TypeScriptOnGitLabPushEvents

import { getBranchCustomField, getJiraTicketKey } from './Utils';
import JiraCloud from './api/jira/cloud';
import { PushEvent } from '@sr-connect/gitlab/events';

/**
 * Entry point to Push event
 *
 * @param event Object that holds Push event data
 * @param context Object that holds function invocation context data
 */
export default async function(event: PushEvent, context: Context<EV>): Promise<void> {
    if (context.triggerType === 'MANUAL') {
        console.error('This script is designed to be triggered externally. If you need to trigger the script manually, consider emulating the event with a test payload instead.');
        return;
    }

    const branchCustomField = await getBranchCustomField(context.environment.vars.BRANCH_NAME_CUSTOM_FIELD_NAME);
    const { ref } = event;
    if (ref === 'refs/heads/main') {
        console.warn('Ignoring push events to the main branch');
        return;
    }

    //Assuming the branch is in the format: JIRA-TICKET/BRANCH-NAME
    const branchName = ref.split('refs/heads/')[1];
    const jiraTicket = getJiraTicketKey(branchName);

    //Get the branch name of the Jira ticket
    const issue = await JiraCloud.Issue.getIssue({
        issueIdOrKey: jiraTicket,
        fields: [branchCustomField]
    });

    if (issue.fields[branchCustomField] !== branchName) {
        await JiraCloud.Issue.editIssue({
            issueIdOrKey: issue.key,
            body: {
                update: {
                    [branchCustomField]: [{
                        set: branchName
                    }]
                }
            }
        })
    }
}
TypeScriptUtils

import JiraCloud from './api/jira/cloud';

export function getJiraTicketKey(branchName: string): string {
    //Assuming the branch is in the format: JIRA-TICKET/BRANCH-NAME
    return branchName.split('/')[0];
}

export async function getBranchCustomField(fieldName: string): Promise<string> {
    // Get all custom issue fields in Jira Cloud
    const customFields = await JiraCloud.Issue.Field.getFields();
    console.log(customFields)

    // Find the custom field where the Gitlab branch name is stored
    const customField = customFields.find(f => f.name === fieldName);

    // If no such field is found, throw an error
    if (!customField?.key) {
        throw Error(`Custom field ${fieldName} not found in Jira Cloud`);
    }
    return customField.key;
}

export function createParagraphInADF(text: string) {
    return {
        type: 'doc' as const,
        version: 1 as const,
        content: [
            {
                type: 'paragraph' as const,
                content: [
                    {
                        type: 'text' as const,
                        text
                    }
                ]
            }
        ]
    };
};

© 2025 ScriptRunner · Terms and Conditions · Privacy Policy · Legal Notice