diff --git a/graphql-server/schema.gql b/graphql-server/schema.gql index 3978bb7c..c0ae0ea6 100644 --- a/graphql-server/schema.gql +++ b/graphql-server/schema.gql @@ -291,6 +291,17 @@ type RemoteList { list: [Remote!]! } +type PullRes { + fastForward: Boolean! + conflicts: Int! + message: String! +} + +type PushRes { + success: Boolean! + message: String! +} + type Query { branch(databaseName: String!, branchName: String!): Branch branchOrDefault(databaseName: String!, branchName: String): Branch @@ -361,8 +372,10 @@ type Mutation { resetDatabase(newDatabase: String): Boolean! loadDataFile(schemaName: String, tableName: String!, refName: String!, databaseName: String!, importOp: ImportOperation!, fileType: FileType!, file: Upload!, modifier: LoadDataModifier): Boolean! mergePull(fromBranchName: String!, toBranchName: String!, databaseName: String!, author: AuthorInfo): Boolean! - addRemote(databaseName: String!, remoteName: String!, remoteUrl: String!): String! + addRemote(remoteName: String!, databaseName: String!, remoteUrl: String!): String! deleteRemote(databaseName: String!, remoteName: String!): Boolean! + pullFromRemote(remoteName: String!, databaseName: String!, branchName: String!): PullRes! + pushToRemote(remoteName: String!, databaseName: String!, branchName: String!): PushRes! restoreAllTables(databaseName: String!, refName: String!): Boolean! createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!, author: AuthorInfo): String! deleteTag(databaseName: String!, tagName: String!): Boolean! diff --git a/graphql-server/src/queryFactory/dolt/index.ts b/graphql-server/src/queryFactory/dolt/index.ts index 234372e2..0a778891 100644 --- a/graphql-server/src/queryFactory/dolt/index.ts +++ b/graphql-server/src/queryFactory/dolt/index.ts @@ -457,6 +457,22 @@ export class DoltQueryFactory args.databaseName, ); } + + async callPullRemote(args: t.PushOrPullRemoteArgs): t.PR { + return this.query( + qh.callPullRemote, + [args.remoteName, args.branchName], + args.databaseName, + ); + } + + async callPushRemote(args: t.PushOrPullRemoteArgs): t.PR { + return this.query( + qh.callPushRemote, + [args.remoteName, args.branchName], + args.databaseName, + ); + } } async function getTableInfoWithQR( diff --git a/graphql-server/src/queryFactory/dolt/queries.ts b/graphql-server/src/queryFactory/dolt/queries.ts index 8eb926e2..2cb7c870 100644 --- a/graphql-server/src/queryFactory/dolt/queries.ts +++ b/graphql-server/src/queryFactory/dolt/queries.ts @@ -80,6 +80,10 @@ export const callAddRemote = `CALL DOLT_REMOTE("add", ?, ?)`; export const callDeleteRemote = `CALL DOLT_REMOTE("remove", ?)`; +export const callPullRemote = `CALL DOLT_PULL(?, ?)`; + +export const callPushRemote = `CALL DOLT_PUSH(?, ?)`; + // TAGS export const callDeleteTag = `CALL DOLT_TAG("-d", ?)`; diff --git a/graphql-server/src/queryFactory/doltgres/index.ts b/graphql-server/src/queryFactory/doltgres/index.ts index bb841132..3eacd034 100644 --- a/graphql-server/src/queryFactory/doltgres/index.ts +++ b/graphql-server/src/queryFactory/doltgres/index.ts @@ -448,6 +448,22 @@ export class DoltgresQueryFactory args.databaseName, ); } + + async callPullRemote(args: t.PushOrPullRemoteArgs): t.PR { + return this.query( + qh.callPullRemote, + [args.remoteName, args.branchName], + args.databaseName, + ); + } + + async callPushRemote(args: t.PushOrPullRemoteArgs): t.PR { + return this.query( + qh.callPushRemote, + [args.remoteName, args.branchName], + args.databaseName, + ); + } } async function getTableInfoWithQR( diff --git a/graphql-server/src/queryFactory/doltgres/queries.ts b/graphql-server/src/queryFactory/doltgres/queries.ts index 85eefa7b..9ec69c4e 100644 --- a/graphql-server/src/queryFactory/doltgres/queries.ts +++ b/graphql-server/src/queryFactory/doltgres/queries.ts @@ -166,3 +166,7 @@ export const callCheckoutTable = `SELECT DOLT_CHECKOUT($1::text)`; export const callAddRemote = `SELECT DOLT_REMOTE('add', $1::text, $2::text)`; export const callDeleteRemote = `SELECT DOLT_REMOTE('remove', $1::text)`; + +export const callPullRemote = `SELECT DOLT_PULL($1::text, $2::text)`; + +export const callPushRemote = `SELECT DOLT_PUSH($1::text, $2::text)`; diff --git a/graphql-server/src/queryFactory/index.ts b/graphql-server/src/queryFactory/index.ts index 0ce739b7..97cc09a7 100644 --- a/graphql-server/src/queryFactory/index.ts +++ b/graphql-server/src/queryFactory/index.ts @@ -150,4 +150,8 @@ export declare class QueryFactory { addRemote(args: t.AddRemoteArgs): t.PR; callDeleteRemote(args: t.RemoteArgs): t.PR; + + callPullRemote(args: t.PushOrPullRemoteArgs): t.PR; + + callPushRemote(args: t.PushOrPullRemoteArgs): t.PR; } diff --git a/graphql-server/src/queryFactory/mysql/index.ts b/graphql-server/src/queryFactory/mysql/index.ts index 9c8bcb81..c08f0db8 100644 --- a/graphql-server/src/queryFactory/mysql/index.ts +++ b/graphql-server/src/queryFactory/mysql/index.ts @@ -313,4 +313,12 @@ export class MySQLQueryFactory async callDeleteRemote(_: t.RemoteArgs): t.PR { throw notDoltError("delete remote"); } + + async callPullRemote(_: t.PushOrPullRemoteArgs): t.PR { + throw notDoltError("pull remote"); + } + + async callPushRemote(_: t.PushOrPullRemoteArgs): t.PR { + throw notDoltError("push remote"); + } } diff --git a/graphql-server/src/queryFactory/types.ts b/graphql-server/src/queryFactory/types.ts index 60db548b..e3c630ea 100644 --- a/graphql-server/src/queryFactory/types.ts +++ b/graphql-server/src/queryFactory/types.ts @@ -9,6 +9,7 @@ export type RefMaybeSchemaArgs = RefArgs & { schemaName?: string }; export type BranchArgs = DBArgs & { branchName: string }; export type RemoteArgs = DBArgs & { remoteName: string }; export type AddRemoteArgs = RemoteArgs & { remoteUrl: string }; +export type PushOrPullRemoteArgs = RemoteArgs & { branchName?: string }; export type TagArgs = DBArgs & { tagName: string }; export type TableArgs = RefArgs & { tableName: string }; export type TableMaybeSchemaArgs = TableArgs & { schemaName?: string }; diff --git a/graphql-server/src/remotes/remote.model.ts b/graphql-server/src/remotes/remote.model.ts index b9732505..b1d42b00 100644 --- a/graphql-server/src/remotes/remote.model.ts +++ b/graphql-server/src/remotes/remote.model.ts @@ -1,4 +1,4 @@ -import { Field, ID, ObjectType } from "@nestjs/graphql"; +import { Field, ID, Int, ObjectType } from "@nestjs/graphql"; import { __Type } from "graphql"; import { getNextOffset, ROW_LIMIT } from "../utils"; import { RawRow } from "../queryFactory/types"; @@ -25,6 +25,27 @@ export class RemoteList extends ListOffsetRes { list: Remote[]; } +@ObjectType() +export class PullRes { + @Field() + fastForward: boolean; + + @Field(_type => Int) + conflicts: number; + + @Field() + message: string; +} + +@ObjectType() +export class PushRes { + @Field() + success: boolean; + + @Field() + message: string; +} + export function fromDoltRemotesRow(databaseName: string, r: RawRow): Remote { return { _id: `databases/${databaseName}/remotes/${r.name}`, @@ -45,3 +66,18 @@ export function getRemoteListRes( nextOffset: getNextOffset(remotes.length, args.offset ?? 0), }; } + +export function fromPullRes(r: RawRow): PullRes { + return { + fastForward: r.fast_forward === "1", + conflicts: parseInt(r.conflicts, 10), + message: r.message, + }; +} + +export function fromPushRes(r: RawRow): PushRes { + return { + success: r.status === "0", + message: r.message, + }; +} diff --git a/graphql-server/src/remotes/remote.resolver.ts b/graphql-server/src/remotes/remote.resolver.ts index 9184d747..2a30b829 100644 --- a/graphql-server/src/remotes/remote.resolver.ts +++ b/graphql-server/src/remotes/remote.resolver.ts @@ -7,16 +7,27 @@ import { Resolver, } from "@nestjs/graphql"; import { ConnectionProvider } from "../connections/connection.provider"; -import { DBArgs, DBArgsWithOffset, RemoteArgs } from "../utils/commonTypes"; -import { getRemoteListRes, Remote, RemoteList } from "./remote.model"; +import { DBArgsWithOffset, RemoteArgs } from "../utils/commonTypes"; +import { + fromPullRes, + fromPushRes, + getRemoteListRes, + PullRes, + PushRes, + Remote, + RemoteList, +} from "./remote.model"; @ArgsType() -export class AddRemoteArgs extends DBArgs { +export class AddRemoteArgs extends RemoteArgs { @Field() - remoteName: string; + remoteUrl: string; +} +@ArgsType() +export class PullOrPushRemoteArgs extends RemoteArgs { @Field() - remoteUrl: string; + branchName: string; } @Resolver(_of => Remote) @@ -44,4 +55,24 @@ export class RemoteResolver { await conn.callDeleteRemote(args); return true; } + + @Mutation(_returns => PullRes) + async pullFromRemote(@Args() args: PullOrPushRemoteArgs): Promise { + const conn = this.conn.connection(); + const res = await conn.callPullRemote(args); + if (res.length === 0) { + throw new Error("No response from pull"); + } + return fromPullRes(res[0]); + } + + @Mutation(_returns => PushRes) + async pushToRemote(@Args() args: PullOrPushRemoteArgs): Promise { + const conn = this.conn.connection(); + const res = await conn.callPushRemote(args); + if (res.length === 0) { + throw new Error("No response from push"); + } + return fromPushRes(res[0]); + } } diff --git a/web/renderer/components/SqlDataTable/useSqlQuery.ts b/web/renderer/components/SqlDataTable/useSqlQuery.ts index 6e1c3c1b..b58bf823 100644 --- a/web/renderer/components/SqlDataTable/useSqlQuery.ts +++ b/web/renderer/components/SqlDataTable/useSqlQuery.ts @@ -6,7 +6,7 @@ import { import { useSessionQueryHistory } from "@dolthub/react-hooks"; import useSqlParser from "@hooks/useSqlParser"; import { SqlQueryParams } from "@lib/params"; -import { refetchSqlUpdateQueriesCacheEvict } from "@lib/refetchQueries"; +import { refetchUpdateDatabaseQueriesCacheEvict } from "@lib/refetchQueries"; import { databases } from "@lib/urls"; import { useRouter } from "next/router"; import { useEffect } from "react"; @@ -33,7 +33,7 @@ export default function useSqlQuery( // time to finish setTimeout(() => { client - .refetchQueries(refetchSqlUpdateQueriesCacheEvict) + .refetchQueries(refetchUpdateDatabaseQueriesCacheEvict) .catch(console.error); }, 300); }, [gqlError, isMut, client]); diff --git a/web/renderer/components/StatusWithOptions/ResetModal.tsx b/web/renderer/components/StatusWithOptions/ResetModal.tsx index 232d52d1..326b4569 100644 --- a/web/renderer/components/StatusWithOptions/ResetModal.tsx +++ b/web/renderer/components/StatusWithOptions/ResetModal.tsx @@ -6,7 +6,7 @@ import useSqlBuilder from "@hooks/useSqlBuilder"; import { ModalProps } from "@lib/modalProps"; import { RefParams } from "@lib/params"; import { getPostgresTableName } from "@lib/postgres"; -import { refetchSqlUpdateQueriesCacheEvict } from "@lib/refetchQueries"; +import { refetchUpdateDatabaseQueriesCacheEvict } from "@lib/refetchQueries"; import { sqlQuery } from "@lib/urls"; import css from "./index.module.css"; @@ -27,7 +27,7 @@ export default function ResetModal(props: Props) { await mutateFn({ variables: props.params }); props.setIsOpen(false); client - .refetchQueries(refetchSqlUpdateQueriesCacheEvict) + .refetchQueries(refetchUpdateDatabaseQueriesCacheEvict) .catch(console.error); } catch (_) { // Handled by useMutation diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/AddRemotePage/index.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/AddRemotePage/index.tsx index 3bbd582a..8d3bfbca 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/AddRemotePage/index.tsx +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/AddRemotePage/index.tsx @@ -1,9 +1,9 @@ -import { DatabaseParams } from "@lib/params"; +import { OptionalRefParams } from "@lib/params"; import AddRemoteForm from "./AddRemoteForm"; import css from "./index.module.css"; type Props = { - params: DatabaseParams; + params: OptionalRefParams; }; export default function AddRemotePage(props: Props): JSX.Element { diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/Inner.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/Inner.tsx index db7a0082..33e096c3 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/Inner.tsx +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/Inner.tsx @@ -8,12 +8,12 @@ import { newRemote } from "@lib/urls"; import { useState } from "react"; import DeleteModal from "@components/DeleteModal"; import { refetchRemoteQueries } from "@lib/refetchQueries"; -import { DatabaseParams } from "@lib/params"; +import { OptionalRefParams } from "@lib/params"; import RemoteRow from "./RemoteRow"; import css from "./index.module.css"; type InnerProps = { - params: DatabaseParams; + params: OptionalRefParams; remotes: RemoteFragment[]; loadMore: () => Promise; hasMore: boolean; diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PullFromRemoteModal.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PullFromRemoteModal.tsx new file mode 100644 index 00000000..8b895f1b --- /dev/null +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PullFromRemoteModal.tsx @@ -0,0 +1,121 @@ +import { RemoteFragment, usePullFromRemoteMutation } from "@gen/graphql-types"; +import { + Button, + FormInput, + Loader, + ModalButtons, + ModalInner, + ModalOuter, +} from "@dolthub/react-components"; +import { SyntheticEvent, useState } from "react"; +import useMutation from "@hooks/useMutation"; +import { OptionalRefParams } from "@lib/params"; +import Link from "@components/links/Link"; +import { refetchUpdateDatabaseQueriesCacheEvict } from "@lib/refetchQueries"; +import { database } from "@lib/urls"; +import router from "next/router"; +import useDefaultBranch from "@hooks/useDefaultBranch"; +import css from "./index.module.css"; + +type Props = { + isOpen: boolean; + setIsOpen: (d: boolean) => void; + remote: RemoteFragment; + params: OptionalRefParams; +}; + +export default function PullFromRemoteModal({ + isOpen, + setIsOpen, + remote, + params, +}: Props) { + const { defaultBranchName } = useDefaultBranch(params); + const pullIntoBranch = params.refName || defaultBranchName; + const [branchName, setBranchName] = useState(""); + const { mutateFn: pull, ...res } = useMutation({ + hook: usePullFromRemoteMutation, + }); + const [message, setMessage] = useState(""); + + const onClose = () => { + setIsOpen(false); + res.setErr(undefined); + setBranchName(""); + setMessage(""); + }; + + const onSubmit = async (e: SyntheticEvent) => { + e.preventDefault(); + const pullRes = await pull({ + variables: { + databaseName: params.databaseName, + remoteName: remote.name, + branchName, + }, + }); + if (!pullRes.data) return; + const msg = pullRes.data.pullFromRemote.message; + if ( + pullRes.data.pullFromRemote.conflicts || + msg.includes("cannot fast forward") + ) { + res.setErr(new Error(msg)); + return; + } + + if (msg === "Everything up-to-date") { + setMessage(msg); + return; + } + await res.client + .refetchQueries(refetchUpdateDatabaseQueriesCacheEvict) + .catch(console.error); + const { href, as } = database(params); + router.push(href, as).catch(console.error); + }; + + return ( + +
+ +

+ Fetch from remote {remote.name} ( + {remote.url}) and merge into current branch{" "} + {pullIntoBranch}. To learn more + about pulling from remotes, see our{" "} + + documentation + +

+ { + setBranchName(s); + setMessage(""); + res.setErr(undefined); + }} + placeholder="Enter branch name from the remote to pull from" + light + /> +
+ + {message.includes("Everything up-to-date") ? ( + + ) : ( + + )} + + {message &&

{message}

} +
+ +
+ ); +} diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PushToRemoteModal.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PushToRemoteModal.tsx new file mode 100644 index 00000000..9eae25ad --- /dev/null +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/PushToRemoteModal.tsx @@ -0,0 +1,124 @@ +import { RemoteFragment, usePushToRemoteMutation } from "@gen/graphql-types"; +import { + Button, + FormInput, + Loader, + ModalButtons, + ModalInner, + ModalOuter, + SuccessMsg, +} from "@dolthub/react-components"; +import { SyntheticEvent, useState } from "react"; +import useMutation from "@hooks/useMutation"; +import { OptionalRefParams } from "@lib/params"; +import Link from "@components/links/Link"; +import useDefaultBranch from "@hooks/useDefaultBranch"; +import css from "./index.module.css"; + +type Props = { + isOpen: boolean; + setIsOpen: (d: boolean) => void; + remote: RemoteFragment; + params: OptionalRefParams; +}; + +export default function PushToRemoteModal({ + isOpen, + setIsOpen, + remote, + params, +}: Props) { + const { defaultBranchName } = useDefaultBranch(params); + const [branchName, setBranchName] = useState( + params.refName || defaultBranchName, + ); + const [message, setMessage] = useState(""); + const { mutateFn: push, ...res } = useMutation({ + hook: usePushToRemoteMutation, + }); + + const onClose = () => { + setIsOpen(false); + res.setErr(undefined); + setBranchName(""); + setMessage(""); + }; + + const onSubmit = async (e: SyntheticEvent) => { + e.preventDefault(); + const pushRes = await push({ + variables: { + databaseName: params.databaseName, + remoteName: remote.name, + branchName, + }, + }); + if (!pushRes.data) { + return; + } + const msg = pushRes.data.pushToRemote.message; + if (!pushRes.data.pushToRemote.success) { + res.setErr(new Error(msg)); + return; + } + setMessage(msg); + }; + + return ( + +
+ +

+ Update remote {remote.name} ( + {remote.url}) with changes from the specified branch. To learn more + about pushing to remotes, see our{" "} + + documentation + +

+ { + setBranchName(s); + setMessage(""); + res.setErr(undefined); + }} + placeholder="Enter branch name to push to remote" + light + /> +
+ + {message ? ( + + ) : ( + + )} + + + + +
+ ); +} + +type PushMessageProps = { + message: string; +}; + +function PushMessage({ message }: PushMessageProps) { + if (message.includes("Everything up-to-date")) { + return

{message}

; + } + if (message) { + return ( +
+ Push successful +

{message}

+
+ ); + } + return
; +} diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/RemoteRow.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/RemoteRow.tsx index 38b59b10..3b4f6f0b 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/RemoteRow.tsx +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/RemoteRow.tsx @@ -1,34 +1,89 @@ import { RemoteFragment } from "@gen/graphql-types"; import HideForNoWritesWrapper from "@components/util/HideForNoWritesWrapper"; -import { Button } from "@dolthub/react-components"; +import { ButtonWithPopup } from "@dolthub/react-components"; import { FaRegTrashAlt } from "@react-icons/all-files/fa/FaRegTrashAlt"; -import { DatabaseParams } from "@lib/params"; +import { IoPushOutline } from "@react-icons/all-files/io5/IoPushOutline"; +import { OptionalRefParams } from "@lib/params"; +import { useState } from "react"; +import { DropdownItem } from "@components/DatabaseOptionsDropdown"; +import { fakeEscapePress } from "@dolthub/web-utils"; +import PullFromRemoteModal from "./PullFromRemoteModal"; +import PushToRemoteModal from "./PushToRemoteModal"; import css from "./index.module.css"; type Props = { remote: RemoteFragment; onDeleteClicked: () => void; - params: DatabaseParams; + params: OptionalRefParams; }; export default function RemoteRow({ remote, onDeleteClicked, params }: Props) { + const [open, setOpen] = useState(false); + const [pullModalOpen, setPullModalOpen] = useState(false); + const [pushModalOpen, setPushModalOpen] = useState(false); + return ( {remote.name} {remote.url} {remote.fetchSpecs?.join(",")} - + - setOpen(true)} + onClose={() => setOpen(false)} + triggerText="Actions" + open={open} > - - +
+
    + { + setPullModalOpen(true); + fakeEscapePress(); + }} + icon={} + > + Pull from remote + + { + setPushModalOpen(true); + fakeEscapePress(); + }} + icon={} + > + Push to remote + + } + > + Remove remote + +
+
+
+ + ); } diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.module.css b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.module.css index 30c7b99f..ae19acfa 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.module.css +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.module.css @@ -46,10 +46,18 @@ @apply text-primary my-4 text-center; } -.trashColumn { - @apply flex justify-end; +.pullIcon { + transform: rotateX(180deg); } -.icon { - @apply text-lg m-4; +.trashIcon { + @apply text-red-300; +} + +.bold { + @apply font-semibold; +} + +.message { + @apply mx-8 mb-8 whitespace-pre-wrap; } diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.tsx index 1d92200c..8171a8c8 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.tsx +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/index.tsx @@ -1,15 +1,13 @@ import { Loader, QueryHandler } from "@dolthub/react-components"; - -import { DatabaseParams } from "@lib/params"; +import { OptionalRefParams } from "@lib/params"; import { gqlDepNotFound } from "@lib/errors/graphql"; import { errorMatches } from "@lib/errors/helpers"; import Database404 from "@components/Database404"; - import { useRemoteList } from "./useRemoteList"; import Inner from "./Inner"; type Props = { - params: DatabaseParams; + params: OptionalRefParams; }; export default function RemotesPage({ params }: Props) { diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/queries.ts b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/queries.ts index 0c0f2f8f..99671f29 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/queries.ts +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/RemotesPage/queries.ts @@ -22,3 +22,44 @@ export const DELETE_REMOTE = gql` deleteRemote(remoteName: $remoteName, databaseName: $databaseName) } `; + +export const PULL_FROM_REMOTE = gql` + fragment PullRes on PullRes { + fastForward + conflicts + message + } + mutation PullFromRemote( + $remoteName: String! + $branchName: String! + $databaseName: String! + ) { + pullFromRemote( + remoteName: $remoteName + branchName: $branchName + databaseName: $databaseName + ) { + ...PullRes + } + } +`; + +export const PUSH_TO_REMOTE = gql` + fragment PushRes on PushRes { + success + message + } + mutation PushToRemote( + $remoteName: String! + $branchName: String! + $databaseName: String! + ) { + pushToRemote( + remoteName: $remoteName + branchName: $branchName + databaseName: $databaseName + ) { + ...PushRes + } + } +`; diff --git a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/index.tsx b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/index.tsx index 7ea410c3..a2af9421 100644 --- a/web/renderer/components/pageComponents/DatabasePage/ForRemotes/index.tsx +++ b/web/renderer/components/pageComponents/DatabasePage/ForRemotes/index.tsx @@ -1,13 +1,13 @@ import RemotesBreadcrumbs from "@components/breadcrumbs/RemotesBreadcrumbs"; import NotDoltWrapper from "@components/util/NotDoltWrapper"; -import { DatabaseParams } from "@lib/params"; +import { OptionalRefParams } from "@lib/params"; import { remotes } from "@lib/urls"; import ForDefaultBranch from "../ForDefaultBranch"; import AddRemotePage from "./AddRemotePage"; import RemotesPage from "./RemotesPage"; type Props = { - params: DatabaseParams; + params: OptionalRefParams; newRemote?: boolean; }; diff --git a/web/renderer/gen/graphql-types.tsx b/web/renderer/gen/graphql-types.tsx index 4b757316..6634c25f 100644 --- a/web/renderer/gen/graphql-types.tsx +++ b/web/renderer/gen/graphql-types.tsx @@ -233,6 +233,8 @@ export type Mutation = { deleteTag: Scalars['Boolean']['output']; loadDataFile: Scalars['Boolean']['output']; mergePull: Scalars['Boolean']['output']; + pullFromRemote: PullRes; + pushToRemote: PushRes; removeDatabaseConnection: Scalars['Boolean']['output']; resetDatabase: Scalars['Boolean']['output']; restoreAllTables: Scalars['Boolean']['output']; @@ -321,6 +323,20 @@ export type MutationMergePullArgs = { }; +export type MutationPullFromRemoteArgs = { + branchName: Scalars['String']['input']; + databaseName: Scalars['String']['input']; + remoteName: Scalars['String']['input']; +}; + + +export type MutationPushToRemoteArgs = { + branchName: Scalars['String']['input']; + databaseName: Scalars['String']['input']; + remoteName: Scalars['String']['input']; +}; + + export type MutationRemoveDatabaseConnectionArgs = { name: Scalars['String']['input']; }; @@ -356,6 +372,13 @@ export type PullDetailSummary = { export type PullDetails = PullDetailCommit | PullDetailSummary; +export type PullRes = { + __typename?: 'PullRes'; + conflicts: Scalars['Int']['output']; + fastForward: Scalars['Boolean']['output']; + message: Scalars['String']['output']; +}; + export enum PullState { Merged = 'Merged', Open = 'Open', @@ -376,6 +399,12 @@ export type PullWithDetails = { summary?: Maybe; }; +export type PushRes = { + __typename?: 'PushRes'; + message: Scalars['String']['output']; + success: Scalars['Boolean']['output']; +}; + export type Query = { __typename?: 'Query'; allBranches: Array; @@ -1224,6 +1253,28 @@ export type DeleteRemoteMutationVariables = Exact<{ export type DeleteRemoteMutation = { __typename?: 'Mutation', deleteRemote: boolean }; +export type PullResFragment = { __typename?: 'PullRes', fastForward: boolean, conflicts: number, message: string }; + +export type PullFromRemoteMutationVariables = Exact<{ + remoteName: Scalars['String']['input']; + branchName: Scalars['String']['input']; + databaseName: Scalars['String']['input']; +}>; + + +export type PullFromRemoteMutation = { __typename?: 'Mutation', pullFromRemote: { __typename?: 'PullRes', fastForward: boolean, conflicts: number, message: string } }; + +export type PushResFragment = { __typename?: 'PushRes', success: boolean, message: string }; + +export type PushToRemoteMutationVariables = Exact<{ + remoteName: Scalars['String']['input']; + branchName: Scalars['String']['input']; + databaseName: Scalars['String']['input']; +}>; + + +export type PushToRemoteMutation = { __typename?: 'Mutation', pushToRemote: { __typename?: 'PushRes', success: boolean, message: string } }; + export type LoadDataMutationVariables = Exact<{ databaseName: Scalars['String']['input']; refName: Scalars['String']['input']; @@ -1650,6 +1701,19 @@ export const RemoteFragmentDoc = gql` fetchSpecs } `; +export const PullResFragmentDoc = gql` + fragment PullRes on PullRes { + fastForward + conflicts + message +} + `; +export const PushResFragmentDoc = gql` + fragment PushRes on PushRes { + success + message +} + `; export const ColumnForDataTableFragmentDoc = gql` fragment ColumnForDataTable on Column { name @@ -3631,6 +3695,84 @@ export function useDeleteRemoteMutation(baseOptions?: Apollo.MutationHookOptions export type DeleteRemoteMutationHookResult = ReturnType; export type DeleteRemoteMutationResult = Apollo.MutationResult; export type DeleteRemoteMutationOptions = Apollo.BaseMutationOptions; +export const PullFromRemoteDocument = gql` + mutation PullFromRemote($remoteName: String!, $branchName: String!, $databaseName: String!) { + pullFromRemote( + remoteName: $remoteName + branchName: $branchName + databaseName: $databaseName + ) { + ...PullRes + } +} + ${PullResFragmentDoc}`; +export type PullFromRemoteMutationFn = Apollo.MutationFunction; + +/** + * __usePullFromRemoteMutation__ + * + * To run a mutation, you first call `usePullFromRemoteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `usePullFromRemoteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [pullFromRemoteMutation, { data, loading, error }] = usePullFromRemoteMutation({ + * variables: { + * remoteName: // value for 'remoteName' + * branchName: // value for 'branchName' + * databaseName: // value for 'databaseName' + * }, + * }); + */ +export function usePullFromRemoteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(PullFromRemoteDocument, options); + } +export type PullFromRemoteMutationHookResult = ReturnType; +export type PullFromRemoteMutationResult = Apollo.MutationResult; +export type PullFromRemoteMutationOptions = Apollo.BaseMutationOptions; +export const PushToRemoteDocument = gql` + mutation PushToRemote($remoteName: String!, $branchName: String!, $databaseName: String!) { + pushToRemote( + remoteName: $remoteName + branchName: $branchName + databaseName: $databaseName + ) { + ...PushRes + } +} + ${PushResFragmentDoc}`; +export type PushToRemoteMutationFn = Apollo.MutationFunction; + +/** + * __usePushToRemoteMutation__ + * + * To run a mutation, you first call `usePushToRemoteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `usePushToRemoteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [pushToRemoteMutation, { data, loading, error }] = usePushToRemoteMutation({ + * variables: { + * remoteName: // value for 'remoteName' + * branchName: // value for 'branchName' + * databaseName: // value for 'databaseName' + * }, + * }); + */ +export function usePushToRemoteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(PushToRemoteDocument, options); + } +export type PushToRemoteMutationHookResult = ReturnType; +export type PushToRemoteMutationResult = Apollo.MutationResult; +export type PushToRemoteMutationOptions = Apollo.BaseMutationOptions; export const LoadDataDocument = gql` mutation LoadData($databaseName: String!, $refName: String!, $schemaName: String, $tableName: String!, $importOp: ImportOperation!, $fileType: FileType!, $file: Upload!, $modifier: LoadDataModifier) { loadDataFile( diff --git a/web/renderer/lib/refetchQueries.ts b/web/renderer/lib/refetchQueries.ts index 3c1c8b68..fddb28c9 100644 --- a/web/renderer/lib/refetchQueries.ts +++ b/web/renderer/lib/refetchQueries.ts @@ -113,7 +113,7 @@ export const refetchRemoteQueries = ( variables: DatabaseParams, ): RefetchQueries => [{ query: gen.RemoteListDocument, variables }]; -export const refetchSqlUpdateQueriesCacheEvict: RefetchOptions = { +export const refetchUpdateDatabaseQueriesCacheEvict: RefetchOptions = { updateCache(cache: TCacheShape) { [ "rows", diff --git a/web/renderer/pages/database/[databaseName]/remotes/index.tsx b/web/renderer/pages/database/[databaseName]/remotes/index.tsx index 961d0270..3b3f3abb 100644 --- a/web/renderer/pages/database/[databaseName]/remotes/index.tsx +++ b/web/renderer/pages/database/[databaseName]/remotes/index.tsx @@ -1,25 +1,31 @@ import Page from "@components/util/Page"; -import { DatabaseParams } from "@lib/params"; +import { DatabaseParams, MaybeRefParams } from "@lib/params"; import DatabasePage from "@pageComponents/DatabasePage"; import { GetServerSideProps, NextPage } from "next"; type Props = { - params: DatabaseParams; + params: MaybeRefParams; }; const DatabaseRemotesPage: NextPage = ({ params }) => ( - + ); // #!if !isElectron export const getServerSideProps: GetServerSideProps = async ({ params, + query, }) => { return { props: { - params: params as DatabaseParams, + params: { + ...(params as DatabaseParams), + refName: query.refName ? String(query.refName) : null, + }, }, }; };