Skip to content

Commit

Permalink
Merge pull request #25 from dolthub/taylor/config
Browse files Browse the repository at this point in the history
Improve configuration page
  • Loading branch information
tbantle22 authored Nov 1, 2023
2 parents 945e18e + 0c96335 commit db7cd62
Show file tree
Hide file tree
Showing 31 changed files with 580 additions and 174 deletions.
3 changes: 2 additions & 1 deletion packages/graphql-server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,9 @@ enum DiffRowType {
type Mutation {
createBranch(databaseName: String!, newBranchName: String!, fromRefName: String!): Branch!
deleteBranch(databaseName: String!, branchName: String!): Boolean!
addDatabaseConnection(url: String, useEnv: Boolean): String!
addDatabaseConnection(url: String, useEnv: Boolean, hideDoltFeatures: Boolean): String!
createDatabase(databaseName: String!): Boolean!
resetDatabase: Boolean!
loadDataFile(tableName: String!, refName: String!, databaseName: String!, importOp: ImportOperation!, fileType: FileType!, file: Upload!, modifier: LoadDataModifier): Boolean!
createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!): Tag!
deleteTag(databaseName: String!, tagName: String!): Boolean!
Expand Down
60 changes: 42 additions & 18 deletions packages/graphql-server/src/dataSources/dataSource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import { RawRows } from "../utils/commonTypes";
export const dbNotFoundErr = "Database connection not found";
export type ParQuery = (q: string, p?: any[] | undefined) => Promise<RawRows>;

class WorkbenchConfig {
hideDoltFeatures: boolean;

connectionUrl: string;
}

@Injectable()
export class DataSourceService {
constructor(
private ds: DataSource | undefined,
private mysqlConfig: mysql.ConnectionOptions | undefined, // Used for file upload
private workbenchConfig: WorkbenchConfig | undefined,
) {}

getDS(): DataSource {
Expand All @@ -19,10 +25,24 @@ export class DataSourceService {
return ds;
}

// Used for file upload only
getMySQLConfig(): mysql.ConnectionOptions {
const { mysqlConfig } = this;
if (!mysqlConfig) throw new Error("MySQL config not found");
return mysqlConfig;
const { workbenchConfig } = this;
if (!workbenchConfig) {
throw new Error("Workbench config not found for MySQL connection");
}

return {
uri: workbenchConfig.connectionUrl,
ssl: {
rejectUnauthorized: false,
},
connectionLimit: 1,
dateStrings: ["DATE"],

// Allows file upload via LOAD DATA
flags: ["+LOCAL_FILES"],
};
}

getQR(): QueryRunner {
Expand Down Expand Up @@ -83,14 +103,21 @@ export class DataSourceService {
});
}

async addDS(connUrl: string) {
getWorkbenchConfig(): WorkbenchConfig | undefined {
return this.workbenchConfig;
}

async addDS(config: WorkbenchConfig) {
if (this.ds?.isInitialized) {
await this.ds.destroy();
}

this.workbenchConfig = config;

this.ds = new DataSource({
type: "mysql",
connectorPackage: "mysql2",
url: connUrl,
url: config.connectionUrl,
ssl: {
rejectUnauthorized: false,
},
Expand All @@ -104,20 +131,17 @@ export class DataSourceService {
},
});

this.mysqlConfig = {
uri: connUrl,
ssl: {
rejectUnauthorized: false,
},
connectionLimit: 1,
dateStrings: ["DATE"],

// Allows file upload via LOAD DATA
flags: ["+LOCAL_FILES"],
};

await this.ds.initialize();
}

async resetDS() {
if (!this.workbenchConfig) {
throw new Error(
"Workbench config not found. Please add connectivity information.",
);
}
await this.addDS(this.workbenchConfig);
}
}

// Cannot use params here for the database revision. It will incorrectly
Expand Down
24 changes: 20 additions & 4 deletions packages/graphql-server/src/databases/database.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class AddDatabaseConnectionArgs {

@Field({ nullable: true })
useEnv?: boolean;

@Field({ nullable: true })
hideDoltFeatures?: boolean;
}

@ObjectType()
Expand Down Expand Up @@ -73,13 +76,13 @@ export class DatabaseResolver {

@Query(_returns => DoltDatabaseDetails)
async doltDatabaseDetails(): Promise<DoltDatabaseDetails> {
const hideDoltFeatures = this.configService.get("HIDE_DOLT_FEATURES");
const workbenchConfig = this.dss.getWorkbenchConfig();
const qr = this.dss.getQR();
try {
const isDolt = await getIsDolt(qr);
return {
isDolt,
hideDoltFeatures: !!hideDoltFeatures && hideDoltFeatures === "true",
hideDoltFeatures: workbenchConfig?.hideDoltFeatures ?? false,
};
} finally {
await qr.release();
Expand All @@ -93,9 +96,16 @@ export class DatabaseResolver {
if (args.useEnv) {
const url = this.configService.get("DATABASE_URL");
if (!url) throw new Error("DATABASE_URL not found in env");
await this.dss.addDS(url);
const hideDoltFeatures = this.configService.get("HIDE_DOLT_FEATURES");
await this.dss.addDS({
connectionUrl: url,
hideDoltFeatures: !!hideDoltFeatures && hideDoltFeatures === "true",
});
} else if (args.url) {
await this.dss.addDS(args.url);
await this.dss.addDS({
connectionUrl: args.url,
hideDoltFeatures: !!args.hideDoltFeatures,
});
} else {
throw new Error("database url not provided");
}
Expand All @@ -115,4 +125,10 @@ export class DatabaseResolver {
await qr.release();
}
}

@Mutation(_returns => Boolean)
async resetDatabase(): Promise<boolean> {
await this.dss.resetDS();
return true;
}
}
10 changes: 5 additions & 5 deletions packages/graphql-server/src/schemas/schema.enums.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { registerEnumType } from "@nestjs/graphql";

export enum SchemaType {
View,
Procedure,
Event,
Trigger,
Table,
View = "view",
Procedure = "procedure",
Event = "event",
Trigger = "trigger",
Table = "table",
}

registerEnumType(SchemaType, { name: "SchemaType" });
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Button from "@components/Button";
import ErrorMsg from "@components/ErrorMsg";
import Modal from "@components/Modal";
import SmallLoader from "@components/SmallLoader";
import Tooltip from "@components/Tooltip";
import { useResetDatabaseMutation } from "@gen/graphql-types";
import useMutation from "@hooks/useMutation";
import { IoReloadSharp } from "@react-icons/all-files/io5/IoReloadSharp";
import { useState } from "react";
import css from "./index.module.css";

export default function ResetConnectionButton() {
const { mutateFn, loading, err, setErr } = useMutation({
hook: useResetDatabaseMutation,
});
const [errorModalOpen, setErrorModalOpen] = useState(false);

const onClose = () => {
setErrorModalOpen(false);
setErr(undefined);
};

return (
<>
<Button.Link
className={css.resetButton}
onClick={async () => mutateFn()}
data-tooltip-content="Reset connection"
data-tooltip-id="reset-connection"
>
<SmallLoader
loaded={!loading}
options={{ color: "#fff", top: "0", opacity: 0.5, left: "-0.5rem" }}
>
<IoReloadSharp />
</SmallLoader>
</Button.Link>
<Tooltip id="reset-connection" />
<Modal
isOpen={errorModalOpen}
onRequestClose={onClose}
title="Error resetting connection"
>
<ErrorMsg err={err} />
<Button onClick={onClose}>Close</Button>
</Modal>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Btn from "@components/Btn";
import { DatabaseParams } from "@lib/params";
import { GiHamburgerMenu } from "@react-icons/all-files/gi/GiHamburgerMenu";
import AddItemDropdown from "./AddItemDropdown";
import ResetConnectionButton from "./ResetConnectionButton";
import css from "./index.module.css";

type Props = {
Expand All @@ -12,6 +13,7 @@ type Props = {
export default function RightHeaderButtons(props: Props) {
return (
<div className={css.topRight}>
<ResetConnectionButton />
<AddItemDropdown params={props.params} />
<div className={css.menu}>
<Btn onClick={props.onMenuClick}>
Expand Down
4 changes: 4 additions & 0 deletions packages/web/components/DatabaseHeaderAndNav/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@
@apply mt-1 ml-3 block;
}
}

.resetButton {
@apply mr-5 text-xl text-white hover:text-ld-lightblue;
}
6 changes: 6 additions & 0 deletions packages/web/components/DatabaseHeaderAndNav/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ export const CURRENT_DATABASE = gql`
currentDatabase
}
`;

export const RESET_DATABASE = gql`
mutation ResetDatabase {
resetDatabase
}
`;
3 changes: 3 additions & 0 deletions packages/web/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default function Navbar(props: Props) {
<div className={css.inner}>
<div className={css.left}>
<DocsLink className={css.link}>Documentation</DocsLink>
<Link className={css.link} href="/configuration">
Configuration
</Link>
</div>

<div>
Expand Down
2 changes: 2 additions & 0 deletions packages/web/components/SmallLoader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cx from "classnames";
import { ReactNode } from "react";
import ReactLoader from "react-loader";
import css from "./index.module.css";

Expand Down Expand Up @@ -28,6 +29,7 @@ type Props = {
loaded: boolean;
className?: string;
options?: Partial<typeof smallLoaderDefaultOptions>;
children?: ReactNode;
};

export default function SmallLoader(props: Props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Button from "@components/Button";
import ButtonsWithError from "@components/ButtonsWithError";
import FormInput from "@components/FormInput";
import Loader from "@components/Loader";
import QueryHandler from "@components/util/QueryHandler";
import {
useAddDatabaseConnectionMutation,
Expand All @@ -10,29 +8,22 @@ import {
import { database } from "@lib/urls";
import { useRouter } from "next/router";
import { SyntheticEvent, useState } from "react";
import Form from "./Form";
import css from "./index.module.css";

type InnerProps = {
hasDatabaseEnv: boolean;
};

function Inner(props: InnerProps) {
const router = useRouter();
const [url, setUrl] = useState("");
const [showForm, setShowForm] = useState(!props.hasDatabaseEnv);
const [addDb, res] = useAddDatabaseConnectionMutation();

const onCancel = props.hasDatabaseEnv
? () => {
setShowForm(false);
setUrl("");
}
: undefined;

const onSubmit = async (e: SyntheticEvent, useEnv = false) => {
const onSubmit = async (e: SyntheticEvent) => {
e.preventDefault();
const variables = useEnv ? { useEnv: true } : { url };
try {
const db = await addDb({ variables });
const db = await addDb({ variables: { useEnv: true } });
await res.client.clearStore();
if (!db.data) {
return;
Expand All @@ -48,38 +39,27 @@ function Inner(props: InnerProps) {

if (!showForm) {
return (
<form onSubmit={async e => onSubmit(e, true)}>
<div className={css.top}>
<p>
A database connection URL was found in the environment. You can use
this connection or create a new one.
</p>
<ButtonsWithError error={res.error}>
<Button type="submit">Use connection URL from env</Button>
<Button onClick={() => setShowForm(true)}>
Change connection URL
</Button>
</ButtonsWithError>
</form>
<form onSubmit={onSubmit}>
<ButtonsWithError error={res.error}>
<Button type="submit">Use connection URL from env</Button>
<Button onClick={() => setShowForm(true)}>
Change connection URL
</Button>
</ButtonsWithError>
</form>
</div>
);
}

return (
<form onSubmit={onSubmit}>
<Loader loaded={!res.loading} />
<FormInput
value={url}
onChangeString={setUrl}
label="Connection string"
placeholder="mysql://[username]:[password]@[host]/[database]"
/>
<ButtonsWithError error={res.error} onCancel={onCancel}>
<Button type="submit">Launch Workbench</Button>
</ButtonsWithError>
</form>
);
return <Form {...props} setShowForm={setShowForm} />;
}

export default function AddConnectionForm() {
export default function AddConnectionOptions() {
const res = useHasDatabaseEnvQuery();
return (
<QueryHandler
Expand Down
Loading

0 comments on commit db7cd62

Please sign in to comment.