Skip to content

Commit

Permalink
feat: author searching (#42)
Browse files Browse the repository at this point in the history
* feat: author searching

This commit adds commit searching based on the name of the author,
utilising fuzzy matching.

I initially thought about just adding a basic search, but quickly
realise that GitHub usernames can be a pain to type, so any help getting
to the search destination faster is a bonus - hence the fuzzy matching.

I didn't do too much digging into the fuzzy searching library, but
Fuse.JS seemed to be a sensible choice. The trade-off for performance is
a complex balance of the setup, search and teardown times.

* fix selected users updating on search

We were recreating the list of users that we pick for deployment on each
render and, due to the way the input works, each keypress causes a
render.

This means that we need to memoise `developerCommits` so that it's only
recreated when the relevant information changes (or on refresh).

However, we _also_ need to memoise `<DeploymentUsers />` otherwise the
order of the users _could_ change with each render.

* PR review feedback
  • Loading branch information
scallaway committed Oct 17, 2023
1 parent 3bb451c commit c8afd57
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 20 deletions.
9 changes: 9 additions & 0 deletions qvet-web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions qvet-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"ajv": "^8.12.0",
"axios": "^1.2.6",
"dayjs": "1.11.7",
"fuse.js": "^6.6.2",
"ky": "^0.33.2",
"octokit": "^2.0.14",
"prettier": "^3.0.0",
Expand Down
62 changes: 46 additions & 16 deletions qvet-web/src/components/CommitSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { TextField } from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Collapse from "@mui/material/Collapse";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useState, useCallback } from "react";
import Fuse from "fuse.js";
import { useState, useCallback, useMemo } from "react";
import { Link } from "react-router-dom";

import AddEmbargoDialog from "src/components/AddEmbargoDialog";
import CommitTable from "src/components/CommitTable";
import ConfigStatus from "src/components/ConfigStatus";
import DeploymentHeadline from "src/components/DeploymentHeadline";
import { CommitComparison, Repository } from "src/octokitHelpers";
import { Commit, CommitComparison, Repository } from "src/octokitHelpers";
import { Config } from "src/utils/config";

interface CommitSummaryProps {
Expand All @@ -25,23 +27,28 @@ export default function CommitSummary({
repo,
}: CommitSummaryProps): React.ReactElement {
const [expand, setExpand] = useState<boolean>(false);
const [search, setSearch] = useState<string>("");

const authorLogins = config.commit.ignore.authors;
const merges = config.commit.ignore.merges;
const base_branch = config.commit.base
? config.commit.base
: repo.default_branch;
const developerCommits = comparison.commits.filter((commit) => {
// Is a merge (and we don't want merges)
if (merges && commit.parents.length > 1) {
return false;
}
const developerCommits = useMemo(
() =>
comparison.commits.filter((commit) => {
// Is a merge (and we don't want merges)
if (merges && commit.parents.length > 1) {
return false;
}

// Author is in the ignore list
return !authorLogins.some(
(ignoredLogin) => ignoredLogin === commit.author?.login,
);
});
// Author is in the ignore list
return !authorLogins.some(
(ignoredLogin) => ignoredLogin === commit.author?.login,
);
}),
[authorLogins, comparison, merges],
);
const ignoredCommitCount =
comparison.commits.length - developerCommits.length;
const ignoredParts = [];
Expand All @@ -66,10 +73,12 @@ export default function CommitSummary({
</Link>
);

// Duplicate the commits to show, so we can reverse the array
const visibleCommits =
const visibleCommits = useFuzzySearch(
// If we're expanding ignored commits, use original without filtering
(expand ? comparison.commits : developerCommits).slice();
(expand ? comparison.commits : developerCommits).slice(),
search,
);
// Reverse the commits to get the latest on top
visibleCommits.reverse();

const deploymentHeadline = (
Expand All @@ -82,11 +91,16 @@ export default function CommitSummary({

return (
<Stack spacing={1}>
<Box padding={1}>
<Box padding={1} display="flex" justifyContent="space-between">
<Stack spacing={1}>
<RepoSummary repo={repo} />
<RepoActions baseSha={comparison.base_commit.sha} />
</Stack>
<TextField
variant="outlined"
placeholder="Search commits"
onChange={(e) => setSearch(e.target.value)}
/>
</Box>
{
<Collapse in={!!deploymentHeadline || !!configStatus}>
Expand Down Expand Up @@ -143,3 +157,19 @@ function RepoActions({ baseSha }: { baseSha: string }) {
</div>
);
}

const useFuzzySearch = (list: Array<Commit>, search: string) => {
const fuse = useMemo(() => {
return new Fuse(list, {
keys: ["author.login", "commit.author.name", "commit.message"],
findAllMatches: true,
// We don't want sorting here as it will mess up the order (since it will
// order by closest match first)
shouldSort: false,
});
}, [list]);

// Only apply fuzzy filtering if we have a search, since Fuse won't return
// the entire list for `.search("")` which is sad
return search ? fuse.search(search).map((value) => value.item) : list;
};
2 changes: 1 addition & 1 deletion qvet-web/src/components/CommitTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Commit } from "src/octokitHelpers";
export default function CommitTable({
commits: rawCommits,
}: {
commits: Array<Commit>;
commits: ReadonlyArray<Commit>;
}): React.ReactElement {
// FIXME warn/paginate on large numbers
const commits = rawCommits.slice(0, 100);
Expand Down
6 changes: 3 additions & 3 deletions qvet-web/src/components/DeploymentHeadline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
UseQueryResult,
QueriesResults,
} from "@tanstack/react-query";
import { useCallback } from "react";
import { memo, useCallback } from "react";

import RelativeTime from "src/components/RelativeTime";
import UserLink from "src/components/UserLink";
Expand Down Expand Up @@ -106,7 +106,7 @@ interface DeploymentUsersProps {
commits: Array<Commit>;
}

function DeploymentUsers({ commits }: DeploymentUsersProps) {
const DeploymentUsers = memo(function ({ commits }: DeploymentUsersProps) {
const allUsers = useTeamMembers();
if (allUsers.isLoading || allUsers.isError || allUsers.data === null)
return null;
Expand Down Expand Up @@ -141,7 +141,7 @@ function DeploymentUsers({ commits }: DeploymentUsersProps) {
</Stack>
</Typography>
);
}
});

export default function DeploymentHeadline({
commits,
Expand Down

0 comments on commit c8afd57

Please sign in to comment.