Bug: Macro configuration will not dynamically render components

TL;DR: Components rendered in a Confluence Macro’s configuration pane will re-render themselves on state change but none of the rendering changes will appear in the UI.

I have developed a Confluence macro that needs configuration, however certain elements of the configuration are dependent on other elements of the configuration.

Taking a hypothetical example, imagine a macro that wants to follow information about some set of city council members for a given city. The configuration for this macro might consist of two properties, a string representing the city and a list of council members that the user has selected from a Select dropdown configured for multi-select.

In this scenario, the Select component containing a list of city council members can not be populated until the city is known. The workflow on the configuration pane is to present a text box where the user enters a city name, and then the application fetches the list of council members

A minimal sample follows building from the UI Kit Hello World example, and adding the Config.

import React, { useEffect, useState } from 'react';
import ForgeReconciler, { Text, Textfield, Select } from '@forge/react';
import { invoke, view } from '@forge/bridge';

const Tag = "macro-config-example: ";

function log(message) {
  console.log(Tag + message);
}

const mockCouncilMembers = [
  { label: "John Perry",   value: "1" },
  { label: "Harry Wilson", value: "2" },
  { label: "Jane Sagan",   value: "3" },
];

function getCouncilMembers(city) {
  /*
   * In a real application this would make an api call to fetch council members for
   * a given city.
   */
  if (city == "") {
    log("No city no council");
    return [];
  } else {
    log("City council for: " + city);
    return mockCouncilMembers;
  }
}

function CouncilMembers({city}) {

  const [councilMembers, setCouncilMembers] = useState([]);

  useEffect(() => {
    var cm = getCouncilMembers(city);
    setCouncilMembers(cm);
  }, [city]);

  if (city != "") {
    log(city + " council members: " + JSON.stringify(councilMembers));
  }

  return (
    <>
    { <Select 
          isMulti
          inputId="selected-councilpeople"
          placeholder="Select desired council members..."
          name="council"
          label="Council Members"
          options={councilMembers}/> }
    </>
  )
}

const Config = () => {
  const [city, setCity] = useState("");

  useEffect(() => {
    view.getContext().then((ctx) => {
        if (ctx?.extension.config?.city != undefined) {
          setCity(ctx.extension.config.city);
        }
    })
  }, []);

  log("City: " + city);
  return (
    <>
    <Textfield name="city" label="City" />
    <CouncilMembers city={city}/> 
    </>
  );
};

const App = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    invoke('getText', { example: 'my-invoke-variable' }).then(setData);
  }, []);

  return (
    <>
      <Text>Hello world!</Text>
      <Text>{data ? data : 'Loading...'}</Text>
    </>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
ForgeReconciler.addConfig(<Config />);

Upon adding the macro to a confluence page, we see in the log that Config and CouncilMembers were rendered and there is no ‘city’ property as the macro has not been configured yet.

From console logs:

16:12:24.890 macro-config-example: City: 
16:12:24.891 macro-config-example: No city no council

After entering ‘San Francisco’ into the text field and pressing tab we can see that the components are rendered two more times.

16:12:24.890 macro-config-example: City: 
16:12:24.891 macro-config-example: No city no council 
16:25:22.410 macro-config-example: City:
16:25:22.412 macro-config-example: No city no council
16:25:22.578 macro-config-example: City: San Francisco 
16:25:22.578 macro-config-example: San Francisco council members: [] 
16:25:22.580 macro-config-example: City council for: San Francisco 
16:25:22.580 macro-config-example: San Francisco council members: [{"label":"John Perry","value":"1"},{"label":"Harry Wilson","value":"2"},{"label":"Jane Sagan","value":"3"}] 

Once when ‘city’ changed from the empty string to ‘San Francisco’, and again, when the view.getContext() promise resolved and the config was updated in the context.

Despite the component being re-rendered, the options do not appear in the dropdown.

If we close the configuration pane, and re-open the pane the options will appear. Note that even though the options now appear on reopening, there are no additional log messages indicating that the components were re-rendered.

This is likely to cause great confusion among users of a macro that needs this ability as they enter a city and don’t see the Council Members dropdown populated. But then may come back later and see it populated and not know why or how.

This only seems to apply on the first load of the configuration. After that, it seems to dynamically load. At least for me, if I change the text field twice while using the config, I get a response the second time, but not the first. Might be worth opening a bug report.

Chuckle. I couldn’t figure out where/how to open a bug report for an Atlassian component, so that’s what this post is, the bug report.

Do you have a url for bug reports? I’d be happy to formally file something elsewhere if it would result in more traction from Atlassian staff.

I think I opened a report here: Atlassian Support

Yeah submit a ticket here: https://developer.atlassian.com/support

I’ve also had this dynamic issue in UI Kit 1. Inconsistency in the macro config, and I have to force users to toggle a checkbox in order to update the macro render. It’s a joke.

You’ll discover over time that almost every app you build on this platform will require a workaround hack.

Do you have an example? The checkbox component is not supported in the list of config components.

And there’s a glass pane over the macro while you editing items in the config pane, which is only available while editing the macro.

Can you describe in a little more detail your workflow here?

Checkbox was allowed in MacroConfig in UI Kit 1. I’m yet to migrate. That would be stupid if they’ve dropped support for checkboxes in config.

I’m using useConfig() hook. Maybe they’ve fixed it (not sure) but I had to force the user to manually click a toggle checkbox in order to push changes to the macro render.

Radio boxes are supported, but checkboxes are not. At least per the documentation referenced above. I haven’t actually tried to insert a Checkbox and watch it fail. But I agree that not supporting the Checkbox component in a config seems like an oversight.

The whole documentation surrounding macro configuration needs some real work. It is clear that they have middleware messing with your components, and through trial and error you learn that the way UI Kit components are documented are not the way they behave when rendered in a configuration pane. It would be very useful if Atlassian were to document what they are doing in that middleware and how it effects the behavior of components.

And while I’m ranting about Atlassian documentation it is not at all clear what MacroConfig is relation to this https://developer.atlassian.com/platform/forge/add-configuration-to-a-macro-with-ui-kit/ which clearly doesn’t say anything about MacroConfig.

So, useConfig() must be a MacroConfig thing? And MacroConfig is a UI Kit 1 thing? Except what’s this https://developer.atlassian.com/platform/forge/ui-kit/hooks/use-config/ which references MacroConfig at the bottom? And why wouldn’t the other page tell you to use useConfig? More ranting, Atlassian really screwed the pooch when they decided it would be a good idea to rebrand UI Kit 2 as UI Kit, and UI Kit as UI Kit 1. Do they realize how that makes documentation over time kind of useless?

I’m using Custom UI in the macro, and hence UI Kit 2 in the config. And the “add configuration to your macro” documentation says to use useEffect() along with view.getContext() to retrieve and use the config.

I must say that if you are coming to the Atlassian development from a blank slate, the way they’ve evolved their developer products makes it exceedingly difficult to know which documentation to trust for the application you are trying to implement.

2 Likes

I’m moving a different app over to UI Kit 2 now and it is not a pleasant experience. Yes, Checkboxes are not allowed (got an error when trying to include one). And the configuration does not work as smoothly. Not having clear documentation that is attached to a specific version is definitely making it harder.

1 Like

@JamesDumay do you know why Checkbox in MacroConfig was dropped in UI Kit 2? And also any suggestions for OP’s issue with dynamic rendering?

Hey @nathanwaters, @AaronCollier, @skatefriday!

Apologies and understand the frustration. For UI Kit 2 macro config, dynamic rendering is fixed now - give it a go and let me know if it works.

In order to use Checkbox in the UI Kit 1 macro config, we have to wrap Checkbox with CheckboxGroup - even if it’s with a single checkbox. Surprised if there is an app with Checkbox without the CheckboxGroup parent - let me know if that’s the case.

For UI Kit (2), this behaviour is replaced with defining an options array for CheckboxGroup and you can specify just one option in the array to achieve the behaviour of a singular checkbox. We’ve made a new section in the documentation about the supported components in the macro config.

Cheers,
Yuwei

You need to also make it clear in the docs that onChange/onBlur/onFocus events don’t work in macro config. I just wasted far too much time on that because it’s undocumented on the component docs.

Yeah, I ran into that landmine also, see…

Never got a response from Atlassian on that.

@YuweiShen (you’re missing your Atlassian staff label btw) to me it seems that dynamic components in macro config are now much worse in UI Kit 2. The entire config re-renders after field values are updated. This did not happen in UI Kit 1.

You can test this easily with @forge/react 10.6.0: console.log(‘foo’) in macro config, throw in a static textfield, change the field value, it’ll console log again.

So wtf are we supposed to do? The only workaround seems to be showing a loading indicator and re-fetching the dynamic fields every single time the user makes a change.

The solution I’d like to see is:

  1. Not re-render macro config each time user updates a field value lol.
  2. Allow programmatic updates: const [config, setConfig] = useConfig()
  3. Allow onChange/onFocus/onBlur events on config fields.