Skip to content

Commit

Permalink
Merge pull request #27 from neo4j-labs/feature/connectionModalAuraParser
Browse files Browse the repository at this point in the history
Feature/connection modal aura parser
  • Loading branch information
msenechal authored Apr 2, 2024
2 parents 85aa994 + c478756 commit 106de6a
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 28 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions docs/modules/ROOT/pages/Components/ConnectionModal.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@ https://needle-starterkit.graphapp.io/connection-modal-preview[Link to the live
https://github.com/neo4j-labs/neo4j-needle-starterkit/blob/2.0/src/templates/shared/components/ConnectionModal.tsx[Link to the component code,window=_blank]
The connection modal also comes with a dropzone for the user to upload a file containing the connection details. The file can be either an Aura credential file you downloaded when creating an instance, or your own file containing the connection details.
The accepted file format are either `.env` or `.txt` file with the following structure:
[cols="1,2,1"]
|===
| Name | Description | Example
| `NEO4J_URI`
| The URI of the Neo4j database. Ideally you would have the protocol, hostname and port, but all are optional and will be defaulted if not present.
| neo4j+s://abcd1234.databases.neo4j.io:7687
| `NEO4J_USERNAME`
| Your neo4j username
| neo4j
| `NEO4J_PASSWORD`
| Your neo4j password
| password
| `NEO4J_DATABASE?`
| The database name
| neo4j
|===
Example of a `local.env` file:
[source,env]
----
NEO4J_URI=neo4j://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=password
NEO4J_DATABASE=neo4j
----
== Pre-requisite
- Ensure you have the `@neo4j-ndl` library installed in your project to use this `ConnectionModal` component.
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"preview": "vite preview"
},
"dependencies": {
"@neo4j-ndl/base": "^2.6.0",
"@neo4j-ndl/react": "^2.6.2",
"@neo4j-ndl/base": "2.6.0",
"@neo4j-ndl/react": "2.6.2",
"@tanstack/react-table": "^8.9.3",
"autoprefixer": "^10.4.17",
"eslint-plugin-react": "^7.33.2",
Expand All @@ -21,7 +21,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.3",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.11.16",
Expand All @@ -35,7 +36,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"prettier": "^2.7.1",
"typescript": "^5.0.2",
"typescript": "5.3.3",
"vite": "^4.4.5"
}
}
10 changes: 9 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import ConnectionModal from './templates/shared/components/ConnectionModal';
import Header from './templates/shared/components/Header';
import User from './templates/shared/components/User';

import { FileContextProvider } from './context/connectionFile';

import './ConnectionModal.css';

function App() {
const messages = messagesData.listMessages;
const [activeTab, setActiveTab] = useState<string>('Home');
Expand All @@ -33,7 +37,11 @@ function App() {
<Route path='/cybersecurity-preview' element={<Cybersecurity />} />
<Route
path='/connection-modal-preview'
element={<ConnectionModal open={true} setOpenConnection={() => null} setConnectionStatus={() => null} />}
element={
<FileContextProvider>
<ConnectionModal open={true} setOpenConnection={() => null} setConnectionStatus={() => null} />
</FileContextProvider>
}
/>
<Route path='/chat-widget-preview' element={<Chatbot messages={messages} />} />
<Route
Expand Down
5 changes: 5 additions & 0 deletions src/ConnectionModal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.ndl-dropzone-header{
display: flex!important;
flex-direction: row!important;
gap: 30px!important;
}
26 changes: 26 additions & 0 deletions src/context/connectionFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { createContext, useContext, useState, ReactNode, Dispatch, SetStateAction } from 'react';

interface FileContextType {
file: File[] | [];
setFile: Dispatch<SetStateAction<File[]>>;
}
const FileContext = createContext<FileContextType | undefined>(undefined);
interface FileContextProviderProps {
children: ReactNode;
}
const FileContextProvider: React.FC<FileContextProviderProps> = ({ children }) => {
const [file, setFile] = useState<File[] | []>([]);
const value: FileContextType = {
file,
setFile,
};
return <FileContext.Provider value={value}>{children}</FileContext.Provider>;
};
const useFileContext = () => {
const context = useContext(FileContext);
if (!context) {
throw new Error('useFileContext must be used within a FileContextProvider');
}
return context;
};
export { FileContextProvider, useFileContext };
102 changes: 80 additions & 22 deletions src/templates/shared/components/ConnectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Dialog, TextInput, Dropdown, Banner } from '@neo4j-ndl/react';
import { Button, Dialog, TextInput, Dropdown, Banner, Dropzone } from '@neo4j-ndl/react';
import { useState } from 'react';
import { setDriver } from '../utils/Driver';

Expand All @@ -21,16 +21,62 @@ export default function ConnectionModal({
message,
}: ConnectionModalProps) {
const protocols = ['neo4j', 'neo4j+s', 'neo4j+ssc', 'bolt', 'bolt+s', 'bolt+ssc'];
const [selectedProtocol, setSelectedProtocol] = useState<string>('neo4j');
const [hostname, setHostname] = useState<string>('localhost');
const [protocol, setProtocol] = useState<string>('neo4j+s');
const [URI, setURI] = useState<string>('localhost');
const [port, setPort] = useState<number>(7687);
const [database, setDatabase] = useState<string>('neo4j');
const [username, setUsername] = useState<string>('neo4j');
const [password, setPassword] = useState<string>('password');
const [connectionMessage, setMessage] = useState<Message | null>(null);

const [isLoading, setIsLoading] = useState<boolean>(false);

const parseAndSetURI = (uri: string) => {
const uriParts = uri.split('://');
const uriHost = uriParts.pop() || URI;
setURI(uriHost);
const uriProtocol = uriParts.pop() || protocol;
setProtocol(uriProtocol);
const uriPort = Number(uriParts.pop()) || port;
setPort(uriPort);
};

const handleHostPasteChange: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
event.clipboardData.items[0]?.getAsString((value) => {
parseAndSetURI(value);
});
};

const onDropHandler = async (files: Partial<globalThis.File>[]) => {
setIsLoading(true);
if (files.length) {
const [file] = files;

if (file.text) {
const text = await file.text();
const lines = text.split(/\r?\n/);
const configObject = lines.reduce((acc: Record<string, string>, line: string) => {
if (line.startsWith('#') || line.trim() === '') {
return acc;
}

const [key, value] = line.split('=');
if (['NEO4J_URI', 'NEO4J_USERNAME', 'NEO4J_PASSWORD', 'NEO4J_DATABASE'].includes(key)) {
acc[key] = value;
}
return acc;
}, {});
parseAndSetURI(configObject.NEO4J_URI);
setUsername(configObject.NEO4J_USERNAME);
setPassword(configObject.NEO4J_PASSWORD);
setDatabase(configObject.NEO4J_DATABASE);
}
}
setIsLoading(false);
};

function submitConnection() {
const connectionURI = `${selectedProtocol}://${hostname}:${port}`;
const connectionURI = `${protocol}://${URI}${URI.split(':')[1] ? '' : `:${port}`}`;
setDriver(connectionURI, username, password).then((isSuccessful) => {
setConnectionStatus(isSuccessful);
isSuccessful
Expand All @@ -49,41 +95,53 @@ export default function ConnectionModal({
<Dialog.Content className='n-flex n-flex-col n-gap-token-4'>
{message && <Banner type={message.type}>{message.content}</Banner>}
{connectionMessage && <Banner type={connectionMessage.type}>{connectionMessage.content}</Banner>}
<div className='n-flex max-h-24'>
<Dropzone
isTesting={false}
customTitle={<>Drop your env file here</>}
className='n-p-6 end-0 top-0 w-full h-full'
acceptedFileExtensions={['.txt', '.env']}
dropZoneOptions={{
onDrop: (f: Partial<globalThis.File>[]) => {
onDropHandler(f);
},
maxSize: 500,
onDropRejected: (e) => {
if (e.length) {
// eslint-disable-next-line no-console
console.log(`Failed To Upload, File is larger than 500 bytes`);
}
},
}}
/>
{isLoading && <div>Loading...</div>}
</div>
<div className='n-flex n-flex-row n-flex-wrap'>
<Dropdown
id='protocol'
label='Protocol'
type='select'
size='medium'
disabled={false}
selectProps={{
onChange: (newValue) => newValue && setSelectedProtocol(newValue.value),
onChange: (newValue) => newValue && setProtocol(newValue.value),
options: protocols.map((option) => ({ label: option, value: option })),
value: { label: selectedProtocol, value: selectedProtocol },
value: { label: protocol, value: protocol },
}}
className='w-1/4 inline-block'
fluid
/>
<div className='ml-[2.5%] w-[55%] mr-[2.5%] inline-block'>
<div className='ml-[5%] w-[70%] inline-block'>
<TextInput
id='url'
value={hostname}
value={URI}
disabled={false}
label='Hostname'
label='URI'
placeholder='localhost'
autoFocus
fluid
onChange={(e) => setHostname(e.target.value)}
/>
</div>
<div className='w-[15%] inline-block'>
<TextInput
id='port'
value={port}
disabled={false}
label='Port'
placeholder='7687'
fluid
onChange={(e) => setPort(Number(e.target.value))}
onChange={(e) => setURI(e.target.value)}
onPaste={(e) => handleHostPasteChange(e)}
/>
</div>
</div>
Expand All @@ -97,7 +155,7 @@ export default function ConnectionModal({
onChange={(e) => setDatabase(e.target.value)}
className='w-full'
/>
<div className='n-flex n-flex-row n-flex-wrap'>
<div className='n-flex n-flex-row n-flex-wrap mb-2'>
<div className='w-[48.5%] mr-1.5 inline-block'>
<TextInput
id='username'
Expand Down
2 changes: 1 addition & 1 deletion src/templates/shared/utils/Driver.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import neo4j, { Driver } from 'neo4j-driver';

let driver: Driver;
export let driver: Driver;

export async function setDriver(connectionURI: string, username: string, password: string) {
try {
Expand Down

0 comments on commit 106de6a

Please sign in to comment.