@atlassianintegrations/polaris-forge-object-resolver v0.1.2
Polaris Forge Object Resolver
The foundation of each Forge function is the Polaris Forge Object Resolver.
This package handles the core logic regarding the process of resolving third party data into Polaris JSON schema. It is made up of two parts, Client
and Provider
.
Client
The Client is responsible for making calls out to third parties. This includes interacting with the Forge infrastructure to make requests with the correct OAuth2 tokens, as well as serializing and deserializing the response body to the correct format. It also handles error responses and returning the necessary responses to complete the OAuth2 flow when required. It uses the Forge API under the hood to make requests.
Creating your Client
The client is used in the foundation of a Forge Object Provider. It accepts the following:
baseUrl
: A base URL for your API that will be suffixed to every request. (e.g. https://www.googleapis.com)outboundAuthorization
: An object containing the following used to set up authentication attributes:authMethod
: This is set before the Authorization header. Default set to 'Bearer'.authKey
: This references theexternalAuth.key
in yourmanifest.yml
authHeaders
: Any additional headers you may want to pass into the requeset.
authHeaderName
: Auth header name. Defaults toAuthorization
(Optional)
Making Requests with the Client
Requests are made with the client with the following methods:
GET
POST
PUT
DELETE
They may be invoked both as methods (e.g. client.get('www.google.com', <funtion opts>)
or as a function param (e.g. client.request('www.google.com', {method: 'GET', ...}
)). Headers and query params may be passed as the second function argument.
Client Examples
Here's us instantiating a client:
new Client({
baseUrl: 'https://www.googleapis.com',
outboundAuthorization: { authKey: 'my-auth-key', authMethod: 'Token', authHeaders: { myHeader1: 'myHeader' } },
authHeaderName: 'Authorization', // optional
});
Here's a simple example where we make a get request with some headers:
export const getFile = async (client: Client, { fileId: any }) => {
return client.get(`api.fishsticks.com/v1/${fileId}`, {
query: { queryParam1: 'someParam' },
headers: { header1: 'someHeader' },
body: { bodyAttribute1: 'someData' },
});
};
Provider
The provider orchestrates the flow in which third party data should be resolved into Polaris JSON schema. The provider can take one of two forms depending on the type of data the developer wishes to resolve.
ObjectProvider
The object provider is responsible for resolver data related to links. Links corresponds to URLs pasted in the editor.
linkResolvers
required in the constructor of the ObjectProvider
.
Creating a ObjectResolver
The ObjectResolver accepts the following:
Client
: This is the client mentioned above, it is used to create and send requests to third parties.linkResolver
: A Forge Object Provider linkResolver, as seen below.errorHandlers
: Maps error codes / errors to error handlers.
Executing a ObjectResolver
A ObjectResolver
is run simply by using the execute
method. It accepts an EventPayload
, which is composed of a ResolvePayload
which looks like the following:
type
: determines whether it is a 'resolve' payloadpayload
: Contains data pertaining to theResolvePayload
- ResolvePayload:
resourceUrl
: Contains a URL to resolve
- ResolvePayload:
e.g. return await objectResolver.execute({type: 'resolve', {payload: resourceUrl: 'test.com'}})
linkResolver
Concepts
To write a Forge Object Provider linkResolver, you need to understand the three-step process involved:
- Patterns: first, an incoming URL or request is matched against a set of regex patterns with named groups. E.g. https://drive.google.com/file/123 will extract
123
as afileId
; - Resolvers: second, the results of the match are provided to a resolver function. This function fetches raw JSON data from the third-party API;
- Formatters: third, the result of the third-party API calls are transformed to Polaris JSON schema.
The final result, the Polaris JSON schema response
linkResolvers: {
file: {
pattern: [/^https:\/\/dogs\.com\.\/(<?dogName>.*?)$/],
resolver: async (client, url, matches) => await client.get(`/api/get/${matches.dogName}`);
formatter: (data) => ({
"type": "card",
"group": {
"name": "New Dogs",
"id": "dogs_1"
},
"context": {
"url": "https://dog-house.com",
"icon": "https://dog-house.com/icon.png",
"title": "House of dogs"
},
"content": {
"description": "'In the Doghouse' is a science fiction short story by American writers Orson Scott Card and Jay A Parry."
}
}),
},
}
ObjectResolver
Example
Here's a full example, with an instantiated Client passed into the ObjectProvider. Taking each of the three steps above into account, a set of utilities have been provided for your use. To write a Polaris JSON schema object provider for, say, Dogs API, we may choose to do the following:
import {
ObjectProvider,
Client,
EventPayload,
ResolverResponse,
ClientError,
defaults,
} from '@atlassian/polaris-forge-object-resolver';
export async function run(event: EventPayload): Promise<ResolverResponse> {
const provider = new ObjectProvider({
client: new Client({
baseUrl: 'https://api.dogs.com',
outboundAuthorization: { authKey: 'woofer' },
authHeaderName: 'Authorization', // optional
}),
linkResolvers: {
file: {
pattern: [/^https:\/\/dogs\.com\.\/(<?dogName>.*?)$/],
resolver: async (client, url, matches) => await client.get(`/api/get/${matches.dogName}`),
formatter: data => ({
type: 'card',
group: {
name: 'New Dogs',
id: 'dogs_1',
},
context: {
url: 'https://dog-house.com',
icon: 'https://dog-house.com/icon.png',
title: 'House of dogs',
},
content: {
description:
"'In the Doghouse' is a science fiction short story by American writers Orson Scott Card and Jay A Parry.",
},
properties: {
hotdog_price: {
name: 'Hotdog Price',
value: 100,
},
},
}),
},
},
errorHandlers: {
401: () => ({ meta: defaults.meta.unauthorized, data: undefined }),
403: () => ({ meta: defaults.meta.permissionDenied, data: undefined }),
404: () => ({ meta: defaults.meta.notFound, data: undefined }),
},
});
return await provider.execute(event);
}
The key detail here is to ensure your authKey
matches up with the externalAuth.key
in your manifest.yml
. If everything else is setup correctly, you are good to go!
ObjectResolver
Example for "api_key" auth type
export async function runPendo(event: EventPayload): Promise<ResolverResponse> {
const provider = new ObjectProvider({
client: new Client({
baseUrl: 'https://app.pendo.io',
authHeaderName: 'x-pendo-integration-key', // optional, by default is "Authorization"
}),
linkResolvers: {
message: {
pattern: [/^https:\/\/app\.pendo\.io\/pages\/(?<pageId>.+)$/],
resolver: async (client, url, matches) => {
const response = await client.get(`/api/v1/page/?=${matches.pageId}`);
return { page: response[0], url };
},
formatter: ({ page, url }) => {
return {
type: 'card',
group: {
name: 'New Dogs',
id: 'dogs_1',
},
context: {
icon: 'https://pendo.io/icon.png',
url: url,
title: `${page.kind} ${page.id}`,
},
properties: {
createdByUser: {
name: 'Created by user',
value: `${page.createdByUser.first} ${page.createdByUser.last}`,
},
},
};
},
},
},
errorHandlers: {
401: () => ({ meta: defaults.meta.unauthorized, data: undefined }),
403: () => ({ meta: defaults.meta.unauthorized, data: undefined }),
},
});
/* Setting custom auth token decorator is optional, default implementation is:
* const defaultAuthTokenDecorator = (authToken: string) => `Basic ${Buffer.from(authToken).toString('base64')}`;
*/
provider.setAuthTokenDecorator(authToken => authToken);
return await provider.execute(event);
}
2 years ago
2 years ago
2 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago