The Atlassian Community Forums are currently in read-only mode. We will be relaunching on a new platform on September 22 (read more here). We apologize for the extended downtime. For concerns or questions, please email communitymanagers@atlassian.com. See you on the other side, on the new Atlassian Community Forums! :)

×

Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Define custom layout per project with forge

Manar El Kefi
August 7, 2024

Hello , I want to customize layout per project , so I created  a module : jira project setting where there is a form to select the columns that the user want to belayed in subtask panel . The problem is that the subtasks are not displayed and there is the error : 

Error fetching issue: {"code":401,"message":"Unauthorized; scope does not match"} Error loading panel data: Error: Failed to fetch issue

manifest.yml : 

 

modules:
  jira:projectSettingsPage:
    - key: subtask-column-config
      function: configFunction
      title: "Subtask Column Configuration"
      layout: basic
  jira:issuePanel:
    - key: subtask-panel
      title: My Issue Panel
      function: panelFunction
  function:
    - key: configFunction
      handler: index.configFunction
    - key: panelFunction
      handler: index.panelFunction
permissions:
  scopes:
    - read:jira-user
    - write:jira-work
    - read:jira-work
    - manage:jira-project
app:
  runtime:
    name: nodejs18.x
  id: ari:cloud:ecosystem::app/00ba7460-efec-42a5-ac54-247377f9ff23
  licensing:
    enabled: true
index.js : import ForgeUI, { render, ProjectSettingsPage, Fragment, Text, Table, Head, Cell, Row, Link, Button, Select, Option, Form, useState, useEffect, useProductContext, IssuePanel } from '@forge/ui';
import api, { route } from '@forge/api';

// Fonction pour récupérer les détails d'une issue par sa clé
const fetchIssueByKey = async (issueKey) => {
  const url = route(`/rest/api/3/issue/${issueKey}`);
  const response = await api.asApp().requestJira(url);

  if (!response.ok) {
    const errorMessage = await response.text();
    console.error('Error fetching issue:', errorMessage);
    throw new Error('Failed to fetch issue');
  }

  const data = await response.json();
  return data;
};

// Fonction de test de l'API
const testApiCall = async () => {
  try {
    const url = route('/rest/api/3/myself');
    const response = await api.asApp().requestJira(url);

    if (!response.ok) {
      const errorMessage = await response.text();
      console.error('Test API Call Response Error:', errorMessage);
      throw new Error('Failed to test API call');
    }

    const data = await response.json();
    console.log('Test API Call Response:', data);
  } catch (error) {
    console.error('Error in Test API Call:', error);
  }
};

// Configuration Panel Function
const ConfigPanel = () => {
  const [columns, setColumns] = useState([]);

  const handleSubmit = async (formData) => {
    const projectKey = formData.projectKey;
    const selectedColumns = formData.columns;
    const url = route(`/rest/api/3/project/${projectKey}/properties/subtask-columns`);
   
    try {
      const response = await api.asApp().requestJira(url, {
        method: 'PUT',
        body: JSON.stringify({ columns: selectedColumns }),
      });

      if (!response.ok) {
        const errorMessage = await response.text();
        console.error('Error saving configuration:', errorMessage);
        throw new Error('Failed to save configuration');
      }
    } catch (error) {
      console.error('Error in handleSubmit:', error);
    }

    // Réinitialisation du formulaire
    setColumns([]);
  };

  return (
    <ProjectSettingsPage>
      <Fragment>
        <Text content="Configure Subtask Columns" />
        <Form onSubmit={handleSubmit}>
          <Select
            label="Select Columns"
            isMulti
            name="columns"
            value={columns}
            onChange={(values) => setColumns(values)}
          >
            <Option value="Key" label="Key" />
            <Option value="Summary" label="Summary" />
            <Option value="Status" label="Status" />
            <Option value="Assignee" label="Assignee" />
            <Option value="Priority" label="Priority" />
            <Option value="Created" label="Created" />
          </Select>
          <Button text="Save" type="submit" />
        </Form>
      </Fragment>
    </ProjectSettingsPage>
  );
};

// Récupération de la configuration du layout
const fetchLayoutConfig = async (projectKey) => {
  const url = route(`/rest/api/3/project/${projectKey}/properties/subtask-columns`);
  const response = await api.asApp().requestJira(url);

  if (!response.ok) {
    const errorMessage = await response.text();
    console.error('Error fetching layout config:', errorMessage);
    throw new Error('Failed to fetch layout config');
  }

  const data = await response.json();
  return data.columns || ['Key', 'Summary', 'Status', 'Assignee', 'Priority', 'Created'];
};

// Récupération des sous-tâches
const fetchSubtasks = async (issueKey) => {
  const response = await api.asApp().requestJira(route(`/rest/api/3/search?jql=parent=${issueKey}&fields=summary,status,assignee,priority,created`));

  if (!response.ok) {
    const errorMessage = await response.text();
    console.error('Error fetching subtasks:', errorMessage);
    throw new Error('Failed to fetch subtasks');
  }

  const data = await response.json();
  return data.issues || [];
};

// Fonction pour le panneau de sous-tâches
const SubtaskPanel = () => {
  const { platformContext: { issueKey } } = useProductContext();
  const [subtasks, setSubtasks] = useState([]);
  const [layout, setLayout] = useState([]);

  useEffect(async () => {
    if (!issueKey) {
      console.error('No issueKey found in context.');
      return;
    }

    try {
      const issue = await fetchIssueByKey(issueKey);
      if (!issue.fields || !issue.fields.project) {
        console.error('Issue or project data is missing.');
        return;
      }
     
      const projectKey = issue.fields.project.key;
      const layoutConfig = await fetchLayoutConfig(projectKey);

      setLayout(layoutConfig);
      const subtasks = await fetchSubtasks(issueKey);
      setSubtasks(subtasks);
    } catch (error) {
      console.error('Error loading panel data:', error);
    }
  }, [issueKey]);

  return (
    <Fragment>
      <Text content="Subtasks" />
      <Table>
        <Head>
          {layout.includes('Key') && <Cell><Text content="Key" /></Cell>}
          {layout.includes('Summary') && <Cell><Text content="Summary" /></Cell>}
          {layout.includes('Status') && <Cell><Text content="Status" /></Cell>}
          {layout.includes('Assignee') && <Cell><Text content="Assignee" /></Cell>}
          {layout.includes('Priority') && <Cell><Text content="Priority" /></Cell>}
          {layout.includes('Created') && <Cell><Text content="Created" /></Cell>}
        </Head>
        {subtasks.map(subtask => (
          <Row key={subtask.id}>
            {layout.includes('Key') && (
              <Cell>
                <Text>
                  <Link href={`/browse/${subtask.key}`}>{subtask.key}</Link>
                </Text>
              </Cell>
            )}
            {layout.includes('Summary') && <Cell><Text content={subtask.fields.summary} /></Cell>}
            {layout.includes('Status') && <Cell><Text content={subtask.fields.status.name} /></Cell>}
            {layout.includes('Assignee') && <Cell><Text content={subtask.fields.assignee ? subtask.fields.assignee.displayName : 'Unassigned'} /></Cell>}
            {layout.includes('Priority') && <Cell><Text content={subtask.fields.priority ? subtask.fields.priority.name : 'N/A'} /></Cell>}
            {layout.includes('Created') && <Cell><Text content={new Date(subtask.fields.created).toLocaleString()} /></Cell>}
          </Row>
        ))}
      </Table>
    </Fragment>
  );
};

// Export Functions
export const configFunction = render(<ConfigPanel />);
export const panelFunction = render(<IssuePanel><SubtaskPanel /></IssuePanel>);

1 answer

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

0 votes
Lígia Zanchet
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
August 12, 2024

Hello Manar El Kefi

For module questions, you can redirect to our Developer community here: 
https://developer.atlassian.com/platform/forge/get-help/#ask-the-community

They should be able to assist you properly.

Thanks 

DEPLOYMENT TYPE
CLOUD
PRODUCT PLAN
STANDARD
PERMISSIONS LEVEL
Product Admin
TAGS
AUG Leaders

Atlassian Community Events