diff --git a/BuildaKit/SyncPairResolver.swift b/BuildaKit/SyncPairResolver.swift index 496be36..d8778b8 100644 --- a/BuildaKit/SyncPairResolver.swift +++ b/BuildaKit/SyncPairResolver.swift @@ -21,6 +21,7 @@ public class SyncPairResolver { commit: String, issue: Issue?, bot: Bot, + hostname: String, integrations: [Integration]) -> SyncPair.Actions { var integrationsToCancel: [Integration] = [] @@ -114,12 +115,17 @@ public class SyncPairResolver { $0.currentStep == .Completed } + let link = { (integration: Integration) -> String in + SyncPairResolver.linkToServer(hostname, bot: bot, integration: integration) + } + //resolve to a status let actions = self.resolveCommitStatusFromLatestIntegrations( commit, issue: issue, pending: latestPendingIntegration, running: runningIntegration, + link: link, completed: completedIntegrations) //merge in nested actions @@ -186,22 +192,35 @@ public class SyncPairResolver { return sortedHeadCommitIntegrations } + class func linkToServer(hostname: String, bot: Bot, integration: Integration) -> String { + + //unfortunately, since github doesn't allow non-https links anywhere, we + //must proxy through Satellite (https://github.com/czechboy0/satellite) + //all it does is it redirects to the desired xcbot://... url, which in + //turn opens Xcode on the integration page. all good! + +// let link = "xcbot://\(hostname)/botID/\(bot.id)/integrationID/\(integration.id)" + let link = "https://stlt.herokuapp.com/v1/xcs_deeplink/\(hostname)/\(bot.id)/\(integration.id)" + return link + } + func resolveCommitStatusFromLatestIntegrations( commit: String, issue: Issue?, pending: Integration?, running: Integration?, + link: (Integration) -> String, completed: Set) -> SyncPair.Actions { let statusWithComment: HDGitHubXCBotSyncer.GitHubStatusAndComment var integrationsToCancel: [Integration] = [] //if there's any pending integration, we're ["Pending" - Waiting in the queue] - if let _ = pending { + if let pending = pending { //TODO: show how many builds are ahead in the queue and estimate when it will be //started and when finished? (there is an average running time on each bot, it should be easy) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Build waiting in the queue...") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Build waiting in the queue...", targetUrl: link(pending)) statusWithComment = (status: status, comment: nil) //also, cancel the running integration, if it's there any @@ -216,7 +235,7 @@ public class SyncPairResolver { //there is a running integration. //TODO: estimate, based on the average running time of this bot and on the started timestamp, when it will finish. add that to the description. let currentStepString = running.currentStep.rawValue - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Integration step: \(currentStepString)...") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Integration step: \(currentStepString)...", targetUrl: link(running)) statusWithComment = (status: status, comment: nil) } else { @@ -225,12 +244,12 @@ public class SyncPairResolver { if completed.count > 0 { //we have some completed integrations - statusWithComment = self.resolveStatusFromCompletedIntegrations(completed) + statusWithComment = self.resolveStatusFromCompletedIntegrations(completed, link: link) } else { //this shouldn't happen. Log.error("LOGIC ERROR! This shouldn't happen, there are no completed integrations!") - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "* UNKNOWN STATE, Builda ERROR *") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "* UNKNOWN STATE, Builda ERROR *", targetUrl: nil) statusWithComment = (status: status, "Builda error, unknown state!") } } @@ -255,7 +274,9 @@ public class SyncPairResolver { } func resolveStatusFromCompletedIntegrations( - integrations: Set) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { + integrations: Set, + link: (Integration) -> String + ) -> HDGitHubXCBotSyncer.GitHubStatusAndComment { //get integrations sorted by number let sortedDesc = Array(integrations).sort { $0.number > $1.number } @@ -272,9 +293,10 @@ public class SyncPairResolver { } }).first { - var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(passingIntegration) + let linkToIntegration = link(passingIntegration) + var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(passingIntegration, link: linkToIntegration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Success, description: "Build passed!") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Success, description: "Build passed!", targetUrl: linkToIntegration) let summary = passingIntegration.buildResultSummary! if passingIntegration.result == .Succeeded { @@ -302,8 +324,9 @@ public class SyncPairResolver { $0.result! == Integration.Result.TestFailures }).first { - var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(testFailingIntegration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Failure, description: "Build failed tests!") + let linkToIntegration = link(testFailingIntegration) + var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(testFailingIntegration, link: linkToIntegration) + let status = HDGitHubXCBotSyncer.createStatusFromState(.Failure, description: "Build failed tests!", targetUrl: linkToIntegration) let summary = testFailingIntegration.buildResultSummary! let testFailureCount = summary.testFailureCount lines.append(resultString + "**Build failed \(testFailureCount) " + "test".pluralizeStringIfNecessary(testFailureCount) + "** out of \(summary.testsCount)") @@ -315,9 +338,10 @@ public class SyncPairResolver { $0.result! != Integration.Result.Canceled }).first { - var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(erroredIntegration) + let linkToIntegration = link(erroredIntegration) + var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(erroredIntegration, link: linkToIntegration) let errorCount: Int = erroredIntegration.buildResultSummary?.errorCount ?? -1 - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build error!") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build error!", targetUrl: linkToIntegration) lines.append(resultString + "**\(errorCount) " + "error".pluralizeStringIfNecessary(errorCount) + ", failing state: \(erroredIntegration.result!.rawValue)**") return self.statusAndCommentFromLines(lines, status: status) } @@ -327,8 +351,9 @@ public class SyncPairResolver { $0.result! == Integration.Result.Canceled }).first { - var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(canceledIntegration) - let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build canceled!") + let linkToIntegration = link(canceledIntegration) + var lines = HDGitHubXCBotSyncer.baseCommentLinesFromIntegration(canceledIntegration, link: linkToIntegration) + let status = HDGitHubXCBotSyncer.createStatusFromState(.Error, description: "Build canceled!", targetUrl: linkToIntegration) //TODO: find out who canceled it and add it to the comment? lines.append("Build was **manually canceled**.") @@ -336,7 +361,7 @@ public class SyncPairResolver { } //hmm no idea, if we got all the way here. just leave it with no state. - let status = HDGitHubXCBotSyncer.createStatusFromState(.NoState, description: nil) + let status = HDGitHubXCBotSyncer.createStatusFromState(.NoState, description: nil, targetUrl: nil) return (status: status, comment: nil) } } diff --git a/BuildaKit/SyncPair_Branch_Bot.swift b/BuildaKit/SyncPair_Branch_Bot.swift index 19743d0..299f01b 100644 --- a/BuildaKit/SyncPair_Branch_Bot.swift +++ b/BuildaKit/SyncPair_Branch_Bot.swift @@ -42,24 +42,33 @@ public class SyncPair_Branch_Bot: SyncPair { let headCommit = self.branch.commit.sha let issue: Issue? = nil //TODO: only pull/create if we're failing - self.getIntegrations(bot, completion: { (integrations, error) -> () in + self.syncer.xcodeServer.getHostname { (hostname, error) -> () in if let error = error { completion(error: error) return } - let actions = self.resolver.resolveActionsForCommitAndIssueWithBotIntegrations( - headCommit, - issue: issue, - bot: bot, - integrations: integrations) - - //in case of branches, we also (optionally) want to add functionality for creating an issue if the branch starts failing and updating with comments the same way we do with PRs. - //also, when the build is finally successful on the branch, the issue will be automatically closed. - //TODO: add this functionality here and add it as another action available from a sync pair - - self.performActions(actions, completion: completion) - }) + self.getIntegrations(bot, completion: { (integrations, error) -> () in + + if let error = error { + completion(error: error) + return + } + + let actions = self.resolver.resolveActionsForCommitAndIssueWithBotIntegrations( + headCommit, + issue: issue, + bot: bot, + hostname: hostname!, + integrations: integrations) + + //in case of branches, we also (optionally) want to add functionality for creating an issue if the branch starts failing and updating with comments the same way we do with PRs. + //also, when the build is finally successful on the branch, the issue will be automatically closed. + //TODO: add this functionality here and add it as another action available from a sync pair + + self.performActions(actions, completion: completion) + }) + } } } diff --git a/BuildaKit/SyncPair_PR_Bot.swift b/BuildaKit/SyncPair_PR_Bot.swift index 9ed8875..7e17968 100644 --- a/BuildaKit/SyncPair_PR_Bot.swift +++ b/BuildaKit/SyncPair_PR_Bot.swift @@ -43,7 +43,7 @@ public class SyncPair_PR_Bot: SyncPair { let pr = self.pr let headCommit = pr.head.sha let issue = pr - + self.getIntegrations(bot, completion: { (integrations, error) -> () in if let error = error { @@ -61,19 +61,28 @@ public class SyncPair_PR_Bot: SyncPair { if isEnabled { - let actions = self.resolver.resolveActionsForCommitAndIssueWithBotIntegrations( - headCommit, - issue: issue, - bot: bot, - integrations: integrations) - self.performActions(actions, completion: completion) + self.syncer.xcodeServer.getHostname { (hostname, error) -> () in + + if let error = error { + completion(error: error) + return + } + + let actions = self.resolver.resolveActionsForCommitAndIssueWithBotIntegrations( + headCommit, + issue: issue, + bot: bot, + hostname: hostname!, + integrations: integrations) + self.performActions(actions, completion: completion) + } } else { //not enabled, make sure the PR reflects that and the instructions are clear Log.verbose("Bot \(bot.name) is not yet enabled, ignoring...") - let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Waiting for \"lttm\" to start testing") + let status = HDGitHubXCBotSyncer.createStatusFromState(.Pending, description: "Waiting for \"lttm\" to start testing", targetUrl: nil) let notYetEnabled: HDGitHubXCBotSyncer.GitHubStatusAndComment = (status: status, comment: nil) syncer.updateCommitStatusIfNecessary(notYetEnabled, commit: headCommit, issue: pr, completion: completion) } diff --git a/BuildaKit/SyncerBotUtils.swift b/BuildaKit/SyncerBotUtils.swift index 0e4979b..55a6481 100644 --- a/BuildaKit/SyncerBotUtils.swift +++ b/BuildaKit/SyncerBotUtils.swift @@ -26,11 +26,17 @@ extension HDGitHubXCBotSyncer { } } - class func baseCommentLinesFromIntegration(integration: Integration) -> [String] { + class func baseCommentLinesFromIntegration(integration: Integration, link: String?) -> [String] { var lines = [String]() - lines.append("Result of Integration **\(integration.number)**") + var integrationText = "Integration \(integration.number)" + if let link = link { + //linkify + integrationText = "[\(integrationText)](\(link))" + } + + lines.append("Result of \(integrationText)") lines.append("---") if let duration = self.formattedDurationOfIntegration(integration) { diff --git a/BuildaKit/SyncerGitHubUtils.swift b/BuildaKit/SyncerGitHubUtils.swift index 0160661..b36035a 100644 --- a/BuildaKit/SyncerGitHubUtils.swift +++ b/BuildaKit/SyncerGitHubUtils.swift @@ -12,11 +12,11 @@ import BuildaUtils extension HDGitHubXCBotSyncer { - class func createStatusFromState(state: Status.State, description: String?) -> Status { + class func createStatusFromState(state: Status.State, description: String?, targetUrl: String?) -> Status { - //TODO: add useful targetUrl and potentially have multiple contexts to show multiple stats on the PR + //TODO: potentially have multiple contexts to show multiple stats on the PR let context = "Buildasaur" - return Status(state: state, description: description, targetUrl: nil, context: context) + return Status(state: state, description: description, targetUrl: targetUrl, context: context) } func updateCommitStatusIfNecessary( diff --git a/BuildaKitTests/SyncPair_PR_Bot_Tests.swift b/BuildaKitTests/SyncPair_PR_Bot_Tests.swift index ee46e40..2ece2db 100644 --- a/BuildaKitTests/SyncPair_PR_Bot_Tests.swift +++ b/BuildaKitTests/SyncPair_PR_Bot_Tests.swift @@ -41,7 +41,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { let (pr, bot, commit) = self.mockedPRAndBotAndCommit() let integrations = [Integration]() - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel?.count ?? 0, 0) XCTBAssertNil(actions.githubStatusToSet) XCTAssertNotNil(actions.startNewIntegrationBot) @@ -54,7 +54,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 1, step: Integration.Step.Pending) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel?.count ?? 0, 0) XCTAssertNil(actions.startNewIntegrationBot) XCTBAssertNotNil(actions.githubStatusToSet) @@ -70,7 +70,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 3, step: Integration.Step.Pending) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) //should cancel all except for the last one let toCancel = Set(actions.integrationsToCancel!) @@ -89,7 +89,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 1, step: Integration.Step.Building), ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) @@ -104,7 +104,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 1, step: Integration.Step.Completed, sha: "head_sha", result: Integration.Result.TestFailures) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) @@ -119,7 +119,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 1, step: Integration.Step.Completed, sha: "head_sha", result: Integration.Result.Succeeded) ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 0) XCTAssertNil(actions.startNewIntegrationBot) @@ -135,7 +135,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 2, step: Integration.Step.Pending, sha: "head_sha") ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 1) XCTAssertNil(actions.startNewIntegrationBot) @@ -150,7 +150,7 @@ class SyncPair_PR_Bot_Tests: XCTestCase { MockIntegration(number: 1, step: Integration.Step.Building, sha: "head_sha_old"), ] - let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, integrations: integrations) + let actions = SyncPairPRResolver().resolveActionsForCommitAndIssueWithBotIntegrations(commit, issue: pr, bot: bot, hostname: "localhost", integrations: integrations) XCTAssertEqual(actions.integrationsToCancel!.count, 1) XCTAssertNotNil(actions.startNewIntegrationBot)