Jira writeback integration

This commit is contained in:
Chase King 2025-02-19 09:55:51 -05:00
parent a421bf213e
commit e7284d4434
2 changed files with 578 additions and 0 deletions

View file

@ -0,0 +1,434 @@
trigger:
branches:
include: [ develop, release/* ]
paths:
exclude:
- deployment/*
include:
- "*"
pr:
branches:
include: [ '*' ]
paths:
exclude:
- deployment/*
include:
- "*"
resources:
containers:
- container: node
image: packagerepository.sagaws.net:8082/safelite/node-build:18
- container: awscli
image: packagerepository.sagaws.net:8082/safelite/awscli2-build:3.9
repositories:
- repository: AzureDevOps
type: github
name: Safelite/AzureDevOps
endpoint: Safelite
ref: refs/tags/t5.5.40
variables:
- group: Digital-Infrastructure
- group: ISS-BuildBranches
- name: dockerImageName
value: 'playwright-tests'
- name: imageTag
value: '$(Build.BuildId)'
- name: totalShards
value: 40 # TODO: Temporarily changed to 40, change back to 4
stages:
# PR's
- ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(1,1)) }}: # TODO: Temporarily changed to always be true, change back
- stage: TestPr
displayName: Run Tests For PullRequest
jobs:
- template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps
parameters:
nodeContainer: node
npmLocation: $(Build.SourcesDirectory)
testResultsFile: junit.xml
summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml
- job: playwright_tests
continueOnError: true
strategy:
matrix:
shard1:
shardNumber: 1
shard2:
shardNumber: 2
shard3:
shardNumber: 3
shard4:
shardNumber: 4
steps:
- task: Docker@2
displayName: 'Build Docker Image'
inputs:
command: build
dockerfile: Dockerfile.playwright
repository: $(dockerImageName)
tags: $(imageTag)
arguments: '--no-cache --pull'
- script: |
# Create container and run tests
container_id=$(docker create \
--ipc=host \
-e CCIS_API_AUTH=$(CCIS_API_AUTH) \
-e BASE_URL=$(BASE_URL) \
-e CCIS_API_URL=$(CCIS_API_URL) \
-e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \
-e SHARD=$(shardNumber) \
-e CI=true \
-e NODE_ENV=$(NODE_ENV) \
$(dockerImageName):$(imageTag) \
npx concurrently -k -n "server,playwright"\
"sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\
"npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@smoke | @Advanced\"")
# Start container and stream logs
echo "Starting tests for shard $(shardNumber)..."
docker start -a $container_id
# Create directory for test results
echo "Creating test results directory..."
mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)
# Copy test results from container
echo "Copying test results..."
docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/
# Remove container
echo "Cleaning up container..."
docker rm $container_id
# Check if tests failed
if [ $? -ne 0 ]; then
echo "Tests failed in shard $(shardNumber)!"
exit 1
fi
displayName: 'Run Playwright Tests - Shard $(shardNumber)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Test Reports - Shard $(shardNumber)'
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)'
artifact: 'playwright-report-shard-$(shardNumber)'
publishLocation: 'pipeline'
- script: |
docker rmi $(dockerImageName):$(imageTag) -f
displayName: 'Cleanup Docker Image'
condition: always()
- job: download_and_merge_reports
container:
image: mcr.microsoft.com/playwright:v1.48.0-noble
dependsOn: playwright_tests
steps:
- task: DownloadPipelineArtifact@2
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports'
- script: |
npm i ortoni-report &&
for dir in $(System.DefaultWorkingDirectory)/playwright-reports/*/; do
if [ -d "$dir" ]; then
mv "$dir"* $(System.DefaultWorkingDirectory)/playwright-reports/
rmdir "$dir"
fi
done
npx playwright merge-reports --reporter=ortoni-report,junit $(System.DefaultWorkingDirectory)/playwright-reports
ls
displayName: merge_reports
env:
PLAYWRIGHT_JUNIT_OUTPUT_FILE: "test-results/results.xml"
- script: |
$(System.DefaultWorkingDirectory)/jira_writeback.sh
add_comment() $(Build.SourceBranchName) "AUTOMATED TEST RUN: $(date)" $(System.DefaultWorkingDirectory)/ortoni-report/index.html
displayName: publish_results_to_jira
env:
JIRA_API_KEY: $(JIRA_API_KEY)
# TODO: Enable later when working on PR gate
# - task: PublishTestResults@2
# displayName: 'Publish test results'
# inputs:
# searchFolder: 'test-results'
# testResultsFormat: 'JUnit'
# testResultsFiles: 'results.xml'
# mergeTestResults: true
# failTaskOnFailedTests: false
# testRunTitle: 'Playwright Tests'
# condition: succeededOrFailed()
- task: PublishPipelineArtifact@1
displayName: 'Publish Merged Report'
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report'
artifact: 'playwright-merged-report'
publishLocation: 'pipeline'
- ${{ else }}:
# Dev Build/Deploy
- stage: Dev
condition: eq(variables['Build.SourceBranch'], variables['dev-branch'] )
variables:
- group: ISS-Dev
jobs:
- deployment: devBuildDeployment
displayName: Build and Deploy ISS - Dev
environment: digitalCloud-dev
container: node
workspace:
clean: all
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: true
- task: CopyFiles@2
inputs:
SourceFolder: 'public/apple-pay/dev'
Contents: '**'
TargetFolder: 'public/.well-known'
CleanTargetFolder: true
- task: DeleteFiles@1
inputs:
SourceFolder: 'public/apple-pay'
Contents: '**'
RemoveSourceFolder: true
- template: templates/digital/step-build-vue.yml@AzureDevOps
parameters:
buildOutputDir: dist
environment: Dev
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
parameters:
artifactName: vueDistDev
awsProfile: $(devDeploymentProfile)
deployBuckets:
safelite-dev-iss-us-east-2:
clearFolder: true
deployFolder: ''
region: us-east-2
safelite-dev-iss-us-east-1:
clearFolder: true
deployFolder: ''
region: us-east-1
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters:
awsCliContainer: awscli
distributionId: $(cfDistributionId)
paths: /*
awsProfile: $(devDeploymentProfile)
# Test Build/Deploy
- stage: Test
condition: eq(variables['Build.SourceBranch'], variables['test-branch'] )
variables:
- group: ISS-Test
jobs:
- deployment: testBuildDeployment
displayName: Build and Deploy ISS - Test
environment: NoApproval-All
container: node
workspace:
clean: all
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: true
- task: CopyFiles@2
inputs:
SourceFolder: 'public/apple-pay/test'
Contents: '**'
TargetFolder: 'public/.well-known'
CleanTargetFolder: true
- task: DeleteFiles@1
inputs:
SourceFolder: 'public/apple-pay'
Contents: '**'
RemoveSourceFolder: true
- template: templates/digital/step-build-vue.yml@AzureDevOps
parameters:
buildOutputDir: dist
environment: Test
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
parameters:
artifactName: vueDistTest
awsProfile: $(sysDeploymentProfile)
deployBuckets:
safelite-sys-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-sys-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters:
awsCliContainer: awscli
distributionId: $(cfDistributionId)
paths: /*
awsProfile: $(sysDeploymentProfile)
# QA Build/Deploy
- stage: QA
condition: eq(variables['Build.SourceBranch'], variables['qa-branch'] )
variables:
- group: ISS-QA
jobs:
- deployment: qaBuildDeployment
displayName: Build and Deploy ISS - QA
environment: NoApproval-All
container: node
workspace:
clean: all
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: true
- task: CopyFiles@2
inputs:
SourceFolder: 'public/apple-pay/qa'
Contents: '**'
TargetFolder: 'public/.well-known'
CleanTargetFolder: true
- task: DeleteFiles@1
inputs:
SourceFolder: 'public/apple-pay'
Contents: '**'
RemoveSourceFolder: true
- template: templates/digital/step-build-vue.yml@AzureDevOps
parameters:
buildOutputDir: dist
environment: Qa
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
parameters:
artifactName: vueDistQa
awsProfile: $(qaDeploymentProfile)
deployBuckets:
safelite-qa-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-qa-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters:
awsCliContainer: awscli
distributionId: $(cfDistributionId)
paths: /*
awsProfile: $(qaDeploymentProfile)
# Prod Build/Deploy
- stage: Prod
condition: succeeded('QA')
variables:
- group: ISS-Prod
jobs:
- deployment: prodBuildDeployment
displayName: Build and Deploy ISS - Prod
environment: digitalCloud-prod
container: node
workspace:
clean: all
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: true
- task: CopyFiles@2
inputs:
SourceFolder: 'public/apple-pay/prod'
Contents: '**'
TargetFolder: 'public/.well-known'
CleanTargetFolder: true
- task: DeleteFiles@1
inputs:
SourceFolder: 'public/apple-pay'
Contents: '**'
RemoveSourceFolder: true
- template: templates/digital/step-build-vue.yml@AzureDevOps
parameters:
buildOutputDir: dist
environment: Prod
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
parameters:
artifactName: vueDistProd
awsProfile: $(prodDeploymentProfile)
deployBuckets:
safelite-prod-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-prod-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
parameters:
awsCliContainer: awscli
distributionId: $(cfDistributionId)
paths: /*
awsProfile: $(prodDeploymentProfile)
- template: templates/digital/auto-tag.yml@AzureDevOps
parameters:
dependsOn: prodBuildDeployment
userName: SafeliteAzureDevops
userEmail: githubazuredevops@safelite.com

144
devops/scripts/jira_writeback.sh Executable file
View file

@ -0,0 +1,144 @@
create_issue() {
local title="$1"
local project_key="$2"
local issue_type="$3"
local parent_issue_key="$4"
AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0)
local parent_issue=$(curl -s -H "Authorization: Basic $AUTH" \
"$JIRA_SERVER/rest/api/3/issue/$parent_issue_key")
local parent_fix_versions=$(echo $parent_issue | jq -r '.fields.fixVersions')
local created_issue=$(curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Basic $AUTH" \
-d '{
"fields": {
"summary": "'"$title"'",
"project": {
"key": "'"$project_key"'"
},
"issuetype": {
"name": "'"$issue_type"'"
},
"parent": {
"key": "'"$parent_issue_key"'"
},
"fixVersions": '"$parent_fix_versions"'
}
}' \
"$JIRA_SERVER/rest/api/3/issue")
local created_issue_id=$(echo $created_issue | jq -r '.id')
curl -X PUT -H "Content-Type: application/json" \
-H "Authorization: Basic $AUTH" \
-d '{
"fields": {
"fixVersions": '"$parent_fix_versions"'
}
}' \
"$JIRA_SERVER/rest/api/3/issue/$created_issue_id"
}
extract_uuid() {
local url="$1"
local uuid_regex='[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
if [[ "$url" =~ $uuid_regex ]]; then
echo "${BASH_REMATCH}"
else
echo "No UUID found in the URL."
fi
}
update_issue_status() {
local issue_key="$1"
local status_name="$2"
AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0)
local transitions=$(curl -s -H "Authorization: Basic $AUTH" \
"$JIRA_SERVER/rest/api/3/issue/$issue_key/transitions")
local transition_id=$(echo "$transitions" | jq -r --arg status_name "$status_name" '
.transitions[] | select(.isAvailable == true and .to.name == $status_name) | .id
')
if [ -z "$transition_id" ]; then
echo "BadRequestError"
exit 1
else
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Basic $AUTH" \
-d '{
"transition": {
"id": "'"$transition_id"'"
}
}' \
"$JIRA_SERVER/rest/api/3/issue/$issue_key/transitions"
fi
}
add_attachments() {
AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0)
local issue_key="$1"
shift
local attachments=("$@")
echo $issue_key
echo $attachments
local form_data=""
for attachment in "${attachments[@]}"; do
form_data+="--form file=@$attachment "
done
echo $form_data
echo $(curl -X POST $form_data \
-H "X-Atlassian-Token: no-check" \
-H "Authorization: Basic $AUTH" \
"$JIRA_SERVER/rest/api/3/issue/$issue_key/attachments")
}
add_comment() {
AUTH=$(echo -ne "$JIRA_USERNAME:$JIRA_API_KEY" | base64 --wrap 0)
local issue_key="$1"
shift
local comment_items_input=("$@")
local comment_json="[]"
for item in "${comment_items_input[@]}"; do
if [ -e "$item" ]; then
# If it's a file path
local attachment=$(add_attachments "$issue_key" "$item")
local id=$(echo $attachment | grep -oP '"id":\s*"\K[^"]+')
local attachment_content=$(curl -s -I -L -H "Authorization: Basic $AUTH" "$JIRA_SERVER/rest/api/3/attachment/content/$id" \
| grep -i "Location:" | tail -1 | awk '{print $2}' | tr -d '\r')
echo "$JIRA_SERVER/rest/api/3/attachment/content/$id"
echo "$attachment_content"
local uuid=$(extract_uuid "$attachment_content")
echo "$uuid"
json_object=$(jq -n --arg uuid "$uuid" '{ type: "mediaSingle", attrs: { layout: "align-start" }, content: [{ type: "media", attrs: { type: "file", id: $uuid, width: 200, height: 200, collection: "", alt: "" } }]}')
comment_json=$(echo "$comment_json" | jq --argjson obj "$json_object" '. += [$obj]')
else
# If it's a string
json_object=$(jq -n --arg text "$item" '{ type: "paragraph", content: [{ type: "text", text: $text }]}')
comment_json=$(echo "$comment_json" | jq --argjson obj "$json_object" '. += [$obj]')
fi
done
request=$(jq -n --argjson content "$comment_json" '{body: { type: "doc", version: 1, content: $content }}')
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Basic $AUTH" \
-d "$request" \
"$JIRA_SERVER/rest/api/3/issue/$issue_key/comment"
}