Skip to content

Instantly share code, notes, and snippets.

@alurim
Created November 18, 2020 23:53
Show Gist options
  • Save alurim/2e9f99bf43f6eaa96fc3f200bfbce908 to your computer and use it in GitHub Desktop.
Save alurim/2e9f99bf43f6eaa96fc3f200bfbce908 to your computer and use it in GitHub Desktop.

Google -> Linear Bug Report Form

It's helpful for internal teammates who don't have access to Linear to be able to file bug reports so we created a Google Form that folks can fill out with their bug reports. The form has some basic fields (see image below).

Screen_Shot_2020-11-18_at_3_49_03_PM

When users submit the form it adds it to a Google Spreadsheet where the google-app-script.js file is added as a script (to add this go to the spreadsheet, click "Tools" > "Script editor"). From there it uploads all the screenshots to Linear and creates a Linear issue with the submitted info.

To get this to work for you, you'll need to update any of the text in angle brackets in google-apps-scipts.js (like <LINEAR_KEY>). Good luck!

const linearKey = "<LINEAR_KEY>";
function Initialize() {
const triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("FileLinearIssue")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
const questions = {
TIMESTAMP: 0,
EMAIL_ADDRESS: 1,
TITLE: 2,
STEPS_TO_REPRODUCE: 3,
EXPECTED_RESULTS: 4,
ACTUAL_RESULTS: 5,
SCREENSHOTS: 6,
};
function FileLinearIssue(e) {
const s = SpreadsheetApp.getActiveSheet();
const formResponse = s
.getRange(s.getLastRow(), 1, 1, s.getLastColumn())
.getValues()[0];
const screenshots = formResponse[questions.SCREENSHOTS].split(",");
const assetUrls = [];
for (let idx = 0; idx < screenshots.length; idx++) {
const screenshotBlob = DriveApp.getFileById(
screenshots[idx].split("id=")[1]
).getBlob();
const screenshotBytes = screenshotBlob.getBytes();
const size = screenshotBytes.length;
const responseJson = UrlFetchApp.fetch("https://api.linear.app/graphql", {
contentType: "application/json",
method: "post",
headers: { Authorization: linearKey },
muteHttpExceptions: true,
payload: JSON.stringify({
query: `mutation {
fileUpload(
filename: "${screenshotBlob.getName()}",
contentType: "${screenshotBlob.getContentType()}",
size: ${size}
) {
success
uploadFile {
uploadUrl,
headers {
key,
value
},
assetUrl
}
}
}
`,
}),
});
const response = JSON.parse(responseJson);
const header = response.data.fileUpload.uploadFile.headers[0];
const imageUploadResponse = UrlFetchApp.fetch(
response.data.fileUpload.uploadFile.uploadUrl,
{
method: "PUT",
headers: {
[header.key]: header.value,
"cache-control": "max-age=31536000",
},
contentLength: size,
contentType: screenshotBlob.getContentType(),
payload: screenshotBytes,
muteHttpExceptions: true,
}
);
assetUrls.push(response.data.fileUpload.uploadFile.assetUrl);
}
const assets = assetUrls
.map((assetUrl) => `![${assetUrl}](${encodeURI(assetUrl)})`)
.join("\n");
const response = UrlFetchApp.fetch("https://api.linear.app/graphql", {
contentType: "application/json",
method: "post",
headers: { Authorization: linearKey },
muteHttpExceptions: true,
payload: JSON.stringify({
query: `mutation {
issueCreate(
input: {
title: "${formResponse[questions.TITLE]}"
projectId: "<REPORTED_BUG_PROJECT_ID>"
labelIds: ["<BUG_LABEL_ID>", "<NEEDS_TRIAGE_LABEL_ID>"]
description: """
### Steps to Reproduce
${formResponse[questions.STEPS_TO_REPRODUCE]}
### Expected Result
${formResponse[questions.EXPECTED_RESULTS]}
### Actual Result
${formResponse[questions.ACTUAL_RESULTS]}
${assets}
"""
teamId: "<TEAM_ID>"
}
) {
success
issue {
id
}
}
}
`,
}),
});
Logger.log(response);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment