Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
seancolsen committed Dec 19, 2023
1 parent 85ed622 commit 8225cc5
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 165 deletions.
2 changes: 1 addition & 1 deletion mathesar_ui/src/AppTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TreeItem } from '@mathesar-component-library/types';

/** @deprecated Use either Connection or ConnectionModel interface instead */
/** @deprecated in favor of Connection */
export interface Database {
id: number;
nickname: string;
Expand Down
10 changes: 10 additions & 0 deletions mathesar_ui/src/component-library/common/utils/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export function requiredNonNullable<T>(
return defaultValue;
}

/**
* Used to make sure that a value is defined before using it.
*/
export function defined<T, U>(
v: T | undefined,
f: (v: T) => U | undefined,
): U | undefined {
return v === undefined ? undefined : f(v);
}

/**
* From https://stackoverflow.com/a/51365037/895563
*/
Expand Down
10 changes: 6 additions & 4 deletions mathesar_ui/src/components/breadcrumb/DatabaseSelector.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
const { connections, currentConnectionId } = connectionsStore;
function makeBreadcrumbSelectorItem(
function makeBreadcrumbSelectorEntry(
connection: Connection,
): BreadcrumbSelectorEntry {
return {
Expand All @@ -20,12 +20,14 @@
isActive: () => connection.id === $currentConnectionId,
};
}
$: breadcrumbEntries = [...$connections.values()].map(
makeBreadcrumbSelectorEntry,
);
</script>

<BreadcrumbSelector
data={new Map([
[$_('connections'), $connections.map(makeBreadcrumbSelectorItem)],
])}
data={new Map([[$_('connections'), breadcrumbEntries]])}
triggerLabel={$_('choose_connection')}
persistentLinks={[
{
Expand Down
37 changes: 13 additions & 24 deletions mathesar_ui/src/pages/connections/ConnectionsPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,42 @@
let filterQuery = '';
$: connections = connectionsStore.connections;
$: ({ connections } = connectionsStore);
$: connectionsRequestStatus = connectionsStore.requestStatus;
function isMatch(connection: Connection, q: string) {
return (
connection.nickname.toLowerCase().includes(q) ||
connection.database.toLowerCase().includes(q)
);
}
function filterConnections(_connections: Connection[], query: string) {
return _connections.filter((connection) => {
if (query) {
const sanitizedQuery = query.trim().toLowerCase();
return isMatch(connection, sanitizedQuery);
}
return true;
});
function filterConnections(allConnections: Connection[], query: string) {
if (!query) return allConnections;
const sanitizedQuery = query.trim().toLowerCase();
const match = (t: string) => t.toLowerCase().includes(sanitizedQuery);
return allConnections.filter((c) => match(c.nickname) || match(c.database));
}
function handleClearFilterQuery() {
filterQuery = '';
}
$: filteredConnections = filterConnections($connections ?? [], filterQuery);
$: filteredConnections = filterConnections(
[...$connections.values()],
filterQuery,
);
</script>

<svelte:head>
<title>{makeSimplePageTitle($_('connections'))}</title>
</svelte:head>

<LayoutWithHeader
cssVariables={{
'--page-padding': '0',
}}
>
<LayoutWithHeader cssVariables={{ '--page-padding': '0' }}>
<div data-identifier="connections-header">
<span>
{$_('database_connections')}
{#if $connections.length}({$connections.length}){/if}
{#if $connections.size}({$connections.size}){/if}
</span>
</div>

<section data-identifier="connections-container">
{#if $connectionsRequestStatus.state === 'failure'}
<Errors errors={$connectionsRequestStatus.errors} />
{:else if $connections.length === 0}
{:else if $connections.size === 0}
<ConnectionsEmptyState />
{:else}
<EntityContainerWithFilterBar
Expand Down
29 changes: 12 additions & 17 deletions mathesar_ui/src/routes/AuthenticatedRoutes.svelte
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
<script lang="ts">
import { Route } from 'tinro';
import { connectionsStore } from '@mathesar/stores/databases';
import { getUserProfileStoreFromContext } from '@mathesar/stores/userProfile';
import { getDatabasePageUrl, CONNECTIONS_URL } from '@mathesar/routes/urls';
import AppendBreadcrumb from '@mathesar/components/breadcrumb/AppendBreadcrumb.svelte';
import WelcomePage from '@mathesar/pages/WelcomePage.svelte';
import ConnectionsPage from '@mathesar/pages/connections/ConnectionsPage.svelte';
import AppendBreadcrumb from '@mathesar/components/breadcrumb/AppendBreadcrumb.svelte';
import { CONNECTIONS_URL, getDatabasePageUrl } from '@mathesar/routes/urls';
import { connectionsStore } from '@mathesar/stores/databases';
import { getUserProfileStoreFromContext } from '@mathesar/stores/userProfile';
import { mapExactlyOne } from '@mathesar/utils/iterUtils';
import AdminRoute from './AdminRoute.svelte';
import DatabaseRoute from './DatabaseRoute.svelte';
import UserProfileRoute from './UserProfileRoute.svelte';
import AdminRoute from './AdminRoute.svelte';
const userProfileStore = getUserProfileStoreFromContext();
$: userProfile = $userProfileStore;
$: ({ connections } = connectionsStore);
$: rootPathRedirectUrl = (() => {
const numberOfConnections = $connections?.length ?? 0;
if (numberOfConnections === 0) {
// There is no redirection when `redirect` is `undefined`.
return undefined;
}
if (numberOfConnections > 1) {
return CONNECTIONS_URL;
}
const firstConnection = $connections[0];
return getDatabasePageUrl(firstConnection.id);
})();
$: rootPathRedirectUrl = mapExactlyOne($connections, {
whenZero: undefined,
whenOne: ([id]) => getDatabasePageUrl(id),
whenMany: CONNECTIONS_URL,
});
</script>

<Route path="/" redirect={rootPathRedirectUrl}>
Expand Down
2 changes: 1 addition & 1 deletion mathesar_ui/src/routes/DatabaseRoute.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
$: connectionsStore.setCurrentConnectionId(connectionId);
$: ({ connections } = connectionsStore);
$: connection = $connections?.find((c) => c.id === connectionId);
$: connection = $connections.get(connectionId);
function handleUnmount() {
connectionsStore.clearCurrentConnectionId();
Expand Down
155 changes: 53 additions & 102 deletions mathesar_ui/src/stores/databases.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable max-classes-per-file */

import {
derived,
get,
Expand All @@ -8,6 +6,11 @@ import {
type Writable,
} from 'svelte/store';

import {
ImmutableMap,
WritableMap,
defined,
} from '@mathesar-component-library';
import connectionsApi, {
type Connection,
type CreateFromKnownConnectionProps,
Expand All @@ -18,77 +21,61 @@ import connectionsApi, {
import type { RequestStatus } from '@mathesar/api/utils/requestUtils';
import { preloadCommonData } from '@mathesar/utils/preloadData';
import type { MakeWritablePropertiesReadable } from '@mathesar/utils/typeUtils';
import { some } from 'iter-tools';

const commonData = preloadCommonData();

export class ConnectionModel {
readonly id: Connection['id'];

readonly nickname: Connection['nickname'];

readonly database: Connection['database'];

readonly username: Connection['username'];

readonly host: Connection['host'];

readonly port: Connection['port'];

constructor(connectionDetails: Connection) {
this.id = connectionDetails.id;
this.nickname = connectionDetails.nickname;
this.database = connectionDetails.database;
this.username = connectionDetails.username;
this.host = connectionDetails.host;
this.port = connectionDetails.port;
}

getConnectionJson(): Connection {
return {
id: this.id,
nickname: this.nickname,
database: this.database,
username: this.username,
host: this.host,
port: this.port,
};
}
function sortConnections(c: Iterable<Connection>): Connection[] {
return [...c].sort((a, b) => a.nickname.localeCompare(b.nickname));
}

with(connectionDetails: Partial<Connection>): ConnectionModel {
return new ConnectionModel({
...this.getConnectionJson(),
...connectionDetails,
});
}
/**
* @returns true if the given connection is the only one that points to the same
* database among all the supplied connections. Connections with the same ids
* will not be compared.
*/
export function connectionHasUniqueDatabaseReference(
connection: Connection,
allConnections: Iterable<Connection>,
): boolean {
return !some(
(c) =>
c.id !== connection.id &&
c.host === connection.host &&
c.port === connection.port &&
c.database === connection.database,
allConnections,
);
}

class ConnectionsStore {
/** TODO_3311: remove this or utilize it */
readonly requestStatus: Writable<RequestStatus> = writable();

/** TODO_3311: make this a Map and sort by nickname */
readonly connections = writable<ConnectionModel[]>([]);
private readonly unsortedConnections = new WritableMap<
Connection['id'],
Connection
>();

/** TODO_3311: make this derived */
readonly count = writable(0);
readonly connections: Readable<ImmutableMap<Connection['id'], Connection>>;

readonly currentConnectionId = writable<Connection['id'] | undefined>();

readonly currentConnection: Readable<ConnectionModel | undefined>;
readonly currentConnection: Readable<Connection | undefined>;

constructor() {
this.requestStatus.set({ state: 'success' });
this.connections.set(
commonData?.connections.map(
(connection) => new ConnectionModel(connection),
) ?? [],
this.unsortedConnections.reconstruct(
(commonData?.connections ?? []).map((c) => [c.id, c]),
);
this.connections = derived(
this.unsortedConnections,
(uc) =>
new ImmutableMap(sortConnections(uc.values()).map((c) => [c.id, c])),
);
this.count.set(commonData?.connections.length ?? 0);
this.currentConnectionId.set(commonData?.current_connection ?? undefined);
this.currentConnection = derived(
[this.connections, this.currentConnectionId],
([connections, currentConnectionId]) =>
connections.find((c) => c.id === currentConnectionId),
([connections, id]) => defined(id, (v) => connections.get(v)),
);
}

Expand All @@ -101,10 +88,7 @@ class ConnectionsStore {
}

private addConnection(connection: Connection) {
this.connections.update((connections) => [
...connections,
new ConnectionModel(connection),
]);
this.unsortedConnections.set(connection.id, connection);
}

async createFromKnownConnection(props: CreateFromKnownConnectionProps) {
Expand All @@ -125,56 +109,25 @@ class ConnectionsStore {
return connection;
}

// async setupConnection(props: SetupConnectionProps) {
// const connection = await connectionsApi.setup(props);
// this.connections.update((connections) => [
// ...connections,
// new ConnectionModel(connection),
// ]);
// return connection;
// }

async updateConnection(
connectionId: Connection['id'],
id: Connection['id'],
properties: Partial<UpdatableConnectionProperties>,
): Promise<Connection> {
const updatedConnection = await connectionsApi.update(
connectionId,
properties,
);
const newConnectionModel = new ConnectionModel(updatedConnection);
this.connections.update((connections) =>
connections.map((connection) => {
if (connection.id === connectionId) {
return newConnectionModel;
}
return connection;
}),
);
return newConnectionModel;
const connection = await connectionsApi.update(id, properties);
this.unsortedConnections.set(id, connection);
return connection;
}

async deleteConnection(
connectionId: Connection['id'],
deleteMathesarSchemas = false,
) {
async deleteConnection(id: Connection['id'], deleteMathesarSchemas = false) {
const connections = get(this.connections);
const connectionToDelete = connections.find(
(conn) => conn.id === connectionId,
);
const otherConnectionsUseSameDb = !!connections.find(
(conn) =>
conn.id !== connectionId &&
conn.database === connectionToDelete?.database,
);
const mathesarSchemasShouldBeDeleted =
!otherConnectionsUseSameDb && deleteMathesarSchemas;
// TODO_3311: do this first so that if there's an error we don't clear the
// UI state
await connectionsApi.delete(connectionId, mathesarSchemasShouldBeDeleted);
this.connections.update((conns) =>
conns.filter((conn) => conn.id !== connectionId),
const connection = connections.get(id);
if (!connection) return;
const databaseIsUnique = connectionHasUniqueDatabaseReference(
connection,
connections.values(),
);
await connectionsApi.delete(id, deleteMathesarSchemas && databaseIsUnique);
this.unsortedConnections.delete(id);
}
}

Expand All @@ -183,5 +136,3 @@ export const connectionsStore: MakeWritablePropertiesReadable<ConnectionsStore>

/** @deprecated Use connectionsStore.currentConnection instead */
export const currentDatabase = connectionsStore.currentConnection;

/* eslint-enable max-classes-per-file */
Loading

0 comments on commit 8225cc5

Please sign in to comment.