From 8c657fce5928886f9da025ef271c96ececa6bbf4 Mon Sep 17 00:00:00 2001 From: Nikita Belonogov Date: Wed, 13 Jul 2022 16:05:08 +0400 Subject: [PATCH] ci: Sync .github directory from develop (#2655) --- .github/autolabeler.yml | 32 ++ .github/pr-title-checker-config.json | 28 ++ .github/workflows/bandit.yml | 57 +++ .github/workflows/cancel_cicd_pipeline.yml | 21 ++ .github/workflows/cicd_pipeline.yml | 152 ++++++++ .github/workflows/docker-build.yml | 85 +++-- .github/workflows/frontend-build.yml | 65 ++++ .github/workflows/gitleaks.yml | 68 ++++ .github/workflows/pr-labeler.yml | 29 ++ .github/workflows/security-linters.yml | 71 ---- .github/workflows/submodules-validator.yml | 175 +++++++++ .github/workflows/sync-pr.yml | 1 + .github/workflows/tests.yml | 144 ++++---- .github/workflows/upload_to_pypi.yml | 45 ++- .github/workflows/upstream_repo_sync.yml | 389 +++++++++++++++++++++ 15 files changed, 1193 insertions(+), 169 deletions(-) create mode 100644 .github/autolabeler.yml create mode 100644 .github/pr-title-checker-config.json create mode 100644 .github/workflows/bandit.yml create mode 100644 .github/workflows/cancel_cicd_pipeline.yml create mode 100644 .github/workflows/cicd_pipeline.yml create mode 100644 .github/workflows/frontend-build.yml create mode 100644 .github/workflows/gitleaks.yml create mode 100644 .github/workflows/pr-labeler.yml delete mode 100644 .github/workflows/security-linters.yml create mode 100644 .github/workflows/submodules-validator.yml create mode 100644 .github/workflows/upstream_repo_sync.yml diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml new file mode 100644 index 000000000000..ac374d479b56 --- /dev/null +++ b/.github/autolabeler.yml @@ -0,0 +1,32 @@ +template: "Mandatory field" #https://github.com/release-drafter/release-drafter/blob/master/bin/generate-schema.js#L15 +autolabeler: + - label: 'breaking' + body: + - '/BREAKING CHANGE/i' + - label: 'fix' + title: + - '/^fix:/' + - label: 'feat' + title: + - '/^feat:/' + - label: 'docs' + title: + - '/^docs:/' + - label: 'chore' + title: + - '/^chore:/' + - label: 'ci' + title: + - '/^ci:/' + - label: 'perf' + title: + - '/^perf:/' + - label: 'refactor' + title: + - '/^refactor:/' + - label: 'style' + title: + - '/^style:/' + - label: 'test' + title: + - '/^test:/' \ No newline at end of file diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json new file mode 100644 index 000000000000..3a268827865e --- /dev/null +++ b/.github/pr-title-checker-config.json @@ -0,0 +1,28 @@ +{ + "LABEL": { + "name": "title needs formatting", + "color": "EEEEEE" + }, + "CHECKS": { + "prefixes": [ + "fix: ", + "feat: ", + "docs: ", + "chore: ", + "ci: ", + "perf: ", + "refactor: ", + "style: ", + "test: " + ], + "ignoreLabels": [ + "skip-changelog", + "skip-ci" + ] + }, + "MESSAGES": { + "success": "PR title is valid", + "failure": "PR title is invalid", + "notice": "Valid prefixes are: fix, feat, docs, chore, ci, perf, refactor, style, test." + } +} \ No newline at end of file diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml new file mode 100644 index 000000000000..a69662e7f2ec --- /dev/null +++ b/.github/workflows/bandit.yml @@ -0,0 +1,57 @@ +name: "Bandit" + +on: + workflow_call: + inputs: + head_sha: + required: true + type: string + repo: + required: true + type: string + default: heartexlabs/label-studio + +env: + BANDIT_VERSION: 1.7.4 + PROJECT_PATH: 'label_studio/' + REPORT_PATH: 'bandit_results/bandit_security_report.txt' + ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' + +jobs: + bandit: + name: "Bandit" + timeout-minutes: 2 + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Checkout + uses: actions/checkout@v3 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.head_sha }} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.7' + + - name: Install Bandit + run: | + pip install bandit==$BANDIT_VERSION + + - name: Run Bandit + run: | + mkdir -p bandit_results + touch ${{ env.REPORT_PATH }} + bandit -r $PROJECT_PATH -o ${{ env.REPORT_PATH }} -f 'txt' -ll + + - name: Print scan results + if: always() + run: cat ${{ env.REPORT_PATH }} + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: Security check results + path: ${{ env.REPORT_PATH }} diff --git a/.github/workflows/cancel_cicd_pipeline.yml b/.github/workflows/cancel_cicd_pipeline.yml new file mode 100644 index 000000000000..2c06bfbfbb73 --- /dev/null +++ b/.github/workflows/cancel_cicd_pipeline.yml @@ -0,0 +1,21 @@ +name: "Cancel PR CI/CD pipeline" + +on: + pull_request_target: + types: + - closed + - converted_to_draft + - locked + branches: + - develop + +concurrency: + group: CI/CD Pipeline-${{ github.event.pull_request.number || github.event.pull_request.head.ref || github.ref_name }} + cancel-in-progress: true + +jobs: + cancel: + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + - run: echo CI/CD Pipeline-${{ github.event.pull_request.number || github.event.pull_request.head.ref || github.ref_name }} diff --git a/.github/workflows/cicd_pipeline.yml b/.github/workflows/cicd_pipeline.yml new file mode 100644 index 000000000000..74d35b44273d --- /dev/null +++ b/.github/workflows/cicd_pipeline.yml @@ -0,0 +1,152 @@ +name: "CI/CD Pipeline" + +on: + push: + branches: + - develop + - 'ls-release/**' + paths: + - deploy/** + - label_studio/** + - setup.py + - .github/workflows/cicd_pipeline.yml + - .github/workflows/pr-labeler.yml + - .github/workflows/submodules-validator.yml + - .github/workflows/gitleaks.yml + - .github/workflows/bandit.yml + - .github/workflows/docker-build.yml + - .github/workflows/tests.yml + pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review + branches: + - develop + - 'ls-release/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.pull_request.head.ref || github.ref }} + cancel-in-progress: true + +jobs: + changed_files: + name: "Changed files" + runs-on: ubuntu-latest + outputs: + src: ${{ steps.changes.outputs.src }} + frontend: ${{ steps.changes.outputs.frontend }} + docker: ${{ steps.changes.outputs.docker }} + commit-message: ${{ steps.commit-details.outputs.message }} + timeout-minutes: 5 + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Checkout + if: github.event_name == 'push' + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + src: + - 'label_studio/!(frontend)/**' + - 'deploy/requirements**' + - 'setup.py' + frontend: + - 'label_studio/frontend/**' + docker: + - 'label_studio/**' + - 'deploy/**' + - 'Dockerfile**' + - 'setup.py' + - '.github/workflows/cicd_pipeline.yml' + - '.github/workflows/docker-build.yml' + + - uses: actions/github-script@v6 + id: commit-details + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + const { data: commit } = await github.rest.repos.getCommit({ + owner, + repo, + ref: '${{ github.event.pull_request.head.sha || github.event.after }}' + }); + core.setOutput("message", commit.commit.message); + + pr_labeler: + name: "Validate" + if: github.event_name == 'pull_request_target' + uses: heartexlabs/label-studio/.github/workflows/pr-labeler.yml@develop + secrets: inherit + + validate_submodules: + name: "Validate" + uses: heartexlabs/label-studio/.github/workflows/submodules-validator.yml@develop + with: + repo: ${{ github.event.pull_request.head.repo.full_name || github.event.repo.name || github.repository }} + head_sha: ${{ github.event.pull_request.head.sha || github.event.after }} + base_sha: ${{ github.event.pull_request.base.sha || github.event.before }} + target_branch: ${{ github.event.pull_request.base.ref || github.event.ref }} + secrets: inherit + + gitleaks: + name: "Linter" + if: github.event_name == 'pull_request_target' + uses: heartexlabs/label-studio/.github/workflows/gitleaks.yml@develop + with: + repo: ${{ github.event.pull_request.head.repo.full_name || github.event.repo.name || github.repository }} + head_sha: ${{ github.event.pull_request.head.sha || github.event.after }} + base_sha: ${{ github.event.pull_request.base.sha || github.event.before }} + secrets: inherit + + bandit: + name: "Linter" + needs: + - changed_files + if: needs.changed_files.outputs.src == 'true' + uses: heartexlabs/label-studio/.github/workflows/bandit.yml@develop + with: + repo: ${{ github.event.pull_request.head.repo.full_name || github.event.repo.name || github.repository }} + head_sha: ${{ github.event.pull_request.head.sha || github.event.after }} + secrets: inherit + + frontend-build: + name: "Build" + needs: + - changed_files + if: | + github.event_name == 'pull_request_target' && + needs.changed_files.outputs.frontend == 'true' && + needs.changed_files.outputs.commit-message != 'Build frontend' + uses: heartexlabs/label-studio/.github/workflows/frontend-build.yml@develop + with: + ref: ${{ github.event.pull_request.head.ref || github.ref }} + secrets: inherit + + build: + name: "Build" + needs: + - changed_files + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' && needs.changed_files.outputs.docker == 'true' + uses: heartexlabs/label-studio/.github/workflows/docker-build.yml@develop + with: + ref: ${{ github.event.pull_request.head.ref || github.ref }} + secrets: inherit + + pytest: + name: "Tests" + needs: + - changed_files + if: needs.changed_files.outputs.src == 'true' + uses: heartexlabs/label-studio/.github/workflows/tests.yml@develop + with: + repo: ${{ github.event.pull_request.head.repo.full_name || github.event.repo.name || github.repository }} + ref: ${{ github.event.pull_request.head.ref || github.ref }} + secrets: inherit diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 9fe55b56c217..39fe00a3de80 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,46 +1,39 @@ name: "Docker build & push" on: - push: - branches: - - develop - paths: - - deploy/** - - label_studio/** - - setup.py - - .github/workflows/docker-build.yml - tags: [ '*' ] + workflow_call: + inputs: + ref: + required: true + type: string env: DOCKER_CLI_EXPERIMENTAL: enabled - IMAGE_NAME: heartexlabs/label-studio - DOCKER_TAG: 'latest' + RELEASE_TAG: 'latest' + DEVELOP_TAG: 'develop' + UBI_DEVELOP_TAG: 'ubi_develop' + PREFLIGHT_REPO: quay.io/opdev/preflight:stable + DOCKER_CONFIG_PATH: /home/runner/.docker/config.json jobs: docker_build_and_push: - name: "Docker build and push" + name: "Docker image" timeout-minutes: 10 runs-on: ubuntu-latest steps: - - name: Override image tag on 'tag' - if: startsWith(github.ref, 'refs/tags/') - run: | - echo "DOCKER_TAG=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV + - uses: hmarr/debug-action@v2.0.1 - name: Checkout uses: actions/checkout@v3 + with: + token: ${{ secrets.GIT_PAT }} + ref: ${{ inputs.ref }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2.0.0 - - name: Login to DockerHub - uses: docker/login-action@v2.0.0 - with: - username: heartexlabs - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: '3.8' @@ -50,15 +43,53 @@ jobs: cat $(pwd)/label_studio/core/version_.py - name: Download feature flags + env: + LAUNCHDARKLY_COMMUNITY_SDK_KEY: ${{ secrets.LAUNCHDARKLY_COMMUNITY_SDK_KEY }} run: | - curl -H "Authorization: ${{ secrets.LAUNCHDARKLY_COMMUNITY_SDK_KEY }}" https://app.launchdarkly.com/sdk/latest-all >label_studio/feature_flags.json + curl -H "Authorization: $LAUNCHDARKLY_COMMUNITY_SDK_KEY" https://app.launchdarkly.com/sdk/latest-all > label_studio/feature_flags.json + + - name: Login to DockerHub + uses: docker/login-action@v2.0.0 + with: + username: heartexlabs + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Build and push ubuntu uses: docker/build-push-action@v3.0.0 - id: docker_build_and_push + id: docker_build_and_push_ubuntu with: context: . + file: Dockerfile push: true - tags: ${{ env.IMAGE_NAME }}:${{ env.DOCKER_TAG }} + tags: heartexlabs/label-studio:${{ env.DEVELOP_TAG }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Login to RedHat Registry + uses: docker/login-action@v2.0.0 + with: + registry: scan.connect.redhat.com + username: unused + password: ${{ secrets.REDHAT_MARKETPLACE_LS_PASSWORD }} + + - name: Build and push ubi + uses: docker/build-push-action@v3.0.0 + id: docker_build_and_push_ubi + with: + context: . + file: Dockerfile.redhat + push: true + tags: scan.connect.redhat.com/${{ secrets.REDHAT_MARKETPLACE_LS_OSPID }}/label-studio:${{ env.DEVELOP_TAG }},heartexlabs/label-studio:${{ env.UBI_DEVELOP_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run preflight and submit validation results to RedHat + run: | + docker pull ${{ env.PREFLIGHT_REPO }} + docker run --rm -v ${{ env.DOCKER_CONFIG_PATH }}:${{ env.DOCKER_CONFIG_PATH }} \ + --env PFLT_DOCKERCONFIG=${{ env.DOCKER_CONFIG_PATH }} \ + --env PFLT_PYXIS_API_TOKEN=${{ secrets.REDHAT_MARKETPLACE_LS_PYXIS_TOKEN }} \ + --env PFLT_CERTIFICATION_PROJECT_ID=${{ secrets.REDHAT_MARKETPLACE_LS_PROJECT_ID }} \ + ${{ env.PREFLIGHT_REPO }} \ + check container scan.connect.redhat.com/${{ secrets.REDHAT_MARKETPLACE_LS_OSPID }}/label-studio:${{ env.DEVELOP_TAG }} \ + --submit diff --git a/.github/workflows/frontend-build.yml b/.github/workflows/frontend-build.yml new file mode 100644 index 000000000000..0c1bccf757b1 --- /dev/null +++ b/.github/workflows/frontend-build.yml @@ -0,0 +1,65 @@ +name: "Frontend build" + +on: + workflow_call: + inputs: + ref: + required: true + type: string + +env: + NODE: 14 + CACHE_NAME_PREFIX: v1 + FRONTEND_DIR: label_studio/frontend + FRONTEND_BUILD_COMMIT_MESSAGE: Build frontend + +jobs: + build: + name: "Frontend" + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Configure git + shell: bash + run: | + set -xeuo pipefail + git config --global user.name 'robot-ci-heartex' + git config --global user.email 'robot-ci-heartex@users.noreply.github.com' + + - name: Checkout + uses: actions/checkout@v3 + with: + token: ${{ secrets.GIT_PAT }} + ref: ${{ inputs.ref }} + + - uses: actions/setup-node@v3 + with: + node-version: "${{ env.NODE }}" + + - name: Get npm cache directory path + id: npm-cache + run: echo "::set-output name=dir::$(npm config get cache)" + + - name: Configure npm cache + uses: actions/cache@v3 + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/package.json') }}-${{ hashFiles('**/yarn.lock') }} + + - name: Print Yarn cache size + run: du -d 0 -h ${{ steps.npm-cache.outputs.dir }} || true + + - name: Build + run: | + npm ci + npm run build:production + working-directory: ${{ env.FRONTEND_DIR }} + + - name: Commit and Push + run: | + git add "${{ env.FRONTEND_DIR }}" + git status -s + git commit -m '${{ env.FRONTEND_BUILD_COMMIT_MESSAGE }}' || true + git push origin HEAD diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 000000000000..a60b8187143e --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,68 @@ +name: "Gitleaks" + +on: + workflow_call: + inputs: + head_sha: + required: true + type: string + base_sha: + required: true + type: string + repo: + required: true + type: string + default: heartexlabs/label-studio + +env: + GIT_GITLEAKS_VERSION: 8.8.7 + ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' + +jobs: + gitleaks: + name: "Gitleaks" + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Configure gitleaks binary cache + id: cache + uses: actions/cache@v3 + with: + path: /usr/local/bin/gitleaks + key: gitleaks-${{ env.GIT_GITLEAKS_VERSION }} + + - name: Install tools + if: steps.cache.outputs.cache-hit != 'true' + run: | + wget -O - \ + "https://github.com/zricethezav/gitleaks/releases/download/v${{ env.GIT_GITLEAKS_VERSION }}/gitleaks_${{ env.GIT_GITLEAKS_VERSION }}_linux_x64.tar.gz" \ + | sudo tar xzf - -C /usr/local/bin + + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + repository: ${{ inputs.repo }} + + - name: Run gitleaks + run: | + set -euo pipefail ${ACTIONS_STEP_DEBUG:+-x} + + gitleaks \ + detect \ + --source="." \ + --redact \ + -v \ + --exit-code=2 \ + --report-format=sarif \ + --report-path=results.sarif \ + --log-level=debug \ + --log-opts='${{ inputs.base_sha }}..${{ inputs.head_sha }}' + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: GitLeaks results + path: results.sarif diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000000..cdb286ac7679 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,29 @@ +name: "PR labeler" + +on: + workflow_call: + +env: + ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' + +jobs: + autolabel: + name: "PR title" + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: "Validate PR's title" + uses: thehanimo/pr-title-checker@v1.3.4 + with: + GITHUB_TOKEN: ${{ secrets.GIT_PAT }} + pass_on_octokit_error: false + configuration_path: ".github/pr-title-checker-config.json" + + - name: "Set PR's label based on title" + uses: release-drafter/release-drafter@v5.20.0 + with: + disable-releaser: true + config-name: autolabeler.yml + env: + GITHUB_TOKEN: ${{ secrets.GIT_PAT }} diff --git a/.github/workflows/security-linters.yml b/.github/workflows/security-linters.yml deleted file mode 100644 index bd503409a453..000000000000 --- a/.github/workflows/security-linters.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: "Security linters" - -on: - push: - paths: - - 'label_studio/**' - - '.github/workflows/security-linters.yml' - - 'deploy/requirements**' - tags-ignore: - - '**' - pull_request_target: - types: [ opened, synchronize, reopened, ready_for_review ] - branches: - - develop - -env: - BANDIT_VERSION: 1.7.4 - PROJECT_PATH: 'label_studio/' - REPORT_PATH: 'bandit_results/bandit_security_report.txt' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.pull_request.head.ref || github.ref }} - cancel-in-progress: true - -jobs: - bandit: - name: "Bandit" - timeout-minutes: 2 - runs-on: ubuntu-latest - steps: - - uses: hmarr/debug-action@v2.0.1 - - - name: Extract branch name on direct push to a branch - if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/') - run: | - echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV - - - name: Extract branch name on 'pull_request_target' - if: github.event_name == 'pull_request_target' - run: | - echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF})" >> $GITHUB_ENV - - - name: Checkout - uses: actions/checkout@v3 - with: - ref: "${{ env.BRANCH_NAME }}" - - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.7' - - - name: Install Bandit - run: | - pip install bandit==$BANDIT_VERSION - - - name: Run Bandit - run: | - mkdir -p bandit_results - touch ${{ env.REPORT_PATH }} - bandit -r $PROJECT_PATH -o ${{ env.REPORT_PATH }} -f 'txt' -ll - - - name: Print scan results - if: always() - run: cat ${{ env.REPORT_PATH }} - - - uses: actions/upload-artifact@v3 - if: always() - with: - name: Security check results - path: ${{ env.REPORT_PATH }} diff --git a/.github/workflows/submodules-validator.yml b/.github/workflows/submodules-validator.yml new file mode 100644 index 000000000000..671f4b7561b1 --- /dev/null +++ b/.github/workflows/submodules-validator.yml @@ -0,0 +1,175 @@ +name: "Dependencies check" + +on: + workflow_call: + inputs: + head_sha: + required: true + type: string + base_sha: + required: true + type: string + target_branch: + required: true + type: string + repo: + required: true + type: string + default: heartexlabs/label-studio + +env: + ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' + +jobs: + validate_submodules_and_ls_dependencies: + name: "Submodules/deps" + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Validate + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + var [owner, repo] = '${{ inputs.repo }}'.split('/'); + const head_sha = '${{ inputs.head_sha }}' + const base_sha = '${{ inputs.base_sha }}' + const targetBranch = '${{ inputs.target_branch }}'.replace('refs/heads/', '') + const strictCheckBranchPrefixes = ['ls-release/'] + + let submodules = [ + {owner: 'heartexlabs', repo: 'label-studio-frontend'}, + {owner: 'heartexlabs', repo: 'dm2'} + ] + + + async function getLSSubmoduleVersions(sha) { + let {data: lsTreeData} = await github.rest.git.getTree({ + owner, + repo, + tree_sha: sha + }) + lsTreeData = (await github.rest.git.getTree({ + owner, + repo, + tree_sha: lsTreeData.tree.find(e => e.path === 'label_studio' && e.type === 'tree').sha + })).data + lsTreeData = (await github.rest.git.getTree({ + owner, + repo, + tree_sha: lsTreeData.tree.find(e => e.path === 'frontend' && e.type === 'tree').sha + })).data + lsTreeData = (await github.rest.git.getTree({ + owner, + repo, + tree_sha: lsTreeData.tree.find(e => e.path === 'dist' && e.type === 'tree').sha + })).data + const {data: lsDMTreeData} = await github.rest.git.getTree({ + owner, + repo, + tree_sha: lsTreeData.tree.find(e => e.path === 'dm' && e.type === 'tree').sha + }) + const {data: dmfVersion} = await github.rest.git.getBlob({ + owner, + repo, + file_sha: lsDMTreeData.tree.find(e => e.path === 'version.json' && e.type === 'blob').sha + }) + const dmVersionContent = Buffer.from(dmfVersion.content, dmfVersion.encoding).toString("utf8") + const matchDM = dmVersionContent.match('"commit": "(.*)",') + const {data: lsLSFTreeData} = await github.rest.git.getTree({ + owner, + repo, + tree_sha: lsTreeData.tree.find(e => e.path === 'lsf' && e.type === 'tree').sha + }) + const {data: lsfVersion} = await github.rest.git.getBlob({ + owner, + repo, + file_sha: lsLSFTreeData.tree.find(e => e.path === 'version.json' && e.type === 'blob').sha + }) + const lsfVersionContent = Buffer.from(lsfVersion.content, lsfVersion.encoding).toString("utf8") + const matchLSF = lsfVersionContent.match('"commit": "(.*)",') + return { + 'label-studio-frontend': matchLSF[1], + 'dm2': matchDM[1], + } + } + + let base_sha_redacted = base_sha + if (base_sha_redacted === '0000000000000000000000000000000000000000') { + console.log(`Branch creation event. Using head_sha (${head_sha}) parent as base_sha`) + const {data: commit} = await github.rest.git.getCommit({ + owner, + repo, + commit_sha: head_sha, + }); + console.log(commit.parents) + base_sha_redacted = commit.parents[0].sha + } + + const baseVersions = await getLSSubmoduleVersions(base_sha_redacted) + console.log(`before: ${base_sha_redacted}`) + console.log(baseVersions) + + const headVersions = await getLSSubmoduleVersions(head_sha) + console.log(`after: ${head_sha}`) + console.log(headVersions) + + const strictCheck = strictCheckBranchPrefixes.some(e => targetBranch.startsWith(e)) + console.log(`Strict check: ${strictCheck}`) + + let failed = [] + for (let submodule of submodules) { + if (baseVersions[submodule.repo] === headVersions[submodule.repo] && !strictCheck) { + console.log(`${submodule.repo}: Is not changed`) + continue + } + + const {data: submoduleRepo} = await github.rest.repos.get({ + owner: submodule.owner, + repo: submodule.repo, + }); + const submoduleBranch = targetBranch === 'develop' ? submoduleRepo.default_branch : targetBranch + const {data: listCommits} = await github.rest.repos.listCommits({ + owner: submodule.owner, + repo: submodule.repo, + per_page: 100, + sha: submoduleBranch + }); + + const commits = listCommits.map(e => e.sha) + + const headCommitNumber = commits.indexOf(headVersions[submodule.repo]) + if (headCommitNumber === -1) { + console.log(`${submodule.repo}: ${headVersions[submodule.repo]} from PR is not found in submodule ${submoduleBranch} branch`) + failed.push(submodule.repo) + continue + } + if (strictCheck && headCommitNumber !== 0) { + console.log(`${submodule.repo}: For the release branch, submodule should be pointed to the latest commit in submodule corresponding release branch which is ${listCommits[0].html_url}`) + failed.push(submodule.repo) + continue + } + + const baseCommitNumber = commits.indexOf(baseVersions[submodule.repo]) + if (baseCommitNumber === -1) { + console.log(`${submodule.repo}: ${baseVersions[submodule.repo]} from ${targetBranch} is not found in submodule ${submoduleBranch} branch`) + continue + } + + const {data: compare} = await github.rest.repos.compareCommits({ + owner: submodule.owner, + repo: submodule.repo, + base: baseVersions[submodule.repo], + head: headVersions[submodule.repo], + }); + console.log(`${submodule.repo}: ${headVersions[submodule.repo]} is ${compare.ahead_by} ahead and ${compare.behind_by} behind ${baseVersions[submodule.repo]}: ${compare.html_url}`) + if (compare.behind_by > 0) { + failed.push(submodule.repo) + continue + } + } + + if (failed.length !== 0) { + throw `Versions for ${failed.toString()} are downgraded or not found`; + } diff --git a/.github/workflows/sync-pr.yml b/.github/workflows/sync-pr.yml index f1a19da3557f..3bedf3431eaf 100644 --- a/.github/workflows/sync-pr.yml +++ b/.github/workflows/sync-pr.yml @@ -13,6 +13,7 @@ on: - 'lse-release/**' paths-ignore: - 'label_studio/frontend/**' + - 'docs/**' concurrency: group: ${{ github.head_ref }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3bd53f2d31a4..f31f53b6e22e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,15 +1,15 @@ -name: pytest:ubnt +name: "Tests" + on: - push: - branches: ['*', '*/*', '*/**', master] - paths: - - 'label_studio/**' - - '!label_studio/frontend/**' - - '.github/workflows/tests.yml' - - 'deploy/requirements.txt' - - 'deploy/requirements-mw.txt' - - 'deploy/requirements-test.txt' - - 'deploy/prebuild_wo_frontend.sh' + workflow_call: + inputs: + ref: + required: true + type: string + repo: + required: true + type: string + default: heartexlabs/label-studio env: NODE: '14' @@ -37,9 +37,16 @@ jobs: # SENTRY_DSN: steps: - - uses: actions/checkout@v3 + - uses: hmarr/debug-action@v2.0.1 + + - name: Checkout + uses: actions/checkout@v3 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.ref }} + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -104,7 +111,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8] + python-version: [ 3.8 ] env: DJANGO_SETTINGS_MODULE: core.settings.label_studio DJANGO_DB: default @@ -153,9 +160,16 @@ jobs: - 6379:6379 steps: - - uses: actions/checkout@v3 + - uses: hmarr/debug-action@v2.0.1 + + - name: Checkout + uses: actions/checkout@v3 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.ref }} + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -239,50 +253,56 @@ jobs: # SENTRY_DSN: steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 - name: Configure pip cache - with: - path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/requirements-test.txt') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - pip install --upgrade cython - if (Test-Path -Path '.\deploy\requirements.txt' -PathType Leaf) - {pip install -r deploy\requirements.txt} - if (Test-Path -Path '.\deploy\requirements-test.txt' -PathType Leaf) - {pip install -r deploy/requirements-test.txt} - pip install -e . - - - name: Fix sqlite.dll for python < 3.9 - if: ${{ matrix.python-version < '3.9' }} - run: | - set PYTHONIOENCODING=utf-8 - set PYTHONLEGACYWINDOWSSTDIO=utf-8 - label-studio init my_project --agree-fix-sqlite --force-fix-sqlite - cp sqlite3.dll %pythonLocation%/DLLs/sqlite3.dll - shell: cmd - - - name: Init project - run: | - set PYTHONIOENCODING=utf-8 - set PYTHONLEGACYWINDOWSSTDIO=utf-8 - label-studio init my_project --username test@test.com --password testpwd - shell: cmd - - - name: Test with pytest - env: - collect_analytics: 0 - run: | - cd label_studio/ - python -m pytest -vv -n auto + - uses: hmarr/debug-action@v2.0.1 + + - name: Checkout + uses: actions/checkout@v3 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.ref }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/cache@v3 + name: Configure pip cache + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/requirements-test.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + pip install --upgrade cython + if (Test-Path -Path '.\deploy\requirements.txt' -PathType Leaf) + {pip install -r deploy\requirements.txt} + if (Test-Path -Path '.\deploy\requirements-test.txt' -PathType Leaf) + {pip install -r deploy/requirements-test.txt} + pip install -e . + + - name: Fix sqlite.dll for python < 3.9 + if: ${{ matrix.python-version < '3.9' }} + run: | + set PYTHONIOENCODING=utf-8 + set PYTHONLEGACYWINDOWSSTDIO=utf-8 + label-studio init my_project --agree-fix-sqlite --force-fix-sqlite + cp sqlite3.dll %pythonLocation%/DLLs/sqlite3.dll + shell: cmd + + - name: Init project + run: | + set PYTHONIOENCODING=utf-8 + set PYTHONLEGACYWINDOWSSTDIO=utf-8 + label-studio init my_project --username test@test.com --password testpwd + shell: cmd + + - name: Test with pytest + env: + collect_analytics: 0 + run: | + cd label_studio/ + python -m pytest -vv -n auto diff --git a/.github/workflows/upload_to_pypi.yml b/.github/workflows/upload_to_pypi.yml index 3e315fc69e48..5f507d3f6d68 100644 --- a/.github/workflows/upload_to_pypi.yml +++ b/.github/workflows/upload_to_pypi.yml @@ -1,6 +1,16 @@ name: "Upload to PYPI" on: + workflow_dispatch: + inputs: + version: + description: 'Version' + type: string + required: true + ref: + description: 'Ref' + type: string + required: true release: types: - released @@ -15,12 +25,19 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ github.event.release.tag_name || inputs.ref }} + + - name: Manage version + env: + PROVIDED_VERSION: ${{ github.event.release.tag_name || inputs.version }} + run: | + version=$(sed "s/^v//g" <<< ${PROVIDED_VERSION}) + sed -i "s/^__version__[ ]*=.*/__version__ = '${version}'/g" label_studio/__init__.py - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Configure pip cache uses: actions/cache@v3 @@ -42,14 +59,24 @@ jobs: python label_studio/manage.py collectstatic - name: Download feature flags + env: + LAUNCHDARKLY_COMMUNITY_SDK_KEY: ${{ secrets.LAUNCHDARKLY_COMMUNITY_SDK_KEY }} run: | - curl -H "Authorization: ${{ secrets.LAUNCHDARKLY_COMMUNITY_SDK_KEY }}" https://app.launchdarkly.com/sdk/latest-all >label_studio/feature_flags.json + curl -H "Authorization: $LAUNCHDARKLY_COMMUNITY_SDK_KEY" https://app.launchdarkly.com/sdk/latest-all >label_studio/feature_flags.json - - name: Package and Upload + - name: Package + run: python setup.py sdist bdist_wheel + + - name: Upload to PYPI + if: github.event_name == 'release' env: - STACKMANAGER_VERSION: ${{ github.event.release.tag_name }} TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_APIKEY }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + run: twine upload dist/* + + - name: Upload to artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: Dist + path: dist/ diff --git a/.github/workflows/upstream_repo_sync.yml b/.github/workflows/upstream_repo_sync.yml new file mode 100644 index 000000000000..a95b93ba40e1 --- /dev/null +++ b/.github/workflows/upstream_repo_sync.yml @@ -0,0 +1,389 @@ +name: Sync PR + +on: + repository_dispatch: + types: + - upstream_repo_update + +concurrency: + group: ${{ github.workflow }}-${{ github.event.client_payload.branch_name || 'schedule' }} + +env: + NODE: 14 + CACHE_NAME_PREFIX: v1 + STATIC_DIST: 'label_studio/frontend/dist' + +jobs: + open: + name: Sync PR + if: | + github.event_name != 'schedule' && ( + github.event.client_payload.event_action == 'opened' || + github.event.client_payload.event_action == 'synchronize' || + github.event.client_payload.event_action == 'merged' + ) + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Configure git + shell: bash + run: | + set -xeuo pipefail + git config --global user.name '${{ github.event.client_payload.author_username }}' + git config --global user.email '${{ github.event.client_payload.author_email }}' + + - name: Checkout + uses: actions/checkout@v3 + with: + token: ${{ secrets.GIT_PAT }} + fetch-depth: 0 + ref: ${{ github.event.repository.default_branch }} + + - name: Checkout module + uses: actions/checkout@v3 + with: + repository: ${{ github.event.client_payload.repo_name }} + path: tmp + token: ${{ secrets.GIT_PAT }} + fetch-depth: 1 + ref: ${{ github.event.client_payload.commit_sha }} + + - uses: actions/setup-node@v3 + with: + node-version: "${{ env.NODE }}" + + - name: Upgrade Yarn + run: npm install -g yarn@1.22 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Configure yarn cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/package.json') }}-${{ hashFiles('**/yarn.lock') }} + + - name: Print Yarn cache size + run: du -d 0 -h ${{ steps.yarn-cache-dir-path.outputs.dir }} + + - name: Install dependencies + run: | + rm package-lock.json || true + yarn install + working-directory: tmp + + - name: Build module + run: | + yarn run build:module + if [[ "${{ github.event.client_payload.event_action }}" == 'merged' ]]; then + branch="${{ github.event.client_payload.base_branch_name }}" + else + branch="${{ github.event.client_payload.branch_name }}" + fi + cat << EOF > "build/static/version.json" + { + "message": "${{ github.event.client_payload.title }}", + "commit": "${{ github.event.client_payload.commit_sha }}", + "branch": "${branch}", + "date": "$(git log -1 --date=format:"%Y/%m/%d %T" --format="%ad" | cat)" + } + EOF + + working-directory: tmp + env: + CI: false # on true webpack breaks on warnings, and we have them a lot + NODE_ENV: 'production' + + - name: Create branch + shell: bash + run: | + set -xeuo pipefail + + if grep -q '^ls-release/.*$' <<< '${{ github.event.client_payload.base_branch_name }}'; then + git checkout '${{ github.event.client_payload.base_branch_name }}' + else + git checkout '${{ github.event.repository.default_branch }}' + fi + + git checkout '${{ github.event.client_payload.branch_name }}' || true + git checkout -b '${{ github.event.client_payload.branch_name }}' || true + + if [[ "${{ github.event.client_payload.repo_name }}" == */dm2* ]]; then + module_dist="${{ env.STATIC_DIST }}/dm" + elif [[ "${{ github.event.client_payload.repo_name }}" == */label-studio-frontend* ]]; then + module_dist="${{ env.STATIC_DIST }}/lsf" + else + echo "Repo ${{ github.event.client_payload.repo_name }} is not supported" + exit 1 + fi + + rm -rf "${module_dist}" + mkdir -p "${module_dist}" + cp -r tmp/build/static/* "${module_dist}" + + ls tmp/build/static/ + ls "${module_dist}" + + git add "${{ env.STATIC_DIST }}" + git status -s + git commit --allow-empty -m '[submodules] Build static ${{ github.event.client_payload.repo_name }}' + git push origin HEAD + + - name: Get PRs + uses: actions/github-script@v6 + id: get-pr + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + const listPullsResponse = await github.rest.pulls.list({ + owner, + repo, + head: '${{ github.repository_owner }}:${{ github.event.client_payload.branch_name }}', + per_page: 100 + }); + return listPullsResponse + + - name: Create PR + id: create-pr + uses: actions/github-script@v6 + env: + TITLE: ${{ github.event.client_payload.title }} + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + listPullsResponse = ${{ steps.get-pr.outputs.result }} + if (listPullsResponse.data.length) { + core.info(`PR already exist ${ listPullsResponse.data[0].html_url }`) + if ( listPullsResponse.data[0].body.includes('${{ github.event.client_payload.html_url }}') ) { + core.info(`${{ github.event.client_payload.html_url }} already referenced in PR description`) + return listPullsResponse + } else { + core.info(`Adding new ref on ${{ github.event.client_payload.html_url }} to PR`) + const newBody = listPullsResponse.data[0].body + `\n- ${{ github.event.client_payload.html_url }}` + const updatePullResponse = await github.rest.pulls.update({ + title: process.env.TITLE, + owner, + repo, + pull_number: listPullsResponse.data[0].number, + body: newBody + }); + return updatePullResponse + } + } else { + let base = '${{ github.event.repository.default_branch }}' + if ( '${{ github.event.client_payload.base_branch_name }}'.startsWith('lse-release/') ) { + base = '${{ github.event.client_payload.base_branch_name }}' + } + const createPullResponse = await github.rest.pulls.create({ + title: process.env.TITLE, + owner, + repo, + head: '${{ github.event.client_payload.branch_name }}', + base: base, + draft: true, + body: [ + 'Hi @${{ github.event.client_payload.actor }}!', + '', + 'This PR was created in response to a PRs in upstream repo:', + '- ${{ github.event.client_payload.html_url }}', + ].join('\n') + }); + return createPullResponse + } + + - name: Check all submodules + id: check-all-submodules + if: github.event.client_payload.event_action == 'merged' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GIT_PAT }} + result-encoding: string + script: | + submodules = [ + { owner: 'heartexlabs', repo: 'label-studio-frontend' }, + { owner: 'heartexlabs', repo: 'dm2' } + ] + openPRs = [] + for (let submodule of submodules) { + core.info(`Checking ${ submodule.owner }/${ submodule.repo }`) + const listPullsResponse = await github.rest.pulls.list({ + owner: submodule.owner, + repo: submodule.repo, + head: `${ submodule.owner }:${{ github.event.client_payload.branch_name }}`, + status: 'open', + per_page: 100 + }); + + for (let pr of listPullsResponse.data ) { + if ( submodule.hasOwnProperty('paths-ignore') ) { + core.info(`Checking ${ submodule.owner }/${ submodule.repo } for ignore files`) + const getCommitResponse = await github.rest.repos.getCommit({ + owner: submodule.owner, + repo: submodule.repo, + ref: pr.merge_commit_sha + }); + if ( getCommitResponse.data.files.every(e => e.filename.startsWith(submodule['paths-ignore'])) ) { + core.info(`Skiping ${ pr.html_url } since it only change ${ submodule['paths-ignore'] } files`) + continue + } + } + openPRs.push(pr) + } + } + + if ( openPRs.length === 0 ) { + return true + } else { + let comment_lines = ['To enable Auto Merge for this PR also merge those PRs:'] + core.info(`Found ${ openPRs.length } open PRs`) + for (let pr of openPRs) { + core.info(`${ pr.html_url } is not merged yet`) + comment_lines.push(`- ${ pr.html_url }`) + } + return comment_lines.join('\n') + } + + - name: Comment PR + if: | + github.event.client_payload.event_action == 'merged' && + steps.check-all-submodules.outputs.result != 'true' + id: comment-pr + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + github.rest.issues.createComment({ + owner, + repo, + issue_number: '${{ fromJson(steps.get-pr.outputs.result).data[0].number }}', + body: `${{ steps.check-all-submodules.outputs.result }}`, + }); + + - name: Convert to ready for review + if: | + github.event.client_payload.event_action == 'merged' && + steps.check-all-submodules.outputs.result == 'true' + id: ready-for-review-pr + shell: bash + env: + GIT_PAT: ${{ secrets.GIT_PAT }} + run: | + echo "$GIT_PAT" | gh auth login --with-token + gh api graphql -F id='${{ fromJson(steps.get-pr.outputs.result).data[0].node_id }}' -f query=' + mutation($id: ID!) { + markPullRequestReadyForReview(input: { pullRequestId: $id }) { + pullRequest { + id + } + } + } + ' + + - name: Enable AutoMerge + id: enable-pr-automerge + if: | + github.event.client_payload.event_action == 'merged' && + steps.check-all-submodules.outputs.result == 'true' + shell: bash + env: + GIT_PAT: ${{ secrets.GIT_PAT }} + run: | + echo "$GIT_PAT" | gh auth login --with-token + gh api graphql -f pull='${{ fromJson(steps.get-pr.outputs.result).data[0].node_id }}' -f query=' + mutation($pull: ID!) { + enablePullRequestAutoMerge(input: {pullRequestId: $pull, mergeMethod: SQUASH}) { + pullRequest { + id + number + } + } + }' + + others: + name: Other actions with PR + if: | + github.event_name != 'schedule' && ( + github.event.client_payload.event_action == 'converted_to_draft' || + github.event.client_payload.event_action == 'ready_for_review' || + github.event.client_payload.event_action == 'closed' + ) + runs-on: ubuntu-latest + steps: + - uses: hmarr/debug-action@v2.0.1 + + - name: Get PRs + uses: actions/github-script@v6 + id: get-pr + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + const listPullsResponse = await github.rest.pulls.list({ + owner, + repo, + head: '${{ github.repository_owner }}:${{ github.event.client_payload.branch_name }}', + per_page: 100 + }); + return listPullsResponse + + - name: Close PR + if: github.event.client_payload.event_action == 'closed' + id: close-pr + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GIT_PAT }} + script: | + const { repo, owner } = context.repo; + const listPullsResponse = ${{ steps.get-pr.outputs.result }} + for (let pr of listPullsResponse.data ) { + core.info(`Closing ${ pr.html_url }`) + github.rest.pulls.update({ + owner, + repo, + pull_number: pr.number, + state: 'close' + }); + } + + - name: Convert to draft + if: github.event.client_payload.event_action == 'converted_to_draft' + id: convert-pr-to-draft + shell: bash + env: + GIT_PAT: ${{ secrets.GIT_PAT }} + run: | + echo "$GIT_PAT" | gh auth login --with-token + gh api graphql -F id='${{ fromJson(steps.get-pr.outputs.result).data[0].node_id }}' -f query=' + mutation($id: ID!) { + convertPullRequestToDraft(input: { pullRequestId: $id }) { + pullRequest { + id + isDraft + } + } + } + ' + + - name: Convert to ready for review + if: github.event.client_payload.event_action == 'ready_for_review' + id: ready-for-review-pr + shell: bash + env: + GIT_PAT: ${{ secrets.GIT_PAT }} + run: | + echo "$GIT_PAT" | gh auth login --with-token + gh api graphql -F id='${{ fromJson(steps.get-pr.outputs.result).data[0].node_id }}' -f query=' + mutation($id: ID!) { + markPullRequestReadyForReview(input: { pullRequestId: $id }) { + pullRequest { + id + } + } + } + '