Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ jobs:
forge deploy -e staging
fi

# Install Forge app dependencies
# Install or upgrade Forge app
# https://developer.atlassian.com/platform/forge/cli-reference/install/
- name: Install Forge dependencies
- name: Install/Upgrade Forge app
if: github.event_name == 'pull_request'
run: forge install -e staging -s gitauto.atlassian.net -p Jira --upgrade --confirm-scopes --non-interactive --verbose
run: |
forge install -e staging -s gitauto.atlassian.net -p Jira --confirm-scopes --non-interactive --verbose || \
forge install -e staging -s gitauto.atlassian.net -p Jira --upgrade --confirm-scopes --non-interactive --verbose
27 changes: 27 additions & 0 deletions manifest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
modules:
# The trigger module invokes a function or calls a remote backend when a product event, app lifecycle event, or data security policy event is fired.
# https://developer.atlassian.com/platform/forge/manifest-reference/modules/trigger/
# trigger:
# - key: app-lifecycle-trigger
# function: lifecycleHandler
# events:
# - installed # https://developer.atlassian.com/platform/forge/events-reference/life-cycle/
# - uninstalled

# The jira module provides functionality for Jira products.
jira:issuePanel:
- key: gitauto-jira-hello-world-issue-panel
resource: main
Expand All @@ -7,16 +17,33 @@ modules:
render: native
title: GitAuto
icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg

# https://developer.atlassian.com/platform/forge/runtime-reference/forge-resolver/
function:
- key: resolver
handler: index.handler

resources:
- key: main
path: src/frontend/index.jsx
app:
runtime:
name: nodejs20.x # Has to be 'sandbox', 'nodejs18.x', 'nodejs20.x'
id: ari:cloud:ecosystem::app/f434bcc5-834f-45e5-ba1d-62e2ee8952cd

# Environment variables are not supported in the manifest.yml file.
# https://developer.atlassian.com/platform/forge/manifest-reference/permissions/
permissions:
scopes:
- read:jira-work
external:
fetch:
backend:
- https://dkrxtcbaqzrodvsagwwn.supabase.co
- https://awegqusxzsmlgxaxyyrq.supabase.co

# https://developer.atlassian.com/platform/forge/manifest-reference/variables/
environment:
variables:
- SUPABASE_URL
- SUPABASE_API_KEY
40 changes: 36 additions & 4 deletions src/frontend/index.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useEffect, useState } from "react";
import ForgeReconciler, { Select, Text, useProductContext } from "@forge/react";
import { requestJira } from "@forge/bridge";
import { invoke } from "@forge/bridge";

const App = () => {
// Get Jira cloud ID (== workspace ID)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider consolidating the multiple state variables and effects into a single custom hook for managing GitHub repository data

The current implementation uses multiple chained useEffect hooks and state variables that could be simplified into a single custom hook. Here's how to reduce complexity while maintaining functionality:

const useGithubRepos = () => {
  const context = useProductContext();
  const [githubRepos, setGithubRepos] = useState([]);
  const [selectedRepo, setSelectedRepo] = useState(null);

  useEffect(() => {
    const fetchRepositories = async () => {
      if (!context) return;

      try {
        const response = await invoke("getGithubRepos", {
          cloudId: context.cloudId,
          projectId: context.extension.project.id,
        });

        const repos = response.map(
          (repo) => `${repo.github_owner_name}/${repo.github_repo_name}`
        );
        setGithubRepos(repos);
        setSelectedRepo(repos[0]); // Set initial selection
      } catch (error) {
        console.error("Error fetching repositories:", error);
        setGithubRepos([]);
      }
    };

    fetchRepositories();
  }, [context]);

  return { githubRepos, selectedRepo, setSelectedRepo };
};

Then simplify the App component:

const App = () => {
  const { githubRepos, selectedRepo, setSelectedRepo } = useGithubRepos();

  return (
    <>
      <Text>Target GitHub Repository:</Text>
      <Select
        value={selectedRepo}
        onChange={setSelectedRepo}
        options={githubRepos.map((repo) => ({ label: repo, value: repo }))}
      />
    </>
  );
};

This approach:

  • Eliminates intermediate state variables
  • Reduces the number of useEffect hooks
  • Encapsulates related logic in a reusable hook
  • Maintains the same functionality with less complexity

const context = useProductContext();

// Get Jira cloud ID
const [cloudId, setCloudId] = useState(null);
useEffect(() => {
if (context) {
Expand All @@ -14,17 +16,47 @@ const App = () => {
}
}, [context]);

// Get Jira project ID
const [projectId, setProjectId] = useState(null);
useEffect(() => {
if (context) setProjectId(context.extension.project.id);
}, [context]);

// Get corresponding GitHub repositories from Supabase
const [githubRepos, setGithubRepos] = useState([]);
useEffect(() => {
const fetchRepositories = async () => {
if (cloudId && projectId) {
try {
const response = await invoke("getGithubRepos", {
cloudId,
projectId,
});

// response will be an array of repositories from Supabase
setGithubRepos(
response.map((repo) => `${repo.github_owner_name}/${repo.github_repo_name}`)
);
} catch (error) {
console.error("Error fetching repositories:", error);
setGithubRepos([]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add user-facing error feedback when repository fetching fails

Consider showing an error message in the UI to inform users when repositories cannot be loaded.

          setGithubRepos([]);
          setError("Unable to load repositories. Please try again later.");

}
}
};

fetchRepositories();
}, [cloudId, projectId]);

// Get repository list where GitAuto is installed
const repositories = ["gitautoai/gitauto", "gitautoai/gitauto-jira"];
const [selectedRepo, setSelectedRepo] = useState(repositories[0]);
const [selectedRepo, setSelectedRepo] = useState(githubRepos[0]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider handling the case where githubRepos is empty to avoid setting undefined as the initial value

You could set it to null or provide a placeholder value when the array is empty.

Suggested change
const [selectedRepo, setSelectedRepo] = useState(githubRepos[0]);
const [selectedRepo, setSelectedRepo] = useState(githubRepos.length > 0 ? githubRepos[0] : null);


return (
<>
<Text>Target GitHub Repository:</Text>
<Select
value={selectedRepo}
onChange={setSelectedRepo}
options={repositories.map((repo) => ({ label: repo, value: repo }))}
options={githubRepos.map((repo) => ({ label: repo, value: repo }))}
/>
</>
);
Expand Down
33 changes: 29 additions & 4 deletions src/resolvers/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import Resolver from '@forge/resolver';
import Resolver from "@forge/resolver";
import forge from "@forge/api";

const resolver = new Resolver();

resolver.define('getText', (req) => {
console.log(req);
return 'Hello, world!';
// https://developer.atlassian.com/platform/forge/runtime-reference/forge-resolver/
resolver.define("getGithubRepos", async ({ payload }) => {
const { cloudId, projectId } = payload;

// https://supabase.com/docs/guides/api/sql-to-rest
const queryParams = new URLSearchParams({
select: "*",
jira_site_id: `eq.${cloudId}`,
jira_project_id: `eq.${projectId}`,
}).toString();
const url = `${process.env.SUPABASE_URL}/rest/v1/jira_github_links?${queryParams}`;
console.log(url);

const response = await forge.fetch(url, {
method: "GET",
headers: {
apikey: process.env.SUPABASE_API_KEY,
Authorization: `Bearer ${process.env.SUPABASE_API_KEY}`,
"Content-Type": "application/json",
},
});

if (!response.ok) throw new Error(`Failed to fetch repositories: ${response.status}`);

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

export const handler = resolver.getDefinitions();
Loading