472 lines
18 KiB
YAML
472 lines
18 KiB
YAML
name: CI
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: "(Optional) Ref to checkout"
|
|
required: false
|
|
type: string
|
|
python-versions:
|
|
description: "Python Versions"
|
|
required: false
|
|
type: string
|
|
default: "['3.10']"
|
|
frontend-tests-folder:
|
|
description: "Frontend Tests Folder"
|
|
required: false
|
|
type: string
|
|
default: "tests/core"
|
|
release:
|
|
description: "Release"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
run-all-tests:
|
|
description: "Run all tests regardless of file changes (skips path filtering)"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
runs-on:
|
|
description: "Runner to use for the tests"
|
|
required: false
|
|
type: string
|
|
default: "ubuntu-latest"
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: "(Optional) Ref to checkout"
|
|
required: false
|
|
type: string
|
|
openai_api_key:
|
|
description: "OpenAI API Key"
|
|
required: false
|
|
type: string
|
|
store_api_key:
|
|
description: "Store API Key"
|
|
required: false
|
|
type: string
|
|
python-versions:
|
|
description: "Python Versions"
|
|
required: false
|
|
type: string
|
|
default: "['3.10']"
|
|
runs-on:
|
|
description: "Runner to use for the tests"
|
|
required: false
|
|
type: choice
|
|
options:
|
|
- ubuntu-latest
|
|
- self-hosted
|
|
- '["self-hosted", "linux", "ARM64", "langflow-ai-arm64-40gb-ephemeral"]'
|
|
default: ubuntu-latest
|
|
pull_request:
|
|
types: [opened, synchronize, labeled]
|
|
merge_group:
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
STORE_API_KEY: ${{ secrets.STORE_API_KEY }}
|
|
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
|
|
|
|
jobs:
|
|
echo-inputs:
|
|
name: Echo Inputs
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Echo inputs
|
|
run: |
|
|
echo "Inputs:"
|
|
echo " ref: ${{ inputs.ref }}"
|
|
echo " python-versions: ${{ inputs.python-versions }}"
|
|
echo " frontend-tests-folder: ${{ inputs.frontend-tests-folder }}"
|
|
echo " release: ${{ inputs.release }}"
|
|
echo " run-all-tests: ${{ inputs.run-all-tests }}"
|
|
echo " runs-on: ${{ inputs.runs-on }}"
|
|
|
|
check-nightly-status:
|
|
name: Check PyPI Version Update
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should-proceed: ${{ steps.check-pypi.outputs.success }}
|
|
steps:
|
|
- name: Check PyPI package update
|
|
id: check-pypi
|
|
run: |
|
|
# Get today's date in ISO format for comparison
|
|
TODAY=$(date -u +"%Y-%m-%d")
|
|
echo "Today's date: $TODAY"
|
|
|
|
# Query PyPI API for the langflow package
|
|
HTTP_STATUS=$(curl -s -o response.json -w "%{http_code}" https://pypi.org/pypi/langflow-nightly/json)
|
|
|
|
# Check HTTP status code first
|
|
if [ "$HTTP_STATUS" -ne 200 ]; then
|
|
echo "Error: PyPI API returned HTTP status $HTTP_STATUS"
|
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Check if response is valid JSON before proceeding
|
|
if ! jq -e . response.json >/dev/null 2>&1; then
|
|
echo "Error: Invalid JSON response from PyPI API"
|
|
echo "Response preview:"
|
|
head -n 10 response.json
|
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Extract the latest version
|
|
LATEST_VERSION=$(jq -r '.info.version // empty' response.json)
|
|
|
|
if [ -z "$LATEST_VERSION" ]; then
|
|
echo "Could not extract latest version"
|
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Extract the release date of the latest version
|
|
RELEASE_DATE=$(jq -r --arg ver "$LATEST_VERSION" '.releases[$ver][0].upload_time_iso_8601 // empty' response.json | cut -d'T' -f1)
|
|
|
|
if [ -z "$RELEASE_DATE" ]; then
|
|
echo "Could not extract release date"
|
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
echo "Latest version: $LATEST_VERSION"
|
|
echo "Release date: $RELEASE_DATE"
|
|
|
|
# Check if the release date is today
|
|
if [[ "$RELEASE_DATE" == "$TODAY" ]]; then
|
|
echo "Package was updated today"
|
|
echo "success=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "Package was not updated today"
|
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Clean up
|
|
rm -f response.json
|
|
|
|
set-ci-condition:
|
|
name: Should Run CI
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should-run-ci: ${{ github.event.pull_request.draft == false || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group') }}
|
|
should-run-tests: ${{ !contains(github.event.pull_request.labels.*.name, 'fast-track') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' }}
|
|
steps:
|
|
# Do anything just to make the job run
|
|
- run: echo "Debug CI Condition"
|
|
- run: echo "Labels -> ${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
|
- run: echo "IsDraft -> ${{ github.event.pull_request.draft }}"
|
|
- run: echo "Event name -> ${{ github.event_name }}"
|
|
- run: echo "Should run ci -> ${{ (github.event.pull_request.draft == false) || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group') }}"
|
|
- run: echo "Should run tests -> ${{ !contains(github.event.pull_request.labels.*.name, 'fast-track') || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' }}"
|
|
|
|
path-filter:
|
|
needs: set-ci-condition
|
|
if: ${{ needs.set-ci-condition.outputs.should-run-ci == 'true' && !inputs.run-all-tests }}
|
|
name: Filter Paths
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
python: ${{ steps.filter.outputs.python }}
|
|
frontend: ${{ steps.filter.outputs.frontend }}
|
|
docs: ${{ steps.filter.outputs.docs }}
|
|
frontend-tests: ${{ steps.filter.outputs.frontend-tests }}
|
|
components-changes: ${{ steps.filter.outputs.components-changes }}
|
|
starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }}
|
|
starter-projects: ${{ steps.filter.outputs.starter-projects }}
|
|
components: ${{ steps.filter.outputs.components }}
|
|
workspace: ${{ steps.filter.outputs.workspace }}
|
|
api: ${{ steps.filter.outputs.api }}
|
|
database: ${{ steps.filter.outputs.database }}
|
|
docker: ${{ steps.filter.outputs.docker }}
|
|
docs-only: ${{
|
|
steps.filter.outputs.docs == 'true' &&
|
|
steps.filter.outputs.python != 'true' &&
|
|
steps.filter.outputs.frontend != 'true' &&
|
|
steps.filter.outputs['frontend-tests'] != 'true' &&
|
|
steps.filter.outputs['components-changes'] != 'true' &&
|
|
steps.filter.outputs['starter-projects-changes'] != 'true' &&
|
|
steps.filter.outputs['starter-projects'] != 'true' &&
|
|
steps.filter.outputs.components != 'true' &&
|
|
steps.filter.outputs.workspace != 'true' &&
|
|
steps.filter.outputs.api != 'true' &&
|
|
steps.filter.outputs.database != 'true' &&
|
|
steps.filter.outputs.docker != 'true'
|
|
}}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
- name: Filter Paths
|
|
id: filter
|
|
uses: dorny/paths-filter@v3
|
|
with:
|
|
filters: ./.github/changes-filter.yaml
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Install deps for coverage check
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install --disable-pip-version-check --no-cache-dir pyyaml
|
|
- name: Validate Filter Coverage
|
|
continue-on-error: true
|
|
shell: bash
|
|
run: |
|
|
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
BASE_REF="${{ github.event.pull_request.base.ref || 'main' }}"
|
|
# Ensure base commit is present locally
|
|
git fetch --no-tags --depth=1 origin "$BASE_REF" || true
|
|
git fetch --no-tags --depth=1 origin "$BASE_SHA" || true
|
|
git diff --name-only "${BASE_SHA:-origin/$BASE_REF}"...HEAD | python scripts/check_changes_filter.py
|
|
|
|
test-backend:
|
|
needs: [path-filter, set-ci-condition]
|
|
name: Run Backend Tests
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
needs.set-ci-condition.outputs.should-run-tests == 'true' &&
|
|
(
|
|
inputs.run-all-tests ||
|
|
(needs.path-filter.result != 'skipped' &&
|
|
needs.path-filter.outputs.docs-only != 'true')
|
|
)
|
|
uses: ./.github/workflows/python_test.yml
|
|
with:
|
|
python-versions: ${{ inputs.python-versions || '["3.10"]' }}
|
|
runs-on: ${{ inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
secrets:
|
|
OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}"
|
|
ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
|
|
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
|
|
|
|
test-frontend-unit:
|
|
needs: [path-filter, set-ci-condition]
|
|
name: Run Frontend Unit Tests
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
needs.set-ci-condition.outputs.should-run-tests == 'true' &&
|
|
(
|
|
inputs.run-all-tests ||
|
|
(needs.path-filter.result != 'skipped' &&
|
|
needs.path-filter.outputs.docs-only != 'true')
|
|
)
|
|
uses: ./.github/workflows/jest_test.yml
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
secrets:
|
|
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
|
|
|
|
test-frontend:
|
|
needs: [path-filter, set-ci-condition]
|
|
name: Run Frontend Tests
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
needs.set-ci-condition.outputs.should-run-tests == 'true' &&
|
|
(
|
|
inputs.run-all-tests ||
|
|
(needs.path-filter.result != 'skipped' &&
|
|
needs.path-filter.outputs.docs-only != 'true')
|
|
)
|
|
uses: ./.github/workflows/typescript_test.yml
|
|
with:
|
|
tests_folder: ${{ inputs.frontend-tests-folder }}
|
|
release: ${{ inputs.release || false }}
|
|
runs-on: ${{ inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
secrets:
|
|
OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}"
|
|
STORE_API_KEY: "${{ secrets.STORE_API_KEY }}"
|
|
ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
|
|
TAVILY_API_KEY: "${{ secrets.TAVILY_API_KEY }}"
|
|
|
|
lint-backend:
|
|
needs: path-filter
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
(inputs.run-all-tests || needs.path-filter.outputs.python == 'true')
|
|
name: Lint Backend
|
|
uses: ./.github/workflows/lint-py.yml
|
|
|
|
test-docs-build:
|
|
needs: path-filter
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
(inputs.run-all-tests || needs.path-filter.outputs.docs == 'true')
|
|
name: Test Docs Build
|
|
uses: ./.github/workflows/docs_test.yml
|
|
|
|
test-templates:
|
|
needs: [path-filter, set-ci-condition]
|
|
name: Test Starter Templates
|
|
if: |
|
|
always() &&
|
|
!cancelled() &&
|
|
needs.set-ci-condition.outputs.should-run-tests == 'true' &&
|
|
(inputs.run-all-tests || needs.path-filter.outputs.python == 'true' || needs.path-filter.outputs.frontend == 'true')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Set up Python 3.12
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: 3.12
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
version: "latest"
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
uv sync --dev
|
|
|
|
- name: Test all starter project templates
|
|
run: |
|
|
uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto
|
|
|
|
test-docker:
|
|
needs: [path-filter, set-ci-condition]
|
|
name: Test Docker Images
|
|
if: ${{ needs.path-filter.outputs.docker == 'true' && needs.set-ci-condition.outputs.should-run-tests == 'true' }}
|
|
uses: ./.github/workflows/docker_test.yml
|
|
secrets:
|
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
# https://github.com/langchain-ai/langchain/blob/master/.github/workflows/check_diffs.yml
|
|
ci_success:
|
|
name: "CI Success"
|
|
needs:
|
|
[
|
|
test-backend,
|
|
test-frontend-unit,
|
|
test-frontend,
|
|
lint-backend,
|
|
test-docs-build,
|
|
test-templates,
|
|
test-docker,
|
|
set-ci-condition,
|
|
path-filter,
|
|
check-nightly-status,
|
|
]
|
|
|
|
if: ${{ always() }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
JOBS_JSON: ${{ toJSON(needs) }}
|
|
RESULTS_JSON: ${{ toJSON(needs.*.result) }}
|
|
# Skip nightly build check if only docs files changed
|
|
DOCS_ONLY: ${{ needs.path-filter.outputs.docs-only }}
|
|
EXIT_CODE: ${{ (contains(needs.*.result, 'failure') ||
|
|
contains(needs.*.result, 'cancelled') ||
|
|
(needs.check-nightly-status.outputs.should-proceed != 'true' && github.event_name != 'workflow_dispatch' && needs.path-filter.outputs.docs-only != 'true'))
|
|
&& '1' || '0' }}
|
|
steps:
|
|
- name: "CI Success"
|
|
run: |
|
|
echo "=== CI Status Summary ==="
|
|
echo "Should run tests: ${{ needs.set-ci-condition.outputs.should-run-tests }}"
|
|
echo "Should run CI: ${{ needs.set-ci-condition.outputs.should-run-ci }}"
|
|
echo "Nightly build status: ${{ needs.check-nightly-status.outputs.should-proceed }}"
|
|
echo "Event type: ${{ github.event_name }}"
|
|
echo "Docs only changes: $DOCS_ONLY"
|
|
echo "Python changes: ${{ needs.path-filter.outputs.python }}"
|
|
echo "Frontend changes: ${{ needs.path-filter.outputs.frontend }}"
|
|
echo "Docs changes: ${{ needs.path-filter.outputs.docs }}"
|
|
echo ""
|
|
|
|
# Check for job failures
|
|
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
|
|
echo "❌ CI FAILED: One or more jobs failed"
|
|
echo ""
|
|
echo "Failed jobs:"
|
|
|
|
# Dynamically list failed jobs with helpful descriptions
|
|
echo "$JOBS_JSON" | jq -r '
|
|
to_entries[]
|
|
| select(.value.result=="failure")
|
|
| .key as $job
|
|
| if $job == "test-backend" then
|
|
" - Backend Tests: Check Python code, tests, and dependencies"
|
|
elif $job == "test-frontend-unit" then
|
|
" - Frontend Unit Tests: Check React components and unit test logic"
|
|
elif $job == "test-frontend" then
|
|
" - Frontend E2E Tests: Check integration tests and UI functionality"
|
|
elif $job == "lint-backend" then
|
|
" - Backend Linting: Run '\''make format_backend'\'' then '\''make lint'\'' to fix code style issues"
|
|
elif $job == "test-docs-build" then
|
|
" - Documentation Build: Check documentation syntax and build process"
|
|
elif $job == "test-templates" then
|
|
" - Template Tests: Check starter project templates"
|
|
elif $job == "test-docker" then
|
|
" - Docker Tests: Check Docker image builds and version verification"
|
|
elif $job == "path-filter" then
|
|
" - Path Filter: File path filtering failed"
|
|
elif $job == "set-ci-condition" then
|
|
" - CI Condition Check: CI condition evaluation failed"
|
|
elif $job == "check-nightly-status" then
|
|
" - Nightly Status Check: PyPI package status check failed"
|
|
else
|
|
" - \($job): See job log for details"
|
|
end
|
|
'
|
|
|
|
echo ""
|
|
echo "🔧 Next steps:"
|
|
echo " 1. Review the failed job logs above"
|
|
echo " 2. Fix the identified issues in your code"
|
|
echo " 3. Push your changes to re-run the tests"
|
|
|
|
elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
|
|
echo "⚠️ CI CANCELLED: One or more jobs were cancelled"
|
|
echo ""
|
|
echo "🔧 Next steps:"
|
|
echo " 1. Check if the cancellation was intentional"
|
|
echo " 2. Re-run the workflow if needed"
|
|
|
|
elif [[ "${{ needs.check-nightly-status.outputs.should-proceed }}" != "true" && "${{ github.event_name }}" != "workflow_dispatch" && "$DOCS_ONLY" != "true" ]]; then
|
|
echo "🚫 CI BLOCKED: Nightly build is broken"
|
|
echo ""
|
|
echo "The nightly PyPI package was not updated today, indicating the nightly build failed."
|
|
echo ""
|
|
echo "🔧 Next steps:"
|
|
echo " 1. Work with the team to investigate and fix the nightly build"
|
|
echo " 2. Check the nightly build logs for errors"
|
|
echo " 3. Once the nightly build is fixed and publishes successfully, re-run this workflow"
|
|
echo " 4. Alternatively, use 'workflow_dispatch' to manually override this check if needed"
|
|
echo " 5. Note: PRs with only documentation changes can bypass this check"
|
|
|
|
else
|
|
echo "✅ CI SUCCESS: All checks passed!"
|
|
echo ""
|
|
echo "🎉 Your changes are ready:"
|
|
echo " - All tests passed"
|
|
echo " - Code quality checks passed"
|
|
echo " - Nightly build is healthy"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Exit code: $EXIT_CODE"
|
|
exit $EXIT_CODE
|