@atlassianintegrations/polaris-forge-object-resolver v0.1.7
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.keyin yourmanifest.ymlauthHeaders: 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:
GETPOSTPUTDELETE
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
123as 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);
}8 months ago
9 months ago
6 months ago
6 months ago
9 months ago
9 months ago
8 months ago
9 months ago
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
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago