Skip to content

Commit 391a699

Browse files
feat(medium): Refactor page readiness logic and consolidate CI scripts (#9069)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: arii <342438+arii@users.noreply.github.com>
1 parent 536ba20 commit 391a699

5 files changed

Lines changed: 76 additions & 64 deletions

File tree

.github/actions/setup-env/action.yml

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,18 @@ runs:
5353
- name: Install GitHub CLI
5454
shell: bash
5555
run: |
56-
mkdir -p $HOME/.local/bin
57-
# Detect OS and Architecture
58-
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
59-
ARCH="$(uname -m)"
60-
if [ "$ARCH" = "x86_64" ]; then
61-
ARCH="amd64"
62-
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
63-
ARCH="arm64"
56+
if ! command -v gh &> /dev/null; then
57+
echo "GitHub CLI not found, installing..."
58+
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
59+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
60+
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
61+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
62+
&& sudo apt update \
63+
&& sudo apt install gh -y
64+
else
65+
echo "GitHub CLI already installed: $(gh --version)"
6466
fi
6567
66-
GH_VERSION=2.63.0
67-
URL="https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_${OS}_${ARCH}.tar.gz"
68-
69-
echo "Downloading GitHub CLI from $URL"
70-
curl -L "$URL" -o gh.tar.gz
71-
tar xvf gh.tar.gz
72-
73-
# Move binary to local bin
74-
mv "gh_${GH_VERSION}_${OS}_${ARCH}/bin/gh" "$HOME/.local/bin/"
75-
# Add to PATH for this and future steps
76-
echo "$HOME/.local/bin" >> $GITHUB_PATH
77-
7868
- name: 'Install Dependencies'
7969
shell: bash
8070
run: |
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Parses JSON output from an LLM, handling markdown code blocks and truncated JSON.
3+
*
4+
* @param {string} rawData - The raw output string from the LLM.
5+
* @returns {any} The parsed JSON object.
6+
* @throws {Error} If parsing fails completely.
7+
*/
8+
module.exports = function parseGeminiOutput(rawData) {
9+
if (!rawData) {
10+
throw new Error('No data provided to parse.');
11+
}
12+
13+
// 1. Try direct parsing first
14+
try {
15+
return JSON.parse(rawData);
16+
} catch (e) {
17+
// Continue to fallback methods
18+
}
19+
20+
// 2. Try to extract from markdown code blocks (```json ... ``` or just ``` ... ```)
21+
const codeBlockMatch = rawData.match(/```(?:json)?\s*([\s\S]*?)```/);
22+
if (codeBlockMatch) {
23+
try {
24+
return JSON.parse(codeBlockMatch[1].trim());
25+
} catch (e) {
26+
// Continue
27+
}
28+
}
29+
30+
// 3. Try to extract a JSON object structure using regex
31+
const jsonObjectMatch = rawData.match(/\{[\s\S]*\}/);
32+
if (jsonObjectMatch) {
33+
try {
34+
return JSON.parse(jsonObjectMatch[0]);
35+
} catch (e) {
36+
// Continue
37+
}
38+
}
39+
40+
// 4. Last resort: Specific fallback for "description" field (common in PR enrichment)
41+
// This handles cases where the JSON might be malformed but the description string is extractable.
42+
if (rawData.includes('"description"')) {
43+
const descMatch = rawData.match(/"description"\s*:\s*"([\s\S]*?)(?<!\\)"/);
44+
if (descMatch) {
45+
try {
46+
// Manually reconstruct the object
47+
return { description: descMatch[1].replace(/\\n/g, '\n').replace(/\\\//g, '/') };
48+
} catch (e) {
49+
// Ignore
50+
}
51+
}
52+
}
53+
54+
throw new Error('Failed to parse JSON from output.');
55+
};

.github/workflows/gemini-triage.yml

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,13 @@ jobs:
105105
return;
106106
}
107107
const rawData = fs.readFileSync(filePath, 'utf8');
108-
// The model may return a JSON object wrapped in markdown.
108+
const parseGeminiOutput = require('./.github/scripts/parse-gemini-output.cjs');
109+
109110
let result;
110111
try {
111-
result = JSON.parse(rawData);
112+
result = parseGeminiOutput(rawData);
112113
} catch (e) {
113-
// If direct parsing fails, try to extract from markdown code blocks
114-
const cleanedData = rawData.trim().replace(/^```json|```$/g, '').trim();
115-
try {
116-
result = JSON.parse(cleanedData);
117-
} catch (innerError) {
118-
// Final fallback: regex match for the first block
119-
const match = rawData.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
120-
if (match) {
121-
result = JSON.parse(match[1].trim());
122-
} else {
123-
throw innerError;
124-
}
125-
}
114+
throw new Error(`Failed to parse triage result: ${e.message}`);
126115
}
127116
128117
// ---- Start of Validation Block ----

.github/workflows/pr-enrichment.yml

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -258,35 +258,13 @@ jobs:
258258
259259
try {
260260
const rawData = fs.readFileSync('pr_description.json', 'utf8');
261+
const parseGeminiOutput = require('./.github/scripts/parse-gemini-output.cjs');
261262
262263
let result;
263264
try {
264-
result = JSON.parse(rawData);
265-
} catch (parseError) {
266-
// If parsing fails, try to extract JSON from markdown code blocks
267-
// This is needed because LLMs sometimes wrap JSON in preamble/postamble
268-
const jsonMatch = rawData.match(/```(?:json)?\s*([\s\S]*?)```/);
269-
if (jsonMatch) {
270-
try {
271-
result = JSON.parse(jsonMatch[1].trim());
272-
} catch (e) {
273-
// Manual extraction fallback for corrupted/truncated JSON
274-
const descMatch = rawData.match(/"description"\s*:\s*"([\s\S]*?)(?<!\\)"/);
275-
if (descMatch) {
276-
result = { description: descMatch[1].replace(/\\n/g, '\n').replace(/\\\//g, '/') };
277-
} else {
278-
throw new Error(`Failed to parse extracted JSON and extract description: ${e.message}`);
279-
}
280-
}
281-
} else {
282-
// Final fallback for raw text without blocks
283-
const descMatch = rawData.match(/"description"\s*:\s*"([\s\S]*?)(?<!\\)"/);
284-
if (descMatch) {
285-
result = { description: descMatch[1].replace(/\\n/g, '\n').replace(/\\\//g, '/') };
286-
} else {
287-
throw new Error(`Failed to parse JSON and extract description: ${parseError.message}`);
288-
}
289-
}
265+
result = parseGeminiOutput(rawData);
266+
} catch (e) {
267+
throw new Error(`Failed to parse PR description: ${e.message}`);
290268
}
291269
292270
// Support both 'description' (normal) and 'reviewComment' (fallback for errors)

app/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useState, useCallback } from 'react'
1313
import HrmConnectionPanel from '@/components/HrmConnectionPanel'
1414
import TimerDisplay from '@/components/TimerDisplay'
1515
import { useAudio } from '@/hooks/useAudio'
16+
import { useTestPageReady } from '@/hooks/useTestPageReady'
1617

1718
// Dynamically import SpotifyDisplay with SSR disabled.
1819
// This prevents the heavy Spotify SDK logic from blocking the initial server HTML or hydration.
@@ -62,7 +63,6 @@ const Dashboard = () => {
6263

6364
const [docIsManuallyShrunk, setDocIsManuallyShrunk] = useState(false)
6465
const [audioInitialized, setAudioInitialized] = useState(false)
65-
<<<<<<< HEAD
6666

6767
// Track the readiness of dynamic components to ensure accurate VRT snapshots.
6868
// data-ready will only be set to true once all critical sections are hydrated.
@@ -99,8 +99,7 @@ const Dashboard = () => {
9999
: componentLoadStatus.googleDoc)
100100

101101
const isReady = useTestPageReady(allComponentsReady)
102-
=======
103-
>>>>>>> origin/leader
102+
104103
const [refreshKey, setRefreshKey] = useState(0)
105104
const { initializeAudio } = useAudio()
106105

@@ -118,6 +117,7 @@ const Dashboard = () => {
118117
return (
119118
<Container
120119
data-testid="dashboard"
120+
data-ready={isReady}
121121
maxWidth="xl"
122122
onClick={handleInteraction}
123123
sx={{

0 commit comments

Comments
 (0)