> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Auto-label your GitHub PRs with Sonar Scans

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

Open plan mode. Implement this Port guide in my org via MCP:

https://docs.port.io/guides/all/apply-labels-to-github-pr-based-on-sonar-scans/

Goal: get the guide's core flow working end-to-end in my org; adapting it to fit my existing setup takes priority over matching the guide 1:1.

Plan:
1. Confirm MCP is connected, in the right org, with sufficient permissions.
2. Diff the guide's data model (blueprints, properties, relations, actions, agents, automations, integrations, secrets) against mine.
3. Propose adaptations for gaps, reusing existing blueprints/relations over guide-named duplicates.
4. Flag what needs a UI click, credential, or secret from me, testing MCP capability empirically before ruling anything out.
5. Stop on any blocker and give me options. Approving this plan authorizes the writes it lists; pause only for writes beyond what's listed.

Build:
- Extend blueprint schema additively when upserting; don't remove or overwrite existing properties, and treat type conflicts as a blocker, not an auto-fix.
- List any mock data in the plan, minimal and labeled mock; once approved, seed it without re-asking, and tell me what you seeded.
- For anything the guide writes downstream (e.g. a webhook target), use a real entity, not a mock.
- For pages/widgets, use the real page identifier from the app URL, not a guessed slug.
- When you hit a UI step confirmed (not assumed) unsupported via MCP, pause, give exact clicks, then resume via MCP.
- Validate and give links after each meaningful step (only a tool-returned URL, no guessed paths); don't proceed if the last run wasn't a success.

Done:
- Confirm the guide's expected output exists and runs in Port.
- Summarize adaptations, seeded data, what was mocked or skipped, remaining UI steps, and how to verify.

This guide demonstrates how to set up an automation in Port that applies color-coded labels to your GitHub pull requests based on SonarCloud scan reports. These labels help you classify vulnerabilities, code smells, security hotspots, and bugs right from the pull request view.

Common use cases​

  • Enforce code quality standards: Highlight PRs with poor test coverage, high duplication, or critical issues.
  • Encourage developer accountability: Make quality regressions visible and traceable at the PR level.

Prerequisites​

This guide assumes the following:

Set up data model​

To connect scan data with the correct pull requests, you'll need to link the Pull Request blueprint with the SonarQube Analysis blueprint.

Add a relation between the githubPullRequest blueprint and the sonarQubeAnalysis blueprint, then update your GitHub integration mapping to match pull requests to SonarQube analyses by title and branch or by commit SHA. For the relation mapping pattern, see mapping relations.

Set up automation​

Once the SonarQube scan entities are linked to their corresponding pull requests, you can configure an automation in Port that triggers a GitHub workflow on PR updates and applies Sonar-based labels directly to the pull request.

This setup involves two parts:

  1. Defining the automation in Port.
  2. Creating the GitHub workflow in your dedicated workflows repository.

Add Port secrets​

When using the Legacy GITHUB or Ocean INTEGRATION_ACTION invocation methods, you do not need to add GitHub tokens to Port secrets. Ensure your GitHub workflow repository has the required secrets (see the workflow section below).

Define automation backend​

  1. Go to the Automations page in Port.

  2. Click on the + Automation button.

  3. Copy and paste the following JSON configuration into the editor:

    Apply SonarCloud label automation (Click to expand)
    Replace placeholders

    Make sure to replace <YOUR_GITHUB_OCEAN_INTEGRATION_ID>, <YOUR_GITHUB_ORG>, and <YOUR_GITHUB_REPO> with your GitHub Ocean integration ID and the organization and repository where your apply-sonar-scan-on-pr.yaml workflow resides.

    {
    "identifier": "addLabelOnGithubPR",
    "title": "Add Sonar Scan Label On PR Updated",
    "description": "Automation to add Sonar scan label to the GitHub PR upon update",
    "trigger": {
    "type": "automation",
    "event": {
    "type": "ENTITY_UPDATED",
    "blueprintIdentifier": "githubPullRequest"
    },
    "condition": {
    "type": "JQ",
    "expressions": [
    ".diff.after.relations.sonarAnalysis != null"
    ],
    "combinator": "and"
    }
    },
    "invocationMethod": {
    "type": "INTEGRATION_ACTION",
    "installationId": "<YOUR_GITHUB_OCEAN_INTEGRATION_ID>",
    "integrationActionType": "dispatch_workflow",
    "integrationActionExecutionProperties": {
    "org": "<YOUR_GITHUB_ORG>",
    "repo": "<YOUR_GITHUB_REPO>",
    "workflow": "apply-sonar-scan-on-pr.yaml",
    "workflowInputs": {
    "prNumber": "{{ .event.diff.after.properties.prNumber | tostring }}",
    "repository": "{{ .event.diff.after.relations.repository }}",
    "sonarEntity": "{{ .event.diff.after.relations.sonarAnalysis }}",
    "runID": "{{ .run.id }}"
    },
    "reportWorkflowStatus": true
    }
    },
    "publish": true
    }
  4. Click Save.

Create the GitHub workflow​

Now let us define the GitHub Actions workflow that receives the input and applies labels to the pull request.

Dedicated Workflows Repository

We recommend creating a dedicated repository for the workflows that are used by Port actions.

In your dedicated workflow repository, ensure you have a .github/workflows directory.

  1. Create a new file named apply-sonar-scan-on-pr.yaml

  2. Copy and paste the following workflow configuration:

    Apply SonarCloud labels workflow (Click to expand)
    name: Apply Sonar Scan on PR

    on:
    workflow_dispatch:
    inputs:
    prNumber:
    required: true
    type: string
    repository:
    required: true
    type: string
    sonarEntity:
    required: true
    type: string
    runID:
    required: true
    type: string
    jobs:
    analyze_sonar:
    runs-on: ubuntu-latest
    env:
    GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
    steps:
    - name: Checkout
    uses: actions/checkout@v6

    - name: Fetch Port Access Token
    id: fetch_port_token
    run: |
    PORT_ACCESS_TOKEN=$(curl -s -L 'https://api.port.io/v1/auth/access_token' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -d '{
    "clientId": "${{ secrets.PORT_CLIENT_ID }}",
    "clientSecret": "${{ secrets.PORT_CLIENT_SECRET }}"
    }' | jq -r '.accessToken')
    echo "PORT_ACCESS_TOKEN=$PORT_ACCESS_TOKEN" >> "$GITHUB_ENV"

    - name: Get Sonar Entity from Port
    id: get_sonar
    run: |
    sonar_entity_id="${{ github.event.inputs.sonarEntity }}"
    echo "πŸ” Fetching Sonar entity $sonar_entity_id"

    sonar_response=$(curl -s -X GET "https://api.port.io/v1/blueprints/sonarQubeAnalysis/entities/$sonar_entity_id" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${{ env.PORT_ACCESS_TOKEN }}")

    echo "$sonar_response"

    FIXED_ISSUES=$(echo "$sonar_response" | jq '.entity.properties.fixedIssues // 0')
    NEW_ISSUES=$(echo "$sonar_response" | jq '.entity.properties.newIssues // 0')
    COVERAGE=$(echo "$sonar_response" | jq '.entity.properties.coverage // 0')
    DUPLICATIONS=$(echo "$sonar_response" | jq '.entity.properties.duplications // 0')

    echo "FIXED_ISSUES=$FIXED_ISSUES" >> "$GITHUB_ENV"
    echo "NEW_ISSUES=$NEW_ISSUES" >> "$GITHUB_ENV"
    echo "COVERAGE=$COVERAGE" >> "$GITHUB_ENV"
    echo "DUPLICATIONS=$DUPLICATIONS" >> "$GITHUB_ENV"

    - name: Classify and Apply Sonar Labels
    id: apply_pr_label
    run: |
    set -e

    repo="${{ github.event.inputs.repository }}"
    owner="${{ github.repository_owner }}"
    pr_number=$(echo "${{ github.event.inputs.prNumber }}" | grep -o '[0-9]\+$')

    # Classify coverage
    if (( $(echo "$COVERAGE < 25" | bc -l) )); then
    coverage_label="Sonar: Coverage - 0-25%"
    elif (( $(echo "$COVERAGE < 50" | bc -l) )); then
    coverage_label="Sonar: Coverage - 25-50%"
    elif (( $(echo "$COVERAGE < 75" | bc -l) )); then
    coverage_label="Sonar: Coverage - 50-75%"
    else
    coverage_label="Sonar: Coverage - 75-100%"
    fi

    # Classify new issues
    if (( NEW_ISSUES == 0 )); then
    new_issues_label="Sonar: Issues - A"
    elif (( NEW_ISSUES <= 5 )); then
    new_issues_label="Sonar: Issues - B"
    elif (( NEW_ISSUES <= 10 )); then
    new_issues_label="Sonar: Issues - C"
    elif (( NEW_ISSUES <= 20 )); then
    new_issues_label="Sonar: Issues - D"
    else
    new_issues_label="Sonar: Issues - E"
    fi

    # Classify fixed issues
    if (( FIXED_ISSUES == 0 )); then
    fixed_issues_label="Sonar: Fixed - A"
    elif (( FIXED_ISSUES <= 5 )); then
    fixed_issues_label="Sonar: Fixed - B"
    elif (( FIXED_ISSUES <= 10 )); then
    fixed_issues_label="Sonar: Fixed - C"
    elif (( FIXED_ISSUES <= 20 )); then
    fixed_issues_label="Sonar: Fixed - D"
    else
    fixed_issues_label="Sonar: Fixed - E"
    fi

    # Classify duplications
    if (( $(echo "$DUPLICATIONS < 5" | bc -l) )); then
    dup_label="Sonar: Duplication - A"
    elif (( $(echo "$DUPLICATIONS < 10" | bc -l) )); then
    dup_label="Sonar: Duplication - B"
    elif (( $(echo "$DUPLICATIONS < 20" | bc -l) )); then
    dup_label="Sonar: Duplication - C"
    elif (( $(echo "$DUPLICATIONS < 30" | bc -l) )); then
    dup_label="Sonar: Duplication - D"
    else
    dup_label="Sonar: Duplication - E"
    fi

    labels_to_apply=("$coverage_label" "$new_issues_label" "$fixed_issues_label" "$dup_label")

    echo "🏷️ Will apply labels: ${labels_to_apply[*]}"

    # Define a function to assign colors based on grade
    get_label_color() {
    label="$1"
    if [[ "$label" == *" - A" || "$label" == *"75-100%" ]]; then
    echo "2cbe4e" # Green
    elif [[ "$label" == *" - B" || "$label" == *"50-75%" ]]; then
    echo "a2eeef" # Light blue
    elif [[ "$label" == *" - C" || "$label" == *"25-50%" ]]; then
    echo "fbca04" # Yellow
    elif [[ "$label" == *" - D" || "$label" == *"0-25%" ]]; then
    echo "f66a0a" # Orange
    else
    echo "d73a4a" # Red for E or anything else
    fi
    }

    # Create labels if they don’t exist, using dynamic colors
    for label in "${labels_to_apply[@]}"; do
    color=$(get_label_color "$label")
    echo "πŸ› οΈ Ensuring label exists: $label with color #$color"
    curl -s -o /dev/null -w "%{http_code}" -X POST "https://api.github.com/repos/$owner/$repo/labels" \
    -H "Authorization: Bearer $GH_TOKEN" \
    -H "Accept: application/vnd.github+json" \
    -d "{\"name\": \"$label\", \"color\": \"$color\"}" | grep -qE "201|422"
    done

    # Apply to PR
    echo "🏷️ Applying labels to PR #$pr_number..."
    curl -s -X POST "https://api.github.com/repos/$owner/$repo/issues/$pr_number/labels" \
    -H "Authorization: Bearer $GH_TOKEN" \
    -H "Accept: application/vnd.github+json" \
    -d "{\"labels\": [\"${labels_to_apply[0]}\", \"${labels_to_apply[1]}\", \"${labels_to_apply[2]}\", \"${labels_to_apply[3]}\"]}"


    - name: Update Port action status
    if: always()
    run: |
    if [ "${{ steps.apply_pr_label.outcome }}" == "failure" ]; then
    STATUS="FAILURE"
    else
    STATUS="SUCCESS"
    fi

    curl -L -X PATCH "https://api.port.io/v1/actions/runs/${{ github.event.inputs.runID }}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer ${{ env.PORT_ACCESS_TOKEN }}" \
    -d '{
    "status": "'"$STATUS"'",
    "statusLabel": "'"$STATUS"'",
    "link": "'"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"'",
    "summary": "Pull request labeling completed with status: '"$STATUS"'"
    }'
    Required GitHub Secrets

    For this workflow to function properly, you need to add the following secrets to your GitHub repository:

    • PORT_CLIENT_ID: The client ID of your Port account.
    • PORT_CLIENT_SECRET: The client secret of your Port account.
    • MY_GITHUB_TOKEN: The fine grained GitHub personal access token with Read and Write access to issues, pull requests across all repositories in your organization.
  3. Commit and push the changes to your repository.

Once a pull request associated with a SonarCloud analysis is updated, the automation will be triggered automatically. It will evaluate the latest scan results and apply color-coded labels to the PR, reflecting the quality status of the code.

GitHub PRs with SonarCloud quality labels