name: Pull Request Docs Draft

on:
  pull_request:
    branches:
      - '**'
    paths:
      - 'docs/**'
      - '.github/workflows/deploy-docs-draft.yml'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    if: "! github.event.pull_request.head.repo.fork"

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: ./docs/package-lock.json

      - name: Validate Branch Names
        env:
          BRANCH_REF: ${{ github.event.pull_request.head.ref }}
        run: |
          # Check if branch names contain invalid characters. Only alphanumeric, _, -, ., and / are allowed.
          validate_branch_name() {
              local branch_name="$1"
              if [[ ! "$branch_name" =~ ^[a-zA-Z0-9/_\.-]+$ ]]; then
                  echo "Error: Branch name contains invalid characters. Only alphanumeric, _, -, ., and / are allowed."
                  exit 1
              fi
          }
          validate_branch_name "$BRANCH_REF"

      - name: Extract Branch Names
        id: extract_branch
        env:
          BRANCH_REF: ${{ github.event.pull_request.head.ref }}
        run: |
          # Extract and transform branch names
          extract_branch() {
              local input_branch="$1"
              # Check if input_branch starts with "refs/heads/"
              if [[ "$input_branch" == refs/heads/* ]]; then
                  # Remove "refs/heads/" prefix safely using parameter expansion
                  branch_name="${input_branch#refs/heads/}"
                  echo "$branch_name"
              else
                  echo "$input_branch"
              fi
          }

          # Transform branch names in form of `refs/heads/main` to `main`
          draft_branch=$(extract_branch "$BRANCH_REF")

          # Replace / with - in the draft branch name to use as a directory name
          draft_directory=$(echo "$draft_branch" | tr / -)

          # Safe echo to $GITHUB_OUTPUT
          {
              echo "draft_branch=$draft_branch"
              echo "draft_directory=$draft_directory"
          } >> "$GITHUB_OUTPUT"

      - name: Set Draft URL
        id: draft_url
        if: success()
        run: |
          echo "url=${{ vars.DOCS_DRAFT_BASE_URL }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/index.html" >> $GITHUB_OUTPUT

      - name: Install dependencies
        run: cd docs && npm install

      - name: Build website
        if: success()
        run: |
          set -o pipefail
          cd docs
          npm run build |& tee $GITHUB_WORKSPACE/build.log
        env:
          BASE_URL: /langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}
          FORCE_COLOR: 0 # Disable color output
          SEGMENT_PUBLIC_WRITE_KEY: ${{ vars.DOCS_DRAFT_IBM_SEGMENT_PUBLIC_WRITE_KEY }}

      - name: Check Build Result
        id: buildLogFail
        if: failure()
        run: |
          MULTILINE_LOG=$(cat $GITHUB_WORKSPACE/build.log)
          echo "BUILD_FAILURE<<EOF" >> $GITHUB_ENV
          echo $MULTILINE_LOG >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Hide Previous Build Comments
        if: ${{ github.event.pull_request.number && (success() || failure()) }}
        run: |
          set -e

          # Get all comments on the PR that match our build comments
          comments=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
            --jq '.[] | select(.body | test("Build failure! :x:|Build successful! :white_check_mark:")) | .node_id')

          # Minimize each matching comment using GraphQL API
          if [[ -n "$comments" ]]; then
            echo "Found previous build comments to hide"
            while IFS= read -r comment_id; do
              if [[ -n "$comment_id" ]]; then
                echo "Minimizing comment: $comment_id"
                gh api graphql \
                  --field id="$comment_id" \
                  --field classifier="OUTDATED" \
                  --raw-field query='
                    mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
                      minimizeComment(input: { subjectId: $id, classifier: $classifier }) {
                        minimizedComment {
                          isMinimized
                        }
                      }
                    }' || echo "Failed to minimize comment $comment_id, continuing..."
                echo
              fi
            done <<< "$comments"
          else
            echo "No previous build comments found to hide"
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Assemble Build Success Comment
        if: success()
        run: |
          build_success_comment="Build successful! :white_check_mark:"
          build_success_comment+="\nDeploying docs draft."

          echo "BUILD_SUCCESS_COMMENT<<EOF" >> $GITHUB_ENV
          echo -e "$build_success_comment" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Create Build Success Comment
        if: success()
        uses: peter-evans/create-or-update-comment@v4
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: "${{ env.BUILD_SUCCESS_COMMENT }}"
          reactions: rocket

      - name: Create Build Failure Comment
        if: failure()
        uses: peter-evans/create-or-update-comment@v4
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            Build failure! :x:
            > ${{ env.BUILD_FAILURE }}
          reactions: confused

      - name: Find Comment
        id: fc
        if: success()
        uses: peter-evans/find-comment@v3
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body-includes: Build successful!
          direction: last

      - name: Configure AWS CLI
        if: success()
        run: |
          aws configure set aws_access_key_id ${{ secrets.DOCS_AWS_ACCESS_KEY_ID }}
          aws configure set aws_secret_access_key ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY }}
          aws configure set region us-west-2

      - name: Check for New Assets
        run: |
          set -o pipefail
          echo "Checking for new assets." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 sync docs/build/assets/ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/ --size-only --dryrun --no-progress" | tee -a $GITHUB_WORKSPACE/deploy.log
          aws s3 sync docs/build/assets/ "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/" --size-only --dryrun --no-progress | tee $GITHUB_WORKSPACE/assets.log

      - name: Determine Standard or Full Publish
        id: check_full_publish
        run: |
          # Determine if a full publish is required because of new assets.
          if grep -qE '(upload:|delete:)' "$GITHUB_WORKSPACE/assets.log"; then
            echo "New assets. Perform full publish: true" | tee -a "$GITHUB_WORKSPACE/deploy.log"
            echo "perform_full_publish=true" >> "$GITHUB_OUTPUT"
          else
            echo "No new assets. Perform full publish: false" | tee -a "$GITHUB_WORKSPACE/deploy.log"
            echo "perform_full_publish=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Deploy to S3
        if: success()
        run: |
          set -o pipefail
          cd docs
          mkdir langflow-drafts
          mv build langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}
          cd langflow-drafts

          # Records the repository that originally triggered the build so we can post back
          # comments upon clean up of a stale draft if it still has an open pull request.
          echo "${{ github.event.repository.full_name }}" > ${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository

          s3_params=(
            # Hide upload progress for a cleaner sync log
            --no-progress
            --delete
            --exclude "*"
            --include "${{ steps.extract_branch.outputs.draft_directory }}/*"
          )

          if [[ "${{ steps.check_full_publish.outputs.perform_full_publish }}" == "false" ]]; then
            s3_params+=(--size-only)
          fi

          echo "Deploying draft to S3." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 sync . s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts ${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log
          aws s3 sync . "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts" "${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log

          # Update .github_source_repository file metadata to mark last modified time of the draft.
          # This will allow us to later determine if a draft is stale and needs to be cleaned up.
          echo "Marking last modified time of the draft." |& tee -a $GITHUB_WORKSPACE/deploy.log
          echo "aws s3 cp --metadata '{\"touched\": \"now\"}' \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository" \
            |& tee -a $GITHUB_WORKSPACE/deploy.log

          aws s3 cp --metadata '{ "touched": "now" }' \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \
            |& tee -a $GITHUB_WORKSPACE/deploy.log

      - name: Invalidate CloudFront Cache
        if: success()
        run: |
          invalidation_batch="{ \"Paths\": { \"Quantity\": 1, \"Items\": [\"/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/*\"] }, \"CallerReference\": \"langflow-docs-draft-files-$(date +%s)\" }"

          echo $invalidation_batch | jq . |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          echo "Creating invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          invalidation_id=$(aws cloudfront create-invalidation --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --invalidation-batch "$invalidation_batch" --query 'Invalidation.Id' --output text |& tee -a "$GITHUB_WORKSPACE/deploy.log")

          echo "Awaiting invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          aws cloudfront wait invalidation-completed --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --id "$invalidation_id" |& tee -a "$GITHUB_WORKSPACE/deploy.log"
          echo "Invalidation complete." |& tee -a "$GITHUB_WORKSPACE/deploy.log"

      - name: Update Comment
        if: ${{ steps.fc.outputs.comment-id != '' }}
        uses: peter-evans/create-or-update-comment@v4
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          body: |
            Deploy successful! [View draft](${{ steps.draft_url.outputs.url }})
          reactions: hooray

      - name: Upload Deploy Log
        uses: actions/upload-artifact@v6
        if: always()
        with:
          name: deploy.log
          path: ${{ github.workspace }}/deploy.log
