Skip to content

Commit

Permalink
Feature: time to response (#41)
Browse files Browse the repository at this point in the history
* Feature: Add types for time to response

* Feature: Time to response

* Feature: fixed linter

* Feature: fixed readme

* Feature: returned old review requests
  • Loading branch information
AlexSim93 committed Jun 22, 2024
1 parent 93eeb5c commit a91d0a2
Show file tree
Hide file tree
Showing 23 changed files with 626 additions and 886 deletions.
101 changes: 56 additions & 45 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Use this section to tell users about which versions of your project are currentl

| Version | Supported |
| ------- | ------------------ |
| 3.x.x | :white_check_mark: |
| 2.x.x | :white_check_mark: |
| < 1.x | :x: |

Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ inputs:
description: "Github organizations separated by comma"
required: false
SHOW_STATS_TYPES:
description: "Stats types that should be displayed in report. Values must be separated by comma. Can take values: 'timeline', 'workload', 'pr-quality', 'code-review-engagement'"
description: "Stats types that should be displayed in report. Values must be separated by comma. Can take values: 'timeline', 'workload', 'pr-quality', 'code-review-engagement', 'response-time'"
required: false
default: "timeline, workload, pr-quality, code-review-engagement"
default: "timeline, workload, pr-quality, code-review-engagement, response-time"
AMOUNT:
description: "Amount of PRs"
required: false
Expand Down
1,102 changes: 282 additions & 820 deletions build/index.js

Large diffs are not rendered by default.

File renamed without changes.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "pull-request-analytics-action",
"version": "2.2.10",
"version": "3.0.0",
"description": "Generates detailed PR analytics reports within GitHub, focusing on review efficiency and team performance.",
"main": "build/index.js",
"scripts": {
"start": "node ./build/index.js",
"build": "tsc && ncc build ./dist/index.js -o build",
"tsc": "tsc",
"lint": "eslint ./src",
"lint": "eslint ./src/**/*.ts",
"test": "TZ=utc jest",
"test:watch": "TZ=utc jest --watch",
"prepare": "husky install"
Expand Down
8 changes: 2 additions & 6 deletions src/common/utils/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const validate = () => {
"workload",
"pr-quality",
"code-review-engagement",
"response-time",
],
required: true,
},
Expand All @@ -35,12 +36,7 @@ export const validate = () => {
required: false,
},
EXECUTION_OUTCOME: {
validValues: [
"new-issue",
"collection",
"markdown",
"existing-issue",
],
validValues: ["new-issue", "collection", "markdown", "existing-issue"],
required: true,
},
});
Expand Down
9 changes: 8 additions & 1 deletion src/converters/collectData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
preparePullRequestInfo,
preparePullRequestStats,
preparePullRequestTimeline,
prepareResponseTime,
prepareRequestedReviews,
} from "./utils";
import {
Expand Down Expand Up @@ -55,7 +56,6 @@ export const collectData = (
if (!collection[userKey]) {
collection[userKey] = {};
}

prepareRequestedReviews(reviewRequests, collection, dateKey);

["total", userKey].forEach((key) => {
Expand All @@ -81,6 +81,12 @@ export const collectData = (
userKey,
getPullRequestSize(pullRequest?.additions, pullRequest?.deletions)
);
prepareResponseTime(
data.events[index],
pullRequest,
collection,
dateKey
);
prepareDiscussions(data.comments, collection, index, dateKey, userKey);
});

Expand All @@ -89,5 +95,6 @@ export const collectData = (
collection[key][innerKey] = preparePullRequestStats(innerValue);
});
});

return collection;
};
1 change: 1 addition & 0 deletions src/converters/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const reviewRequestedTimelineEvent = "review_requested";
export const readyForReviewTimelineEvent = "ready_for_review";
export const convertToDraftTimelineEvent = "convert_to_draft";
export const reviewedTimelineEvent = "reviewed";
export const reviewRequestRemoved = "review_request_removed";
8 changes: 8 additions & 0 deletions src/converters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ type TimelinePoints = {
timeToReview?: number;
timeToApprove?: number;
timeToMerge?: number;
timeFromInitialRequestToResponse?: number;
timeFromOpenToResponse?: number;
timeFromRepeatedRequestToResponse?: number;
};

type DiscussionResult = {
Expand Down Expand Up @@ -58,6 +61,11 @@ export type Collection = {
timeToReview?: number[];
timeToApprove?: number[];
timeToMerge?: number[];
timeFromOpenToResponse?: number[];
timeFromInitialRequestToResponse?: number[];
timeFromRepeatedRequestToResponse?: number[];
timeReviewerInDraft?: number[];
unrespondedRequests?: number;
comments?: number;
totalReviewComments?: number;
reviewComments?: number;
Expand Down
42 changes: 42 additions & 0 deletions src/converters/utils/calculations/getResponses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
invalidUserLogin,
reviewRequestRemoved,
reviewRequestedTimelineEvent,
reviewedTimelineEvent,
} from "../../constants";

export const getResponses = (events: any[] | undefined | null = []) => {
return events?.reduce((acc, event) => {
if (event.event === reviewRequestedTimelineEvent) {
const user = event.requested_reviewer?.login || invalidUserLogin;
return {
...acc,
[user]: [...(acc?.[user] || []), [(event as any)?.created_at]],
};
}
if (event.event === reviewedTimelineEvent) {
const user = event.user?.login || invalidUserLogin;
return {
...acc,
[user]: acc[user]?.map((el: any, index: number, arr: any[]) =>
index === arr.length - 1 && el.length < 2
? [el[0], event.submitted_at]
: el
) || [[null, event.submitted_at]],
};
}
if (event.event === reviewRequestRemoved) {
const user = event.requested_reviewer?.login || invalidUserLogin;
return {
...acc,
[user]: acc[user].map(
(el: [string | undefined | null, string | undefined | null]) => [
el[0],
null,
]
),
};
}
return acc;
}, {});
};
5 changes: 3 additions & 2 deletions src/converters/utils/calculations/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { prepareIntervals } from './prepareIntervals';
export { calcIntervals } from './calcIntervals';
export { prepareIntervals } from "./prepareIntervals";
export { calcIntervals } from "./calcIntervals";
export { calcPercentileValue } from "./calcPercentileValue";
export { getApproveTime } from "./getApproveTime";
export { calcWeekendMinutes } from "./calcWeekendMinutes";
Expand All @@ -9,3 +9,4 @@ export { calcDifferenceInMinutes } from "./calcDifferenceInMinutes";
export { calcAverageValue } from "./calcAverageValue";
export { getPullRequestSize } from "./getPullRequestSize";
export { calcDraftTime } from "./calcDraftTime";
export { getResponses } from "./getResponses";
3 changes: 2 additions & 1 deletion src/converters/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { prepareRequestedReviews } from './prepareRequestedReviews';
export { preparePullRequestTimeline } from "./preparePullRequestTimeline";
export { preparePullRequestStats } from "./preparePullRequestStats";
export { preparePullRequestInfo } from "./preparePullRequestInfo";
export { prepareDiscussions } from "./prepareDiscussions";
export { prepareReviews } from "./prepareReviews";
export { prepareResponseTime } from "./prepareResponseTime";
export { prepareRequestedReviews } from "./prepareRequestedReviews";
27 changes: 27 additions & 0 deletions src/converters/utils/preparePullRequestStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,47 @@ export const preparePullRequestStats = (collection: Collection) => {
timeToMerge: calcMedianValue(collection.timeToMerge),
timeToReviewRequest: calcMedianValue(collection.timeToReviewRequest),
timeInDraft: calcMedianValue(collection.timeInDraft),
timeFromInitialRequestToResponse: calcMedianValue(
collection.timeFromInitialRequestToResponse
),
timeFromOpenToResponse: calcMedianValue(
collection.timeFromOpenToResponse
),
timeFromRepeatedRequestToResponse: calcMedianValue(
collection.timeFromRepeatedRequestToResponse
),
},
percentile: {
timeToReview: calcPercentileValue(collection.timeToReview),
timeToApprove: calcPercentileValue(collection.timeToApprove),
timeToMerge: calcPercentileValue(collection.timeToMerge),
timeToReviewRequest: calcPercentileValue(collection.timeToReviewRequest),
timeInDraft: calcPercentileValue(collection.timeInDraft),
timeFromInitialRequestToResponse: calcPercentileValue(
collection.timeFromInitialRequestToResponse
),
timeFromOpenToResponse: calcPercentileValue(
collection.timeFromOpenToResponse
),
timeFromRepeatedRequestToResponse: calcPercentileValue(
collection.timeFromRepeatedRequestToResponse
),
},
average: {
timeToReview: calcAverageValue(collection.timeToReview),
timeToApprove: calcAverageValue(collection.timeToApprove),
timeToMerge: calcAverageValue(collection.timeToMerge),
timeToReviewRequest: calcAverageValue(collection.timeToReviewRequest),
timeInDraft: calcAverageValue(collection.timeInDraft),
timeFromInitialRequestToResponse: calcAverageValue(
collection.timeFromInitialRequestToResponse
),
timeFromOpenToResponse: calcAverageValue(
collection.timeFromOpenToResponse
),
timeFromRepeatedRequestToResponse: calcAverageValue(
collection.timeFromRepeatedRequestToResponse
),
},
};
};
2 changes: 1 addition & 1 deletion src/converters/utils/prepareRequestedReviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export const prepareRequestedReviews = (
};
});
});
};
};
105 changes: 105 additions & 0 deletions src/converters/utils/prepareResponseTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { getMultipleValuesInput, getValueAsIs } from "../../common/utils";
import { makeComplexRequest } from "../../requests";
import { Collection } from "../types";
import { calcDifferenceInMinutes, getResponses } from "./calculations";

export const prepareResponseTime = (
events: any[] | undefined | null = [],
pullRequest: Awaited<
ReturnType<typeof makeComplexRequest>
>["pullRequestInfo"][number],
collection: Record<string, Record<string, Collection>>,
dateKey: string
) => {
const responses = getResponses(events);

Object.entries(responses as Record<string, any[][]>).forEach(
([user, responses]) => {
["total", dateKey].forEach((key) => {

const timeFromInitialRequestToResponse = calcDifferenceInMinutes(
responses[0]?.[0],
responses[0]?.[1],
{
endOfWorkingTime: getValueAsIs("CORE_HOURS_END"),
startOfWorkingTime: getValueAsIs("CORE_HOURS_START"),
},
getMultipleValuesInput("HOLIDAYS")
);

const timeFromOpenToResponse = calcDifferenceInMinutes(
pullRequest?.created_at,
responses[0]?.[1],
{
endOfWorkingTime: getValueAsIs("CORE_HOURS_END"),
startOfWorkingTime: getValueAsIs("CORE_HOURS_START"),
},
getMultipleValuesInput("HOLIDAYS")
);

const timeFromRepeatedRequestToResponse = responses
.filter((el, index) => index > 0)
.map((element) =>
calcDifferenceInMinutes(
element?.[0],
element?.[1],
{
endOfWorkingTime: getValueAsIs("CORE_HOURS_END"),
startOfWorkingTime: getValueAsIs("CORE_HOURS_START"),
},
getMultipleValuesInput("HOLIDAYS")
)
);

collection[user][key] = {
...collection[user][key],
timeFromInitialRequestToResponse:
typeof timeFromInitialRequestToResponse === "number"
? [
...(collection[user][key].timeFromInitialRequestToResponse ||
[]),
timeFromInitialRequestToResponse,
]
: collection[user][key].timeFromInitialRequestToResponse,
timeFromOpenToResponse:
typeof timeFromOpenToResponse === "number"
? [
...(collection[user][key].timeFromOpenToResponse || []),
timeFromOpenToResponse,
]
: collection[user][key].timeFromOpenToResponse,
timeFromRepeatedRequestToResponse: [
...(collection[user][key].timeFromRepeatedRequestToResponse || []),
...(timeFromRepeatedRequestToResponse.filter(
(el) => typeof el === "number"
) as number[]),
],
};
collection.total[key] = {
...collection.total[key],
timeFromInitialRequestToResponse:
typeof timeFromInitialRequestToResponse === "number"
? [
...(collection.total[key].timeFromInitialRequestToResponse ||
[]),
timeFromInitialRequestToResponse,
]
: collection.total[key].timeFromInitialRequestToResponse,
timeFromOpenToResponse:
typeof timeFromOpenToResponse === "number"
? [
...(collection.total[key].timeFromOpenToResponse || []),
timeFromOpenToResponse,
]
: collection.total[key].timeFromOpenToResponse,
timeFromRepeatedRequestToResponse: [
...(collection.total[key].timeFromRepeatedRequestToResponse || []),
...(timeFromRepeatedRequestToResponse.filter(
(el) => typeof el === "number"
) as number[]),
],
};
});
}
);
};
1 change: 0 additions & 1 deletion src/converters/utils/prepareReviews.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { makeComplexRequest } from "../../requests";
import { invalidUserLogin } from "../constants";
import { Collection } from "../types";
import { PullRequestSize } from "./calculations/getPullRequestSize";
Expand Down
2 changes: 2 additions & 0 deletions src/view/createMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createConfigParamsCode,
createPullRequestQualityTable,
createReferences,
createResponseTable,
createReviewTable,
createTimelineContent,
createTotalTable,
Expand All @@ -26,6 +27,7 @@ export const createMarkdown = (
workload: createTotalTable(data, users, date),
"code-review-engagement": createReviewTable(data, users, date),
"pr-quality": createPullRequestQualityTable(data, users, date),
"response-time": createResponseTable(data, users, date),
};

return `
Expand Down
5 changes: 5 additions & 0 deletions src/view/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ export const commentsReceivedHeader = "Comments received";
export const reviewTypesHeader = "Changes requested / Commented / Approved";
export const requestChangesReceived = "Changes requested received";
export const prSizesHeader = "PR size: xs/s/m/l/xl";
export const timeFromRequestToResponseHeader =
"Time from initial request to response";
export const timeFromOpenToResponseHeader = "Time from opening to response";
export const timeFromRepeatedRequestToResponseHeader =
"Time from re-request to response";
Loading

0 comments on commit a91d0a2

Please sign in to comment.