From c5c107a0b235317a85801acdb4e205edc6743506 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 21 Sep 2022 21:33:38 +0100 Subject: [PATCH] feat: uncommitted changes UI --- .../app/pages/dashboard/utils/SeedsViewer.tsx | 247 ++++++++++++++---- .../src/server/router/api/v1/seeds/router.ts | 4 + 2 files changed, 203 insertions(+), 48 deletions(-) diff --git a/client/src/app/pages/dashboard/utils/SeedsViewer.tsx b/client/src/app/pages/dashboard/utils/SeedsViewer.tsx index f59275ff2..15d46be4a 100644 --- a/client/src/app/pages/dashboard/utils/SeedsViewer.tsx +++ b/client/src/app/pages/dashboard/utils/SeedsViewer.tsx @@ -4,14 +4,14 @@ import useSetSubheader from "components/layout/header/useSetSubheader"; import Loading from "components/util/Loading"; import useApiQuery from "components/util/query/useApiQuery"; import Select from "components/util/Select"; -import React, { useEffect, useState } from "react"; -import { Col, Row } from "react-bootstrap"; -import { useParams } from "react-router-dom"; +import React, { useEffect, useMemo, useState } from "react"; +import { Button, Col, Modal, Row } from "react-bootstrap"; import Divider from "components/util/Divider"; import Icon from "components/util/Icon"; import { SetState } from "types/react"; import { TachiConfig } from "lib/config"; import ExternalLink from "components/util/ExternalLink"; +import Card from "components/layout/page/Card"; export default function SeedsViewer() { useSetSubheader(["Developer Utils", "Database Seeds Management"]); @@ -58,54 +58,174 @@ export default function SeedsViewer() { // return ; } +type Revision = { c: GitCommit; repo: string }; + function InnerSeedsViewer({ hasLocalAPI }: { hasLocalAPI: boolean }) { - const { repo: initRepo } = useParams<{ - repo: string | undefined; - }>(); + // base, rev a-la traditional git comparisons. Head is expected to be 'after' + // the base, but no order is enforced. + const [baseRev, setBaseRev] = useState(null); + const [headRev, setHeadRev] = useState(null); - // a repo is one of the following: - // null - nothing has been selected yet - // "local" - we're referring to the files on the local development disk - // "gh:NAME/REPO" - we're referring to a repository on github, like gh:TNG-Dev/Tachi - const [repo, setRepo] = useState(initRepo ?? null); - - // A revision is any valid pointer to a commit that Github understands. - const [rev, setRev] = useState(null); + // if we have a base and a head, render the two separately. + if (baseRev && headRev) { + } return ( - -
- Repository: - - {rev && ( - <> - / - {rev.sha} - - )} -
- - - {repo && !rev && ( - <> - - - - )} + + + {baseRev && ( + + + + )}
); } +function SeedsPicker({ + hasLocalAPI, + header, + rev, + setRev, +}: { + hasLocalAPI: boolean; + header: string; + rev: Revision | null; + setRev: SetState; +}) { + // a repo is one of the following: + // null - nothing has been selected yet + // "local" - we're referring to the files on the local development disk + // "GitHub:NAME/REPO" - we're referring to a repository on github, like GitHub:TNG-Dev/Tachi + const [repo, setRepo] = useState(null); + + const [show, setShow] = useState(false); + + const [subject, body] = useMemo(() => { + if (rev === null) { + return [null, null]; + } + + const [subject, _gap, ...maybeBodies] = rev.c.commit.message.split("\n"); + + return [subject, maybeBodies?.join("\n") ?? null]; + }, [rev]); + + const [showBody, setShowBody] = useState(false); + + const authorNotCommitter = rev?.c.commit.author.email !== rev?.c.commit.committer.email; + + return ( + <> + + {rev ? ( +
+ + + {rev.repo}/{rev.c.sha} + + : {subject} + + {body && ( + setShowBody(!showBody)} + > + {showBody ? ( + + ) : ( + + )} + + )} + {showBody &&
{body}
} + +
+
+ {authorNotCommitter ? ( + <> + Authored by {rev.c.commit.author.name}, Committed by{" "} + {rev.c.commit.committer.name} + + ) : ( + <> + Authored by {rev.c.commit.author.name} + + )} +
+
+ {FormatTime(Date.parse(rev.c.commit.author.date))} +
+
+ +
+ setShow(true)} + > + Change Commit... + +
+
+ ) : ( + + )} +
+ setShow(false)}> + + Pick {header} + + + + +
+ Repository: + +
+ + + {repo && ( + <> + + { + setRev({ c: commit, repo }); + setShow(false); + }} + /> + + )} + +
+
+
+ + ); +} + // stolen straight from server/src/utils/git.ts interface GitCommit { sha: string; @@ -124,12 +244,12 @@ interface GitCommit { }; } -function RevSelector({ repo, setRev }: { repo: string; setRev: SetState }) { +function RevSelector({ repo, onSelect }: { repo: string; onSelect: (g: GitCommit) => void }) { const [revs, setRevs] = useState>([]); useEffect(() => { (async () => { - if (repo.startsWith("gh:")) { + if (repo.startsWith("GitHub:")) { throw new Error("Unsupported..."); } else { // local @@ -139,7 +259,32 @@ function RevSelector({ repo, setRev }: { repo: string; setRev: SetState( + "/seeds/has-uncommitted-changes" + ); + + if (hasUncommittedRes.success && hasUncommittedRes.body) { + const LOCAL_COMMIT: GitCommit = { + sha: "WORKING_DIRECTORY", + commit: { + author: { + name: "Not Committed Yet", + date: "1970-01-01", + email: "null@example.com", + }, + committer: { + name: "Not Committed Yet", + date: "1970-01-01", + email: "null@example.com", + }, + message: "Uncommitted changes on your local disk.", + }, + }; + + setRevs([LOCAL_COMMIT, ...res.body]); + } else { + setRevs(res.body); + } } })(); }, [repo]); @@ -148,13 +293,19 @@ function RevSelector({ repo, setRev }: { repo: string; setRev: SetState
{revs.map((r) => ( - + ))} ); } -function Revision({ rev, setRev }: { rev: GitCommit; setRev: SetState }) { +function Revision({ + rev, + onSelect: onSelect, +}: { + rev: GitCommit; + onSelect: (g: GitCommit) => void; +}) { const authorNotCommitter = rev.commit.author.email !== rev.commit.committer.email; // if there's a body then there's two newlines. @@ -168,10 +319,10 @@ function Revision({ rev, setRev }: { rev: GitCommit; setRev: SetState
-
+
{rev.sha}:{" "} - setRev(rev)}> + onSelect(rev)}> {subject} diff --git a/server/src/server/router/api/v1/seeds/router.ts b/server/src/server/router/api/v1/seeds/router.ts index f47279f60..8d571ba73 100644 --- a/server/src/server/router/api/v1/seeds/router.ts +++ b/server/src/server/router/api/v1/seeds/router.ts @@ -82,6 +82,10 @@ router.get("/has-uncommitted-changes", async (req, res) => { // local changes. const hasUncommittedChanges = stdout .split("\n") + + // note that doing this properly is frustrating. This has false positives for + // routes that partially contain this route. I've ameliorated this slightly with + // a leading space, but that is not a proper solution. .some((row) => / database-seeds\/collections/u.exec(row)); return res.status(200).json({