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@v5 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@v5 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