[explorer] Improve testing for new features implemented #3384
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: "Issue Work Started" | |
| description: "Update issue work started date when Status field changes to In Progress" | |
| on: | |
| # Manual trigger for testing and specific issue updates | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: 'Issue number to update' | |
| required: false | |
| type: string | |
| default: '' | |
| bulk_update: | |
| description: 'Bulk update all issues' | |
| required: false | |
| type: boolean | |
| default: false | |
| # Automatic trigger for any issue activity | |
| issues: | |
| types: [opened, edited, closed, reopened, assigned, unassigned, labeled, unlabeled] | |
| # Daily schedule to check all issues and update Work Started field | |
| schedule: | |
| # Run at 11 PM Central Time (5 AM UTC the next day) | |
| - cron: '0 5 * * *' | |
| permissions: | |
| repository-projects: write | |
| issues: read | |
| contents: read | |
| jobs: | |
| update_work_started: | |
| # Skip if the actor is a bot to avoid infinite loops | |
| if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' && ( github.event.inputs.issue_number || inputs.issue_number != '') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Determine Issue Number | |
| id: get_issue_number | |
| run: | | |
| # Extract issue number based on trigger type | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| # Manual trigger | |
| issue_number="${{ github.event.inputs.issue_number }}" | |
| echo "Manual trigger for issue #$issue_number" | |
| elif [[ "${{ github.event_name }}" == "issues" ]]; then | |
| # Issue events | |
| issue_number="${{ github.event.issue.number }}" | |
| echo "Issue event triggered for issue #$issue_number" | |
| else | |
| echo "Unknown trigger type: ${{ github.event_name }}, skipping" | |
| exit 0 | |
| fi | |
| if [[ -z "$issue_number" || "$issue_number" == "null" ]]; then | |
| echo "Could not determine issue number, skipping" | |
| exit 0 | |
| fi | |
| echo "issue_number=$issue_number" >> $GITHUB_OUTPUT | |
| echo "Processing issue #$issue_number" | |
| - name: Set Environment Variables | |
| run: | | |
| echo "project_id=PVT_kwDOA9MHEM4AjeTl" >> $GITHUB_ENV | |
| echo "work_started_field_id=PVTF_lADOA9MHEM4AjeTlzgzZQtk" >> $GITHUB_ENV | |
| echo "status_field_id=PVTSSF_lADOA9MHEM4AjeTlzgb1Hjo" >> $GITHUB_ENV | |
| - name: Get Issue ID | |
| id: get_issue_id | |
| run: | | |
| issue_number="${{ steps.get_issue_number.outputs.issue_number }}" | |
| echo "Getting issue ID for issue #$issue_number" | |
| issue_details=$(curl -H "Authorization: Bearer ${{ secrets.TT_FORGE_PROJECT }}" -s "https://api.github.com/repos/${{ github.repository }}/issues/$issue_number") | |
| issue_id=$(echo "$issue_details" | jq -r '.node_id') | |
| echo "issue_id=$issue_id" >> $GITHUB_ENV | |
| - name: Get Project Item ID | |
| id: get_item_id | |
| run: | | |
| source $GITHUB_ENV | |
| found_item_id="" | |
| cursor="" | |
| page_count=0 | |
| echo "Searching for issue ID: $issue_id in project: $project_id" | |
| # Search through all pages to find the issue | |
| while true; do | |
| page_count=$((page_count + 1)) | |
| echo "Searching page $page_count..." | |
| # Build the query with or without cursor | |
| if [[ -z "$cursor" ]]; then | |
| QUERY='query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100) { | |
| pageInfo { | |
| hasNextPage | |
| endCursor | |
| } | |
| nodes { | |
| id | |
| content { | |
| ... on Issue { | |
| id | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg query "$QUERY" \ | |
| --arg projectId "$project_id" \ | |
| '{ query: $query, variables: { projectId: $projectId }}') | |
| else | |
| QUERY='query($projectId: ID!, $cursor: String!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100, after: $cursor) { | |
| pageInfo { | |
| hasNextPage | |
| endCursor | |
| } | |
| nodes { | |
| id | |
| content { | |
| ... on Issue { | |
| id | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg query "$QUERY" \ | |
| --arg projectId "$project_id" \ | |
| --arg cursor "$cursor" \ | |
| '{ query: $query, variables: { projectId: $projectId, cursor: $cursor }}') | |
| fi | |
| # Make the GraphQL request | |
| RESPONSE=$(curl -s -X POST -H "Authorization: Bearer ${{ secrets.TT_FORGE_PROJECT }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$JSON_PAYLOAD" \ | |
| https://api.github.com/graphql) | |
| # Check for errors | |
| ERRORS=$(echo "$RESPONSE" | jq -r '.errors // empty') | |
| if [[ -n "$ERRORS" && "$ERRORS" != "null" ]]; then | |
| echo "GraphQL Error: $ERRORS" | |
| exit 0 | |
| fi | |
| # Look for the issue in current batch | |
| ITEM_ID=$(echo "$RESPONSE" | jq -r --arg issue_id "$issue_id" \ | |
| '.data.node.items.nodes[]? | select(.content.id==$issue_id) | .id') | |
| if [[ -n "$ITEM_ID" && "$ITEM_ID" != "null" ]]; then | |
| found_item_id="$ITEM_ID" | |
| echo "Found issue in project" | |
| break | |
| fi | |
| # Check if there are more pages | |
| has_next_page=$(echo "$RESPONSE" | jq -r '.data.node.items.pageInfo.hasNextPage // false') | |
| if [[ "$has_next_page" != "true" ]]; then | |
| echo "Searched all $page_count pages. No more pages available." | |
| break | |
| fi | |
| # Get cursor for next page | |
| cursor=$(echo "$RESPONSE" | jq -r '.data.node.items.pageInfo.endCursor') | |
| if [[ $page_count -gt 100 ]]; then | |
| echo "Searched 100 pages (10,000+ items). Stopping to prevent infinite loop." | |
| break | |
| fi | |
| done | |
| # If issue not found in project, exit with error | |
| if [[ -z "$found_item_id" ]]; then | |
| echo "Issue not found in project after searching all pages." | |
| echo "The issue may not be added to this project yet." | |
| echo "Please manually add the issue to the project first." | |
| exit 0 | |
| fi | |
| echo "item_id=$found_item_id" >> $GITHUB_OUTPUT | |
| - name: Check Status Field | |
| if: steps.get_item_id.outputs.item_id != '' | |
| id: check_status | |
| run: | | |
| source $GITHUB_ENV | |
| # Query to get the current Status field value with timestamp | |
| QUERY='query($itemId: ID!) { | |
| node(id: $itemId) { | |
| ... on ProjectV2Item { | |
| fieldValues(first: 20) { | |
| nodes { | |
| ... on ProjectV2ItemFieldSingleSelectValue { | |
| field { | |
| ... on ProjectV2Field { | |
| id | |
| name | |
| } | |
| } | |
| name | |
| optionId | |
| updatedAt | |
| } | |
| ... on ProjectV2ItemFieldDateValue { | |
| field { | |
| ... on ProjectV2Field { | |
| id | |
| name | |
| } | |
| } | |
| date | |
| updatedAt | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg query "$QUERY" \ | |
| --arg itemId "${{ steps.get_item_id.outputs.item_id }}" \ | |
| '{ query: $query, variables: { itemId: $itemId }}') | |
| RESPONSE=$(curl -s -X POST \ | |
| -H "Authorization: Bearer ${{ secrets.TT_FORGE_PROJECT }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$JSON_PAYLOAD" \ | |
| https://api.github.com/graphql) | |
| ERRORS=$(echo "$RESPONSE" | jq -r '.errors // empty') | |
| if [[ -n "$ERRORS" && "$ERRORS" != "null" ]]; then | |
| echo "GraphQL Error checking status: $ERRORS" | |
| exit 0 | |
| fi | |
| # Extract Status field value and its update timestamp | |
| # For single select fields, we need to find by matching known Status values | |
| STATUS_VALUE=$(echo "$RESPONSE" | jq -r \ | |
| '.data.node.fieldValues.nodes[] | | |
| select(.name and (.name=="In Progress" or .name=="Assigned" or .name=="Screen" or .name=="Blocked" or .name=="Done" or .name=="In Review")) | .name') | |
| STATUS_UPDATED_AT=$(echo "$RESPONSE" | jq -r \ | |
| '.data.node.fieldValues.nodes[] | | |
| select(.name and (.name=="In Progress" or .name=="Assigned" or .name=="Screen" or .name=="Blocked" or .name=="Done" or .name=="In Review")) | .updatedAt') | |
| # Extract Work Started field value to check if it's already set | |
| WORK_STARTED_VALUE=$(echo "$RESPONSE" | jq -r --arg work_started_field_id "$work_started_field_id" \ | |
| '.data.node.fieldValues.nodes[] | | |
| select(.field.id==$work_started_field_id or .field.name=="Work Started") | .date') | |
| if [[ "$STATUS_VALUE" == "In Progress" ]]; then | |
| if [[ -z "$WORK_STARTED_VALUE" || "$WORK_STARTED_VALUE" == "null" ]]; then | |
| # Convert timestamp to date format (YYYY-MM-DD) | |
| if [[ -n "$STATUS_UPDATED_AT" && "$STATUS_UPDATED_AT" != "null" ]]; then | |
| status_date=$(echo "$STATUS_UPDATED_AT" | cut -d'T' -f1) | |
| echo "Status changed to 'In Progress' on: $status_date (from timestamp: $STATUS_UPDATED_AT)" | |
| echo "should_update=true" >> $GITHUB_OUTPUT | |
| echo "work_started_date=$status_date" >> $GITHUB_OUTPUT | |
| else | |
| echo "Could not determine when status changed - using current date as fallback" | |
| current_date=$(date +%Y-%m-%d) | |
| echo "should_update=true" >> $GITHUB_OUTPUT | |
| echo "work_started_date=$current_date" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Status is 'In Progress' but Work Started already set to '$WORK_STARTED_VALUE' - skipping to avoid overwriting" | |
| echo "should_update=false" >> $GITHUB_OUTPUT | |
| fi | |
| elif [[ -z "$STATUS_VALUE" || "$STATUS_VALUE" == "null" ]]; then | |
| echo "Status field not found or empty - skipping update" | |
| echo "should_update=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Status is '$STATUS_VALUE' - not 'In Progress', skipping update" | |
| echo "should_update=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update Work Started Field | |
| if: steps.check_status.outputs.should_update == 'true' | |
| run: | | |
| source $GITHUB_ENV | |
| work_started_date="${{ steps.check_status.outputs.work_started_date }}" | |
| echo "Setting Work Started date to: $work_started_date (date when status changed to 'In Progress')" | |
| RESPONSE=$(curl -s -X POST \ | |
| -H "Authorization: Bearer ${{ secrets.TT_FORGE_PROJECT }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ \"query\": \"mutation { updateProjectV2ItemFieldValue(input: { projectId: \\\"$project_id\\\", itemId: \\\"${{ steps.get_item_id.outputs.item_id }}\\\", fieldId: \\\"$work_started_field_id\\\", value: { date: \\\"$work_started_date\\\" } }) { clientMutationId } }\" }" \ | |
| https://api.github.com/graphql) | |
| ERRORS=$(echo "$RESPONSE" | jq -r '.errors // empty') | |
| if [[ -n "$ERRORS" && "$ERRORS" != "null" ]]; then | |
| echo "GraphQL Error: $ERRORS" | |
| exit 0 | |
| fi | |
| - name: Verify Work Started Field Update | |
| if: steps.check_status.outputs.should_update == 'true' | |
| run: | | |
| source $GITHUB_ENV | |
| echo "Verifying Work Started field was updated..." | |
| # Query to get the current Work Started field value | |
| VERIFY_QUERY='query($itemId: ID!) { | |
| node(id: $itemId) { | |
| ... on ProjectV2Item { | |
| fieldValues(first: 20) { | |
| nodes { | |
| ... on ProjectV2ItemFieldDateValue { | |
| field { | |
| ... on ProjectV2Field { | |
| id | |
| name | |
| } | |
| } | |
| date | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg query "$VERIFY_QUERY" \ | |
| --arg itemId "${{ steps.get_item_id.outputs.item_id }}" \ | |
| '{ query: $query, variables: { itemId: $itemId }}') | |
| VERIFY_RESPONSE=$(curl -s -X POST \ | |
| -H "Authorization: Bearer ${{ secrets.TT_FORGE_PROJECT }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$JSON_PAYLOAD" \ | |
| https://api.github.com/graphql) | |
| WORK_STARTED_VALUE=$(echo "$VERIFY_RESPONSE" | jq -r --arg work_started_field_id "$work_started_field_id" \ | |
| '.data.node.fieldValues.nodes[] | | |
| select(.field.id==$work_started_field_id or .field.name=="Work Started") | .date') | |
| echo "Work Started field set to: $WORK_STARTED_VALUE (date when status changed to 'In Progress')" | |
| # Bulk processing job for scheduled runs | |
| bulk_update_work_started: | |
| # Only run on scheduled triggers (cron) | |
| if: github.event_name == 'schedule' || inputs.bulk_update | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set Environment Variables | |
| run: | | |
| echo "project_id=PVT_kwDOA9MHEM4AjeTl" >> $GITHUB_ENV | |
| echo "work_started_field_id=PVTF_lADOA9MHEM4AjeTlzgzZQtk" >> $GITHUB_ENV | |
| - name: python install | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Run bulk update | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.TT_FORGE_PROJECT }} | |
| run: | | |
| pip install -r ./.github/scripts/python/bulk_update_issues/requirements-bulk-update.txt | |
| python ./.github/scripts/python/bulk_update_issues/bulk_update_issues.py |