CI #45320
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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]" | |
| 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@v5 | |
| 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.outputs.python == 'true') | |
| uses: ./.github/workflows/python_test.yml | |
| with: | |
| python-versions: ${{ inputs.python-versions || '["3.10"]' }} | |
| runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['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.outputs.frontend == '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.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' || needs.path-filter.outputs.starter-projects == 'true' || needs.path-filter.outputs.components == 'true' || needs.path-filter.outputs.workspace == 'true' || needs.path-filter.outputs.api == 'true' || needs.path-filter.outputs.database == 'true') | |
| uses: ./.github/workflows/typescript_test.yml | |
| with: | |
| tests_folder: ${{ inputs.frontend-tests-folder }} | |
| release: ${{ inputs.release || false }} | |
| runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['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@v5 | |
| 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 |