API post comments with attached files

Is it possible to post comments with attached files with the api to speckle.xyz?

1 Like

It is!

The broad steps are to upload a file to the blob storage API, and then when creating a comment and adding it to a stream or commit, you include any fileIds to that payload.

This is a two-stepper using our REST API with GraphQL or can be achieved with SDK helper functions.

What environment would you like to do this? Python, Javascript, C# or something else?

1 Like

Okey great, is there any more information on the REST and GraphQL apis than here REST API | Speckle Docs ?

We’re using javascript or C#.

There isn’t, that’s on us. :cry: (

I will write examples here, and we’ll work on it!

Oki, do you have an example on how to post a comment to at commit?

I’m working something up for you right now.

In the meantime, I have an example to post thousands of comments over here: Gotta catch-em-all

That example is using python but should be reasonably easy to parse.

1 Like

Hey @Sandra, this took a while longer than I had hoped. Nevertheless, this is an example of attaching files to new comments.

I include below C# code to achieve this. I could post javascript afterwards though it has a few gotchas to work around that I mention later.

The first step is to set the server (host_url) and an personal access token (token), if we are not relying on a local installation using Speckle Manager to handle accounts.

string host_url = "HOST_SERVER";
string token = "ACCESS_TOKEN";

With those in place, we can create a SpeckleApiClient object, which will be used to interact with the server.

using Speckle.Core.Api;
using Speckle.Core.Credentials;

Account account = new Account();
account.serverInfo = new ServerInfo(){ url=host_url };
account.token = token;

var stream_id = "bb8470f5b6";
var commit_id = "34247c2a44";

var client = new Client(account);

In this example I will attach a file I have local to my script and will read it into a Stream and from there into a Blob object.

using System.IO;

var filePath = "./assets/blue.png";

byte[] blob;
using (var stream = new FileStream(filePath, FileMode.Open))
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        blob = memoryStream.ToArray();
    }
}

With that in place and with an authenticated client, we can add that File Blob to the Stream with an effective POST request.

I’m posting this as C# code as it isn’t encombered by web sandboxing. If you are using javascript to do this to the speckle.xyz server, or any server on a different host to your javascript app, you will have to use a CORS proxy.

A POST request with mode no-cors will add the file to the server, but will not allow you to receive the return object.

Also fine would be to use Postman or similar to upload the file.

The server endpoint is in the form of host_url/api/streams/{stream_id}/blob and the request body is a FormData object with a file key and the File Blob as the value.

using System.Net.Http;
using System.Net.Http.Headers;

var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
fileContent.Headers.ContentType = 
    MediaTypeHeaderValue.Parse("application/octet-stream");

var formData = new MultipartFormDataContent();
formData.Add(fileContent, "file", Path.GetFileName(filePath));

System.Net.Http.HttpResponseMessage response;

using (var http_client = new HttpClient())
{
    http_client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", token);
    response = await http_client.PostAsync(
        $"{host_url}/api/stream/{stream_id}/blob", formData);
}

The server will return a JSON object with the file resource information, which you can then use to add the file to a comment.

The response object is in the form:

{
    "uploadResults": [
        {
            "blobId": "9ec.....",
            "fileName": "blue.png",
            "uploadStatus": 1,
            "fileSize": 4760,
            "formKey": "file"
        }
    ]
}

Speckle includes a namespaced version of Newtonsoft.Json, so you can use that to parse the response, but it isn’t critical.

using Speckle.Newtonsoft.Json;

string blobId = null;

if (response.IsSuccessStatusCode)
{
    var responseContent = await response.Content.ReadAsStringAsync();
    dynamic fileUploadResponse = JsonConvert.DeserializeObject(responseContent);
    blobId = fileUploadResponse.uploadResults[0].blobId;
}

With that key blobId, you can add the file to a comment.

We have a few other posts here on the forum that discuss the GraphQL for comments, but the minimal viable query that is effective is:

using GraphQL;
using GraphQL.Client.Http;

var query = @"mutation CommentCreateInput(
    $streamId: String!,
    $blobIds: [String!]!,
    $commitId: String!
  ) { 

  commentCreate(
    input: {
      streamId: $streamId, 
      resources: [
        {
          resourceType: commit
          resourceId: $commitId, 
        }
      ], 
      blobIds: $blobIds
      screenshot: null
      text: {
         type: ""doc"", 
         content: [{type: ""paragraph"", content: [
            {type: ""text"", text: ""My Comment""}]}]}
      },
      data: { location: { x: 10, y: 7.775978194702619, z: 6.737419726085228 } }
    }
  )
}";

var variables = new
{
  streamId = stream_id,
  commitId = commit_id,
  blobIds = new[] { blobId }
};

var request = new GraphQLRequest { Query = query, Variables = variables };    

With the query and variable substitutions in place, we can use the Speckle GQLClient to send the query.

using System.Threading;
using System.Threading.Tasks;

await client.GQLClient
  .SendMutationAsync<LimitedUserData>(request, CancellationToken.None)
  .ConfigureAwait(false);

And that’s it!!

We are missing a few functions with the commenting API that would make replicating the functions available in the Speckle Frontend at speckle.xyz, but this is the gist of it.

Currently missing features:

  • The ability to add a screenshot of the comment-defined viewport
  • The ability to add a selection of objects to the comment

I think the answer is yes; you can add files to comments. Some parts of this workflow have been in flux for a while, but we hear the need to do better with the documentation!!!

4 Likes

I have wrapped the above up into a Polyglot Jupyter Notebook

speckle-comment-attachments.ipynb (12.8 KB)

The only additional code is to get the Environment Variables where I store the Access Token.

3 Likes

I will add that comment text is internally stored in a ProseMirror document structure, so if anyone’s wondering about how to properly format text for the commentCreate mutation you can find more information here:
ProseMirror Guide
ProseMirror Reference manual

Or it might be easier to just see the TypeScript type for this structure: tiptap/types.ts at acca921184e13d7d1ac0eb63fc3dc3b5bcc3f58d · ueberdosis/tiptap · GitHub

Jonathon’s example shows an example of this structure being sent to the mutation - the structure represents a paragraph with a basic text node without any formatting.

3 Likes