99 paths :
1010 - ' examples/**'
1111 - ' docs/v2/examples/**'
12-
1312 workflow_dispatch :
1413
1514permissions :
@@ -31,130 +30,105 @@ jobs:
3130 python-version : ' 3.x'
3231
3332 - name : Install dependencies
34- run : |
35- pip install jupyter nbconvert
33+ run : pip install jupyter nbconvert
3634
37- - name : Convert notebooks to markdown and add to docs
35+ - name : Detect and process notebooks
36+ id : process
3837 run : |
39- set -x # Enable debug mode
40-
41- # Smart change detection - handle both regular commits and merge commits
42- echo "Detecting changed files..."
38+ # Determine comparison range
39+ case "${{ github.event_name }}" in
40+ "push")
41+ BASE_SHA="${{ github.event.before }}"
42+ [[ "$BASE_SHA" == "0000000000000000000000000000000000000000" ]] && BASE_SHA="$(git hash-object -t tree /dev/null)"
43+ HEAD_SHA="${{ github.event.after }}"
44+ ;;
45+ "pull_request")
46+ BASE_SHA="${{ github.event.pull_request.base.sha }}"
47+ HEAD_SHA="${{ github.event.pull_request.head.sha }}"
48+ ;;
49+ "workflow_dispatch")
50+ git fetch origin "${{ github.event.repository.default_branch }}" --depth=50
51+ BASE_SHA="origin/${{ github.event.repository.default_branch }}"
52+ HEAD_SHA="HEAD"
53+ ;;
54+ *)
55+ exit 1
56+ ;;
57+ esac
4358
44- # Check if this is a merge commit
45- if git rev-parse HEAD^2 >/dev/null 2>&1; then
46- echo "Detected merge commit - finding all PR changes"
47- # Get the merge base between the two parent commits
48- BASE_SHA=$(git merge-base HEAD^1 HEAD^2)
49- echo "Merge base: $BASE_SHA"
50- # Get all files changed in the PR
51- CHANGED_FILES=$(git diff --name-only $BASE_SHA HEAD)
52- else
53- echo "Regular commit - checking against previous commit"
54- # For regular commits, just check against the parent
55- CHANGED_FILES=$(git diff --name-only HEAD^ HEAD 2>/dev/null)
56- fi
59+ # Get changed files and filter in one pass
60+ CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
5761
58- # Filter for relevant files and find related MDX files
59- FILES=$(echo "$CHANGED_FILES" | grep -E "(examples/.*\.ipynb|docs/v2/examples/.*\.mdx)" | \
60- xargs -I{} sh -c 'echo {} | grep -q "\.ipynb$" && grep -l "SOURCE_FILE: {}" docs/v2/examples/*.mdx 2>/dev/null || echo {}' | \
61- sort -u)
62+ # Use arrays for better performance
63+ declare -A notebooks_set
6264
63- echo "Total changed files: $(echo "$CHANGED_FILES" | wc -l)"
64- echo "Relevant files to process: $(echo "$FILES" | wc -l)"
65-
66- if [[ -z "$FILES" ]]; then
67- echo "No relevant changes detected - skipping processing"
68- exit 0
69- fi
65+ # Process notebooks directly from changed files
66+ while IFS= read -r file; do
67+ if [[ "$file" =~ ^examples/.*\.ipynb$ ]]; then
68+ notebooks_set["$file"]=1
69+ elif [[ "$file" =~ ^docs/v2/examples/.*\.mdx$ ]] && [[ -f "$file" ]]; then
70+ # Extract source notebook from MDX file
71+ source_notebook=$(grep -oP '(?<=SOURCE_FILE: )[^ ]+' "$file" 2>/dev/null || true)
72+ if [[ -n "$source_notebook" && -f "$source_notebook" ]]; then
73+ notebooks_set["$source_notebook"]=1
74+ fi
75+ fi
76+ done <<< "$CHANGED_FILES"
7077
71- echo "Processing changed files: $FILES"
78+ # Exit if no notebooks found
79+ [[ ${#notebooks_set[@]} -eq 0 ]] && exit 0
7280
73- for file in $FILES; do
74- echo "Processing file: $file"
75- source_file=$(grep -oP '(?<=\{/\* SOURCE_FILE: ).*(?= \*/\})' "$file" || true)
76- if [[ -z "$source_file" ]]; then
77- continue
78- fi
79- echo "Source file: $source_file"
80- if [[ -f "$source_file" ]]; then
81- echo "Converting notebook to markdown"
82- jupyter nbconvert --to markdown "$source_file" || { echo "Error: Failed to convert $source_file" >&2; continue; }
83- markdown_file="${source_file%.ipynb}.md"
84-
85- # Process pip install commands and create installation section using AWK
86- echo "Processing pip install commands"
87-
88- # Use AWK for efficient single-pass processing
89- awk '
90- # Collect pip packages
91- /^%pip install/ {
92- sub(/^%pip install /, "")
93- sub(/#.*/, "") # Remove comments
94- gsub(/^[ \t]+|[ \t]+$/, "") # Trim whitespace
95- if ($0) packages = packages " " $0
96- if (!first) first = NR
97- next
98- }
99- # Store other lines
100- { lines[NR] = $0 }
101- # Process at end
102- END {
103- if (packages) {
104- # Deduplicate packages
105- split(packages, p)
106- delete seen
107- packages = ""
108- for (i in p) if (p[i] && !seen[p[i]]++) packages = packages " " p[i]
109-
110- print "Found pip packages:" packages > "/dev/stderr"
111-
112- # Output with installation section
113- for (i = 1; i <= NR; i++) {
114- if (i == first) {
115- print "## Installation"
116- print "<CodeGroup>"
117- print " ```bash pip"
118- print " pip install" packages
119- print " ```"
120- print " ```bash poetry"
121- print " poetry add" packages
122- print " ```"
123- print " ```bash uv"
124- print " uv add" packages
125- print " ```"
126- print "</CodeGroup>"
127- }
128- if (lines[i]) print lines[i]
129- }
130- } else {
131- # No packages found, output as-is
132- for (i = 1; i <= NR; i++) if (lines[i]) print lines[i]
133- }
134- }' "$markdown_file" > "$markdown_file.tmp" && mv "$markdown_file.tmp" "$markdown_file"
135-
136- echo "Removing existing content after {/* SOURCE_FILE: ... */}"
137- sed -i '\#{/\* SOURCE_FILE:#,$d' "$file"
138- echo "Appending markdown to $file"
139- echo -e "{/* SOURCE_FILE: $source_file */}\n" >> "$file"
140- echo "_View Notebook on <a href={'https://github.com/AgentOps-AI/agentops/blob/main/$source_file'} target={'_blank'}>Github</a>_" >> "$file"
141- echo "" >> "$file"
142-
143- cat "$markdown_file" >> "$file" || { echo "Error: Failed to append markdown to $file" >&2; continue; }
144- rm "$markdown_file" || { echo "Error: Failed to remove $markdown_file" >&2; continue; }
81+ # Process each notebook and track results
82+ failed_count=0
83+ processed_notebooks=""
84+ for notebook in "${!notebooks_set[@]}"; do
85+ if python examples/generate_documentation.py "$notebook"; then
86+ processed_notebooks="$processed_notebooks$notebook"$'\n'
14587 else
146- echo "Error: Source file not found: $source_file" >&2
88+ ((failed_count++))
14789 fi
14890 done
91+
92+ [[ $failed_count -gt 0 ]] && exit 1
93+
94+ # Get modified files for PR description
95+ modified_files=$(git status --porcelain | grep -E '^\s*[AM]' | awk '{print $2}' | sort)
96+
97+ # Save outputs for PR
98+ echo "processed_notebooks<<EOF" >> $GITHUB_OUTPUT
99+ echo "$processed_notebooks" >> $GITHUB_OUTPUT
100+ echo "EOF" >> $GITHUB_OUTPUT
101+
102+ echo "modified_files<<EOF" >> $GITHUB_OUTPUT
103+ echo "$modified_files" >> $GITHUB_OUTPUT
104+ echo "EOF" >> $GITHUB_OUTPUT
149105
150106 - name : Create Pull Request
151107 uses : peter-evans/create-pull-request@v7
152108 with :
109+ token : ${{ secrets.GITHUB_TOKEN }}
153110 committer : github-actions[bot] <github-actions[bot]@users.noreply.github.com>
154- commit-message : GitHub Action - Update examples in docs from notebooks
155- title : GitHub Action - Update examples in docs from notebooks
156- body : Changes detected in examples/** or docs/v2/examples/** triggered an update of the docs/v2/examples/**.mdx files to incorporate markdown from the corresponding notebook in examples/**.
157- branch : update-examples-in-docs-from-notebooks
111+ author : github-actions[bot] <github-actions[bot]@users.noreply.github.com>
112+ commit-message : " docs: update examples from notebooks"
113+ title : " docs: update examples from notebooks"
114+ body : |
115+ Updates documentation examples from their source notebooks.
116+
117+ **Triggered by:** @${{ github.actor }}
118+ **Event:** ${{ github.event_name }}
119+ ${{ steps.process.outputs.processed_notebooks && format('
120+
121+ ## Processed Notebooks
122+ ```
123+ {0}
124+ ```', steps.process.outputs.processed_notebooks) || '' }}
125+ ${{ steps.process.outputs.modified_files && format('
126+
127+ ## Modified Files
128+ ```
129+ {0}
130+ ```', steps.process.outputs.modified_files) || '' }}
131+ branch : update-examples-docs-${{ github.run_number }}
158132 delete-branch : true
159133 # TODO: Uncomment for production use
160134 # assignees: the-praxs,bboynton97,areibman
0 commit comments