From 196f732074514327c8af988394c660d998a6e2ce Mon Sep 17 00:00:00 2001 From: John Ferlito Date: Sat, 27 Nov 2021 18:50:58 +1100 Subject: [PATCH] Keep track of when builds are ready. CLoses #13 --- README.md | 5 +++- src/fetch_app_status.rb | 23 +++++++++++++- src/poll-itc.js | 28 ++++++++++++++++- src/post-update.js | 66 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c4c2a96..7677c5c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ # Intro -App Store Connect Notifier is a node.js app fetches your app info directly from App Store Connect and posts changes in Slack as a bot. Since App Store Connect doesn't provide event webhooks (yet), these scripts use polling with the help of _fastlane_'s [Spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship). +App Store Connect Notifier is a node.js app fetches your app and build info directly from App Store Connect and posts changes in Slack as a bot. Since App Store Connect doesn't provide event webhooks (yet), these scripts use polling with the help of _fastlane_'s [Spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship). # Preview @@ -92,6 +92,9 @@ export BOT_STATUS_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXX/X # How often the script should check for updates (in seconds). Required. export POLL_TIME_IN_SECONDS=120 + +# The number of builds to keep track often (default 5) +export NUMBER_OF_BUILDS=5 ``` ### Method 3: Docker diff --git a/src/fetch_app_status.rb b/src/fetch_app_status.rb index 7d07b48..e37d542 100644 --- a/src/fetch_app_status.rb +++ b/src/fetch_app_status.rb @@ -45,6 +45,10 @@ def itc_team_id_array ENV["ITC_TEAM_IDS"].to_s.split(",") end +def number_of_builds + (ENV["NUMBER_OF_BUILDS"] || 5).to_i +end + unless uses_app_store_connect_auth_token || uses_app_store_connect_auth_credentials puts "Couldn't find valid authentication token or credentials." exit @@ -68,6 +72,19 @@ def get_version_info(app) } end +def get_build_info(app) + builds = app.get_builds.sort_by(&:uploaded_date).reverse[0, number_of_builds] + + builds.map do |build| + { + "version" => build.version, + "uploaded_data" => build.uploaded_date, + "status" => build.processing_state, + } + end +end + + def get_app_version_from(bundle_ids) apps = [] if bundle_ids @@ -77,7 +94,11 @@ def get_app_version_from(bundle_ids) else apps = Spaceship::ConnectAPI::App.all end - apps.map { |app| get_version_info(app) } + apps.map do |app| + info = get_version_info(app) + info["builds"] = get_build_info(app) + info + end end if uses_app_store_connect_auth_token diff --git a/src/poll-itc.js b/src/poll-itc.js index 6612dc4..f786c6f 100644 --- a/src/poll-itc.js +++ b/src/poll-itc.js @@ -36,13 +36,16 @@ function checkAppStatus() { function _checkAppStatus(currentAppInfo) { // Use the live version if edit version is unavailable const appInfoKey = "appInfo-" + currentAppInfo.appId + const buildInfoKey = "builds-appInfo-" + currentAppInfo.appId const submissionStartkey = "submissionStart" + currentAppInfo.appId const lastAppInfo = db.get(appInfoKey) + const lastBuildInfo = db.get(buildInfoKey) || {} + if (!lastAppInfo || lastAppInfo.status != currentAppInfo.status || debug) { if (!lastAppInfo) { poster.slackMessage("App Store Connect Notifier Bot has just restarted.") } else { - poster.slack(currentAppInfo, db.get(submissionStartkey)) + poster.slackApp(currentAppInfo, db.get(submissionStartkey)) } // Store submission start time if (currentAppInfo.status == "WAITING_FOR_REVIEW") { @@ -56,6 +59,29 @@ function _checkAppStatus(currentAppInfo) { // Store latest app info in database db.set(appInfoKey, currentAppInfo) + + const buildChange = lastAppInfo && JSON.stringify(lastAppInfo.builds) != JSON.stringify(currentAppInfo.builds) + if (lastAppInfo && buildChange) { + const builds = currentAppInfo.builds + const newBuildInfo = {} + + builds.forEach((buildInfo) => { + const oldBuildInfo = lastBuildInfo[buildInfo.version] + if (!oldBuildInfo) { + poster.slackBuild(currentAppInfo, buildInfo) + newBuildInfo[buildInfo.version] = buildInfo + } else if (oldBuildInfo.status != buildInfo.status) { + poster.slackBuild(currentAppInfo, buildInfo) + newBuildInfo[buildInfo.version] = buildInfo + } else { + console.log("No build change detected.") + } + }) + + db.set(buildInfoKey, newBuildInfo) + } + + // Store latest build info in database } if (!pollIntervalSeconds) { diff --git a/src/post-update.js b/src/post-update.js index 8699409..cb5e30f 100755 --- a/src/post-update.js +++ b/src/post-update.js @@ -1,9 +1,7 @@ const moment = require("moment") require("./string-utilities.js") -function postToSlack(appInfo, submissionStartDate) { - const message = `The status of your app *${appInfo.name}* has been changed to *${appInfo.status.formatted()}*` - const attachment = slackAttachment(appInfo, submissionStartDate) +function postToSlack(message, attachment) { const webhook_url = process.env.BOT_SLACK_WEBHOOK_URL if (webhook_url) { postUsingWebhook(message, webhook_url, [attachment]) @@ -13,6 +11,18 @@ function postToSlack(appInfo, submissionStartDate) { } } +function postToSlackApp(appInfo, submissionStartDate) { + const message = `The status of your app *${appInfo.name}* has been changed to *${appInfo.status.formatted()}*` + const attachment = slackAttachment(appInfo, submissionStartDate) + postToSlack(message, attachment) +} + +function postToSlackBuild(appInfo, buildInfo) { + const message = `The status of build version *${buildInfo.version}* for your app *${appInfo.name}* has been changed to *${buildInfo.status}*` + const attachment = slackAttachmentBuild(message, appInfo, buildInfo) + postToSlack(message, attachment) +} + function postMessageToSlack(message) { const webhook_url = process.env.BOT_STATUS_SLACK_WEBHOOK_URL if (webhook_url) { @@ -146,6 +156,44 @@ function slackAttachment(appInfo, submissionStartDate) { return attachment } +function slackAttachmentBuild(fallback, appInfo, buildInfo) { + const attachment = { + fallback, + "color": colorForStatus(buildInfo.status), + "title": "App Store Connect", + "author_name": appInfo.name, + "author_icon": appInfo.iconUrl, + "title_link": `https://appstoreconnect.apple.com/apps/${appInfo.appId}/appstore`, + "fields": [ + { + "title": "Build Version", + "value": buildInfo.version, + "short": true + }, + { + "title": "Build Status", + "value": buildInfo.status, + "short": true + }, + { + "title": "Version", + "value": appInfo.version, + "short": true + }, + { + "title": "App Status", + "value": appInfo.status.formatted(), + "short": true + } + ], + "footer": "App Store Connect", + "footer_icon": "https://devimages.apple.com.edgekey.net/app-store/marketing/guidelines/images/app-store-icon.png", + "ts": new Date().getTime() / 1000 + } + + return attachment +} + function colorForStatus(status) { const infoColor = "#8e8e8e" const warningColor = "#f4f124" @@ -153,6 +201,7 @@ function colorForStatus(status) { const successColor2 = "#14ba40" const failureColor = "#e0143d" const colorMapping = { + // App "PREPARE_FOR_SUBMISSION" : infoColor, "WAITING_FOR_REVIEW" : successColor1, "IN_REVIEW" : successColor1, @@ -167,12 +216,19 @@ function colorForStatus(status) { "REMOVED_FROM_SALE" : failureColor, "DEVELOPER_REJECTED" : failureColor, "DEVELOPER_REMOVED_FROM_SALE" : failureColor, - "INVALID_BINARY" : failureColor + "INVALID_BINARY" : failureColor, + + // Build + "PROCESSING" : infoColor, + "FAILED" : failureColor, + "INVALID" : failureColor, + "VALID" : successColor2, } return colorMapping[status] } module.exports = { - slack: postToSlack, + slackApp: postToSlackApp, + slackBuild: postToSlackBuild, slackMessage: postMessageToSlack }