Last updated Oct 12, 2021

Using the extensions API

This is a continuation of the work started in 2. Discovering extension points.

In this section, you will learn how to:

  • Request the Client-side Extensions runtime to update your extension attributes.
  • Execute the cleanup code when your extension is removed from the page.
  • Use the context provided by products.

Extensions API

The Client-side Extensions (CSE) runtime provides an API to interact with products. Your extensions will receive it as the first parameter of your extension factory. For more info, see Extensions API.

To use the Extensions API:

  1. Edit the /src/my-app/extensions/first-extension.js file.
  2. Declare that your extension factory will receive an extensionAPI object as the first parameter.
  3. Use console.log(extensionAPI) to explore the methods you have available.
1
2
// #/src/my-app/extensions/first-extension.js
import { ButtonExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.overview.summary
 */
export default ButtonExtension.factory((extensionAPI) => {
    return {
        label: 'Extensions are awesome!',
        iconBefore: 'app-access',
        onAction: () => {
            console.log(extensionAPI);
        },
    };
});

You should see an object in the console with the methods available for you to use.

updateAttributes

In the majority of cases, you'd like to create extensions that do more than just alert messages. For example, you might want to create an extension that tracks how many times someone clicked it and updates its label after every click.

To do so, you can use updateAttributes to choose a set of attributes to update. The CSE runtime will notify the product about your changes and schedule a re-render of your extension with the new information.

It's important to understand that updateAttributes is scheduling a re-render instead of rendering your extension right away. This is to avoid performance issues with multiple extensions rendering at the same time.

Count the clicks on your extension

Now, change the logic of your Button extension to keep track of the number of times clicked and show it to the user.

In the /src/my-app/extensions/first-extension.js file:

  1. Create a variable to use as the local state of your extension.
  2. Change the onAction method to increment the count.
  3. Change the onAction method to request an update of your label attribute with the new count of times clicked.
1
2
// #/src/my-app/extensions/first-extension.js
import { ButtonExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.overview.summary
 */
export default ButtonExtension.factory((extensionAPI) => {
    let clicked = 0;

    return {
        label: `Go ahead, click me!`,
        iconBefore: 'app-access',
        onAction: () => {
            // increment times clicked
            clicked++;

            // update label with times clicked
            extensionAPI.updateAttributes({
                label: `Clicked: ${clicked} times. Amazing!`,
            });
        },
    };
});

You don't need to set all attributes again, just the ones you want to update.

onCleanup

If your extension needs to listen to an event or subscribe to a stream of information, it’s a good practice to clear these connections once the extension is removed from the page. This will help you avoid memory leaks.

You can do it by using onCleanup - an API that lets you specify the cleanup logic to be executed for your extension.

Update the label of your extension

Now, make the label update very 3 seconds. You will implement a timer that updates the label, and then clear the time when your extension is removed.

In the /src/my-app/extensions/first-extension.js file:

  1. Create a timer that updates the label of your extension every 3 seconds.
  2. Declare the cleanup logic to clear the timer when the extension is deleted from the page.
  3. Add some console.log statements for debugging the update of attributes and the cleanup logic.
1
2
// #/src/my-app/extensions/first-extension.js
import { ButtonExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.overview.summary
 */
export default ButtonExtension.factory((extensionAPI) => {
    let clicked = 0;
    let changed = 0;

    // update the label every 3 seconds
    const interval = setInterval(() => {
        console.log('interval executed...');
        changed += 1;
        extensionAPI.updateAttributes({
            label: `This label has changed ${changed} times...`,
        });
    }, 3000);

    // clear interval before destroying the extension
    extensionAPI.onCleanup(() => {
        console.log('interval cleared');
        clearInterval(interval);
    });

    return {
        label: `Go ahead, click me!`,
        iconBefore: 'app-access',
        onAction: () => {
            clicked += 1;

            extensionAPI.updateAttributes({
                label: `Clicked: ${clicked} times. Amazing!`,
            });
        },
    };
});

After refreshing your pull request, the extension’s label should be changing every 3 seconds. To check if the timer is being cleared, open the diff tab. The timer should stop logging interval executed….

To see what happens if you don’t have the cleanup logic, delete the onCleanup execution, and repeat the test. Now, even if you navigate to another page and your extension isn’t visible, the interval should still be running.

Context

So far, you've been using your local state for the extension, but CSE also lets products share part of their state with extensions. This state is called context, and it needs to be provided as the second argument in your extension factory.

In the /src/my-app/extensions/first-extension.js file:

  1. Declare the second parameter for your extension factory called context.
  2. Use console.log to preview what context is shared with your extension by the Bitbucket extension point.
1
2
// #/src/my-app/extensions/first-extension.js
import { ButtonExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.overview.summary
 */
export default ButtonExtension.factory((extensionAPI, context) => {
    console.log({ context });

    return {
        label: `Go ahead, click me!`,
        iconBefore: 'app-access',
        onAction: () => {
            alert('clicked.');
        },
    };
});

After refreshing your pull request, you should see an object in the console that contains all the information being shared with your extension.

The value of context can be different depending on the page and the extension point you're creating your extension for.

You can explore the extension point information to learn about the context it provides.

Recap and next steps

By finishing this section, you have learned:

  • How to communicate to products when the attributes of your extension changes.
  • How to execute the cleanup code if needed.
  • How products are using context to share information about the location where your extension is rendered.

Next, you will learn how to create an extension with custom HTML content

Rate this page: