Skip to content

Commit

Permalink
Feat: Adds possibility to also comment on commits (solves #392) (#408)
Browse files Browse the repository at this point in the history
* feat: Adds option to comment on a commit instead of the PR

* test: Testing this feature

* feat: Allows multiple selections for the comment-on option

* ci: Fix test for dependabot by using different artifact name
  • Loading branch information
davelosert authored Sep 1, 2024
1 parent 9f92563 commit af7a719
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 24 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
- name: "Upload Coverage"
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.branch }}
## We can't use the branch name as it can contain invalid characters (e.g. dependatbot hast forwared slashes in it's branch name)
name: "coverage-${{ matrix.branch == 'main' && 'main' || 'test-branch' }}"
path: coverage

build-and-report:
Expand All @@ -56,7 +57,7 @@ jobs:
- name: "Download Coverage Artifacts for ${{ github.head_ref}}"
uses: actions/download-artifact@v4
with:
name: coverage-${{ github.head_ref }}
name: coverage-test-branch
path: coverage

- name: "Download Coverage Artifacts for main"
Expand All @@ -70,3 +71,4 @@ jobs:
with:
file-coverage-mode: "all"
json-summary-compare-path: coverage-main/coverage-summary.json
comment-on: 'pr,commit'
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,18 @@ This action requires the `pull-request: write` permission to add a comment to yo

### Options

| Option | Description | Default |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `working-directory` | The main path to search for coverage- and configuration files (adjusting this is especially useful in monorepos). | `./` |
| `json-summary-path` | The path to the json summary file. | `${working-directory}/coverage/coverage-summary.json` |
| `json-final-path` | The path to the json final file. | `${working-directory}/coverage/coverage-final.json` |
| `vite-config-path` | The path to the vite config file. Will check the same paths as vite and vitest | Checks pattern `${working-directory}/vite[st].{config | workspace}.{t\|mt\|ct\|j\|mj\|cj}s` |
| `github-token` | A GitHub access token with permissions to write to issues (defaults to `secrets.GITHUB_TOKEN`). | `${{ github.token }}` |
| `file-coverage-mode` | Defines how file-based coverage is reported. Possible values are `all`, `changes` or `none`. | `changes` |
| `name` | Give the report a custom name. This is useful if you want multiple reports for different test suites within the same PR. Needs to be unique. | '' |
| `json-summary-compare-path` | The path to the json summary file to compare against. If given, will display a trend indicator and the difference in the summary. Respects the `working-directory` option. | undefined |
| `pr-number` | The number of the PR to post a comment to (if any) | If in the context of a PR, the number of that PR.<br/> If in the context of a triggered workflow, the PR of the triggering workflow. <br/>If no PR context is found, it defaults to `undefined` |
| Option | Description | Default |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `working-directory` | The main path to search for coverage- and configuration files (adjusting this is especially useful in monorepos). | `./` |
| `json-summary-path` | The path to the json summary file. | `${working-directory}/coverage/coverage-summary.json` |
| `json-final-path` | The path to the json final file. | `${working-directory}/coverage/coverage-final.json` |
| `vite-config-path` | The path to the vite config file. Will check the same paths as vite and vitest | Checks pattern `${working-directory}/vite[st].{config | workspace}.{t\|mt\|ct\|j\|mj\|cj}s` |
| `github-token` | A GitHub access token with permissions to write to issues (defaults to `secrets.GITHUB_TOKEN`). | `${{ github.token }}` |
| `file-coverage-mode` | Defines how file-based coverage is reported. Possible values are `all`, `changes` or `none`. | `changes` |
| `name` | Give the report a custom name. This is useful if you want multiple reports for different test suites within the same PR. Needs to be unique. | '' |
| `json-summary-compare-path` | The path to the json summary file to compare against. If given, will display a trend indicator and the difference in the summary. Respects the `working-directory` option. | undefined |
| `pr-number` | The number of the PR to post a comment to (if any) | If in the context of a PR, the number of that PR.<br/> If in the context of a triggered workflow, the PR of the triggering workflow. <br/>If no PR context is found, it defaults to `undefined` |
| `comment-on` | Specify where you want a comment to appear: "pr" for pull-request (if one can be found), "commit" for the commit in which context the action was run, or "none" for no comments. You can provide a comma-separated list of "pr" and "commit" to comment on both. | `pr` |

#### File Coverage Mode

Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ inputs:
default: ''
pr-number:
required: false
description: 'An optional, user-defined pull request number.'
description: 'An optional, user-defined pull request number to comment on.'
default: ''
comment-on:
required: false
description: 'Specify where you want a comment to appear: "pr" for pull-request (if one can be found), "commit" for the commit in which context the action was run, or "none" for no comments. You can provide a comma-separated list of "pr" and "commit" to comment on both. Uses "pr" by default.'
default: pr
runs:
using: 'node20'
main: 'dist/index.js'
Expand Down
58 changes: 50 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import * as github from "@actions/github";
import { RequestError } from "@octokit/request-error";
import { FileCoverageMode } from "./inputs/FileCoverageMode.js";
import { getPullChanges } from "./inputs/getPullChanges.js";
import { readOptions } from "./inputs/options.js";
import { type Options, readOptions } from "./inputs/options.js";
import {
parseVitestJsonFinal,
parseVitestJsonSummary,
} from "./inputs/parseJsonReports.js";
import { createOctokit } from "./octokit.js";
import { createOctokit, type Octokit } from "./octokit.js";
import { generateCommitSHAUrl } from "./report/generateCommitSHAUrl.js";
import { generateFileCoverageHtml } from "./report/generateFileCoverageHtml.js";
import { generateHeadline } from "./report/generateHeadline.js";
import { generateSummaryTableHtml } from "./report/generateSummaryTableHtml.js";
import type { JsonSummary } from "./types/JsonSummary.js";
import { writeSummaryToCommit } from "./writeSummaryToComment.js";
import { writeSummaryToPR } from "./writeSummaryToPR.js";
import { aw } from "vitest/dist/chunks/reporters.C_zwCd4j.js";

type GitHubSummary = typeof core.summary;

const run = async () => {
const octokit = createOctokit();
Expand Down Expand Up @@ -71,6 +75,22 @@ const run = async () => {
`<em>Generated in workflow <a href=${getWorkflowSummaryURL()}>#${github.context.runNumber}</a> for commit <a href="${commitSHAUrl}">${options.commitSHA.substring(0, 7)}</a> by the <a href="https://github.com/davelosert/vitest-coverage-report-action">Vitest Coverage Report Action</a></em>`,
);

if (options.commentOn.includes("pr")) {
await commentOnPR(octokit, summary, options);
}

if (options.commentOn.includes("commit")) {
await commentOnCommit(octokit, summary, options);
}

await summary.write();
};

async function commentOnPR(
octokit: Octokit,
summary: GitHubSummary,
options: Options,
) {
try {
await writeSummaryToPR({
octokit,
Expand All @@ -87,18 +107,40 @@ const run = async () => {
(error.status === 404 || error.status === 403)
) {
core.warning(
`Couldn't write a comment to the pull-request. Please make sure your job has the permission 'pull-request: write'.
Original Error was: [${error.name}] - ${error.message}
`,
`Couldn't write a comment to the pull request. Please make sure your job has the permission 'pull-request: write'.
Original Error was: [${error.name}] - ${error.message}`,
);
} else {
// Rethrow to handle it in the catch block of the run()-call.
throw error;
}
}
}

await summary.write();
};
async function commentOnCommit(
octokit: Octokit,
summary: GitHubSummary,
options: Options,
) {
try {
await writeSummaryToCommit({
octokit,
summary,
commitSha: options.commitSHA,
});
} catch (error) {
if (
error instanceof RequestError &&
(error.status === 404 || error.status === 403)
) {
core.warning(
`Couldn't write a comment to the commit. Please make sure your job has the permission 'contents: read'.
Original Error was: [${error.name}] - ${error.message}`,
);
} else {
throw error;
}
}
}

function getMarkerPostfix({
name,
Expand Down
73 changes: 73 additions & 0 deletions src/inputs/getCommentOn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as core from "@actions/core";
import { getCommentOn, type CommentOn } from "./getCommentOn";

vi.mock("@actions/core");

describe("getCommentOn()", () => {
beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
vi.clearAllMocks();
});

it("returns the default value ['pr'] if no valid values are provided", () => {
vi.spyOn(core, "getInput").mockReturnValue("invalid1, invalid2");

const result = getCommentOn();
expect(result).toEqual(["pr"]);
expect(core.warning).toHaveBeenCalledWith(
'No valid options for comment-on found. Falling back to default value "pr".',
);
});

it("logs invalid values", () => {
vi.spyOn(core, "getInput").mockReturnValue("pr, invalid, commit");

const result = getCommentOn();
expect(result).toEqual(["pr", "commit"]);
expect(core.warning).toHaveBeenCalledWith(
'Invalid options for comment-on: invalid. Valid options are "pr" and "commit".',
);
});

it("returns valid values correctly", () => {
vi.spyOn(core, "getInput").mockReturnValue("pr, commit");

const result = getCommentOn();
expect(result).toEqual(["pr", "commit"]);
expect(core.warning).not.toHaveBeenCalled();
});

it("trims whitespace from the input", () => {
vi.spyOn(core, "getInput").mockReturnValue("pr, commit");

const result = getCommentOn();

expect(result).toEqual(["pr", "commit"]);

expect(core.warning).not.toHaveBeenCalled();
});

it("returns valid values and logs invalid values", () => {
vi.spyOn(core, "getInput").mockReturnValue(
"pr, invalid, commit, anotherInvalid",
);

const result = getCommentOn();
expect(result).toEqual(["pr", "commit"]);
expect(core.warning).toHaveBeenCalledWith(
'Invalid options for comment-on: invalid, anotherInvalid. Valid options are "pr" and "commit".',
);
});

it("for value 'none', returns empty array", () => {
vi.spyOn(core, "getInput").mockReturnValue("none");

const result = getCommentOn();

expect(result).toEqual([]);
});
});
43 changes: 43 additions & 0 deletions src/inputs/getCommentOn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as core from "@actions/core";

type CommentOn = "pr" | "commit" | "none";

function getCommentOn(): CommentOn[] {
const commentOnInput = core.getInput("comment-on");
if (commentOnInput === "none") {
return [];
}

const commentOnList = commentOnInput.split(",").map((item) => item.trim());

let validCommentOnValues: Array<CommentOn> = [];
const invalidCommentOnValues: string[] = [];

for (const value of commentOnList) {
if (value === "pr" || value === "commit") {
validCommentOnValues.push(value as CommentOn);
} else {
invalidCommentOnValues.push(value);
}
}

if (validCommentOnValues.length === 0) {
core.warning(
`No valid options for comment-on found. Falling back to default value "pr".`,
);
validCommentOnValues = ["pr"];
return validCommentOnValues;
}

if (invalidCommentOnValues.length > 0) {
core.warning(
`Invalid options for comment-on: ${invalidCommentOnValues.join(", ")}. Valid options are "pr" and "commit".`,
);
}

return validCommentOnValues;
}

export { getCommentOn };

export type { CommentOn };
2 changes: 1 addition & 1 deletion src/inputs/getCommitSHA.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getCommitSHA } from "./getCommitSHA";

const mockContext = vi.hoisted(() => ({
Expand Down
Loading

0 comments on commit af7a719

Please sign in to comment.