From 31919749fc8b9c0261c34bd9845567e9af8e9b77 Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 18:42:23 +0700 Subject: [PATCH 1/8] Feature: Configure approvals number --- README.md | 11 ++++++----- action.yml | 4 ++++ build/index.js | 10 ++++++++-- package.json | 2 +- src/analytics/sendActionRun.ts | 3 +++ src/converters/utils/calculations/getApproveTime.ts | 8 +++++--- src/view/utils/createConfigParamsCode.ts | 1 + 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 094f4f7..4dbcf9c 100644 --- a/README.md +++ b/README.md @@ -302,8 +302,8 @@ Below is a table outlining the various configuration parameters available for ** | Parameter Name | Description | Default Value | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `GITHUB_TOKEN` | `GITHUB_TOKEN` or personal access token. **repo** and **read:org** scopes required for **personal access token(classic)**. For scenarios involving data collection from multiple repositories or handling a large number of pull requests, it's recommended to use a **personal access token (classic)**. This parameter is **required** | - | -| `GITHUB_OWNER_FOR_ISSUE` | Owner of the repository where an issue with the report needs to be created. This parameter is **required** if `EXECUTION_OUTCOME` includes `new-issue` or `existing-issue` values. | - | -| `GITHUB_REPO_FOR_ISSUE` | The repository where an issue with the report needs to be created. This parameter is **required** if `EXECUTION_OUTCOME` includes `new-issue` or `existing-issue` values. | - | +| `GITHUB_OWNER_FOR_ISSUE` | Owner of the repository where an issue with the report needs to be created. This parameter is **required** if `EXECUTION_OUTCOME` includes `new-issue` or `existing-issue` values. | - | +| `GITHUB_REPO_FOR_ISSUE` | The repository where an issue with the report needs to be created. This parameter is **required** if `EXECUTION_OUTCOME` includes `new-issue` or `existing-issue` values. | - | | `GITHUB_OWNERS_REPOS` | Repositories to collect data from. Enter values in the format `owner/repo`, separated by commas. Either `GITHUB_OWNERS_REPOS` or `ORGANIZATIONS` must be set. Example: `owner/repo, owner/another-repo` | - | | `ORGANIZATIONS` | Organizations from whose repositories data needs to be collected., separated by commas. Repositories from these organizations will be added to the `GITHUB_OWNERS_REPOS` list to create an array with unique repositories. Either `GITHUB_OWNERS_REPOS` or `ORGANIZATIONS` must be set. | - | | `SHOW_STATS_TYPES` | Stats types that should be displayed in report. Values must be separated by commas. Can take values: `timeline`, `workload`, `pr-quality`, `code-review-engagement`, `response-time`. Example: `timeline, workload, pr-quality, code-review-engagement, response-time` | `timeline, workload, pr-quality, code-review-engagement, response-time` | @@ -320,9 +320,10 @@ Below is a table outlining the various configuration parameters available for ** | `CORE_HOURS_START` | Start of core hours. Excludes non-working hours from the calculations of time-related metrics. By default, a full day is counted. Time should be entered in the format **HH:mm**. The timezone corresponds to that specified in the `TIMEZONE` input (default is UTC). For correct operation, `CORE_HOURS_END` must also be specified and must be later than `CORE_HOURS_START`. Example: `10:00` | - | | `CORE_HOURS_END` | End of core hours. Excludes non-working hours from the calculations of time-related metrics. By default, a full day is counted. Time should be entered in the format **HH:mm**. The timezone corresponds to that specified in the `TIMEZONE` input (default is UTC). For correct operation, `CORE_HOURS_END` must also be specified and must be later than `CORE_HOURS_START`. Example: `19:00` | - | | `HOLIDAYS` | Dates to be excluded from the calculations of time-related metrics. Saturday and Sunday are already excluded by default. Dates should be entered in the format **d/MM/yyyy**, separated by commas. Example: `01/01/2024, 08/03/2024` | - | -| `WEEKENDS` | Specifies the days of the week considered as weekends. Values are represented as numbers, where 0 corresponds to Sunday | `0,6` | +| `WEEKENDS` | Specifies the days of the week considered as weekends. Values are represented as numbers, where 0 corresponds to Sunday | `0,6` | | `TIMEZONE` | Timezone that will be used in action. Examples: `Europe/Berlin` or `America/New_York`. See the full list of time zones [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `UTC` | | `PERCENTILE` | Percentile value for timeline. This parameter is mandatory if `percentile` is specified in the `SHOW_STATS_TYPES` input. | `75` | +| `REQUIRED_APPROVALS` | Amount of approvals required for PR to be approved. This parameter is **required** | `1` | | `ISSUE_TITLE` | Title for the created/updated issue with report | `Pull requests report(d/MM/yyyy HH:mm)` | | `LABELS` | Labels for the created/updated issue with report separated by commas. Example: `Report` | - | | `ASSIGNEES` | Assignees for the created/updated issue with report separated by commas. Example: `AlexSim93` | - | @@ -333,8 +334,8 @@ Below is a table outlining the various configuration parameters available for ** | `SHOW_USERS` | Displays only specified users in reports, but includes all users in the background analytics. Use `total` to show total stats. Users should be separated by commas. | - | | `EXCLUDE_LABELS` | PRs with mentioned labels will be excluded from the report . Values should be separated by commas. Example: `bugfix, enhancement` | - | | `INCLUDE_LABELS` | Only PRs with mentioned labels will be included in the report. Values should be separated by commas. Example: `bugfix, enhancement` | - | -| `INCLUDE_USERS` | Only data for the specified users will be included in the report. Multiple values should be separated by commas. Example: `dev1, dev2` | - | -| `EXCLUDE_USERS` | Data for the specified users will be excluded from the report. Multiple values should be separated by commas. Example: `dev1, dev2` | - | +| `INCLUDE_USERS` | Only data for the specified users will be included in the report. Multiple values should be separated by commas. Example: `dev1, dev2` | - | +| `EXCLUDE_USERS` | Data for the specified users will be excluded from the report. Multiple values should be separated by commas. Example: `dev1, dev2` | - | | `EXECUTION_OUTCOME` | This parameter allows you to specify the format in which you wish to receive the report. Options include creating a new issue, updating an existing one, obtaining markdown, or JSON. Markdown and JSON will be available in outputs. Can take mulitple values separated by commas: `new-issue`, `markdown`, `collection`, `existing-issue`. This parameter is **required** Example: `existing-issue` | `new-issue` | | `ISSUE_NUMBER` | Issue number to update. Add `existing-issue` to `EXECUTION_OUTCOME` for updating existing issue. The specified issue must already exist at the time the action is executed. This parameter is mandatory if the `EXECUTION_OUTCOME` input includes `existing-issue` value | - | | `ALLOW_ANALYTICS` | Allows sending non-sensitive inputs to mixpanel for better understanding user's needs. Set the value to `false` to disable sending action parameter data | `true` | diff --git a/action.yml b/action.yml index 8857327..c224873 100644 --- a/action.yml +++ b/action.yml @@ -54,6 +54,10 @@ inputs: description: "Percentile value for timeline" required: false default: "75" + REQUIRED_APPROVALS: + description: "Amount of approvals required for PR to be approved" + required: true + default: "1" TOP_LIST_AMOUNT: description: "Amount of items in lists" required: false diff --git a/build/index.js b/build/index.js index 0aa5a7b..0f39df8 100644 --- a/build/index.js +++ b/build/index.js @@ -122,8 +122,11 @@ const sendActionRun = () => { ASSIGNEES: (0, utils_1.getMultipleValuesInput)("ASSIGNEES").length, ISSUE_TITLE: !!(0, utils_1.getValueAsIs)("ISSUE_TITLE"), AGGREGATE_VALUE_METHODS: (0, utils_1.getMultipleValuesInput)("AGGREGATE_VALUE_METHODS"), + INCLUDE_USERS: (0, utils_1.getMultipleValuesInput)("INCLUDE_USERS").length, + EXCLUDE_USERS: (0, utils_1.getMultipleValuesInput)("EXCLUDE_USERS").length, HIDE_USERS: (0, utils_1.getMultipleValuesInput)("HIDE_USERS").length, SHOW_USERS: (0, utils_1.getMultipleValuesInput)("SHOW_USERS").length, + REQUIRED_APPROVALS: (0, utils_1.getValueAsIs)("REQUIRED_APPROVALS"), INCLUDE_LABELS: (0, utils_1.getMultipleValuesInput)("INCLUDE_LABELS").length, EXCLUDE_LABELS: (0, utils_1.getMultipleValuesInput)("EXCLUDE_LABELS").length, EXECUTION_OUTCOME: (0, utils_1.getMultipleValuesInput)("EXECUTION_OUTCOME"), @@ -821,6 +824,7 @@ exports.getApproveTime = void 0; const date_fns_1 = __nccwpck_require__(73314); const constants_1 = __nccwpck_require__(95354); const checkUserInclusive_1 = __nccwpck_require__(50477); +const utils_1 = __nccwpck_require__(41002); const getApproveTime = (reviews) => { const statuses = Object.values(reviews?.reduce((acc, review) => { const user = review.user?.login || constants_1.invalidUserLogin; @@ -845,10 +849,11 @@ const getApproveTime = (reviews) => { [user]: { state: review.state, submittedAt: review.submitted_at }, }; }, {}) || {}); - const isApproved = statuses.some((status) => status.state === "approved") && + const isApproved = statuses.filter((status) => status.state === "approved").length >= + parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) && !statuses.some((status) => status.state === "changes_requested"); return isApproved - ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[0]?.submittedAt + ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) - 1]?.submittedAt : null; }; exports.getApproveTime = getApproveTime; @@ -2893,6 +2898,7 @@ ${[ "AGGREGATE_VALUE_METHODS", "SHOW_CORRELATION_GRAPHS", "SHOW_ACTIVITY_TIME_GRAPHS", + "REQUIRED_APPROVALS", "PERCENTILE", "HIDE_USERS", "SHOW_USERS", diff --git a/package.json b/package.json index b9bb4ad..408bf60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pull-request-analytics-action", - "version": "4.7.0", + "version": "4.8.0", "description": "Generates detailed PR analytics reports within GitHub, focusing on review efficiency and team performance.", "main": "build/index.js", "scripts": { diff --git a/src/analytics/sendActionRun.ts b/src/analytics/sendActionRun.ts index 83dc800..4516c8a 100644 --- a/src/analytics/sendActionRun.ts +++ b/src/analytics/sendActionRun.ts @@ -29,8 +29,11 @@ export const sendActionRun = () => { AGGREGATE_VALUE_METHODS: getMultipleValuesInput( "AGGREGATE_VALUE_METHODS" ), + INCLUDE_USERS: getMultipleValuesInput("INCLUDE_USERS").length, + EXCLUDE_USERS: getMultipleValuesInput("EXCLUDE_USERS").length, HIDE_USERS: getMultipleValuesInput("HIDE_USERS").length, SHOW_USERS: getMultipleValuesInput("SHOW_USERS").length, + REQUIRED_APPROVALS: getValueAsIs("REQUIRED_APPROVALS"), INCLUDE_LABELS: getMultipleValuesInput("INCLUDE_LABELS").length, EXCLUDE_LABELS: getMultipleValuesInput("EXCLUDE_LABELS").length, EXECUTION_OUTCOME: getMultipleValuesInput("EXECUTION_OUTCOME"), diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index 9d7bdc6..d03ba7b 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -2,6 +2,7 @@ import { isBefore, parseISO } from "date-fns"; import { makeComplexRequest } from "../../../requests"; import { invalidUserLogin } from "../../constants"; import { checkUserInclusive } from "./checkUserInclusive"; +import { getValueAsIs } from "../../../common/utils"; export const getApproveTime = ( reviews: Awaited>["events"][number] @@ -13,7 +14,7 @@ export const getApproveTime = ( review: any ) => { const user = review.user?.login || invalidUserLogin; - if(!checkUserInclusive(user)){ + if (!checkUserInclusive(user)) { return acc; } const statusesEntries = Object.keys(acc) as string[]; @@ -42,12 +43,13 @@ export const getApproveTime = ( ); const isApproved = - statuses.some((status) => status.state === "approved") && + statuses.filter((status) => status.state === "approved").length >= + parseInt(getValueAsIs("REQUIRED_APPROVALS")) && !statuses.some((status) => status.state === "changes_requested"); return isApproved ? statuses.sort((a, b) => isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1 - )[0]?.submittedAt + )[parseInt(getValueAsIs("REQUIRED_APPROVALS")) - 1]?.submittedAt : null; }; diff --git a/src/view/utils/createConfigParamsCode.ts b/src/view/utils/createConfigParamsCode.ts index ac26f69..1d5c230 100644 --- a/src/view/utils/createConfigParamsCode.ts +++ b/src/view/utils/createConfigParamsCode.ts @@ -19,6 +19,7 @@ ${[ "AGGREGATE_VALUE_METHODS", "SHOW_CORRELATION_GRAPHS", "SHOW_ACTIVITY_TIME_GRAPHS", + "REQUIRED_APPROVALS", "PERCENTILE", "HIDE_USERS", "SHOW_USERS", From 7c4c831e26d60fe096348d39c52794ce0544372b Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 19:21:16 +0700 Subject: [PATCH 2/8] Feature: Update readme --- README.md | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/README.md b/README.md index 4dbcf9c..839e821 100644 --- a/README.md +++ b/README.md @@ -42,50 +42,6 @@ Displays the time from PR creation to each displayed status. Helps identify bott | **dev3** | 13 minutes | 13 minutes | 4 hours 12 minutes | 2 hours 21 minutes | 4 hours 48 minutes | 22 hours 8 minutes | 54 | | **total** | 10 minutes | 10 minutes | 4 hours 15 minutes | 4 hours 43 minutes | 7 hours 21 minutes | 22 hours 36 minutes | 232 | -```mermaid -gantt -title Pull requests timeline(percentile75) 12/2023 / minutes -dateFormat X -axisFormat %s -section dev1 -Time in draft(34 minutes) : 0, 34 -Time to review request(17 minutes) : 0, 17 -Time to review(3 hours 34 minutes) : 0, 214 -Time to approve(7 hours 32 minutes) : 0, 452 -Time to merge(14 hours 9 minutes) : 0, 849 - -section dev2 -Time in draft(27 minutes) : 0, 21 -Time to review request(12 minutes) : 0, 20 -Time to review(4 hours) : 0, 240 -Time to approve(4 hours) : 0, 240 -Time to merge(23 hours 1 minute) : 0, 1381 - -section dev3 -Time in draft(27 minutes) : 0, 15 -Time to review request(12 minutes) : 0, 18 -Time to review(15 hours 16 minutes) : 0, 916 -Time to approve(24 hours 7 minutes) : 0, 1447 -Time to merge(53 hours 43 minutes) : 0, 3223 - -section total -Time in draft(27 minutes) : 0, 27 -Time to review request(12 minutes) : 0, 18 -Time to review(4 hours 21 minutes) : 0, 261 -Time to approve(7 hours 36 minutes) : 0, 456 -Time to merge(26 hours 14 minutes) : 0, 1574 - -``` - -```mermaid -pie -title Review time total 12/2023 -"0-1 hours(12)":12 -"4-6 hours(7)":7 -"6-9 hours(4)":4 -"12+ hours(2)":2 -``` - ### Contribution Shows the total volume of code merged, reviews conducted, and comments in PRs. Helps to understand the context in which other metrics apply. Use the `workload` value in the `SHOW_STATS_TYPES` parameter. @@ -108,16 +64,6 @@ Measures how discussion-heavy PRs are from the author's perspective, based on op | **dev3** | 2 | 0 | 0 / 0 / 1 | 1 | | **total** | 47 | 6 | 3 / 2 / 25 | 37 | -```mermaid -pie -title Discussions types total 12/2023 -"Bug(12)":12 -"Performance(8)":8 -"Code complexity(3)":3 -"Test coverage(2)":2 -"Formatting(9)":9 -``` - ### Discussion Intensity (Reviewer's Perspective) Measures how discussion-heavy PRs are from the reviewer's perspective, based on discussions, comments, and PR statuses. Helps understand reviewer engagement and decision-making. Use the `code-review-engagement` value in the `SHOW_STATS_TYPES` parameter and add thumbs up/down ( :+1: / :-1: ) reactions on opening comments. From a24e2d8f19ef52e111785852f7e5b7446afcf0f8 Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 21:01:41 +0700 Subject: [PATCH 3/8] Feature: fix approve status --- build/index.js | 3 ++- src/converters/utils/calculations/getApproveTime.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build/index.js b/build/index.js index 0f39df8..ca6085b 100644 --- a/build/index.js +++ b/build/index.js @@ -832,7 +832,8 @@ const getApproveTime = (reviews) => { return acc; } const statusesEntries = Object.keys(acc); - const isApproved = statusesEntries.some((user) => acc[user].state === "approved") && + const isApproved = statusesEntries.filter((user) => acc[user].state === "approved") + .length >= parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) && !statusesEntries.some((user) => acc[user].state === "changes_requested") && review.state !== "changes_requested"; if (isApproved) { diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index d03ba7b..a8c1a3a 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -19,7 +19,8 @@ export const getApproveTime = ( } const statusesEntries = Object.keys(acc) as string[]; const isApproved = - statusesEntries.some((user) => acc[user].state === "approved") && + statusesEntries.filter((user) => acc[user].state === "approved") + .length >= parseInt(getValueAsIs("REQUIRED_APPROVALS")) && !statusesEntries.some( (user) => acc[user].state === "changes_requested" ) && From 0ebae83631b24bd04f2b32e1acbdd152e607d7ae Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 21:07:22 +0700 Subject: [PATCH 4/8] Feature: fix approve status --- build/index.js | 11 +++++------ src/converters/utils/calculations/getApproveTime.ts | 10 +++++----- src/converters/utils/preparePullRequestTimeline.ts | 5 ++++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/build/index.js b/build/index.js index ca6085b..43da3eb 100644 --- a/build/index.js +++ b/build/index.js @@ -824,8 +824,7 @@ exports.getApproveTime = void 0; const date_fns_1 = __nccwpck_require__(73314); const constants_1 = __nccwpck_require__(95354); const checkUserInclusive_1 = __nccwpck_require__(50477); -const utils_1 = __nccwpck_require__(41002); -const getApproveTime = (reviews) => { +const getApproveTime = (reviews, requiredApprovals) => { const statuses = Object.values(reviews?.reduce((acc, review) => { const user = review.user?.login || constants_1.invalidUserLogin; if (!(0, checkUserInclusive_1.checkUserInclusive)(user)) { @@ -833,7 +832,7 @@ const getApproveTime = (reviews) => { } const statusesEntries = Object.keys(acc); const isApproved = statusesEntries.filter((user) => acc[user].state === "approved") - .length >= parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) && + .length >= requiredApprovals && !statusesEntries.some((user) => acc[user].state === "changes_requested") && review.state !== "changes_requested"; if (isApproved) { @@ -851,10 +850,10 @@ const getApproveTime = (reviews) => { }; }, {}) || {}); const isApproved = statuses.filter((status) => status.state === "approved").length >= - parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) && + requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); return isApproved - ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS")) - 1]?.submittedAt + ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[requiredApprovals - 1]?.submittedAt : null; }; exports.getApproveTime = getApproveTime; @@ -1475,7 +1474,7 @@ const preparePullRequestTimeline = (pullRequestInfo, pullRequestReviews = [], re } const firstReview = pullRequestReviews?.find((review) => review.user?.login !== pullRequestInfo?.user?.login && (0, calculations_1.checkUserInclusive)(review.user?.login || constants_1.invalidUserLogin)); - const approveTime = (0, calculations_1.getApproveTime)(pullRequestReviews); + const approveTime = (0, calculations_1.getApproveTime)(pullRequestReviews, parseInt((0, utils_1.getValueAsIs)("REQUIRED_APPROVALS"))); const timeToReviewRequest = (0, calcDifferenceInMinutes_1.calcDifferenceInMinutes)(pullRequestInfo?.created_at, reviewRequest?.created_at, { endOfWorkingTime: (0, utils_1.getValueAsIs)("CORE_HOURS_END"), startOfWorkingTime: (0, utils_1.getValueAsIs)("CORE_HOURS_START"), diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index a8c1a3a..2b12ddc 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -2,10 +2,10 @@ import { isBefore, parseISO } from "date-fns"; import { makeComplexRequest } from "../../../requests"; import { invalidUserLogin } from "../../constants"; import { checkUserInclusive } from "./checkUserInclusive"; -import { getValueAsIs } from "../../../common/utils"; export const getApproveTime = ( - reviews: Awaited>["events"][number] + reviews: Awaited>["events"][number], + requiredApprovals: number ) => { const statuses = Object.values( reviews?.reduce( @@ -20,7 +20,7 @@ export const getApproveTime = ( const statusesEntries = Object.keys(acc) as string[]; const isApproved = statusesEntries.filter((user) => acc[user].state === "approved") - .length >= parseInt(getValueAsIs("REQUIRED_APPROVALS")) && + .length >= requiredApprovals && !statusesEntries.some( (user) => acc[user].state === "changes_requested" ) && @@ -45,12 +45,12 @@ export const getApproveTime = ( const isApproved = statuses.filter((status) => status.state === "approved").length >= - parseInt(getValueAsIs("REQUIRED_APPROVALS")) && + requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); return isApproved ? statuses.sort((a, b) => isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1 - )[parseInt(getValueAsIs("REQUIRED_APPROVALS")) - 1]?.submittedAt + )[requiredApprovals - 1]?.submittedAt : null; }; diff --git a/src/converters/utils/preparePullRequestTimeline.ts b/src/converters/utils/preparePullRequestTimeline.ts index 439d0a5..5f1502e 100644 --- a/src/converters/utils/preparePullRequestTimeline.ts +++ b/src/converters/utils/preparePullRequestTimeline.ts @@ -28,7 +28,10 @@ export const preparePullRequestTimeline = ( review.user?.login !== pullRequestInfo?.user?.login && checkUserInclusive(review.user?.login || invalidUserLogin) ); - const approveTime = getApproveTime(pullRequestReviews); + const approveTime = getApproveTime( + pullRequestReviews, + parseInt(getValueAsIs("REQUIRED_APPROVALS")) + ); const timeToReviewRequest = calcDifferenceInMinutes( pullRequestInfo?.created_at, From 64e1f7160750dbdaa6a6ea550cbc4270ea0efb9a Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 21:08:12 +0700 Subject: [PATCH 5/8] Feature: fix approve status --- .../utils/calculations/getApproveTime.spec.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/converters/utils/calculations/getApproveTime.spec.ts b/src/converters/utils/calculations/getApproveTime.spec.ts index be4fece..81f54a6 100644 --- a/src/converters/utils/calculations/getApproveTime.spec.ts +++ b/src/converters/utils/calculations/getApproveTime.spec.ts @@ -132,43 +132,43 @@ const dismissedBySecondReviewer = [ describe("check getApproveTime", () => { it("Check PR without reviews and return null", () => { - expect(getApproveTime(notReviewed)).toBe(null); + expect(getApproveTime(notReviewed, 1)).toBe(null); }); it("Check PR with only 1 approval", () => { - expect(getApproveTime(approvedReview)).toBe("2024-01-11T07:00:00Z"); + expect(getApproveTime(approvedReview, 1)).toBe("2024-01-11T07:00:00Z"); }); it("Check PR with 2 approval and return the earliest one", () => { - expect(getApproveTime(approvedTwiceReview)).toBe("2024-01-11T07:00:00Z"); + expect(getApproveTime(approvedTwiceReview, 1)).toBe("2024-01-11T07:00:00Z"); }); // changes requested included it("Check PR with approval after changes requested and return time of approval", () => { - expect(getApproveTime(approvedAfterChangesRequestedReview)).toBe( + expect(getApproveTime(approvedAfterChangesRequestedReview, 1)).toBe( "2024-01-11T09:00:00Z" ); }); it("Check PR with changes requested by second developer and approval after it. Should return the last time of approval", () => { - expect(getApproveTime(oneApprovedOneChangesRequestedReview)).toBe( + expect(getApproveTime(oneApprovedOneChangesRequestedReview, 1)).toBe( "2024-01-12T05:00:00Z" ); }); it("Check PR with approval -> changes requested -> approval and return time of the last approve", () => { - expect(getApproveTime(approvedRequestedChangesApprovedReview)).toBe( + expect(getApproveTime(approvedRequestedChangesApprovedReview, 1)).toBe( "2024-01-12T09:00:00Z" ); }); it("Check PR with dismissed changes requested and return time of the approval", () => { - expect(getApproveTime(dismissedChangesRequestedReview)).toBe( + expect(getApproveTime(dismissedChangesRequestedReview, 1)).toBe( "2024-01-11T09:00:00Z" ); }); it("Check PR with changes requested and return null", () => { - expect(getApproveTime(changesRequestedReview)).toBe(null); + expect(getApproveTime(changesRequestedReview, 1)).toBe(null); }); it("Check commented PR and return time of the approval", () => { - expect(getApproveTime(commentedReview)).toBe("2024-01-12T05:00:00Z"); + expect(getApproveTime(commentedReview, 1)).toBe("2024-01-12T05:00:00Z"); }); it("Check PR with dismissed changes requested status from second reviewer and return time of the approval", () => { - expect(getApproveTime(dismissedBySecondReviewer)).toBe( + expect(getApproveTime(dismissedBySecondReviewer, 1)).toBe( "2024-01-11T13:00:00Z" ); }); From 8bdf44e6392d6edfe1323a60313b7b55cff004e1 Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 21:41:21 +0700 Subject: [PATCH 6/8] Feature: fix approve status --- build/index.js | 6 +++++- .../utils/calculations/getApproveTime.spec.ts | 6 ++++++ src/converters/utils/calculations/getApproveTime.ts | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/build/index.js b/build/index.js index 43da3eb..b48d80c 100644 --- a/build/index.js +++ b/build/index.js @@ -849,11 +849,15 @@ const getApproveTime = (reviews, requiredApprovals) => { [user]: { state: review.state, submittedAt: review.submitted_at }, }; }, {}) || {}); + console.log(statuses); const isApproved = statuses.filter((status) => status.state === "approved").length >= requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); + console.log(isApproved + ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1) + : null); return isApproved - ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[requiredApprovals - 1]?.submittedAt + ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[0]?.submittedAt : null; }; exports.getApproveTime = getApproveTime; diff --git a/src/converters/utils/calculations/getApproveTime.spec.ts b/src/converters/utils/calculations/getApproveTime.spec.ts index 81f54a6..af08aaa 100644 --- a/src/converters/utils/calculations/getApproveTime.spec.ts +++ b/src/converters/utils/calculations/getApproveTime.spec.ts @@ -172,4 +172,10 @@ describe("check getApproveTime", () => { "2024-01-11T13:00:00Z" ); }); + it("Check PR with 2 approvals and return time of the second approval", () => { + expect(getApproveTime(approvedTwiceReview, 2)).toBe("2024-01-11T09:00:00Z"); + }); + it("Check PR with 3 approvals and return time of the third approval", () => { + expect(getApproveTime(approvedTwiceReview, 3)).toBe(null); + }); }); diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index 2b12ddc..f38b2da 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -43,14 +43,23 @@ export const getApproveTime = ( ) || {} ); + console.log(statuses); + const isApproved = statuses.filter((status) => status.state === "approved").length >= requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); + console.log( + isApproved + ? statuses.sort((a, b) => + isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1 + ) + : null + ); return isApproved ? statuses.sort((a, b) => isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1 - )[requiredApprovals - 1]?.submittedAt + )[0]?.submittedAt : null; }; From 020a1edf6d9289ab8e51a88f453afea56206e146 Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 22:16:02 +0700 Subject: [PATCH 7/8] Feature: fix approve status --- build/index.js | 1 - .../utils/calculations/getApproveTime.spec.ts | 23 +++++++++++++++++++ .../utils/calculations/getApproveTime.ts | 1 - 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/build/index.js b/build/index.js index b48d80c..50375c3 100644 --- a/build/index.js +++ b/build/index.js @@ -849,7 +849,6 @@ const getApproveTime = (reviews, requiredApprovals) => { [user]: { state: review.state, submittedAt: review.submitted_at }, }; }, {}) || {}); - console.log(statuses); const isApproved = statuses.filter((status) => status.state === "approved").length >= requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); diff --git a/src/converters/utils/calculations/getApproveTime.spec.ts b/src/converters/utils/calculations/getApproveTime.spec.ts index af08aaa..cb3d7a9 100644 --- a/src/converters/utils/calculations/getApproveTime.spec.ts +++ b/src/converters/utils/calculations/getApproveTime.spec.ts @@ -11,6 +11,24 @@ const approvedReview = [ }, ]; +const approvedTwiceReviewWithRequiredApprovals = [ + { + state: "approved", + submitted_at: "2024-01-11T07:00:00Z", + user: { login: "dev1" }, + }, + { + state: "changes_requested", + submitted_at: "2024-01-11T09:00:00Z", + user: { login: "dev2" }, + }, + { + state: "approved", + submitted_at: "2024-01-11T11:00:00Z", + user: { login: "dev2" }, + }, +] + const approvedTwiceReview = [ { state: "approved", @@ -178,4 +196,9 @@ describe("check getApproveTime", () => { it("Check PR with 3 approvals and return time of the third approval", () => { expect(getApproveTime(approvedTwiceReview, 3)).toBe(null); }); + it("Check PR with 2 approvals and return time of the second approval", () => { + expect(getApproveTime(approvedTwiceReviewWithRequiredApprovals, 2)).toBe( + "2024-01-11T11:00:00Z" + ); + }); }); diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index f38b2da..dc35c93 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -43,7 +43,6 @@ export const getApproveTime = ( ) || {} ); - console.log(statuses); const isApproved = statuses.filter((status) => status.state === "approved").length >= From a8624ba4d2de59181333bce14aa531f83fda03ab Mon Sep 17 00:00:00 2001 From: Aleksei Simatov Date: Sun, 13 Jul 2025 22:20:35 +0700 Subject: [PATCH 8/8] Feature: fix approve status --- build/index.js | 3 --- src/converters/utils/calculations/getApproveTime.ts | 7 ------- 2 files changed, 10 deletions(-) diff --git a/build/index.js b/build/index.js index 50375c3..ea8fee7 100644 --- a/build/index.js +++ b/build/index.js @@ -852,9 +852,6 @@ const getApproveTime = (reviews, requiredApprovals) => { const isApproved = statuses.filter((status) => status.state === "approved").length >= requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); - console.log(isApproved - ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1) - : null); return isApproved ? statuses.sort((a, b) => (0, date_fns_1.isBefore)((0, date_fns_1.parseISO)(a.submittedAt), (0, date_fns_1.parseISO)(b.submittedAt)) ? 1 : -1)[0]?.submittedAt : null; diff --git a/src/converters/utils/calculations/getApproveTime.ts b/src/converters/utils/calculations/getApproveTime.ts index dc35c93..2b93e0a 100644 --- a/src/converters/utils/calculations/getApproveTime.ts +++ b/src/converters/utils/calculations/getApproveTime.ts @@ -49,13 +49,6 @@ export const getApproveTime = ( requiredApprovals && !statuses.some((status) => status.state === "changes_requested"); - console.log( - isApproved - ? statuses.sort((a, b) => - isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1 - ) - : null - ); return isApproved ? statuses.sort((a, b) => isBefore(parseISO(a.submittedAt), parseISO(b.submittedAt)) ? 1 : -1