Last updated Apr 19, 2024

Create a data provider app for events and metrics

Events and metrics add useful data to Compass components. Events populate the activity feed, and metrics are displayed right on the component page. Hooking into Compass events and metrics gives your app powerful extension capabilities out of the box that are baked into the core Compass experience. To do this, you can create a Forge app with the compass:dataProvider module.

Creating an event or metric in Compass is relatively easy (see Sending in new events and metrics). The trickier part is automatically linking the correct event or metric with the correct Compass component(s).

Compass apps use component links to automatically infer which events and metrics from a particular Forge app should be connected to a component. For example, all a user has to do is add https://bitbucket.org/atlassian/really-cool-repo to the repository links section on a component to automatically get events and metrics from the really-cool-repo Bitbucket repository on that component if the Bitbucket app is installed.

Setting up your app as a data provider

To establish your app as a provider of data from external systems, add the compass:dataProvider module to your app manifest. Compass automatically triggers this module whenever a new link is added to a component. You can decide how to respond to this link events. The most common use-case is to initialize and backfill events and metrics from the external source that was linked to the component.

Set up permissions

In order for your app to provide events data, add these scopes to your app manifest:

  • write:component:compass
  • write:event:compass

In order for your app to provide metrics data, add this scope to your manifest:

  • write:metric:compass

Add the dataProvider module

Declare the compass:dataProvider module in your app manifest. In the module definition:

  • linkTypes is a list of Compass link types that your app recognizes. The possible values are chat-channel, dashboard, document, on-call, project, repository, and other-link.
  • domains is a list of domains that your app recognizes. Wildcard domains are supported, for example use "*.bitbucket.org" to match any subdomain on bitbucket.org. Wildcard domains must be enclosed within double quotes.
  • function is the function that gets invoked when a user adds a link whose type matches one of the specified linkTypes and whose URL matches one of the specified domains.

Here's an example module definition for an app that handles Bitbucket events and metrics:

1
2
modules:
  compass:dataProvider:
    - key: data-provider
      function: data-provider-fn
      linkTypes:
        - repository
      domains:
        - 'bitbucket.org'
  function:
    - key: data-provider-fn
      handler: index.dataProvider

Implementing the data provider function

Your data provider's handler function receives one input parameter, the url of the component link that was just added. It's sent as an object that looks like this:

1
2
{
  url: string
}

Your function should first decide whether it can actually handle this link. Typically this involves checking with your external events/metrics system to see whether the URL exists. If it doesn’t, your function can just return null at this point, to indicate that no further action is needed.

However, if the URL does match an entity in your external system, then you will specify an external identifier for that entity as the externalSourceId. This value can be can be set to anything you want, as long as it's unique per entity and won't change over time.

For example, we use Bitbucket repository ID as the externalSourceId that uniquely identifies Bitbucket repositories.

Set up events and metrics in Compass

Your handler function returns a payload that tells Compass:

  • whether your Forge app cares about the provided link; and
  • which events and metrics should be set up for the component that the link was added on

The payload should contain the externalSourceId for the URL (if you found one), the events and metrics you’ll be sending in from that external system, and optional initial values for the events and metrics.

The DataProviderResponse class

To help you construct your return information to send to Compass, we suggest that you use the DataProviderResponse class from the GraphQL API Toolkit.

Example implementation

Below is an example implementation for Bitbucket. It includes some function stubs for Bitbucket-specific logic.

1
2
import {
  DataProviderResponse,
  DataProviderEventTypes,
  BuiltinMetricDefinitions,
  DataProviderResult,
} from '@atlassian/forge-graphql'

async function dataProvider(request: { url: string }): Promise<DataProviderResult | null> {
  const { url } = request;
  const externalSourceId = getExternalSourceIdFromUrlStub(url);

  if (!externalSourceId) {
    // The URL doesn't represent a repo in the connected Bitbucket workspace.
    // In this case, just return `null` to show that you don't care about the link.
    return null;
  }

  // Initialize the `DataProviderResponse` class with desired events & metrics to enable
  const response = new DataProviderResponse(externalSourceId, {
    eventTypes: [DataProviderEventTypes.DEPLOYMENTS, DataProviderEventTypes.BUILDS],
    builtInMetricDefinitions: [{ name: BuiltinMetricDefinitions.BUILD_SUCCESS_RATE, derived: true}],
    customMetricDefinitions: [{ name: 'My custom metric', format: { suffix: 'req/min' } }],
  });

  // Retrieve events from the external system & calculate metrics
  const deploymentValues = await getDeploymentValuesFromBitbucketStub(externalSourceId);
  const buildValues = await getBuildValuesFromBitbucketStub(externalSourceId);
  const buildSuccessRate = calculateBuildSuccessRateStub(buildValues);
  const customMetric = 33;

  // Add initial values & return the response
  return response
    .addDeployments(deploymentValues)
    .addBuilds(buildValues)
    .addBuiltInMetricValue(BuiltinMetricDefinitions.BUILD_SUCCESS_RATE, buildSuccessRate)
    .addCustomMetricValue('My custom metric', customMetric)
    .build();
}

Set up initial values on events and metrics

Optionally, your app can provide initial values for all events and metrics that you set up. This lets users see events and metrics in Compass immediately after adding a link to a component, rather than having to wait until the next event or metric update to see data. While this is an optional step, it’s recommended to provide a better user experience.

Regardless of whether you set initial values, the constructor of the DataProviderResponse class must specify all event and metric types you plan on providing. Compass uses this information to set up the event and metric connections on components, which must happen before the component can receive events or metrics.

Built-in vs custom metrics

Compass has a set of predefined metrics that should be used when possible. Predefined metrics can’t be deleted from the UI by end users and represent common component health metrics important to development teams. See the Forge GraphQL Toolkit reference for a list of predefined metric identifiers.

You can also create a custom metric if the metric you want to add isn’t present in the list of pre-defined metrics. Custom metrics must have a name which is a unique identifier for that metric. You can optionally provide a description and custom suffix as well.

Derived metrics

Some built-in metrics can be designated as derived meaning that Compass will automatically calculate them based on event data without your app manually inserting metric value updates.

Using derived metrics removes the burden of building bespoke calculation logic from your app and standardizes calculations across apps.

Adding a callback function

Compass reads the return value of your function and attempts to set up events and metrics for the component where you add a link. However, as with all things in software, this has the potential to fail.

Add the callback property to the module definition to catch any failures that might occur.

1
2
modules:
  compass:dataProvider:
    - key: data-provider
      function: data-provider-fn
      callback:
        function: data-provider-callback-fn
      linkTypes:
        - repository
      domains:
        - 'bitbucket.org'
  function:
    - key: data-provider-fn
      handler: index.dataProvider
    - key: data-provider-callback-fn
      handler: index.dataProviderCallback

This enables you to respond to errors, such as logging what went wrong to help you debug your code. The most likely errors are due to insufficient scopes in your manifest or an invalid return payload, but if the error happens to be an internal Compass issue please reach out in the Compass community.

The input object provided as the first parameter to your callback function will look like this:

1
2
{
  success: boolean, // if the request was successful
  url: string, // the URL the request was performed on
  errorMessage: string | undefined // what went wrong, if an error did occur
}

Sending new events and metrics

The dataProvider module takes care of some important processes: setting up events and metrics for ingestion, linking them to the correct components, and initializing them with data. However, after this is done you actually need to send in the event and metric updates as new events occur and as metrics change over time.

The process for this will vary with your external system, but generally, you’ll want to listen to events in the external system (i.e. through webhooks) and send them into Compass using the createEvent method from the Forge GraphQL Toolkit. You should create events using the same externalSourceId that you used in the dataProvider module so that Compass can link the events to the correct components. If your metrics are based on event data, this is a great time to recalculate your metrics as well and send them to Compass. Otherwise, you can calculate your metrics on a cadence using scheduled triggers or devise your own means of updating cadence.

To insert metrics, you should use the insertMetricValueByExternalId method from the Forge GraphQL Toolkit, and once again use the same externalSourceIds.

To find metricDefinitionId for built-in metrics, refer to the list of predefined metrics. To find metricDefinitionId for custom metrics, use the GetMetricDefinitions query to search for your newly created metric definition by name, and then use the id field on the response object of the matching definition.

At the time your Forge app is installed and configured, there might already be links to external systems your app cares about that have been added to Compass components. Since the data provider module only gets triggered when new links get added, components that already have those links will never get associated with events and metrics from that external system.

To address this, your app should use the synchronizeLinkAssociations method from the Forge GraphQL Toolkit. For example:

1
2
await graphqlGateway
  .compass
  .asApp()
  .synchronizeLinkAssociations({
    cloudId: cloudId,
    forgeAppId: forgeAppId,
});

This iterates through all links currently on the Compass site your app is installed on, calls the dataProvider module for each of them (if they match the filter parameters in your manifest), and sets up the resulting event and metric connections.

This operation should be run every single time your app is first configured or is connected to a new account in the external system it integrates with. To explain why, let’s say a user disconnects their Bitbucket workspace and connects to a different one. This workspace will have a different set of repositories than the previously connected workspace, so the Bitbucket app will be able to fetch events and metrics from an entirely different set of links.

This mutation can also be used anytime you want to modify your data provider implementation to add support for a new event or metric. We suggest triggering it for all users using a scheduled trigger, but you can call it in whatever way makes the most sense for your use case.

This operation is idempotent, so feel empowered to re-run it when needed.

Additional information

Compass automatically handles link deletions and will remove any associated events or metrics that were added to the component based on that link. We store the association internally and won’t need to call the data provider module in your Forge app.

If a link was updated but the URL wasn’t changed, then no action is needed. If the link was updated and the URL was changed, then it will remove the events and metrics associated with the component from the old link URL and re-query your data provider module to get a new set of event and metric information.

If a link remains on a component, but that link was deleted in the external system, you’d have to manually delete any associated event and metric sources with your Forge app. You could do this through the use of the GraphQL SDK, or simply decide to leave them on components as a historical record.

The data provider module does not currently support the deletion of the event or metric data if an empty response is returned for a link that has active data associated with it.

Rate this page: