Is it possible to post comments with attached files with the api to speckle.xyz?
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 fileId
s 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?
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. (
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.
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 a 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 local file to my script and 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 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 encumbered by web sandboxing. Suppose 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/stream/{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 the comments. Some parts of this workflow have been in flux for a while, but we hear the need to do better with the documentation!!!
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.
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.
Thanks! I’m testing with Postman but getting “Cannot POST /api/streams/{stream_id}/blob” ?
Do you have a variable setup to exchange with stream_id?
yes, you mean I’m replacing {stream_id} with my stream id?
32 kB, what is the limit?
more than that!!! ~10MB
Checklist:
Headers:
Authorisation: `Bearer PERSONAL_ACCESS_TOKEN`
Content-Type: multipart/form-data; boundary= (postman should calculate this for you)
Content-Size: (postman should calculate this for you)
Body:
POST https://{{host}}/api/stream/{{stream_id}}/blob
where host is e.g. speckle.xyz
and stream_id
is a stream you are an owner / collaborator
Sorry! Did not realize there was an s too muchin /streamS/, now it works
Is it possible to mention someone in the comment? Something more than adding a @ is needed?
apologies i also totally missed that in my/your example
I’ve actually never tried - this is good to have if we don’t. I’ll get back to you if you don’t manage to test it yourself.
I’m getting links and styling to work, but not mentions?
@Sandra, I have been looking into this for you.
As @fabians expanded on my earlier example, we use a wrapper around ProseMirror to generate comment content. Part of that wrapper is a mentions extension.
A mention
node’s schema is an id
and a label
.
So a theoretical example may be:
...
text: {
type: ""doc"",
content: [{type: ""paragraph"", content: [
{type: ""text"", text: ""I think ""},
{type: ""mention"", attrs: {
id: ""__USER_ID__"",
label: ""__USER_DISPLAYNAME__""
}},
{type: ""text"", text: "" should take a look at this.""},
]}]}
},
...
If you are generating these mentions yourself, you will need to handle the correct id
to be added carefully. The label
should ideally match what would have been generated in the UI, but it isn’t critical.
My understanding of the parsing of new comments is that they will be parsed for mention
s and the server will handle the actions associated with that regardless of whether they are being made programmatically or by user interface submission.
I will add this to a working example, which should confirm the actions, but this snippet should give you some direction.
Thanks, when putting id and label in attrs it works
text: {
type: "doc"
content: [
{
type: "paragraph",
content: [
{
type: "text"
text: "I think "
}
{
type: "mention"
attrs: {
id: "__USER_ID__"
label: "__USER_DISPLAYNAME__"
}
}
{
type: "text"
text: " should take a look at this."
}
]
}
]
}
Also seems like the mention is handled the same way when posted programatically as when posten by UI submission.