From e1be7a80b9d8f7f1d414b68f0ebd648127b7a462 Mon Sep 17 00:00:00 2001
From: Philippe Auriach
Date: Thu, 2 Feb 2023 08:28:27 +0100
Subject: [PATCH] Add support for accept_ms_testers_without_closed_task (#41)
---
action.js | 36 ++++++++++++++++++++++++++++++++-
action.test.js | 47 ++++++++++++++++++++++++++++++++++++++++++++
lib/mobsuccessyml.js | 19 ++++++++++++++++++
3 files changed, 101 insertions(+), 1 deletion(-)
create mode 100644 lib/mobsuccessyml.js
diff --git a/action.js b/action.js
index b34c63f00..b76d4e611 100644
--- a/action.js
+++ b/action.js
@@ -7,6 +7,7 @@ const {
moveTaskToProjectSection,
getProjectSections,
} = require("./lib/actions/asana");
+const { getMobsuccessYMLFromRepo } = require("./lib/mobsuccessyml");
const customFieldLive = require("./lib/asana/custom-fields/live");
const customFieldStorybook = require("./lib/asana/custom-fields/storybook");
@@ -118,12 +119,14 @@ exports.findAsanaTaskId = function findAsanaTaskId({
};
exports.getActionParameters = function getActionParameters() {
+ const repository = github.context.payload.repository;
const pullRequest = github.context.payload.pull_request;
const action = core.getInput("action", { required: true });
const triggerPhrase = core.getInput("trigger-phrase") || "";
const amplifyUri = core.getInput("amplify-uri") || "";
const storybookAmplifyUri = core.getInput("storybook-amplify-uri") || "";
return {
+ repository,
pullRequest,
action,
triggerPhrase,
@@ -311,8 +314,31 @@ async function moveTaskToSprintAndEpicSection({ taskId, sectionId }) {
}
}
+async function checkIfCanMergeWithoutAsanaTask({ repository, pullRequest }) {
+ const { assignees } = pullRequest;
+ const assigneeLogins = assignees.map(({ login }) => login);
+ if (!assigneeLogins.some((login) => login === "ms-testers")) {
+ return false;
+ }
+
+ // if mobsuccess.yml has the `accept_ms_testers_without_closed_task` flag set to true, we can merge
+ const mobsuccessyml = await getMobsuccessYMLFromRepo({
+ owner: repository.owner.login,
+ repo: repository.name,
+ });
+ const asanaSettings = mobsuccessyml.asana || {};
+ if (asanaSettings.accept_ms_testers_without_closed_task) {
+ console.log(
+ "accept_ms_testers_without_closed_task is set to true, ok to merge"
+ );
+ return true;
+ }
+ return false;
+}
+
exports.action = async function action() {
const {
+ repository,
pullRequest,
action,
triggerPhrase,
@@ -391,7 +417,15 @@ exports.action = async function action() {
});
console.log("Task is completed?", completed);
if (!completed) {
- throw new Error("Asana task is not yet completed, blocking merge");
+ // check if can merge without a completed asana task
+ const canMergeWithoutAsanaTask = await checkIfCanMergeWithoutAsanaTask(
+ { repository, pullRequest }
+ );
+ if (!canMergeWithoutAsanaTask) {
+ throw new Error(
+ "Asana task is not yet completed, blocking merge"
+ );
+ }
}
}
}
diff --git a/action.test.js b/action.test.js
index 2ef659b51..7f8c6b327 100644
--- a/action.test.js
+++ b/action.test.js
@@ -355,14 +355,61 @@ describe("Asana GitHub actions", () => {
const spyGetAsanaPRStatus = jest.spyOn(action, "getAsanaPRStatus");
action.getAsanaPRStatus.mockImplementation(() => "test-value");
+ let errorHasBeenThrown = false;
try {
await action.action();
} catch (error) {
+ errorHasBeenThrown = true;
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe(
"Asana task is not yet completed, blocking merge"
);
}
+ expect(errorHasBeenThrown).toBe(true);
+ expect(spyGetAsanaPRStatus).toHaveBeenCalledTimes(1);
+ expect(spyGetAsanaPRStatus).toHaveBeenLastCalledWith({ pullRequest });
+ });
+
+ test("synchronize should not fail for not completed task with asana: accept_ms_testers_without_closed_task", async () => {
+ jest.resetAllMocks();
+ jest.resetModules();
+ jest.mock("./lib/actions/asana");
+ require("./lib/actions/asana").getTask.mockImplementation(() => ({
+ completed: false,
+ memberships: [],
+ }));
+ jest.mock("./lib/mobsuccessyml");
+ require("./lib/mobsuccessyml").getMobsuccessYMLFromRepo.mockImplementation(
+ () => ({
+ asana: { accept_ms_testers_without_closed_task: true },
+ })
+ );
+
+ const action = require("./action");
+ const pullRequest = {
+ number: 1234,
+ body:
+ "test-trigger-phrase https://app.asana.com/0/1200114135468212/1200114477821446/f",
+ requested_reviewers: [],
+ assignees: [{ login: "ms-testers" }],
+ };
+ jest.spyOn(action, "getActionParameters");
+ action.getActionParameters.mockImplementation(() => ({
+ repository: {
+ owner: {
+ login: "test-owner",
+ },
+ name: "test-repo",
+ },
+ pullRequest,
+ action: "synchronize",
+ triggerPhrase: "test-trigger-phrase",
+ }));
+
+ const spyGetAsanaPRStatus = jest.spyOn(action, "getAsanaPRStatus");
+ action.getAsanaPRStatus.mockImplementation(() => "test-value");
+
+ await action.action();
expect(spyGetAsanaPRStatus).toHaveBeenCalledTimes(1);
expect(spyGetAsanaPRStatus).toHaveBeenLastCalledWith({ pullRequest });
diff --git a/lib/mobsuccessyml.js b/lib/mobsuccessyml.js
new file mode 100644
index 000000000..ccffe64e4
--- /dev/null
+++ b/lib/mobsuccessyml.js
@@ -0,0 +1,19 @@
+const yaml = require("js-yaml");
+const { octokit } = require("./actions/octokit");
+
+exports.getMobsuccessYMLFromRepo = async function getMobsuccessYMLFromRepo({
+ owner,
+ repo,
+}) {
+ try {
+ const { data } = await octokit.rest.repos.getContent({
+ owner,
+ repo,
+ path: ".mobsuccess.yml",
+ });
+ const content = Buffer.from(data.content, "base64").toString("utf8");
+ return yaml.load(content);
+ } catch (e) {
+ return {};
+ }
+};