feat: Add model to store as source of truth #22
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
| # | |
| # Copyright 2021-Present The Serverless Workflow Specification Authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| name: Sync Issues to Target GitHub Project | |
| on: | |
| issues: | |
| types: [opened, closed] | |
| workflow_dispatch: | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Parse target project | |
| id: parse | |
| run: | | |
| VALUE="${{ vars.PSYNC_TARGET || vars.GH_TARGET_PROJECT }}" | |
| if ! [[ "$VALUE" =~ ^[^:]+:[0-9]+$ ]]; then | |
| echo "Error: PSYNC_TARGET (or GH_TARGET_PROJECT) value '$VALUE' is invalid. Expected format: org:project_number (e.g. my-org:1)" | |
| exit 1 | |
| fi | |
| ORG="${VALUE%%:*}" | |
| NUMBER="${VALUE##*:}" | |
| echo "org=$ORG" >> "$GITHUB_OUTPUT" | |
| echo "number=$NUMBER" >> "$GITHUB_OUTPUT" | |
| - name: Check author filter | |
| id: author_filter | |
| if: github.event_name == 'issues' | |
| run: | | |
| FILTER="${{ vars.PSYNC_AUTHORS_FILTER || vars.GH_AUTHORS_FILTER }}" | |
| if [ -z "$FILTER" ]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| AUTHOR="${{ github.event.issue.user.login }}" | |
| if echo "$FILTER" | tr ',' '\n' | xargs -I{} echo {} | xargs | tr ' ' '\n' | grep -qx "$AUTHOR"; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Issue author '$AUTHOR' is not in PSYNC_AUTHORS_FILTER — skipping." | |
| echo "allowed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Get project ID | |
| id: project | |
| if: github.event_name == 'workflow_dispatch' || ((vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'false' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'off' && steps.author_filter.outputs.allowed != 'false') | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| PROJECT_ID=$(gh api graphql -f query=' | |
| query($org: String!, $number: Int!) { | |
| organization(login: $org) { | |
| projectV2(number: $number) { | |
| id | |
| } | |
| } | |
| }' \ | |
| -f org="${{ steps.parse.outputs.org }}" \ | |
| -F number=${{ steps.parse.outputs.number }} \ | |
| --jq '.data.organization.projectV2.id') | |
| if [ -z "$PROJECT_ID" ] || [ "$PROJECT_ID" = "null" ]; then | |
| echo "Error: could not resolve project ID for '${{ steps.parse.outputs.org }}' project number ${{ steps.parse.outputs.number }}. Check PSYNC_TARGET (or GH_TARGET_PROJECT) and that PSYNC_PAT (or GH_PAT_TOKEN) has access to the target org's project." | |
| exit 1 | |
| fi | |
| echo "id=$PROJECT_ID" >> "$GITHUB_OUTPUT" | |
| - name: Import existing repo issues | |
| if: github.event_name == 'workflow_dispatch' && (vars.PSYNC_IMPORT_EXISTING || vars.GH_IMPORT_EXISTING_ISSUES) == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| PROJECT_ID="${{ steps.project.outputs.id }}" | |
| INITIAL_VALUES="${{ vars.PSYNC_INITIAL_VALUES || vars.GH_ISSUE_INITIAL_VALUES }}" | |
| # Fetch project fields once if initial values are configured | |
| FIELDS_JSON="[]" | |
| if [ -n "$INITIAL_VALUES" ]; then | |
| FIELDS_JSON=$(gh api graphql -f query=' | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| fields(first: 20) { | |
| nodes { | |
| ... on ProjectV2SingleSelectField { | |
| id | |
| name | |
| options { id name } | |
| } | |
| ... on ProjectV2Field { | |
| id | |
| name | |
| dataType | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="$PROJECT_ID" \ | |
| --jq '.data.node.fields.nodes') | |
| fi | |
| # Helper: apply PSYNC_INITIAL_VALUES (or GH_ISSUE_INITIAL_VALUES) to a project item | |
| apply_fields() { | |
| local ITEM_ID="$1" | |
| local ISSUE_NUMBER="$2" | |
| while IFS= read -r PAIR; do | |
| PAIR=$(echo "$PAIR" | xargs) | |
| [ -z "$PAIR" ] && continue | |
| KEY=$(echo "$PAIR" | cut -d= -f1 | xargs) | |
| VALUE=$(echo "$PAIR" | cut -d= -f2- | xargs) | |
| if [ "$KEY" = "Assignees" ]; then | |
| ASSIGNEES_JSON=$(echo "$VALUE" | tr ' ' '\n' | jq -R . | jq -s '[.[] | select(length > 0)]') | |
| [ "$(echo "$ASSIGNEES_JSON" | jq 'length')" -eq 0 ] && continue | |
| echo "{\"assignees\": $ASSIGNEES_JSON}" | \ | |
| gh api "repos/${{ github.repository }}/issues/$ISSUE_NUMBER/assignees" \ | |
| --method POST --input - | |
| else | |
| FIELD_ID=$(echo "$FIELDS_JSON" | jq -r \ | |
| --arg name "$KEY" \ | |
| '.[] | select(.name == $name) | .id // empty' | head -1) | |
| [ -z "$FIELD_ID" ] && echo "Warning: field '$KEY' not found, skipping." && continue | |
| DATA_TYPE=$(echo "$FIELDS_JSON" | jq -r \ | |
| --arg name "$KEY" \ | |
| '.[] | select(.name == $name) | .dataType // empty' | head -1) | |
| OPTION_ID=$(echo "$FIELDS_JSON" | jq -r \ | |
| --arg name "$KEY" --arg val "$VALUE" \ | |
| '.[] | select(.name == $name) | .options[]? | select(.name == $val) | .id // empty') | |
| if [ -n "$OPTION_ID" ]; then | |
| gh api graphql -f query=' | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { singleSelectOptionId: $optionId } | |
| }) { projectV2Item { id } } | |
| }' \ | |
| -f projectId="$PROJECT_ID" -f itemId="$ITEM_ID" \ | |
| -f fieldId="$FIELD_ID" -f optionId="$OPTION_ID" | |
| elif [ "$DATA_TYPE" = "TEXT" ]; then | |
| gh api graphql -f query=' | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $text: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { text: $text } | |
| }) { projectV2Item { id } } | |
| }' \ | |
| -f projectId="$PROJECT_ID" -f itemId="$ITEM_ID" \ | |
| -f fieldId="$FIELD_ID" -f text="$VALUE" | |
| else | |
| echo "Warning: field '$KEY' has unsupported type '${DATA_TYPE:-single-select}' — only TEXT and single-select fields are supported. Skipping." | |
| fi | |
| fi | |
| done < <(echo "$INITIAL_VALUES" | tr ',' '\n') | |
| } | |
| # Fetch all open repo issues (node_id + number + author) | |
| REPO_ISSUES=$(gh api "repos/${{ github.repository }}/issues" \ | |
| --paginate | jq -s '[ .[][] | select(.pull_request == null) | {node_id, number, author: .user.login} ]') | |
| # Filter by author if PSYNC_AUTHORS_FILTER (or GH_AUTHORS_FILTER) is set | |
| AUTHORS_FILTER="${{ vars.PSYNC_AUTHORS_FILTER || vars.GH_AUTHORS_FILTER }}" | |
| if [ -n "$AUTHORS_FILTER" ]; then | |
| AUTHORS_JSON=$(echo "$AUTHORS_FILTER" | tr ',' '\n' | xargs -I{} echo {} | jq -R . | jq -s '.') | |
| REPO_ISSUES=$(echo "$REPO_ISSUES" | jq --argjson authors "$AUTHORS_JSON" \ | |
| '[ .[] | select(.author | IN($authors[])) ]') | |
| fi | |
| # Fetch all existing project content node IDs (paginated) | |
| PROJECT_IDS="" | |
| CURSOR="" | |
| while true; do | |
| if [ -z "$CURSOR" ]; then | |
| PAGE=$(gh api graphql -f query=' | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100) { | |
| pageInfo { hasNextPage endCursor } | |
| nodes { content { ... on Issue { id } } } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="$PROJECT_ID") | |
| else | |
| PAGE=$(gh api graphql -f query=' | |
| query($projectId: ID!, $cursor: String!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100, after: $cursor) { | |
| pageInfo { hasNextPage endCursor } | |
| nodes { content { ... on Issue { id } } } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="$PROJECT_ID" -f cursor="$CURSOR") | |
| fi | |
| PROJECT_IDS="$PROJECT_IDS"$'\n'"$(echo "$PAGE" | jq -r '.data.node.items.nodes[].content.id // empty')" | |
| HAS_NEXT=$(echo "$PAGE" | jq -r '.data.node.items.pageInfo.hasNextPage') | |
| [ "$HAS_NEXT" != "true" ] && break | |
| CURSOR=$(echo "$PAGE" | jq -r '.data.node.items.pageInfo.endCursor') | |
| done | |
| PROJECT_IDS_JSON=$(echo "$PROJECT_IDS" | grep -v '^$' | jq -R . | jq -s '.') || PROJECT_IDS_JSON='[]' | |
| # Find issues not yet in the project | |
| MISSING=$(echo "$REPO_ISSUES" | jq -c \ | |
| --argjson existing "$PROJECT_IDS_JSON" \ | |
| '.[] | select(.node_id | IN($existing[]) | not)') | |
| if [ -z "$MISSING" ]; then | |
| echo "No new issues to import." | |
| exit 0 | |
| fi | |
| IMPORTED=0 | |
| FAILED=0 | |
| while IFS= read -r ISSUE; do | |
| NODE_ID=$(echo "$ISSUE" | jq -r '.node_id') | |
| ISSUE_NUMBER=$(echo "$ISSUE" | jq -r '.number') | |
| ITEM_ID=$(gh api graphql -f query=' | |
| mutation($projectId: ID!, $contentId: ID!) { | |
| addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { | |
| item { id } | |
| } | |
| }' \ | |
| -f projectId="$PROJECT_ID" \ | |
| -f contentId="$NODE_ID" \ | |
| --jq '.data.addProjectV2ItemById.item.id') || ITEM_ID="" | |
| if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then | |
| echo "Warning: failed to add issue #$ISSUE_NUMBER" | |
| FAILED=$((FAILED + 1)) | |
| continue | |
| fi | |
| [ -n "$INITIAL_VALUES" ] && apply_fields "$ITEM_ID" "$ISSUE_NUMBER" | |
| IMPORTED=$((IMPORTED + 1)) | |
| echo "Imported issue #$ISSUE_NUMBER" | |
| done <<< "$MISSING" | |
| echo "Done. Imported: $IMPORTED, Failed: $FAILED" | |
| - name: Add issue to project | |
| id: add_item | |
| if: github.event.action == 'opened' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'false' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'off' && steps.author_filter.outputs.allowed != 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| ITEM_ID=$(gh api graphql -f query=' | |
| mutation($projectId: ID!, $contentId: ID!) { | |
| addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { | |
| item { id } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f contentId="${{ github.event.issue.node_id }}" \ | |
| --jq '.data.addProjectV2ItemById.item.id' || true) | |
| if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then | |
| echo "Error: failed to add issue to project — item ID is empty or null. The issue may already exist in the project or the PAT may lack access." | |
| exit 1 | |
| fi | |
| echo "item_id=$ITEM_ID" >> "$GITHUB_OUTPUT" | |
| - name: Set initial field values | |
| if: github.event.action == 'opened' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'false' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'off' && steps.author_filter.outputs.allowed != 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| INITIAL_VALUES="${{ vars.PSYNC_INITIAL_VALUES || vars.GH_ISSUE_INITIAL_VALUES }}" | |
| [ -z "$INITIAL_VALUES" ] && exit 0 | |
| # Fetch all project fields (single-select and text) | |
| FIELDS_RESULT=$(gh api graphql -f query=' | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| fields(first: 20) { | |
| nodes { | |
| ... on ProjectV2SingleSelectField { | |
| id | |
| name | |
| options { id name } | |
| } | |
| ... on ProjectV2Field { | |
| id | |
| name | |
| dataType | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}") | |
| # Apply each key=value pair | |
| while IFS= read -r PAIR; do | |
| PAIR=$(echo "$PAIR" | xargs) | |
| [ -z "$PAIR" ] && continue | |
| KEY=$(echo "$PAIR" | cut -d= -f1 | xargs) | |
| VALUE=$(echo "$PAIR" | cut -d= -f2- | xargs) | |
| if [ "$KEY" = "Assignees" ]; then | |
| ASSIGNEES_JSON=$(echo "$VALUE" | tr ' ' '\n' | jq -R . | jq -s '[.[] | select(length > 0)]') | |
| [ "$(echo "$ASSIGNEES_JSON" | jq 'length')" -eq 0 ] && continue | |
| echo "{\"assignees\": $ASSIGNEES_JSON}" | \ | |
| gh api "repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees" \ | |
| --method POST --input - | |
| else | |
| FIELD_ID=$(echo "$FIELDS_RESULT" | jq -r \ | |
| --arg name "$KEY" \ | |
| '.data.node.fields.nodes[] | select(.name == $name) | .id // empty' | head -1) | |
| [ -z "$FIELD_ID" ] && echo "Warning: field '$KEY' not found, skipping." && continue | |
| DATA_TYPE=$(echo "$FIELDS_RESULT" | jq -r \ | |
| --arg name "$KEY" \ | |
| '.data.node.fields.nodes[] | select(.name == $name) | .dataType // empty' | head -1) | |
| OPTION_ID=$(echo "$FIELDS_RESULT" | jq -r \ | |
| --arg name "$KEY" --arg val "$VALUE" \ | |
| '.data.node.fields.nodes[] | select(.name == $name) | .options[]? | select(.name == $val) | .id // empty') | |
| if [ -n "$OPTION_ID" ]; then | |
| gh api graphql -f query=' | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { singleSelectOptionId: $optionId } | |
| }) { | |
| projectV2Item { id } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f itemId="${{ steps.add_item.outputs.item_id }}" \ | |
| -f fieldId="$FIELD_ID" \ | |
| -f optionId="$OPTION_ID" | |
| elif [ "$DATA_TYPE" = "TEXT" ]; then | |
| gh api graphql -f query=' | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $text: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { text: $text } | |
| }) { | |
| projectV2Item { id } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f itemId="${{ steps.add_item.outputs.item_id }}" \ | |
| -f fieldId="$FIELD_ID" \ | |
| -f text="$VALUE" | |
| else | |
| echo "Warning: field '$KEY' has unsupported type '${DATA_TYPE:-single-select}' — only TEXT and single-select fields are supported. Skipping." | |
| fi | |
| fi | |
| done < <(echo "$INITIAL_VALUES" | tr ',' '\n') | |
| - name: Get item ID and close Status option ID | |
| id: find_item | |
| if: github.event.action == 'closed' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'false' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'off' && steps.author_filter.outputs.allowed != 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| CLOSE_STATUS="${{ vars.PSYNC_CLOSE_STATUS || vars.GH_ISSUE_CLOSE_STATUS }}" | |
| CLOSE_STATUS="${CLOSE_STATUS:-Done}" | |
| # Fetch Status field info once | |
| FIELDS_RESULT=$(gh api graphql -f query=' | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| fields(first: 20) { | |
| nodes { | |
| ... on ProjectV2SingleSelectField { | |
| id | |
| name | |
| options { id name } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}") | |
| FIELD_ID=$(echo "$FIELDS_RESULT" | jq -r \ | |
| '.data.node.fields.nodes[] | select(.name == "Status") | .id // empty') | |
| if [ -z "$FIELD_ID" ]; then | |
| echo "Error: Status field not found in the target project. Ensure the project has a single-select field named exactly 'Status' (case-sensitive)." | |
| exit 1 | |
| fi | |
| CLOSE_OPTION_ID=$(echo "$FIELDS_RESULT" | jq -r \ | |
| --arg status "$CLOSE_STATUS" \ | |
| '.data.node.fields.nodes[] | select(.name == "Status") | .options[] | select(.name == $status) | .id // empty') | |
| if [ -z "$CLOSE_OPTION_ID" ]; then | |
| echo "Error: Status option '$CLOSE_STATUS' not found in the target project. Check PSYNC_CLOSE_STATUS (or GH_ISSUE_CLOSE_STATUS, default: Done) — value must match a Status option exactly (case-sensitive)." | |
| exit 1 | |
| fi | |
| # Paginate items until the matching issue is found | |
| ITEM_ID="" | |
| CURSOR="" | |
| while true; do | |
| if [ -z "$CURSOR" ]; then | |
| PAGE_RESULT=$(gh api graphql -f query=' | |
| query($projectId: ID!) { | |
| node(id: $projectId) { | |
| ... on ProjectV2 { | |
| items(first: 100) { | |
| pageInfo { hasNextPage endCursor } | |
| nodes { | |
| id | |
| content { ... on Issue { id } } | |
| } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}") | |
| else | |
| PAGE_RESULT=$(gh api graphql -f 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 } } | |
| } | |
| } | |
| } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f cursor="$CURSOR") | |
| fi | |
| ITEM_ID=$(echo "$PAGE_RESULT" | jq -r \ | |
| --arg issueId "${{ github.event.issue.node_id }}" \ | |
| '.data.node.items.nodes[] | select(.content.id == $issueId) | .id') | |
| [ -n "$ITEM_ID" ] && break | |
| HAS_NEXT=$(echo "$PAGE_RESULT" | jq -r '.data.node.items.pageInfo.hasNextPage') | |
| [ "$HAS_NEXT" != "true" ] && break | |
| CURSOR=$(echo "$PAGE_RESULT" | jq -r '.data.node.items.pageInfo.endCursor') | |
| done | |
| # If not found, add the issue now (handles race where closed fires before opened) | |
| if [ -z "$ITEM_ID" ]; then | |
| echo "Issue not yet in project — adding it now before setting close Status." | |
| ITEM_ID=$(gh api graphql -f query=' | |
| mutation($projectId: ID!, $contentId: ID!) { | |
| addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { | |
| item { id } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f contentId="${{ github.event.issue.node_id }}" \ | |
| --jq '.data.addProjectV2ItemById.item.id') | |
| if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then | |
| echo "Error: failed to add issue to project. Cannot set close Status." | |
| exit 1 | |
| fi | |
| fi | |
| echo "item_id=$ITEM_ID" >> "$GITHUB_OUTPUT" | |
| echo "field_id=$FIELD_ID" >> "$GITHUB_OUTPUT" | |
| echo "close_option_id=$CLOSE_OPTION_ID" >> "$GITHUB_OUTPUT" | |
| - name: Set item close Status | |
| if: github.event.action == 'closed' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'false' && (vars.PSYNC_ENABLED || vars.GH_SYNC_ENABLED) != 'off' && steps.author_filter.outputs.allowed != 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PSYNC_PAT || secrets.GH_PAT_TOKEN }} | |
| run: | | |
| ITEM_ID="${{ steps.find_item.outputs.item_id }}" | |
| OPTION_ID="${{ steps.find_item.outputs.close_option_id }}" | |
| gh api graphql -f query=' | |
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projectId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { singleSelectOptionId: $optionId } | |
| }) { | |
| projectV2Item { id } | |
| } | |
| }' \ | |
| -f projectId="${{ steps.project.outputs.id }}" \ | |
| -f itemId="$ITEM_ID" \ | |
| -f fieldId="${{ steps.find_item.outputs.field_id }}" \ | |
| -f optionId="$OPTION_ID" |