Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

Commit

Permalink
Jim/FEQ-327/change-regression-tag-task-status-to-pending-qa (#8)
Browse files Browse the repository at this point in the history
* feat: add release_tag_task_url input variable

* chore: add prettier

* feat: retrieve tasks from release tag task

* chore: add bundle

* chore: add helper function to extract version from task name

* chore: remove unused variables

* chore: update bundle

* chore: add check for status

* chore: update bundle

* chore: add logic to extract task and team ids

* fix: fix id extraction bug

* fix: fix query params

* fix: fix status update bug

* chore: move status to constants file

* chore: update regression task status to pending qa

* chore: fix end of line

* chore: fix end of line

* chore: fix end of line

* chore: resolve whitespace issue

* refactor: use promise allsettled

* build: update bundle

* docs: update readme

* docs: update readme
  • Loading branch information
jim-deriv authored Jul 3, 2023
1 parent f3766c5 commit e4cb843
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 94 deletions.
20 changes: 10 additions & 10 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"endOfLine": "lf",
"singleQuote": true,
"useTabs": false,
"tabWidth": 4,
"trailingComma": "es5",
"printWidth": 120,
"jsxSingleQuote": true,
"arrowParens": "avoid",
"proseWrap": "preserve"
}
"endOfLine": "lf",
"singleQuote": true,
"useTabs": false,
"tabWidth": 4,
"trailingComma": "es5",
"printWidth": 120,
"jsxSingleQuote": true,
"arrowParens": "avoid",
"proseWrap": "preserve"
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ You may find an example workflow in the `.github/workflows` folder.

## How it works

The action retrieves all tasks in Clickup under a list ID provided in the input `list_id` in Clickup with the status `Ready - Release` and merges them one by one, checking if the task's pull request has the following criterias:
The action retrieves all tasks in Clickup under the `list_relationship` field of the `regression task` whose `url` is provided in the input `release_tag_task_url` in Clickup with the status `Ready - Release` and merges them one by one, checking if the task's pull request has the following criterias:

- Has at least 2 review approvals
- Has passed all pull request checks
- Does not have merge conflicts with base branch
- Does not have merge conflicts with the base branch

Any tasks that do not match all of these criterias will not be merged. Users assigned to the tasks will be notified of the issues above. Tasks that passes all of these criterias will be merged and tagged with the version provided to the action input `tag`
Any tasks that do not match all of these criterias will not be merged. Users assigned to the tasks will be notified of the issues above. Tasks that pass all of these criterias will be merged and the `regression` task will be moved to `PENDING - QA` status after the merge is complete.

### Inputs

Expand Down
177 changes: 143 additions & 34 deletions bundle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ const PULL_REQUEST_CHECKS_LIMIT = 120;
const CIRCLECI_API_URL = "https://circleci.com/api/v2";
const CLICKUP_API_URL = "https://api.clickup.com/api/v2";
const CLICKUP_STATUSES = {
COMPLETED_QA: "completed - qa",
READY_RELEASE: "ready - release"
completed_qa: "completed - qa",
in_progress_dev: "in progress -\xA0dev",
pending_qa: "pending - qa",
ready_release: "ready - release"
};
const REDMINE_API_URL = "https://redmine.deriv.cloud";
// Annotate the CommonJS export names for ESM import in node:
Expand Down Expand Up @@ -579,44 +581,33 @@ class Clickup {
status: task.status.status
};
}
async fetchTasksFromReleaseTagTask(release_tag_task_url) {
async fetchTasksFromReleaseTagTask(task_id, team_id) {
const issues = [];
const { task_id, team_id } = this.getTaskIdAndTeamIdFromUrl(release_tag_task_url);
const task = await this.http.get(`task/${task_id}?team_id=${team_id}&custom_task_ids=true`);
this.regession_task = task;
const { custom_fields } = task;
const task_ids = this.getTasksIdsFromCustomFields(custom_fields);
for (const task_id2 of task_ids) {
const task2 = await this.fetchIssue(task_id2);
issues.push(task2);
}
const fetch_issues_promises = task_ids.map((task_id2) => this.fetchIssue(task_id2));
const fetched_issues = await Promise.allSettled(fetch_issues_promises);
fetched_issues.forEach((fetched_issue) => {
if (fetched_issue.status === "fulfilled") {
issues.push(fetched_issue.value);
}
});
return issues;
}
getTaskIdAndTeamIdFromUrl(url) {
const pattern = /https:\/\/app\.clickup\.com\/t\/([\w-]*)\/*([\w-]*)/;
const matches = pattern.exec(url);
const ids = matches?.slice(matches?.length - 2) ?? ["", ""];
let task_id = "";
let team_id = "";
if (ids.length > 0 && ids[ids.length - 1]) {
[team_id, task_id] = ids;
} else {
[task_id] = ids;
}
return { task_id, team_id };
}
getTasksIdsFromCustomFields(custom_fields) {
const taskIds = [];
for (const custom_field of custom_fields ?? []) {
if (custom_field.value && custom_field.value.length > 0 && custom_field.type === "list_relationship" && Array.isArray(custom_field.value)) {
custom_field.value.forEach((value) => {
if (value.id && value.status === import_constants.CLICKUP_STATUSES.READY_RELEASE) {
taskIds.push(value.id);
getTasksIdsFromCustomFields(custom_fields = []) {
const task_ids = [];
for (const field of custom_fields) {
if (Array.isArray(field.value) && field.value.length > 0 && field.type === "list_relationship") {
field.value.forEach((value) => {
if (value.id && value.status === import_constants.CLICKUP_STATUSES.ready_release) {
task_ids.push(value.id);
}
});
}
}
return taskIds;
return task_ids;
}
}
var clickup_default = new Clickup();
Expand Down Expand Up @@ -1026,6 +1017,59 @@ var github_default = new GitHub();
//# sourceMappingURL=github.js.map


/***/ }),

/***/ 48167:
/***/ ((module) => {

"use strict";

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var helpers_exports = {};
__export(helpers_exports, {
extractVersionFromTaskName: () => extractVersionFromTaskName,
getTaskIdAndTeamIdFromUrl: () => getTaskIdAndTeamIdFromUrl
});
module.exports = __toCommonJS(helpers_exports);
const extractVersionFromTaskName = (task_name = "") => {
const regex = /V\d+/;
const matches = task_name.match(regex);
return matches && matches.length > 0 ? matches[0] : "";
};
const getTaskIdAndTeamIdFromUrl = (url) => {
const pattern = /https:\/\/app\.clickup\.com\/t\/([\w-]*)\/*([\w-]*)/;
const matches = pattern.exec(url);
const ids = matches?.slice(matches.length - 2) ?? ["", ""];
let task_id = "";
let team_id = "";
if (ids.length > 0 && ids[ids.length - 1]) {
[team_id, task_id] = ids;
} else {
[task_id] = ids;
}
return { task_id, team_id };
};
// Annotate the CommonJS export names for ESM import in node:
0 && (0);
//# sourceMappingURL=helpers.js.map


/***/ }),

/***/ 57108:
Expand Down Expand Up @@ -1573,6 +1617,8 @@ var import_slack = __toESM(__nccwpck_require__(4217));
var import_messages = __nccwpck_require__(43648);
var import_logger = __toESM(__nccwpck_require__(76197));
var import_config = __nccwpck_require__(71138);
var import_constants = __nccwpck_require__(26564);
var import_helpers = __nccwpck_require__(48167);
class ReleaseWorkflow {
strategy;
constructor() {
Expand Down Expand Up @@ -1609,7 +1655,8 @@ class ReleaseWorkflow {
}
async run() {
try {
let issues = await this.strategy.fetchTasksFromReleaseTagTask(import_config.RELEASE_TAG_TASK_URL);
const { task_id: release_tag_task_id, team_id } = (0, import_helpers.getTaskIdAndTeamIdFromUrl)(import_config.RELEASE_TAG_TASK_URL);
let issues = await this.strategy.fetchTasksFromReleaseTagTask(release_tag_task_id, team_id);
if (issues.length === 0) {
import_logger.default.log(
'No issues found to be merged! Have you moved the cards to the "Ready - Release" status?',
Expand Down Expand Up @@ -1642,8 +1689,8 @@ class ReleaseWorkflow {
}
const [merged_issues, failed_issues] = await this.strategy.mergeCards();
if (merged_issues.length) {
await this.strategy.updateIssue(import_config.RELEASE_TAG_TASK_URL, {
status: "Pending - QA"
await this.strategy.updateIssue(release_tag_task_id, {
status: import_constants.CLICKUP_STATUSES.pending_qa
});
}
const failed_notifications = [];
Expand Down Expand Up @@ -1687,7 +1734,7 @@ class ReleaseWorkflow {
const status_reqs = failed_issues_by_assignee[email].map(async ({ issue }) => {
if (issue) {
await import_clickup.default.updateIssue(issue.id, {
status: "in progress -\xA0dev"
status: import_constants.CLICKUP_STATUSES.in_progress_dev
}).catch((err) => {
import_logger.default.log(
`There was an issue in updating the task ${issue.title} to In Progress - Dev status: ${err}`,
Expand All @@ -1702,7 +1749,7 @@ class ReleaseWorkflow {
}
if (!import_config.SHOULD_SKIP_SLACK_INTEGRATION) {
try {
const VERSION = extractVersionFromTaskName(this.strategy.regession_task?.name);
const VERSION = (0, import_helpers.extractVersionFromTaskName)(this.strategy.regession_task?.name);
await import_slack.default.updateChannelTopic(
"task_release_planning_fe",
import_config.PLATFORM,
Expand Down Expand Up @@ -86947,6 +86994,40 @@ try {
} catch (er) {}


/***/ }),

/***/ 26564:
/***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => {

"use strict";
__nccwpck_require__.r(__webpack_exports__);
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
/* harmony export */ "CIRCLECI_API_URL": () => (/* binding */ CIRCLECI_API_URL),
/* harmony export */ "CLICKUP_API_URL": () => (/* binding */ CLICKUP_API_URL),
/* harmony export */ "CLICKUP_STATUSES": () => (/* binding */ CLICKUP_STATUSES),
/* harmony export */ "MERGE_DELAY": () => (/* binding */ MERGE_DELAY),
/* harmony export */ "PULL_REQUEST_CHECKS_LIMIT": () => (/* binding */ PULL_REQUEST_CHECKS_LIMIT),
/* harmony export */ "PULL_REQUEST_CHECKS_TIMEOUT": () => (/* binding */ PULL_REQUEST_CHECKS_TIMEOUT),
/* harmony export */ "PULL_REQUEST_REFETCH_LIMIT": () => (/* binding */ PULL_REQUEST_REFETCH_LIMIT),
/* harmony export */ "PULL_REQUEST_REFETCH_TIMEOUT": () => (/* binding */ PULL_REQUEST_REFETCH_TIMEOUT),
/* harmony export */ "REDMINE_API_URL": () => (/* binding */ REDMINE_API_URL)
/* harmony export */ });
const MERGE_DELAY = 2 * 60 * 1000;
const PULL_REQUEST_CHECKS_TIMEOUT = 1 * 60 * 1000;
const PULL_REQUEST_REFETCH_TIMEOUT = 5 * 1000;
const PULL_REQUEST_REFETCH_LIMIT = 10;
const PULL_REQUEST_CHECKS_LIMIT = 120;
const CIRCLECI_API_URL = 'https://circleci.com/api/v2';
const CLICKUP_API_URL = 'https://api.clickup.com/api/v2';
const CLICKUP_STATUSES = {
completed_qa: 'completed - qa',
in_progress_dev: 'in progress - dev',
pending_qa: 'pending - qa',
ready_release: 'ready - release',
};
const REDMINE_API_URL = 'https://redmine.deriv.cloud';


/***/ }),

/***/ 71269:
Expand Down Expand Up @@ -92939,6 +93020,34 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"]
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __nccwpck_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __nccwpck_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __nccwpck_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/node module decorator */
/******/ (() => {
/******/ __nccwpck_require__.nmd = (module) => {
Expand Down
7 changes: 4 additions & 3 deletions src/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ export const PULL_REQUEST_CHECKS_TIMEOUT = 1 * 60 * 1000; // 1 minute
export const PULL_REQUEST_REFETCH_TIMEOUT = 5 * 1000; // 5 seconds
export const PULL_REQUEST_REFETCH_LIMIT = 10; // the max amount of refetches to check for a pull request's status checks
export const PULL_REQUEST_CHECKS_LIMIT = 120;

export const CIRCLECI_API_URL = 'https://circleci.com/api/v2';
export const CLICKUP_API_URL = 'https://api.clickup.com/api/v2';
export const CLICKUP_STATUSES = {
COMPLETED_QA: 'completed - qa',
READY_RELEASE: 'ready - release',
completed_qa: 'completed - qa',
in_progress_dev: 'in progress - dev',
pending_qa: 'pending - qa',
ready_release: 'ready - release',
};
export const REDMINE_API_URL = 'https://redmine.deriv.cloud';
50 changes: 14 additions & 36 deletions src/utils/clickup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,56 +207,34 @@ export class Clickup implements ReleaseStrategy {
};
}

async fetchTasksFromReleaseTagTask(release_tag_task_url: string): Promise<Issue[]> {
async fetchTasksFromReleaseTagTask(task_id: string, team_id: string): Promise<Issue[]> {
const issues: Issue[] = [];

const { task_id, team_id } = this.getTaskIdAndTeamIdFromUrl(release_tag_task_url);

const task = await this.http.get<Task>(`task/${task_id}?team_id=${team_id}&custom_task_ids=true`);
this.regession_task = task;
const { custom_fields } = task;
const task_ids = this.getTasksIdsFromCustomFields(custom_fields);
for (const task_id of task_ids) {
const task = await this.fetchIssue(task_id);
issues.push(task);
}
const fetch_issues_promises = task_ids.map(task_id => this.fetchIssue(task_id));
const fetched_issues = await Promise.allSettled(fetch_issues_promises);
fetched_issues.forEach(fetched_issue => {
if (fetched_issue.status === 'fulfilled') {
issues.push(fetched_issue.value);
}
});

return issues;
}
getTaskIdAndTeamIdFromUrl(url: string) {
const pattern = /https:\/\/app\.clickup\.com\/t\/([\w-]*)\/*([\w-]*)/;
const matches = pattern.exec(url);
const ids = matches?.slice(matches?.length - 2) ?? ['', ''];
let task_id = '';
let team_id = '';

if (ids.length > 0 && ids[ids.length - 1]) {
[team_id, task_id] = ids;
} else {
[task_id] = ids;
}

return { task_id, team_id };
}

getTasksIdsFromCustomFields(custom_fields?: CustomField[] = []): string[] {
getTasksIdsFromCustomFields(custom_fields: CustomField[] = []): string[] {
const task_ids: string[] = [];
for (const field of custom_fields) {
if (
custom_field.value &&
custom_field.value.length > 0 &&
custom_field.type === 'list_relationship' &&
Array.isArray(custom_field.value)
) {
custom_field.value.forEach(value => {
if (value.id && value.status === CLICKUP_STATUSES.READY_RELEASE) {
taskIds.push(value.id);
if (Array.isArray(field.value) && field.value.length > 0 && field.type === 'list_relationship') {
field.value.forEach(value => {
if (value.id && value.status === CLICKUP_STATUSES.ready_release) {
task_ids.push(value.id);
}
});
}
}

return taskIds;
return task_ids;
}
}

Expand Down
Loading

0 comments on commit e4cb843

Please sign in to comment.