Skip to content

Commit

Permalink
#9433: use PaginatedTable for Mod Version History (#9527)
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller authored Nov 15, 2024
1 parent 84e7fe3 commit 72fa4c2
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 69 deletions.
1 change: 1 addition & 0 deletions applications/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"simple-icons": "^5.8.0",
"slugify": "^1.6.6",
"stemmer": "^2.0.1",
"thenby": "^1.3.4",
"uint8array-extras": "^1.4.0",
"use-async-effect": "^2.2.7",
"use-debounce": "^10.0.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface TableProps<
actions?: Actions;
initialPageSize?: number;
rowProps?: (row: UnknownObject) => RowProps;
showSearchFilter: boolean;
showSearchFilter?: boolean;

/**
* Force the table to show a specific record. This can be a function that returns true for the record to show, or
Expand All @@ -57,6 +57,11 @@ interface TableProps<
* tables mounted, this will cause an infinite history loop.
*/
syncURL?: boolean;

/**
* Message to display when there are no records to show.
*/
emptyMessage?: string;
}

const SearchFilter: React.FunctionComponent<{
Expand Down Expand Up @@ -143,7 +148,8 @@ function PaginatedTable<
initialPageSize = 10,
syncURL = false,
rowProps,
showSearchFilter,
emptyMessage = "No records found.",
showSearchFilter = true,
forceShowRecord,
}: TableProps<Row, Actions>): React.ReactElement {
const history = useHistory();
Expand Down Expand Up @@ -299,7 +305,7 @@ function PaginatedTable<
{rows.length === 0 && (
<tr>
<td colSpan={5} className="text-muted">
No records found.
{emptyMessage}
</td>
</tr>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ describe("ModVersionHistory", () => {

await waitFor(() => {
expect(
screen.getByText("Version History requires mod write permission"),
screen.getByText(
"Viewing Version History requires mod write permission",
),
).toBeInTheDocument();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import React, { useMemo } from "react";
import { useSelector } from "react-redux";
import { selectActiveModId } from "@/pageEditor/store/editor/editorSelectors";
import { assertNotNullish } from "@/utils/nullishUtils";
import { Card, Container, Table } from "react-bootstrap";
import { Card, Container } from "react-bootstrap";
import styles from "@/pageEditor/tabs/modVariablesDefinition/ModVariablesDefinitionEditor.module.scss";
import ErrorBoundary from "@/components/ErrorBoundary";
import {
Expand All @@ -33,10 +33,64 @@ import AsyncStateGate from "@/components/AsyncStateGate";
import { dateFormat } from "@/utils/stringUtils";
import { mergeAsyncState, valueToAsyncState } from "@/utils/asyncStateUtils";
import { isInternalRegistryId } from "@/utils/registryUtils";
import type { Column, Row } from "react-table";
import PaginatedTable from "@/components/paginatedTable/PaginatedTable";
import { MemoryRouter } from "react-router";
import { firstBy } from "thenby";
import { compare } from "semver";

function useModPackageVersionsQuery(
modId: RegistryId,
): AsyncState<PackageVersionDeprecated[] | string> {
type TableColumn = Column<PackageVersionDeprecated>;

const COLUMNS: TableColumn[] = [
{
Header: "Version",
accessor: "version",
sortDescFirst: true,
sortType(rowA, rowB, columnId) {
return compare(rowA.original.version, rowB.original.version);
},
},
{
Header: "Timestamp",
accessor: "updated_at",
sortDescFirst: true,
Cell({ value }) {
return <>{dateFormat.format(Date.parse(value))}</>;
},
},
{
Header: "Updated By",
accessor: "updated_by",
sortType: firstBy(
(x: Row<PackageVersionDeprecated>) => x.original.updated_by?.email ?? "",
),
Cell({ value }) {
const { email } = value ?? {};
return email ? (
<a href={`mailto:${email}`}>{email}</a>
) : (
<span className="text-muted">Unknown</span>
);
},
},
{
Header: "Message",
accessor: "message",
disableSortBy: true,
Cell({ value }) {
return value ? (
<>{value}</>
) : (
<span className="text-muted">No message provided</span>
);
},
},
];

function useModPackageVersionsQuery(modId: RegistryId): AsyncState<{
data: PackageVersionDeprecated[];
message: string | undefined;
}> {
// Lookup the surrogate key for the package
const editablePackagesQuery = useGetEditablePackagesQuery(undefined, {
skip: isInternalRegistryId(modId),
Expand All @@ -57,46 +111,30 @@ function useModPackageVersionsQuery(

return useMemo(() => {
if (isInternalRegistryId(modId)) {
return valueToAsyncState("Version History unavailable for unsaved mods");
return valueToAsyncState({
data: [],
message: "Version History unavailable for unsaved mods",
});
}

if (editablePackage) {
return packageVersionsQuery;
return mergeAsyncState(
packageVersionsQuery,
(data: PackageVersionDeprecated[]) => ({
data,
message: undefined,
}),
);
}

return mergeAsyncState(
editablePackagesQuery,
() => "Version History requires mod write permission",
);
return mergeAsyncState(editablePackagesQuery, () => ({
data: [],
message: "Viewing Version History requires mod write permission",
}));
}, [modId, editablePackage, packageVersionsQuery, editablePackagesQuery]);
}

const PackageVersionRow: React.VFC<{ version: PackageVersionDeprecated }> = ({
version,
}) => {
const email = version.updated_by?.email;

return (
<tr>
<td>{version.version}</td>
<td>{dateFormat.format(Date.parse(version.updated_at))}</td>
<td>
{email ? (
<a href={`mailto:${email}`}>{email}</a>
) : (
<span className="text-muted">Unknown</span>
)}
</td>
<td>
{version.message ?? (
<span className="text-muted">No message provided</span>
)}
</td>
</tr>
);
};

const ModVersionHistory: React.FC = () => {
const ModVersionHistory: React.VFC = () => {
const modId = useSelector(selectActiveModId);

assertNotNullish(modId, "No active mod id");
Expand All @@ -108,34 +146,23 @@ const ModVersionHistory: React.FC = () => {
<ErrorBoundary>
<Card>
<Card.Header>Version History</Card.Header>
<Card.Body>
<AsyncStateGate state={packageVersionsQuery}>
{({ data }) => (
<Table>
<thead>
<tr>
<th>Version</th>
<th>Timestamp</th>
<th>Updated By</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{typeof data === "object" ? (
data.map((version) => (
<PackageVersionRow key={version.id} version={version} />
))
) : (
<tr>
<td colSpan={4} className="text-muted">
{data}
</td>
</tr>
)}
</tbody>
</Table>
)}
</AsyncStateGate>
<Card.Body className="p-0">
<MemoryRouter>
<AsyncStateGate state={packageVersionsQuery}>
{({ data: { data, message } }) => (
// PaginatedTable includes a useLocation call because it supports syncing the page number with the URL
// We're not using that feature here, but still need to ensure it's wrapped in a Router so the
// useLocation call doesn't error
<MemoryRouter>
<PaginatedTable
columns={COLUMNS}
data={data}
emptyMessage={message}
/>
</MemoryRouter>
)}
</AsyncStateGate>
</MemoryRouter>
</Card.Body>
</Card>
</ErrorBoundary>
Expand Down
2 changes: 1 addition & 1 deletion applications/browser-extension/src/types/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type Database = components["schemas"]["Database"];
// TODO remove in https://github.com/pixiebrix/pixiebrix-extension/issues/7692
export type PackageVersionDeprecated = SetRequired<
components["schemas"]["PackageVersionDeprecated"],
"updated_at" | "created_at" | "id"
"updated_at" | "created_at" | "id" | "version"
>;

export type PendingInvitation = components["schemas"]["PendingInvitation"];
Expand Down
1 change: 1 addition & 0 deletions knip.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ const knipConfig = {
"@tiptap/extension-underline",
"@tiptap/extension-link",
"@tiptap/extension-image",
"thenby",

// False positives flagged in --production checks.
// In non-production runs, these entries are flagged as unnecessary ignoreDependencies entries
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

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

0 comments on commit 72fa4c2

Please sign in to comment.