How to build a dynamic form in MacroConfig panel?

Hi,

Could you give me an example how to build a dynamic Form in MacroConfig panel component?
On the “example apps” page, i saw only static forms and the examples use old version of UI components like ConfigForm.

Scenario:
Step 1: In MacroConfig panel, the user select a Jira project in a select field
Step 2: Depend of value selected on step 1, the user select a issue type (eg: Requirement) in a second select field.

I don’t know how do ‘depend of a previous value’
Bonus: How to display the second field only if the first one is filled ?

Draft code:

import api from "@forge/api";
import ForgeUI, { render, Fragment, Text, Macro, Form, MacroConfig, Select, useProductContext, useState } from '@forge/ui';

// Get list of Jira Project
const fetchProjects = async () => {
  const response = await api.asUser().requestJira("/rest/api/3/project");
  if (!response.ok) {
    return "Error to get Jira projet list";
  }
  return await response.json();
};

// Get list of Jira issue type depend of Jira Project
const fetchIssueTypes = async (projectIdOrKey: string) => {
  const response = await api.asUser().requestJira("/rest/api/3/project/${projectIdOrKey}");
  if (!response.ok) {
    return "Error to get Jira issue type list";
  }
  return await response.json();
};


const App = () => {
  const [ projects ] = useState(fetchProjects);
  const [ issueTypes ] = useState(fetchIssueTypes);

  return (
    <MacroConfig>
      <Select label="Project" name="projectKey">
        {projects.map(project => {
          return (
            <Option
              label={`${project.name} (${project.key})`}
              value={project.key} />
          )
        })}
      </Select>
      <Select label="Issue type" name="issueTypeId">
        {issueTypes.map(issueType => {
          return (
            <Option label={issueType.name} value={issueType.id} />
          )
        })}
      </Select>
    </MacroConfig>
  );
};

export const run = render(
  <Macro
    app={<App />}
  />
);

Thx

Hi @ThibautFAURE,

Have you tried using the useConfig hook? This should give you the ability to update the form according to the values that have been already filled.

1 Like

Hi @JackShe ,

Thank you for your help. I tried using the ‘useConfig’ with a basic example. But i have a problem, the panel of MacroConfig isn’t displayed when i click on “edit” button".
I

My example code is:

import ForgeUI, { useConfig, render, Fragment, Text, Macro, Form, TextField, MacroConfig, Select, useProductContext, useState } from '@forge/ui';

const defaultConfig = { name: 'Unknown', age: 0 };

const App = () => {
  // Retrieve the configuration
  const config = useConfig() || defaultConfig;

  // Use the configuration values
  return <Text>{config.age} years old.</Text>;

};

export const run = render(
  <Macro app={<App />} />
);


// Function that defines the configuration UI
const Config = () => {
  return (
    <MacroConfig>
      <TextField name="name" label="Pet name" defaultValue={defaultConfig.name} />
      <TextField name="age" label="Pet age" defaultValue={defaultConfig.age} />
    </MacroConfig>
  );
};

export const config = render(<Config />);

Is this a bug? did I forget something in my code ? I tried to re-install my macro several times…

Note: The example given on this page does not work because defaultConfig is missing.

Hi @ThibautFAURE ,
does the manifest.yml contain information about the config?

Thank you @FranzBinder , i added information about config in manifest.yml and it’s work ! :slight_smile:

Goal
I’ve been trying to achieve my goal for several days:
Step 1: In MacroConfig panel, the user select a Jira project in a select field
Step 2: Depend of value selected on step 1, the user select a issue type (eg: Requirement) in a second select field.

Problem:
I don’t know how to get value of selected field in step 1 dynamically.

My code is:

import ForgeUI, { useConfig, useEffect, render, Fragment, Text, Macro, Form, TextField, MacroConfig, Select, Option, useProductContext, useState } from '@forge/ui';
import api from "@forge/api";


// Get list of Jira Project
const fetchProjects = async () => {
  const response = await api.asUser().requestJira("/rest/api/3/project");
  console.log("Jira projet list - STATUS HTTP: "+ response.status);

  if (!response.ok) {
    return "Error to get Jira projet list" + response;
  }

  return await response.json();
};

// Get list of Jira issue type depend of Jira Project
const fetchIssueTypes = async (projectId: string) => {
  const response = await api.asUser().requestJira("/rest/api/3/project/"+projectId);
  console.log("Jira issue type - projectId: "+ projectId);
  console.log("Jira issue type - STATUS HTTP: "+ response.status);

  if (!response.ok) {
    return "Error to get Jira issue type list" + response;
  }

  return await response.json();
};


const App = () => {
  // Retrieve the configuration
  const config = useConfig();

  // Use the configuration values
  return (
    <Fragment>
      <Text>Hello world!</Text>
    </Fragment>
  );
};
export const run = render(
  <Macro app={<App />} />
);


// Function that defines the configuration UI
const Config = () => {
  const [projects] = useState(fetchProjects);
  const [issueTypes] = useState(fetchIssueTypes(10001)); // STATIC VALUE, I NEED A VALUE DEPEND OF SELECTED VALUE

  return (
    <MacroConfig>
      <Select label="Project" name="projectKey">
        {projects.map(project => {
          return (
            <Option label={`${project.name} (${project.key})`} value={project.Id} />
          )
        })
      }
      </Select>
      <Select label="Issue Type" name="issueType">
        {issueTypes.issueTypes.map(issueType => {
          return (
            <Option label={`${issueType.name}`} value={issueType.Id} />
          )
        })}
      </Select>
    </MacroConfig>
  );
};

export const config = render(<Config />);


Currently my MacroConfig panel look like:
Becareful i put a static value of projectId (10001):

const [issueTypes] = useState(fetchIssueTypes(10001));

image

I think i need to use “useEffect” but i didn’t find a easy example to understand how to use it

Thank you for your help

You may use this example from Atlassian to find your solution.

Hi @ThibautFAURE ,
I had a similar problem. I wanted to search for specific attachments on the current page or a specific page.
This source code works for my use case.

import ForgeUI, {
    MacroConfig,
    Option,
    render,
    Select,
    TextField,
    useConfig,
    useProductContext,
    useState
} from '@forge/ui';
import api, { route } from "@forge/api";



const fetchAttachments = async (targetPageId) => {
    const { contentId } = useProductContext();
    let pageId = contentId;
     if (targetPageId && targetPageId.length > 0) {
         pageId = targetPageId;
     }
    const response = await api.asUser().requestConfluence(route`/rest/api/content/${pageId}/child/attachment?mediaType=application/dataforms`);
    let theJson = await response.json();
    return theJson.results ? theJson.results : [];
};

const getNameFromCommentJson = (comment) => {
    try {
        return JSON.parse(comment).name;
    } catch(e) {
        return comment;
    }
}

const Config = () => {
    let targetPageId = undefined;
    try {
        targetPageId = useConfig().targetPageId;
    } catch (e) {
    }
    const [ attachments ] = useState(fetchAttachments(targetPageId));
    return (
        <MacroConfig>
            <TextField label="Page Id (optional)" description="The current page is used if nothing has been entered here." name="targetPageId"  />
            <Select label="Data Form" name="dataformAttachment">
                {attachments.map(attachment => {
                    return (
                        <Option
                            label={`${getNameFromCommentJson(attachment.extensions.comment)} (${attachment.title})`}
                            value={attachment.id} />
                    )
                })}
            </Select>
        </MacroConfig>
    );
};
export const config = render(<Config />);

With this modification it should work (not tested) for your source code.

import ForgeUI, { useConfig, useEffect, render, Fragment, Text, Macro, Form, TextField, MacroConfig, Select, Option, useProductContext, useState } from '@forge/ui';
import api from "@forge/api";


// Get list of Jira Project
const fetchProjects = async () => {
  const response = await api.asUser().requestJira("/rest/api/3/project");
  console.log("Jira projet list - STATUS HTTP: "+ response.status);

  if (!response.ok) {
    return "Error to get Jira projet list" + response;
  }

  return await response.json();
};

// Get list of Jira issue type depend of Jira Project
const fetchIssueTypes = async (projectId: string) => {
  let toUseProjectId = "10001";
  if (projectId && projectId.length > 0) {
    toUseProjectId = projectId
  }
  const response = await api.asUser().requestJira("/rest/api/3/project/"+toUseProjectId);
  console.log("Jira issue type - projectId: "+ toUseProjectId);
  console.log("Jira issue type - STATUS HTTP: "+ response.status);

  if (!response.ok) {
    return "Error to get Jira issue type list" + response;
  }

  return await response.json();
};


const App = () => {
  // Retrieve the configuration
  const config = useConfig();

  // Use the configuration values
  return (
    <Fragment>
      <Text>Hello world!</Text>
    </Fragment>
  );
};
export const run = render(
  <Macro app={<App />} />
);


// Function that defines the configuration UI
const Config = () => {
    let projectKey = undefined;
    try {
        projectKey = useConfig().projectKey;
    } catch (e) {
    }
  const [projects] = useState(fetchProjects);
  const [issueTypes] = useState(fetchIssueTypes(projectKey)); 

  return (
    <MacroConfig>
      <Select label="Project" name="projectKey">
        {projects.map(project => {
          return (
            <Option label={`${project.name} (${project.key})`} value={project.Id} />
          )
        })
      }
      </Select>
      <Select label="Issue Type" name="issueType">
        {issueTypes.issueTypes.map(issueType => {
          return (
            <Option label={`${issueType.name}`} value={issueType.Id} />
          )
        })}
      </Select>
    </MacroConfig>
  );
};

export const config = render(<Config />);