0.1.2 • Published 2 years ago

@atlassianintegrations/polaris-forge-object-resolver v0.1.2

Weekly downloads
504
License
MIT
Repository
bitbucket
Last release
2 years ago

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 the externalAuth.key in your manifest.yml
    • authHeaders: Any additional headers you may want to pass into the requeset.
  • authHeaderName: Auth header name. Defaults to Authorization (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' payload
  • payload: Contains data pertaining to the ResolvePayload
    • ResolvePayload:
      • resourceUrl: Contains a URL to resolve

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:

  1. 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 a fileId;
  2. Resolvers: second, the results of the match are provided to a resolver function. This function fetches raw JSON data from the third-party API;
  3. 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);
}
0.1.2

2 years ago

0.1.1

2 years ago

0.0.22

2 years ago

0.0.21

4 years ago

0.0.21-next.0

4 years ago

0.0.20-next.0

4 years ago

0.0.20

4 years ago

0.0.18

4 years ago

0.0.19

4 years ago

0.0.17

4 years ago

0.0.16

4 years ago

0.0.15

4 years ago

0.0.15-261a336.0

4 years ago

0.0.15-e1b7de1.0

4 years ago

0.0.14

4 years ago

0.0.14-d7124d7.0

4 years ago

0.0.14-2b155d1.0

4 years ago

0.0.14-bb71888.0

4 years ago

0.0.13

4 years ago

0.0.13-next.11

4 years ago

0.0.13-next.10

4 years ago

0.0.13-next.8

4 years ago

0.0.13-next.9

4 years ago

0.0.13-next.7

4 years ago

0.0.13-next.0

4 years ago

0.0.13-next.1

4 years ago

0.0.13-next.2

4 years ago

0.0.13-next.3

4 years ago

0.0.13-next.4

4 years ago

0.0.13-next.5

4 years ago

0.0.13-next.6

4 years ago

0.0.12

4 years ago

0.0.12-next.0

4 years ago

0.0.11

4 years ago

0.0.11-next0.1

4 years ago

0.0.11-next0.2

4 years ago

0.0.9-next.0

4 years ago

0.0.11-next0.0

4 years ago

0.0.9

4 years ago

0.0.8

4 years ago

0.0.8-next.4

4 years ago

0.0.8-next.5

4 years ago

0.0.8-next.6

4 years ago

0.0.8-next.3

4 years ago

0.0.8-next.2

4 years ago

0.0.8-next.0

4 years ago

0.0.7

4 years ago

0.0.5

4 years ago

0.0.6

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago