diff --git a/src/commands/deployment/get.test.ts b/src/commands/deployment/get.test.ts index 8cc3a58af..d3351b5e8 100644 --- a/src/commands/deployment/get.test.ts +++ b/src/commands/deployment/get.test.ts @@ -28,6 +28,7 @@ import { watchGetDeployments, } from "./get"; import * as get from "./get"; +import { IPullRequest } from "spektate/lib/repository/IPullRequest"; const MOCKED_INPUT_VALUES: CommandOptions = { buildId: "", @@ -49,7 +50,7 @@ const MOCKED_VALUES: ValidatedOptions = { imageTag: "", nTop: 0, output: "", - outputFormat: OUTPUT_FORMAT.NORMAL, + outputFormat: OUTPUT_FORMAT.WIDE, service: "", top: "", watch: false, @@ -68,18 +69,25 @@ const data = require("./mocks/data.json"); const fakeDeployments = data; // eslint-disable-next-line @typescript-eslint/no-var-requires const fakeClusterSyncs = require("./mocks/cluster-sync.json"); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fakePR = require("./mocks/pr.json"); const mockedDeps: IDeployment[] = fakeDeployments.data.map( (dep: IDeployment) => { return { commitId: dep.commitId, deploymentId: dep.deploymentId, dockerToHldRelease: dep.dockerToHldRelease, + dockerToHldReleaseStage: dep.dockerToHldReleaseStage, environment: dep.environment, hldCommitId: dep.hldCommitId || "", + hldRepo: dep.hldRepo, hldToManifestBuild: dep.hldToManifestBuild, imageTag: dep.imageTag, manifestCommitId: dep.manifestCommitId, + manifestRepo: dep.manifestRepo, + pr: dep.pr, service: dep.service, + sourceRepo: dep.sourceRepo, srcToDockerBuild: dep.srcToDockerBuild, timeStamp: dep.timeStamp, }; @@ -95,6 +103,7 @@ jest jest .spyOn(AzureDevOpsRepo, "getManifestSyncState") .mockReturnValue(Promise.resolve(mockedClusterSyncs)); +jest.spyOn(Deployment, "fetchPR").mockReturnValue(Promise.resolve(fakePR)); let initObject: InitObject; @@ -338,7 +347,7 @@ describe("Print deployments", () => { table = printDeployments( mockedDeps, - processOutputFormat("normal"), + processOutputFormat("wide"), 3, mockedClusterSyncs ); @@ -380,7 +389,7 @@ describe("Output formats", () => { ); expect(table).not.toBeUndefined(); table!.forEach((field) => { - expect(field).toHaveLength(18); + expect(field).toHaveLength(20); }); }); }); diff --git a/src/commands/deployment/get.ts b/src/commands/deployment/get.ts index 7a94d68ce..5407730c6 100644 --- a/src/commands/deployment/get.ts +++ b/src/commands/deployment/get.ts @@ -7,6 +7,8 @@ import { getDeploymentsBasedOnFilters, IDeployment, status as getDeploymentStatus, + fetchPR, + getRepositoryFromURL, } from "spektate/lib/IDeployment"; import AzureDevOpsPipeline from "spektate/lib/pipeline/AzureDevOpsPipeline"; import { @@ -24,7 +26,10 @@ import { isIntegerString } from "../../lib/validator"; import { logger } from "../../logger"; import { ConfigYaml } from "../../types"; import decorator from "./get.decorator.json"; +import { IPullRequest } from "spektate/lib/repository/IPullRequest"; +const promises: Promise[] = []; +const pullRequests: { [id: string]: IPullRequest } = {}; /** * Output formats to display service details */ @@ -184,13 +189,43 @@ export const getDeployments = ( ); return new Promise((resolve, reject) => { Promise.all([deploymentsPromise, syncStatusesPromise]) - .then((tuple: [IDeployment[] | undefined, ITag[] | undefined]) => { + .then(async (tuple: [IDeployment[] | undefined, ITag[] | undefined]) => { const deployments: IDeployment[] | undefined = tuple[0]; const syncStatuses: ITag[] | undefined = tuple[1]; - if (values.outputFormat === OUTPUT_FORMAT.JSON) { - console.log(JSON.stringify(deployments, null, 2)); - resolve(deployments); - } else { + const displayedDeployments = await displayDeployments( + values, + deployments, + syncStatuses + ); + resolve(displayedDeployments); + }) + .catch((e) => { + reject(new Error(e)); + }); + }); +}; + +/** + * Displays the deployments based on output format requested and top n + * @param values validated command line values + * @param deployments list of deployments to display + * @param syncStatuses cluster sync statuses + */ +export const displayDeployments = ( + values: ValidatedOptions, + deployments: IDeployment[] | undefined, + syncStatuses: ITag[] | undefined +): Promise => { + return new Promise((resolve, reject) => { + if (values.outputFormat === OUTPUT_FORMAT.WIDE) { + getPRs(deployments); + } + if (values.outputFormat === OUTPUT_FORMAT.JSON) { + console.log(JSON.stringify(deployments, null, 2)); + resolve(deployments); + } else { + Promise.all(promises) + .then(() => { printDeployments( deployments, values.outputFormat, @@ -198,11 +233,11 @@ export const getDeployments = ( syncStatuses ); resolve(deployments); - } - }) - .catch((e) => { - reject(new Error(e)); - }); + }) + .catch((e) => { + reject(e); + }); + } }); }; @@ -362,9 +397,16 @@ export const printDeployments = ( "Env", "Hld Commit", "Result", - "HLD to Manifest", - "Result", ]; + let prsExist = false; + if ( + Object.keys(pullRequests).length > 0 && + outputFormat === OUTPUT_FORMAT.WIDE + ) { + header = header.concat(["Approval PR", "Merged By"]); + prsExist = true; + } + header = header.concat(["HLD to Manifest", "Result"]); if (outputFormat === OUTPUT_FORMAT.WIDE) { header = header.concat([ "Duration", @@ -403,6 +445,7 @@ export const printDeployments = ( toDisplay.forEach((deployment) => { const row = []; + let deploymentStatus = getDeploymentStatus(deployment); row.push( deployment.srcToDockerBuild ? deployment.srcToDockerBuild.startTime.toLocaleString() @@ -445,6 +488,25 @@ export const printDeployments = ( ); row.push(deployment.hldCommitId || "-"); row.push(dockerToHldStatus); + + // Print PR if available + if ( + prsExist && + deployment.pr && + deployment.pr.toString() in pullRequests + ) { + row.push(deployment.pr); + if (pullRequests[deployment.pr!.toString()].mergedBy) { + row.push(pullRequests[deployment.pr!.toString()].mergedBy?.name); + } else { + deploymentStatus = "Waiting"; + row.push("-"); + } + } else if (prsExist) { + row.push("-"); + row.push("-"); + } + row.push( deployment.hldToManifestBuild ? deployment.hldToManifestBuild.id : "-" ); @@ -455,7 +517,7 @@ export const printDeployments = ( ); if (outputFormat === OUTPUT_FORMAT.WIDE) { row.push(duration(deployment) + " mins"); - row.push(getDeploymentStatus(deployment)); + row.push(deploymentStatus); row.push(deployment.manifestCommitId || "-"); row.push( deployment.hldToManifestBuild && @@ -488,6 +550,44 @@ export const printDeployments = ( } }; +/** + * Gets PR information for all the deployments + * @param deployments all deployments to be displayed + */ +export const getPRs = (deployments: IDeployment[] | undefined) => { + if (deployments && deployments.length > 0) { + deployments.forEach((deployment: IDeployment) => { + fetchPRInformation(deployment); + }); + } +}; + +/** + * Fetches pull request data for deployments that complete merge into HLD + * by merging a PR + * @param deployment deployment for which PR has to be fetched + */ +export const fetchPRInformation = (deployment: IDeployment) => { + const config = Config(); + if (!deployment.hldRepo || !deployment.pr) { + return; + } + const repo: IAzureDevOpsRepo | IGitHub | undefined = getRepositoryFromURL( + deployment.hldRepo! + ); + const promise = fetchPR( + repo!, + deployment.pr!.toString(), + config.introspection?.azure?.source_repo_access_token + ); + promise.then((pr: IPullRequest | undefined) => { + if (pr) { + pullRequests[deployment.pr!.toString()] = pr; + } + }); + promises.push(promise); +}; + /** * Returns a matching sync status for a deployment * @param deployment Deployment object diff --git a/src/commands/deployment/mocks/data.json b/src/commands/deployment/mocks/data.json index 654aa31ea..502f647cf 100644 --- a/src/commands/deployment/mocks/data.json +++ b/src/commands/deployment/mocks/data.json @@ -50,6 +50,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1123", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "c626394", "hldCommitId": "a3dc9a7", "imageTag": "hello-bedrock-master-6053", @@ -108,6 +112,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1133", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "a0bca78", "hldCommitId": "316abbc", "imageTag": "hello-spektate-master-6042", @@ -166,6 +174,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1173", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "c626394", "hldCommitId": "e4a2730", "imageTag": "hello-bedrock-master-5977", @@ -224,6 +236,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1125", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "5b54eb4", "hldCommitId": "18abc7a", "imageTag": "hello-bedrock-master-6074", @@ -282,6 +298,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1120", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "a0bca78", "hldCommitId": "863d50f", "imageTag": "hello-spektate-master-6049", @@ -340,6 +360,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1119", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "c626394", "hldCommitId": "706685f", "imageTag": "hello-bedrock-master-6046", @@ -398,6 +422,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1118", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "c626394", "hldCommitId": "bac890c", "imageTag": "hello-bedrock-master-6044", @@ -456,6 +484,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1115", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "a0bca78", "hldCommitId": "316abbc", "imageTag": "hello-spektate-master-6042", @@ -514,6 +546,10 @@ "registryURL": "hellorings.azurecr.io", "registryResourceGroup": "bedrock-rings" }, + "pr": "1111", + "hldRepo": "https://github.com/samiyaakhtar/hello-bedrock-hld", + "manifestRepo": "https://github.com/samiyaakhtar/hello-bedrock-manifest", + "sourceRepo": "https://github.com/samiyaakhtar/hello-bedrock", "commitId": "8028a15", "hldCommitId": "0612e7a", "imageTag": "hello-spektate-master-6009", @@ -560,10 +596,10 @@ } }, "deploymentId": "33733a9f4573", - "dockerToHldRelease": { + "dockerToHldReleaseStage": { "URL": "https://dev.azure.com/epicstuff/f8a98d9c-8f11-46ef-89e4-07b4a56d1ad5/_release?releaseId=173&_a=release-summary", "finishTime": "2019-08-28T20:14:30.093Z", - "id": 173, + "id": 5975, "queueTime": "2019-08-28T20:13:56.163Z", "releaseName": "Release-170", "startTime": "2019-08-28T20:14:08.787Z", diff --git a/src/commands/deployment/mocks/pr.json b/src/commands/deployment/mocks/pr.json new file mode 100644 index 000000000..0e2442a1f --- /dev/null +++ b/src/commands/deployment/mocks/pr.json @@ -0,0 +1,14 @@ +{ + "description": "Updating samiya.frontend to master-20200318.1.\nPR created by: samiya2019 with buildId: 14751 and buildNumber: 20200318.1", + "id": 1371, + "mergedBy": { + "imageUrl": "https://dev.azure.com/epicstuff/_api/_common/identityImage?id=e8900b94-217f-4a51-9d86-6bbf5d82b6fb", + "name": "Samiya Akhtar", + "url": "https://dev.azure.com/epicstuff/e7236bd9-a6f9-4554-8dce-ad81ae94faf6/_apis/git/repositories/a491cdad-443c-4419-9726-95e7619673ae/pullRequests/1371", + "username": "saakhta@microsoft.com" + }, + "sourceBranch": "DEPLOY/samiya2019-samiya.frontend-master-20200318.1", + "targetBranch": "master", + "title": "Updating samiya.frontend image tag to master-20200318.1.", + "url": "https://dev.azure.com/epicstuff/hellobedrockprivate/_git/samiya-hld/pullrequest/1371" +}