Skip to content

Commit 2fcbff7

Browse files
committedDec 10, 2024·
Enable GitAuto forge to call GitAuto with payload
1 parent 0e43aaf commit 2fcbff7

File tree

2 files changed

+130
-24
lines changed

2 files changed

+130
-24
lines changed
 

‎src/frontend/index.jsx

+42-9
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,21 @@ const App = () => {
2020
}, [context]);
2121

2222
// Get Jira issue ID
23-
const [issueId, setIssueId] = useState(null);
23+
const [issueDetails, setIssueDetails] = useState(null);
2424
useEffect(() => {
25-
if (context) setIssueId(context.extension.issue.id);
25+
const fetchIssueDetails = async () => {
26+
if (!context?.extension?.issue?.id) return;
27+
try {
28+
const details = await invoke("getIssueDetails", {
29+
issueId: context.extension.issue.id,
30+
});
31+
setIssueDetails(details);
32+
} catch (error) {
33+
console.error("Error fetching issue details:", error);
34+
}
35+
};
36+
37+
fetchIssueDetails();
2638
}, [context]);
2739

2840
// Get corresponding GitHub repositories from Supabase
@@ -47,29 +59,33 @@ const App = () => {
4759

4860
// Handle selected repository
4961
const [isLoading, setIsLoading] = useState(true);
50-
const [selectedRepo, setSelectedRepo] = useState("");
62+
const [selectedRepo, setSelectedRepo] = useState({ label: "", value: "" });
5163
useEffect(() => {
5264
if (!cloudId || !projectId) return;
5365
const loadSavedRepo = async () => {
5466
setIsLoading(true);
5567
try {
56-
const savedRepo = await invoke("getStoredRepo", { cloudId, projectId, issueId });
57-
setSelectedRepo(savedRepo || "");
68+
const savedRepo = await invoke("getStoredRepo", {
69+
cloudId,
70+
projectId,
71+
issueId: issueDetails?.id,
72+
});
73+
setSelectedRepo(savedRepo || { label: "", value: "" });
5874
} catch (error) {
5975
console.error("Error loading saved repo:", error);
6076
} finally {
6177
setIsLoading(false);
6278
}
6379
};
6480
loadSavedRepo();
65-
}, [cloudId, projectId, issueId]);
81+
}, [cloudId, projectId, issueDetails]);
6682

6783
// Save repository when selected
6884
const handleRepoChange = async (value) => {
6985
setSelectedRepo(value);
7086
if (!cloudId || !projectId) return;
7187
try {
72-
await invoke("storeRepo", { cloudId, projectId, issueId, value });
88+
await invoke("storeRepo", { cloudId, projectId, issueId: issueDetails?.id, value });
7389
} catch (error) {
7490
console.error("Error saving repo:", error);
7591
}
@@ -84,7 +100,24 @@ const App = () => {
84100
if (!checked || !selectedRepo) return;
85101
setIsTriggering(true);
86102
try {
87-
await invoke("triggerGitAuto", { cloudId, projectId, issueId, selectedRepo });
103+
const [ownerName, repoName] = selectedRepo.value.split("/");
104+
const repoData = githubRepos.find(
105+
(repo) => repo.github_owner_name === ownerName && repo.github_repo_name === repoName
106+
);
107+
108+
await invoke("triggerGitAuto", {
109+
cloudId,
110+
projectId,
111+
...issueDetails,
112+
owner: {
113+
id: repoData?.github_owner_id,
114+
name: ownerName,
115+
},
116+
repo: {
117+
id: repoData?.github_repo_id,
118+
name: repoName,
119+
},
120+
});
88121
} catch (error) {
89122
console.error("Error triggering GitAuto:", error);
90123
} finally {
@@ -99,7 +132,7 @@ const App = () => {
99132
<Select
100133
value={selectedRepo}
101134
onChange={handleRepoChange}
102-
options={githubRepos.map((repo) => ({ label: repo, value: repo }))}
135+
options={githubRepos.map((repo) => ({ label: repo, value: repo }))} // must be this format
103136
isDisabled={isLoading}
104137
placeholder="Select a repository"
105138
/>

‎src/resolvers/index.js

+88-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Resolver from "@forge/resolver";
22
import forge from "@forge/api";
3-
import { storage } from "@forge/api";
3+
import { route, storage } from "@forge/api";
44

55
const resolver = new Resolver();
66

@@ -14,7 +14,7 @@ resolver.define("getGithubRepos", async ({ payload }) => {
1414

1515
// https://supabase.com/docs/guides/api/sql-to-rest
1616
const queryParams = new URLSearchParams({
17-
select: "*",
17+
select: "github_owner_name,github_repo_name,github_owner_id",
1818
jira_site_id: `eq.${cloudId}`,
1919
jira_project_id: `eq.${projectId}`,
2020
}).toString();
@@ -51,26 +51,99 @@ resolver.define("storeRepo", async ({ payload }) => {
5151
return await storage.set(key, value);
5252
});
5353

54+
// Trigger GitAuto by calling the FastAPI endpoint
5455
resolver.define("triggerGitAuto", async ({ payload }) => {
55-
const { cloudId, projectId, issueId, selectedRepo } = payload;
56-
57-
// Determine the API endpoint based on environment
58-
const endpoint = process.env.GITAUTO_URL + "/webhook";
59-
console.log("Endpoint", endpoint);
56+
const endpoint = process.env.GITAUTO_URL + "/jira-webhook";
6057
const response = await forge.fetch(endpoint, {
6158
method: "POST",
6259
headers: { "Content-Type": "application/json" },
63-
body: JSON.stringify({
64-
cloudId,
65-
projectId,
66-
issueId,
67-
repository: selectedRepo,
68-
}),
60+
body: JSON.stringify({ ...payload }),
6961
});
70-
7162
if (!response.ok) throw new Error(`Failed to trigger GitAuto: ${response.status}`);
72-
7363
return await response.json();
7464
});
7565

66+
// Convert Atlassian Document Format to Markdown
67+
const adfToMarkdown = (adf) => {
68+
if (!adf || !adf.content) return "";
69+
70+
return adf.content
71+
.map((block) => {
72+
switch (block.type) {
73+
case "paragraph":
74+
return block.content?.map((item) => item.text || "").join("") + "\n\n";
75+
case "heading":
76+
const level = block.attrs?.level || 1;
77+
const hashes = "#".repeat(level);
78+
return `${hashes} ${block.content?.map((item) => item.text || "").join("")}\n\n`;
79+
case "bulletList":
80+
return (
81+
block.content
82+
?.map((item) => `- ${item.content?.map((subItem) => subItem.text || "").join("")}`)
83+
.join("\n") + "\n\n"
84+
);
85+
case "orderedList":
86+
return (
87+
block.content
88+
?.map(
89+
(item, index) =>
90+
`${index + 1}. ${item.content?.map((subItem) => subItem.text || "").join("")}`
91+
)
92+
.join("\n") + "\n\n"
93+
);
94+
case "codeBlock":
95+
return `\`\`\`\n${block.content?.map((item) => item.text || "").join("")}\n\`\`\`\n\n`;
96+
default:
97+
return "";
98+
}
99+
})
100+
.join("")
101+
.trim();
102+
};
103+
104+
// Get issue details from Jira
105+
// https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get
106+
resolver.define("getIssueDetails", async ({ payload }) => {
107+
const { issueId } = payload;
108+
const response = await forge.asApp().requestJira(route`/rest/api/3/issue/${issueId}`, {
109+
method: "GET",
110+
headers: { "Content-Type": "application/json" },
111+
});
112+
if (!response.ok) throw new Error(`Failed to fetch issue details: ${response.status}`);
113+
const data = await response.json();
114+
// console.log("Jira issue details:", data);
115+
116+
// Format comments into readable text list
117+
const comments =
118+
data.fields.comment?.comments?.map((comment) => {
119+
const timestamp = new Date(comment.created).toLocaleString();
120+
return `${comment.author.displayName} (${timestamp}):\n${adfToMarkdown(comment.body)}`;
121+
}) || [];
122+
123+
return {
124+
// project: {
125+
// id: data.fields.project.id,
126+
// key: data.fields.project.key,
127+
// name: data.fields.project.name,
128+
// },
129+
issue: {
130+
id: data.id,
131+
key: data.key,
132+
title: data.fields.summary,
133+
body: adfToMarkdown(data.fields.description),
134+
comments: comments,
135+
},
136+
creator: {
137+
id: data.fields.creator.accountId,
138+
displayName: data.fields.creator.displayName,
139+
email: data.fields.creator.emailAddress,
140+
},
141+
reporter: {
142+
id: data.fields.reporter.accountId,
143+
displayName: data.fields.reporter.displayName,
144+
email: data.fields.reporter.emailAddress,
145+
},
146+
};
147+
});
148+
76149
export const handler = resolver.getDefinitions();

0 commit comments

Comments
 (0)
Please sign in to comment.