JetBrains Space Help

Work with Space Endpoints

The Space HTTP client provides direct access to Space endpoints. The corresponding properties and methods of SpaceHttpClient are structured in the same way as in API Playground. You can always use the playground as interactive help and even client code generator:

SDK client structure

Get data from Space

You can use Space endpoints to get data from Space. For example, this is how you can get a user profile from the Team Directory

val client = SpaceHttpClient(HttpClient()) .withServiceAccountTokenSource(id, secret, url) // requires the ViewProfile right val memberProfile = client.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe"))

In its turn, the memberProfile will expose its top level properties, such as id, username, about, and so on.

Manipulate Space modules

Space endpoints let you perform literally any operation in Space, e.g. send a chat message, create a blog post, add a project member, close a code review, and so on. For example, this is how you can publish a blog post:

val client = SpaceHttpClient(HttpClient()) .withServiceAccountTokenSource(id, secret, url) // requires the PublishArticles right client.blog.publishBlogPost( title = "My First Blog Post", content = "Hello World!" )

Application rights

To perform get or set requests to Space endpoints, your application must have corresponding rights. The rights are granted to the application when you register it in Space. In addition, the application may specify which of the rights it currently needs. This is defined during application authorization with the help of scopes.

You can find out what rights are required to perform a certain call in API Playground.

Check rights in API Playground

Fields and properties

All Space responses contain a JSON object with the requested data. By default, if you don't specify the fields you want to get, this JSON object will contain all top-level properties.

HTTP Request:

GET https://jetbrains.team/api/http/team-directory/profiles/username:John.Doe Authorization: Bearer abc123 Accept: application/json

Response:

{ "id": "2asfen24Jx6u", "username": "John.Doe", "name": { "firstName": "John", "lastName": "Doe" }, "speaksEnglish": true, "smallAvatar": "vYK0b1Qisdf2C", "avatar": "40uDKb4RHdsst", ...

You can do the same request with the Space SDK:

val memberProfile = spaceClient.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe"))

Partial responses

For most requests, you can shape the results you want to retrieve from Space. For example, to retrieve only the user id and the about description, you can set the $fields parameter in an API request to $fields=id,about.

Being able to retrieve just the information our application requires, helps to reduce the payload size and results in better overall performance.

HTTP Request:

GET https://jetbrains.team/api/http/team-directory/profiles/username:John.Doe?$fields=id,about Authorization: Bearer abc123 Accept: application/json

Response:

{ "id": "2asfen24Jx6u", "about": "Johny is a nice man" }

To perform such a request with the Space SDK, you should use the so-called partial methods (id() and about() in our case):

val memberProfile = spaceClient.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe") ) { id() about() }

If you try to access the memberProfile.name property, which is not in the response, the Space HTTP client will throw an IllegalStateException with additional information.

try { // This will fail... println("${memberProfile.name.firstName} ${memberProfile.name.lastName}") } catch (e: IllegalStateException) { // ...and you'll get a pointer about why it fails: // Property 'name' was not requested. Reference chain: getAllProfiles->data->[0]->name println("The Space API client tells us which partial query should be added to access the property:"); println(e.message) }

Worth noting that fields can be not only of a value type (integer, string, boolean, and so on), but of a complex type as well. As an example, a user profile has the name field of the TD_ProfileName, which in turn has the firstName and lastName fields. To request this hierarchy, you need to query $fields=name(firstName,lastName)

Nested properties

If you perform a request like that:

val memberProfile = spaceClient.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe"))

Space will return an instance of the TD_MemberProfile type which includes a lot of top-level properties: id, username, about, and others. Among them there is the managers property which is a collection of nested TD_MemberProfile instances. Such a property is not retrieved by default. You must do this explicitly.

For example, if you want to retrieve managers for a user profile including manager names, you should request these properties by extending the default partial result:

val memberProfile = spaceClient.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe")) { defaultPartial() // with all top level fields managers { // include managers id() // with their id username() // and their username name { // and their name firstName() // with firstName lastName() // and firstName } } }

Here:

  • defaultPartial(): a special partial method that adds all fields at the current level (* field definition).

Recursive responses

The example with memberProfile.managers is quite interesting because every manager can have its own manager (who can also have a manager, who ...). As you don't know how many managers are in this tree, you can request a recursive response. It will return the entire managers tree.

The functions that represent such tree structures have special overloads for retrieving recursive responses. For example in case of managers, it's managers(recursiveAs: TD_MemberProfilePartial). This is how you can use it:

val memberProfile = spaceClient.teamDirectory.profiles .getProfile(ProfileIdentifier.Username("John.Doe")) { defaultPartial() // with all top level fields managers(this) // and the same fields for all managers }

Inheritance

There are several Space endpoints that return subclasses (polymorphic responses).

One such example is spaceClient.projects.planning.issues.getAllIssues(), where the createdBy property can be a subclass of CPrincipalDetails:

  • CAutomationTaskPrincipalDetails, if the issue was created by an automation task.

  • CBuiltInServicePrincipalDetails, if the issue was created by Space itself.

  • CExternalServicePrincipalDetails, if the issue was created by an external service.

  • CUserWithEmailPrincipalDetails, if the issue was created by a user that has an e-mail address.

  • CUserPrincipalDetails, if the issue was created by a user.

The partial builder contains properties for all of these classes.

Here's an example retrieving issues from a project. For the createdBy property, you are defining that the response should contain:

  • CUserPrincipalDetails with the user.id property.

  • CUserWithEmailPrincipalDetails with the name and email properties.

val issueStatuses = spaceClient.projects.planning.issues.statuses .getAllIssueStatuses(ProjectIdentifier.Key("CRL")).map { it.id } val issues = spaceClient.projects.planning.issues .getAllIssues(ProjectIdentifier.Key("CRL"), sorting = IssuesSorting.UPDATED, descending = true, statuses = issueStatuses) { defaultPartial() creationTime() createdBy { details { // Available on CUserPrincipalDetails user { id() } // Available on CUserWithEmailPrincipalDetails, // CAutomationTaskPrincipalDetails, CBuiltInServicePrincipalDetails name() email() } } status() }.data.forEach { issue -> when (issue.createdBy.details) { is CUserPrincipalDetails -> { // ... } is CUserWithEmailPrincipalDetails -> { // ... } } }

You can cast these types, use when expressions on them, and so on.

Batches

There are a lot of requests that may return a collection of results. To guarantee performance, these responses will be paginated, and can be retrieved in batches.

A batch not only contains the data but always returns the total count of items that will be returned after fetching all pages:

class Batch<out T>( val next: String, val totalCount: Int?, val data: List<T>)

You must specify the properties of the data type you need. For example, let's retrieve the user's To-Do items for this week including their id and content:

// Get all To-Do var todoBatchInfo = BatchInfo("0", 100) do { val todoBatch = spaceClient.todoItems .getAllTodoItems(from = LocalDate(2020, 01, 01), batchInfo = todoBatchInfo) { id() content() } todoBatch.data.forEach { todo -> // ... } todoBatchInfo = BatchInfo(todoBatch.next, 100) } while (todoBatch.hasNext())

Here:

  • hasNext(): an extension method that lets you to determine whether more results need to be retrieved:

    fun Batch<*>.hasNext() = !data.isEmpty()

The resulting batch will contain one page of results. To retrieve more To-Do items, you should make additional API calls.

Last modified: 27 July 2021