Action

Create tickets in Linear

Posted by Thomas Schoffelen, Last update about 1 year ago

Create issues in Linear based on the current draft document.

Expects a structure like this:

# Project name

## Issue title one
This is the description of the issue. Anything is considered part of the description, up to the following H2 tag.

Descriptions can contain markdown.

## Issue title two
This is the description of the second issue.

Multiple workspaces

If you wish to store multiple sets of credentials for different workspaces, you can specify an identifier for the workspace in the project title, like so:

# Project using default credentials

# Project in other team [OtherTeam]

Running multiple times

The action will add links to the created issues in the draft, and will skip creating issues for previously created issues. A great way to iteratively add more issues for a project.

Steps

  • script

    function createTask(credential, teamId, currentTask, newContent) {
        let {
            title,
            description,
            project
        } = currentTask;
        teamId = credential.getValue("teamId") || teamId;
        const apiKey = credential.getValue("apiKey");
    
    	description = description.join("\n").trim();
    
    	if(description.includes('_Issue: [')){
    		return;
    	}
    
    
        const query = `
    mutation IssueCreate($title: String!, $stateId: String, $teamId: String!, $description: String) {
      issueCreate(
        input: {
          title: $title
          description: $description
          teamId: $teamId,
          stateId: $stateId
        }
      ) {
        success
        issue {
          identifier
          url
        }
      }
    }
    	`;
    
        const http = HTTP.create();
        const response = http.request({
            method: "POST",
            url: "https://api.linear.app/graphql",
            headers: {
                "Authorization": `Bearer ${apiKey}`
            },
            data: {
                query,
                variables: {
                    title,
                    description,
                    stateId,
                    teamId
                }
            }
        });
    
        if (response.success) {
            const {
                issue
            } = response.responseData.data.issueCreate;
            if (newContent.length && newContent[newContent.length - 1].trim() !== "") {
                newContent.push("");
            }
            newContent.push(`_Issue: [${issue.identifier}](${issue.url})_\n\n`);
        } else {
            newContent.push(`\nERROR: ${response.statusCode} -- ${response.error}`);
        }
    }
    
    
    // Save a version to allow easy restoring
    draft.saveVersion();
    
    const lines = draft.content.split("\n");
    const newContent = [];
    
    let project, team;
    if (lines[0].startsWith('#')) {
        project = lines.shift().replace(/^# /, "");
        newContent.push(`# ${project}`);
    }
    
    if(project && project.match(/\[\w+\]/)){
    	team = project.substring(project.indexOf('[')+1).split(']')[0]
    }
    
    const credential = Credential.create('Linear' + (team ? ` - ${team}` : ''), "Credentials for a Linear Team");
    credential.addPasswordField('apiKey', "API key");
    credential.addTextField('teamId', "Team ID (optional)");
    credential.authorize();
    
    // Get default team
    let teamId;
    if (!credential.getValue("teamId")) {
    	const http = HTTP.create();
    	const {responseData: team} = http.request({
    		method: "POST",
    		url: "https://api.linear.app/graphql",
    		headers: {
    			"Authorization": `Bearer ${credential.getValue("apiKey")}`
    		},
    		data: {
    			query: '{ teams { nodes { id } } }'
    		}
    	});
    	teamId = team.data.teams.nodes[0].id;
    }
    
    // Get default state
    let stateId;
    if(!stateId){
    	const http = HTTP.create();
    	const {responseData: states} = http.request({
    		method: "POST",
    		url: "https://api.linear.app/graphql",
    		headers: {
    			"Authorization": `Bearer ${credential.getValue("apiKey")}`
    		},
    		data: {
    			query: '{ workflowStates { nodes { id name } } }'
    		}
    	});
    	stateId = states.data.workflowStates.nodes?.find(({name}) => name.toLowerCase().trim() === 'backlog')?.id;
    }
    
    let currentTask = null;
    for (const line of lines) {
        const l = line.trim();
        if (l.startsWith('## ')) {
            if (currentTask) {
                createTask(credential, teamId, currentTask, newContent);
            }
            currentTask = {
                project,
                stateId,
                title: l.replace(/^[#]+\s+/, ''),
                description: []
            }
        } else if (currentTask) {
            currentTask.description.push(line || ' ');
        }
        newContent.push(line);
    }
    if (currentTask) {
        createTask(credential, teamId, currentTask, newContent);
    }
    
    draft.content = newContent.join("\n");
    draft.update();

Options

  • After Success Default , Tags: linear
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.