Skip to content

Commit 2d9732a

Browse files
authored
Merge pull request #205 from htilly/refactor/confluence-separation
Refactor workflow: Separate Confluence into independent job
2 parents 194b01b + 4fb3907 commit 2d9732a

2 files changed

Lines changed: 141 additions & 177 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Create Confluence page for feature request
2+
// Uses native fetch (Node 20+)
3+
4+
const enhancedTask = process.env.ENHANCED_TASK || "";
5+
const task = process.env.TASK || "";
6+
const requester = process.env.REQUESTER || "unknown";
7+
const issueNumber = process.env.ISSUE_NUMBER || "unknown";
8+
const confluenceUrl = process.env.CONFLUENCE_URL || "";
9+
const confluenceEmail = process.env.CONFLUENCE_EMAIL || "";
10+
const confluenceApiToken = process.env.CONFLUENCE_API_TOKEN || "";
11+
const confluenceSpaceKey = process.env.CONFLUENCE_SPACE_KEY || "AICODE";
12+
const confluenceParentPageId = process.env.CONFLUENCE_PARENT_PAGE_ID || "";
13+
const githubRunId = process.env.GITHUB_RUN_ID || "unknown";
14+
const githubRepo = process.env.GITHUB_REPOSITORY || "unknown";
15+
16+
// Exit gracefully if Confluence not configured
17+
if (!confluenceUrl || !confluenceEmail || !confluenceApiToken) {
18+
console.log("[CONFLUENCE] Confluence credentials not configured, skipping page creation");
19+
process.exit(0);
20+
}
21+
22+
try {
23+
const timestamp = new Date().toISOString().split('T')[0];
24+
const pageTitle = `AICODE: ${task.substring(0, 80)} (${timestamp})`;
25+
26+
const storageContent = `
27+
<h2>Original Task</h2>
28+
<p>${task.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>')}</p>
29+
30+
<h2>User Story</h2>
31+
<p>${enhancedTask.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>')}</p>
32+
33+
<h2>Implementation Status</h2>
34+
<p><ac:structured-macro ac:name="status"><ac:parameter ac:name="colour">Yellow</ac:parameter><ac:parameter ac:name="title">In Progress</ac:parameter></ac:structured-macro></p>
35+
36+
<h2>Related Links</h2>
37+
<p>GitHub Issue: <a href="https://github.com/${githubRepo}/issues/${issueNumber}">#${issueNumber}</a></p>
38+
<p>GitHub Run: <a href="https://github.com/${githubRepo}/actions/runs/${githubRunId}">View Workflow</a></p>
39+
`.trim();
40+
41+
const auth = Buffer.from(`${confluenceEmail}:${confluenceApiToken}`).toString('base64');
42+
43+
const pageData = {
44+
type: 'page',
45+
title: pageTitle,
46+
space: { key: confluenceSpaceKey },
47+
body: {
48+
storage: {
49+
value: storageContent,
50+
representation: 'storage'
51+
}
52+
}
53+
};
54+
55+
if (confluenceParentPageId) {
56+
pageData.ancestors = [{ id: confluenceParentPageId }];
57+
}
58+
59+
console.log(`[CONFLUENCE] Creating page: ${pageTitle}`);
60+
61+
const response = await fetch(`${confluenceUrl}/rest/api/content`, {
62+
method: 'POST',
63+
headers: {
64+
'Authorization': `Basic ${auth}`,
65+
'Content-Type': 'application/json',
66+
'Accept': 'application/json'
67+
},
68+
body: JSON.stringify(pageData)
69+
});
70+
71+
if (!response.ok) {
72+
const errorText = await response.text();
73+
console.error(`[CONFLUENCE] Failed to create page: ${response.status} ${response.statusText}`);
74+
console.error(`[CONFLUENCE] Error details: ${errorText}`);
75+
process.exit(1);
76+
}
77+
78+
const data = await response.json();
79+
const pageUrl = `${confluenceUrl}/wiki${data._links.webui}`;
80+
console.log(`[CONFLUENCE] ✅ Confluence page created: ${pageUrl}`);
81+
console.log(`CONFLUENCE_URL:${pageUrl}`);
82+
83+
process.exit(0);
84+
} catch (error) {
85+
console.error(`[CONFLUENCE] Error creating page: ${error.message}`);
86+
console.error(error.stack);
87+
process.exit(1);
88+
}

.github/workflows/feature-request-enhance.yml

Lines changed: 53 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ jobs:
5353
outputs:
5454
enhanced_task: ${{ steps.preprocess.outputs.enhanced_task }}
5555
task_json: ${{ steps.preprocess.outputs.task_json }}
56-
confluence_url: ${{ steps.preprocess.outputs.confluence_url }}
5756
steps:
5857
- name: Checkout develop
5958
uses: actions/checkout@v4
@@ -74,7 +73,7 @@ jobs:
7473
id: preprocess
7574
env:
7675
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
77-
PREPROCESSING_MODEL: ${{ secrets.PREPROCESSING_MODEL || 'gpt-4o-mini' }}
76+
PREPROCESSING_MODEL: ${{ secrets.PREPROCESSING_MODEL }}
7877
TASK: ${{ github.event.issue.title }}
7978
REQUESTER: ${{ github.event.issue.user.login }}
8079
# Skip Confluence creation in preprocessing - will be done in parallel job
@@ -114,6 +113,9 @@ jobs:
114113
if: needs.preprocess.outcome == 'success'
115114
permissions:
116115
contents: read
116+
issues: write
117+
outputs:
118+
confluence_url: ${{ steps.confluence.outputs.confluence_url }}
117119
steps:
118120
- name: Checkout develop
119121
uses: actions/checkout@v4
@@ -133,107 +135,58 @@ jobs:
133135
- name: Create Confluence page
134136
id: confluence
135137
env:
136-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
137-
PREPROCESSING_MODEL: ${{ secrets.PREPROCESSING_MODEL || 'gpt-4o-mini' }}
138138
TASK: ${{ github.event.issue.title }}
139139
REQUESTER: ${{ github.event.issue.user.login }}
140+
ISSUE_NUMBER: ${{ github.event.issue.number }}
140141
CONFLUENCE_URL: ${{ secrets.CONFLUENCE_URL }}
141142
CONFLUENCE_EMAIL: ${{ secrets.CONFLUENCE_EMAIL }}
142143
CONFLUENCE_API_TOKEN: ${{ secrets.CONFLUENCE_API_TOKEN }}
143-
CONFLUENCE_SPACE_KEY: ${{ secrets.CONFLUENCE_SPACE_KEY || 'AICODE' }}
144-
CONFLUENCE_PARENT_PAGE_ID: ${{ secrets.CONFLUENCE_PARENT_PAGE_ID || '5177529' }}
144+
CONFLUENCE_SPACE_KEY: ${{ secrets.CONFLUENCE_SPACE_KEY }}
145+
CONFLUENCE_PARENT_PAGE_ID: ${{ secrets.CONFLUENCE_PARENT_PAGE_ID }}
145146
GITHUB_RUN_ID: ${{ github.run_id }}
146-
# Use enhanced task from preprocessing
147+
GITHUB_REPOSITORY: ${{ github.repository }}
147148
ENHANCED_TASK: ${{ needs.preprocess.outputs.enhanced_task }}
148149
run: |
149-
# Create a temporary script that only creates Confluence page
150-
cat > /tmp/create-confluence.mjs << 'SCRIPT_EOF'
151-
// Use native fetch (Node 20+)
152-
153-
const enhancedTask = process.env.ENHANCED_TASK || "";
154-
const task = process.env.TASK || "";
155-
const requester = process.env.REQUESTER || "unknown";
156-
const confluenceUrl = process.env.CONFLUENCE_URL || "";
157-
const confluenceEmail = process.env.CONFLUENCE_EMAIL || "";
158-
const confluenceApiToken = process.env.CONFLUENCE_API_TOKEN || "";
159-
const confluenceSpaceKey = process.env.CONFLUENCE_SPACE_KEY || "AICODE";
160-
const confluenceParentPageId = process.env.CONFLUENCE_PARENT_PAGE_ID || "";
161-
const githubRunId = process.env.GITHUB_RUN_ID || "unknown";
162-
163-
if (!confluenceUrl || !confluenceEmail || !confluenceApiToken) {
164-
console.log("[CONFLUENCE] Confluence credentials not configured, skipping page creation");
165-
process.exit(0);
166-
}
167-
168-
try {
169-
const timestamp = new Date().toISOString().split('T')[0];
170-
const pageTitle = `AICODE: ${task.substring(0, 80)} (${timestamp})`;
171-
172-
const storageContent = `
173-
<h2>Original Task</h2>
174-
<p>${task.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>')}</p>
175-
176-
<h2>User Story</h2>
177-
<p>${enhancedTask.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>')}</p>
178-
179-
<h2>Implementation Status</h2>
180-
<p><ac:structured-macro ac:name="status"><ac:parameter ac:name="colour">Yellow</ac:parameter><ac:parameter ac:name="title">In Progress</ac:parameter></ac:structured-macro></p>
181-
182-
<h2>Related Links</h2>
183-
<p>GitHub Run: <a href="https://github.com/htilly/SlackONOS/actions/runs/${githubRunId}">View Workflow</a></p>
184-
`.trim();
185-
186-
const auth = Buffer.from(`${confluenceEmail}:${confluenceApiToken}`).toString('base64');
187-
188-
const pageData = {
189-
type: 'page',
190-
title: pageTitle,
191-
space: { key: confluenceSpaceKey },
192-
body: {
193-
storage: {
194-
value: storageContent,
195-
representation: 'storage'
196-
}
197-
}
198-
};
199-
200-
if (confluenceParentPageId) {
201-
pageData.ancestors = [{ id: confluenceParentPageId }];
202-
}
203-
204-
const response = await fetch(`${confluenceUrl}/rest/api/content`, {
205-
method: 'POST',
206-
headers: {
207-
'Authorization': `Basic ${auth}`,
208-
'Content-Type': 'application/json',
209-
'Accept': 'application/json'
210-
},
211-
body: JSON.stringify(pageData)
212-
});
213-
214-
if (!response.ok) {
215-
const errorText = await response.text();
216-
console.error(`[CONFLUENCE] Failed to create page: ${response.status} ${response.statusText}`);
217-
console.error(`[CONFLUENCE] Error details: ${errorText}`);
218-
process.exit(1);
219-
}
220-
221-
const data = await response.json();
222-
const pageUrl = `${confluenceUrl}/wiki${data._links.webui}`;
223-
console.log(`[CONFLUENCE] ✅ Confluence page created: ${pageUrl}`);
224-
console.log(`CONFLUENCE_URL:${pageUrl}`);
225-
} catch (error) {
226-
console.error(`[CONFLUENCE] Error creating page: ${error.message}`);
227-
process.exit(1);
228-
}
229-
SCRIPT_EOF
230-
231-
OUTPUT=$(node /tmp/create-confluence.mjs 2>&1)
150+
OUTPUT=$(node .github/agent/create-confluence.mjs 2>&1)
232151
echo "$OUTPUT"
233-
152+
234153
# Extract Confluence URL
235-
CONFLUENCE_URL=$(echo "$OUTPUT" | grep "CONFLUENCE_URL:" | sed 's/CONFLUENCE_URL://' || echo "N/A")
236-
echo "confluence_url=$CONFLUENCE_URL" >> $GITHUB_OUTPUT
154+
CONFLUENCE_URL=$(echo "$OUTPUT" | grep "CONFLUENCE_URL:" | sed 's/CONFLUENCE_URL://' || echo "")
155+
if [ -n "$CONFLUENCE_URL" ]; then
156+
echo "confluence_url=$CONFLUENCE_URL" >> $GITHUB_OUTPUT
157+
else
158+
echo "confluence_url=" >> $GITHUB_OUTPUT
159+
fi
160+
161+
- name: Add Confluence link to issue
162+
if: steps.confluence.outputs.confluence_url != ''
163+
env:
164+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
165+
CONFLUENCE_URL: ${{ steps.confluence.outputs.confluence_url }}
166+
run: |
167+
# Get current issue body
168+
CURRENT_BODY=$(curl -s "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}" \
169+
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
170+
-H "Accept: application/vnd.github+json" | jq -r '.body')
171+
172+
# Add Confluence link to the end
173+
UPDATED_BODY=$(jq -n \
174+
--arg current "$CURRENT_BODY" \
175+
--arg confluence "$CONFLUENCE_URL" \
176+
'$current + "\n\n📄 **Requirements documented:** " + $confluence')
177+
178+
# Update issue with Confluence link
179+
PAYLOAD=$(jq -n \
180+
--arg body "$UPDATED_BODY" \
181+
'{body: $body}')
182+
183+
curl -X PATCH "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}" \
184+
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
185+
-H "Accept: application/vnd.github+json" \
186+
-H "Content-Type: application/json" \
187+
-d "$PAYLOAD"
188+
189+
echo "✅ Added Confluence link to issue"
237190
238191
update-github-issue:
239192
runs-on: ubuntu-latest
@@ -268,28 +221,12 @@ jobs:
268221
fi
269222
270223
# Build enhanced body with proper escaping using jq
271-
# Start with base content (without Confluence link for now)
272-
BASE_BODY=$(jq -n \
224+
ENHANCED_BODY=$(jq -n \
273225
--arg enhanced "$ENHANCED_TASK" \
274226
--arg original "$ORIGINAL_BODY" \
275227
--arg requester "${{ github.event.issue.user.login }}" \
276228
--arg created "${{ github.event.issue.created_at }}" \
277-
'## 📝 Enhanced Feature Request
278-
279-
\($enhanced)
280-
281-
---
282-
283-
## 📋 Original Request
284-
285-
\($original)
286-
287-
---
288-
289-
**Requested by:** \($requester)
290-
**Created:** \($created)')
291-
292-
ENHANCED_BODY="$BASE_BODY"
229+
'"## 📝 Enhanced Feature Request\n\n" + $enhanced + "\n\n---\n\n## 📋 Original Request\n\n" + $original + "\n\n---\n\n**Requested by:** " + $requester + " \n**Created:** " + $created')
293230
294231
# Use jq to properly construct JSON payload for PATCH
295232
PAYLOAD=$(jq -n \
@@ -305,82 +242,19 @@ jobs:
305242
306243
echo "✅ Issue #${{ github.event.issue.number }} updated with enhanced description"
307244
308-
add-confluence-link:
309-
runs-on: ubuntu-latest
310-
needs: [preprocess, create-confluence, update-github-issue]
311-
if: needs.create-confluence.outcome == 'success' && needs.update-github-issue.outcome == 'success'
312-
permissions:
313-
issues: write
314-
steps:
315-
- name: Add Confluence link to issue
316-
env:
317-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
318-
run: |
319-
CONFLUENCE_URL="${{ needs.create-confluence.outputs.confluence_url }}"
320-
321-
if [ "$CONFLUENCE_URL" = "N/A" ] || [ -z "$CONFLUENCE_URL" ]; then
322-
echo "No Confluence URL to add"
323-
exit 0
324-
fi
325-
326-
# Get current issue body
327-
CURRENT_BODY=$(curl -s "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}" \
328-
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
329-
-H "Accept: application/vnd.github+json" | jq -r '.body')
330-
331-
# Add Confluence link to the end
332-
UPDATED_BODY=$(jq -n \
333-
--arg current "$CURRENT_BODY" \
334-
--arg confluence "$CONFLUENCE_URL" \
335-
'\($current)
336-
337-
📄 **Requirements documented:** \($confluence)')
338-
339-
# Update issue with Confluence link
340-
PAYLOAD=$(jq -n \
341-
--arg body "$UPDATED_BODY" \
342-
'{body: $body}')
343-
344-
curl -X PATCH "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}" \
345-
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
346-
-H "Accept: application/vnd.github+json" \
347-
-H "Content-Type: application/json" \
348-
-d "$PAYLOAD"
349-
350-
echo "✅ Added Confluence link to issue"
351-
352245
add-comment:
353246
runs-on: ubuntu-latest
354-
needs: [preprocess, create-confluence, update-github-issue]
355-
if: always() && (needs.preprocess.outcome == 'success' || needs.update-github-issue.outcome == 'success')
247+
needs: [preprocess, update-github-issue]
248+
if: needs.update-github-issue.outcome == 'success'
356249
permissions:
357250
issues: write
358251
steps:
359252
- name: Add comment to issue
360253
env:
361254
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
362255
run: |
363-
CONFLUENCE_URL="${{ needs.create-confluence.outputs.confluence_url }}"
364-
365256
# Build comment with proper escaping using jq
366-
if [ "$CONFLUENCE_URL" != "N/A" ] && [ -n "$CONFLUENCE_URL" ]; then
367-
COMMENT=$(jq -n \
368-
--arg confluence "$CONFLUENCE_URL" \
369-
'🤖 **Feature request enhanced!**
370-
371-
This feature request has been automatically enhanced with a structured user story format.
372-
373-
📄 Requirements documented: \($confluence)
374-
375-
The issue description has been updated with acceptance criteria and technical notes.')
376-
else
377-
COMMENT=$(jq -n \
378-
'🤖 **Feature request enhanced!**
379-
380-
This feature request has been automatically enhanced with a structured user story format.
381-
382-
The issue description has been updated with acceptance criteria and technical notes.')
383-
fi
257+
COMMENT=$(jq -n '"🤖 **Feature request enhanced!**\n\nThis feature request has been automatically enhanced with a structured user story format.\n\nThe issue description has been updated with acceptance criteria and technical notes."')
384258
385259
# Use jq to properly construct JSON payload
386260
PAYLOAD=$(jq -n \
@@ -392,3 +266,5 @@ The issue description has been updated with acceptance criteria and technical no
392266
-H "Accept: application/vnd.github+json" \
393267
-H "Content-Type: application/json" \
394268
-d "$PAYLOAD"
269+
270+
echo "✅ Added comment to issue"

0 commit comments

Comments
 (0)