1

I am writing a Google App Script code that will submit attachments to Jira's API. The user uploads a file and submits, at which point my code should send a request to the API to send an attachment. My code does not raise any errors, but the attachments are not added to the Jira issue. I think it might be how I am formatting the payload? I used the same settings in PostMan and the API call works fine. My code is as follows:

index.html

function formSubmit(){
     var form = $("#bugReportRequest")[0];
     google.script.run.withSuccessHandler(BugReportSubmitted).submitBugReport(form)
     //where form is:
     //<form id="bugReportRequest" name="bugReportRequest" action="#">
      // <input type="text" class="form-control" id="BugSubmitterName"  name="BugSubmitterName">
      // <input type="text" class="form-control" id="BugSubmitterEmail"  name="BugSubmitterEmail">
        //<input type="file" class="form-control" id="BugReportFileUpload" name ="BugReportFileUpload" />  
     </form>
}

Code.gs

function submitBugReport(data){
       var file = data.BugReportFileUpload;
       var url = Jira_URL + "rest/api/2/issue/ABC-2/attachments";
       var credentials = Utilities.base64Encode(Jira_Email_Address + ":" + Jira_API_Token);
       let formdata = {'file' : file };
        var header = { 
             "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
             "X-Atlassian-Token": "no-check",
             "muteHttpExceptions": true,
             "Authorization": "Basic " + credentials
        } 
       var options = {
          "method": "POST",
          "headers": header,
          "body": formdata,
          "redirect": "follow",
          "muteHttpExceptions": true       
      } 
    var resp;
    try {
      resp = UrlFetchApp.fetch(url, options ); 
      console.error(url);
      console.log(resp.getContentText());
   }  catch (e) {    
     console.error('myFunction() yielded an error: ' + e);
     console.error(resp.getContentText);
     console.error(resp.getResponseCode);
  }
}

The response code I get is 200 but resp.getContentText() only prints "[]". When I check ABC-2 there is no attachment added. Any suggestions on how to format the data payload? Or, is there another reason this would happen?

jason
  • 3,821
  • 10
  • 63
  • 120
  • In GAS you **cannot** explicitly set the `Content-Type` header in the `headers` object, you have to set it using the `contentType` property instead. [Check out the `UrlFetchApp.fetch(url, params)` method in the reference documentation for details.](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object)) Make sure to read the **Advanced Parameters** section. – TheAddonDepot Feb 02 '21 at 20:24
  • @TheAddonDepot Thanks for the response. I changed Content-Type to contentType and moved it from the header to the options object, but the issue remains the same. Am I missing something or not understanding what you are suggesting? – jason Feb 03 '21 at 00:02

1 Answers1

4

Modification points:

  • When I saw the document of How to add an attachment to a JIRA issue using REST API, it seems that the file is sent as multipart/form-data
  • muteHttpExceptions is not used in the header.
  • params of fetch(url, params) has no properties of body and redirect.
  • When the blob is used to the request body, Content-Type is automatically generated by giving the boundary.
  • From your HTML & Javascript, if the uploaded file is the binary file and you are using V8 runtime, var file = data.BugReportFileUpload is not the correct file blob. Ref I'm worry about this.

When above points are reflected to your script, it becomes as follows.

Modified script:

HTML&Javascript side:

In this modified script, I modified it for testing. So please modify this for your actual situation.

<form id="bugReportRequest" name="bugReportRequest" action="#">
<input type="text" class="form-control" id="BugSubmitterName"  name="BugSubmitterName">
<input type="text" class="form-control" id="BugSubmitterEmail"  name="BugSubmitterEmail">
<input type="file" class="form-control" id="BugReportFileUpload" name ="BugReportFileUpload">
</form>

<input type="button" value="ok" onclick="formSubmit()"> <!-- Added for testing script. -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <!-- Added for testing script. -->

<script>
// I modified this function.
function formSubmit(){
  var BugSubmitterName = $('#BugSubmitterName').val();
  var BugSubmitterEmail = $('#BugSubmitterEmail').val();
  var BugReportFileUpload = $('#BugReportFileUpload').prop('files');

  if (BugReportFileUpload.length == 1) {
    const file = BugReportFileUpload[0];
    const fr = new FileReader();
    fr.onload = function(e) {
      const obj = {
        BugSubmitterName: BugSubmitterName,
        BugSubmitterEmail: BugSubmitterEmail,
        BugReportFileUpload: {
          filename: file.name,
          mimeType: file.type,
          bytes: [...new Int8Array(e.target.result)]
        }
      };
      google.script.run.withSuccessHandler(BugReportSubmitted).submitBugReport(obj);
    };
    fr.readAsArrayBuffer(file);
  } else {
    const obj = {
      BugSubmitterName: BugSubmitterName,
      BugSubmitterEmail: BugSubmitterEmail,
      BugReportFileUpload: null
    };
    google.script.run.withSuccessHandler(BugReportSubmitted).submitBugReport(obj);
  }
}

function BugReportSubmitted(e) {
  console.log(e)
}
</script>

Google Apps Script side:

Please confirm Jira_Email_Address and Jira_API_Token are declared.

function submitBugReport(data){
  if (!data.BugReportFileUpload) return;  // Or, for example, return "no file."
  var file = Utilities.newBlob(data.BugReportFileUpload.bytes, data.BugReportFileUpload.mimeType, data.BugReportFileUpload.filename);
  var url = Jira_URL + "rest/api/2/issue/ABC-2/attachments";
  var credentials = Utilities.base64Encode(Jira_Email_Address + ":" + Jira_API_Token);
  let formdata = {'file' : file};
  var header = { 
    "X-Atlassian-Token": "no-check",
    "Authorization": "Basic " + credentials
  } 
  var options = {
    "method": "POST",
    "headers": header,
    "payload": formdata,
    "muteHttpExceptions": true
  } 
  var resp;
  try {
    resp = UrlFetchApp.fetch(url, options);
    console.error(url);
    console.log(resp.getContentText());
  }  catch (e) {
    console.error('myFunction() yielded an error: ' + e);
    console.error(resp.getContentText);
    console.error(resp.getResponseCode);
  }
}

Note:

  • When I saw your script, it seems that the values of "BugSubmitterName" and "BugSubmitterEmail" are not used. So in this modification, these values are not used.
  • This modified script supposes that your token and URL can be used for achieving your goal. So please be careful this.

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Wow, that is a great answer and worked well! Can I ask, in the code bytes: "[...new Int8Array(e.target.result)]" what are the three ellipses for? But, this really impressive, thank you! – jason Feb 03 '21 at 00:46
  • @jason Thank you for replying and testing it. I'm glad your issue was resolved. About `in the code bytes: "[...new Int8Array(e.target.result)]" what are the three ellipses for?`, if you are saying `...`, this is spread syntax. [Ref](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) I used this for converting `Int8Array` to an array with the numbers for sending to Google Apps Script side. In this case, I think that it is the same with `Array.from(new Int8Array(e.target.result))`. If I misunderstood your additional question, I apologize. – Tanaike Feb 03 '21 at 00:50