Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve configuration page #25

Merged
merged 5 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4,13 +4,19 @@
import { RawRows } from "../utils/commonTypes";

export const dbNotFoundErr = "Database connection not found";
export type ParQuery = (q: string, p?: any[] | undefined) => Promise<RawRows>;

Check warning on line 7 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type

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 @@
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 All @@ -30,7 +50,7 @@
}

async handleAsyncQuery(
work: (qr: QueryRunner) => Promise<any>,

Check warning on line 53 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
): Promise<RawRows> {
const qr = this.getQR();
try {
Expand All @@ -44,12 +64,12 @@

// Assumes Dolt database
async query(
executeQuery: (pq: ParQuery) => any,

Check warning on line 67 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
dbName?: string,
refName?: string,
): Promise<any> {

Check warning on line 70 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
return this.handleAsyncQuery(async qr => {
async function query(q: string, p?: any[] | undefined): Promise<RawRows> {

Check warning on line 72 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
const res = await qr.query(q, p);
return res;
}
Expand All @@ -64,12 +84,12 @@

// Queries that will work on both MySQL and Dolt
async queryMaybeDolt(
executeQuery: (pq: ParQuery, isDolt: boolean) => any,

Check warning on line 87 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
dbName?: string,
refName?: string,
): Promise<any> {

Check warning on line 90 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
return this.handleAsyncQuery(async qr => {
async function query(q: string, p?: any[] | undefined): Promise<RawRows> {

Check warning on line 92 in packages/graphql-server/src/dataSources/dataSource.service.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
const res = await qr.query(q, p);
return res;
}
Expand All @@ -83,14 +103,21 @@
});
}

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 @@
},
});

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
Loading