Skip to content

Commit

Permalink
some changes
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanak-michal committed Feb 20, 2025
1 parent 715230a commit 003b97b
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 216 deletions.
16 changes: 4 additions & 12 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@ jobs:
shardTotal: [8]

steps:
- name: Set up Neo4j apt repository
run: |
add-apt-repository universe
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | gpg --dearmor -o /etc/apt/keyrings/neotechnology.gpg
echo 'deb [signed-by=/etc/apt/keyrings/neotechnology.gpg] https://debian.neo4j.com stable latest' | tee -a /etc/apt/sources.list.d/neo4j.list
apt-get update
- name: Install Cypher Shell
run: sudo apt-get install -y cypher-shell
- name: Load movies dataset
run: |
wget -O /tmp/movies.cypher https://raw.githubusercontent.com/neo4j-graph-examples/movies/main/scripts/movies.cypher
cypher-shell -a bolt://localhost:7687 -u "neo4j" -p "nothing123" -f /tmp/movies.cypher
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
Expand All @@ -58,6 +46,10 @@ jobs:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Load movies dataset
run: node e2e/load-dataset.js
env:
DB_PASSWORD: 'nothing123'
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright tests (neo4j-read)
Expand Down
33 changes: 33 additions & 0 deletions e2e/load-dataset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import neo4j from 'neo4j-driver-lite';
import dotenv from 'dotenv';

dotenv.config();

const uri = process.env.DB_HOSTNAME || 'bolt://localhost:7687';
const user = process.env.DB_USERNAME || 'neo4j';
const password = process.env.DB_PASSWORD;

const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
const session = driver.session();

const cypherUrl = 'https://raw.githubusercontent.com/neo4j-graph-examples/movies/main/scripts/movies.cypher';

async function loadAndRunCypherQueries() {
try {
const response = await fetch(cypherUrl);
const cypherQueries = await response.text();
const queries = cypherQueries.split(';').filter(query => query.trim() !== '');

for (const query of queries) {
await session.run(query);
console.log(`Executed query: ${query}`);
}
} catch (error) {
console.error('Error loading or executing cypher queries:', error);
} finally {
await session.close();
await driver.close();
}
}

loadAndRunCypherQueries();
4 changes: 2 additions & 2 deletions e2e/neo4j-read/label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ test.describe('Label tab', { tag: '@neo4j-read' }, () => {
test('Table view pagination', async ({ page }) => {
const text = await containerLocator(page, 'table tbody').textContent();
await expect(containerLocator(page).getByLabel('Goto previous page')).toBeDisabled();
await containerLocator(page).getByLabel('Goto page 2').click();
await containerLocator(page).getByLabel('Goto page 2', { exact: true }).click();
await expect(containerLocator(page).getByLabel('Goto previous page')).toBeEnabled();
await expect(containerLocator(page, 'table tbody')).not.toHaveText(text);
await containerLocator(page).getByLabel('Goto next page').click();
await expect(containerLocator(page).getByLabel('Goto page 3')).toHaveAttribute('aria-current', 'page');
await expect(containerLocator(page).getByLabel('Goto page 3', { exact: true })).toHaveAttribute('aria-current', 'page');
await containerLocator(page).getByLabel('pagination').getByRole('button', { name: /\d+/ }).last().click();
await expect(containerLocator(page).getByLabel('Goto next page')).toBeDisabled();
});
Expand Down
4 changes: 2 additions & 2 deletions e2e/neo4j-read/type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ test.describe('Type tab', { tag: '@neo4j-read' }, () => {
test('Table view pagination', async ({ page }) => {
const text = await containerLocator(page, 'table tbody').textContent();
await expect(containerLocator(page).getByLabel('Goto previous page')).toBeDisabled();
await containerLocator(page).getByLabel('Goto page 2').click();
await containerLocator(page).getByLabel('Goto page 2', { exact: true }).click();
await expect(containerLocator(page).getByLabel('Goto previous page')).toBeEnabled();
await expect(containerLocator(page, 'table tbody')).not.toHaveText(text);
await containerLocator(page).getByLabel('Goto next page').click();
await expect(containerLocator(page).getByLabel('Goto page 3')).toHaveAttribute('aria-current', 'page');
await expect(containerLocator(page).getByLabel('Goto page 3', { exact: true })).toHaveAttribute('aria-current', 'page');
await containerLocator(page).getByLabel('pagination').getByRole('button', { name: /\d+/ }).last().click();
await expect(containerLocator(page).getByLabel('Goto next page')).toBeDisabled();
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"typescript-eslint": "^8"
},
"scripts": {
"start": "parcel public/index.html",
"start": "parcel public/index.html --no-cache",
"build": "parcel build public/index.html --dist-dir build --no-source-maps --no-cache --no-scope-hoist --public-url ./",
"deploy": "gh-pages -d build",
"format": "npx prettier . --write",
Expand Down
91 changes: 47 additions & 44 deletions src/page/Label.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useContext } from 'react';
import { useState, useEffect, useContext, useRef } from 'react';
import Pagination from '../components/Pagination';
import TableSortIcon from '../components/TableSortIcon';
import { Button, LabelButton } from '../components/form';
Expand Down Expand Up @@ -28,11 +28,15 @@ const Label: React.FC<ILabelProps> = props => {
const [loading, setLoading] = useState(false);
const [queryTabId, setQueryTabId] = useState<string | undefined>(undefined);
const clipboardContext = useContext(ClipboardContext);
const latestRequest = useRef<AbortController>(null);
let timeout: NodeJS.Timeout | null = null;

const requestData = () => {
const checkId = search.length ? /^\d+$/.test(search) : false;
const requestData = async () => {
latestRequest.current?.abort();
const ac: AbortController = new AbortController();
latestRequest.current = ac;

const checkId = search.length ? /^\d+$/.test(search) : false;
let query: string = 'MATCH (' + (props.label.startsWith('*') ? 'n' : 'n:' + props.label) + ')';
if (search.length) {
switch (db.ecosystem) {
Expand All @@ -49,47 +53,45 @@ const Label: React.FC<ILabelProps> = props => {
if (checkId) query += ' OR id(n) = $id';
}

db.query(
query + ' RETURN COUNT(n) AS cnt',
{
search: search,
id: checkId ? db.toInt(search) : null,
},
props.database
)
.then(response1 => {
const cnt: number = db.fromInt(response1.records[0].get('cnt'));
const newPage: number = Math.min(page, Math.ceil(cnt / perPage));
try {
const response1 = await db.query(
query + ' RETURN COUNT(n) AS cnt',
{
search: search,
id: checkId ? db.toInt(search) : null,
},
props.database
);

db.query(
query +
' RETURN n ' +
(sort.length ? 'ORDER BY ' + sort.join(', ') : '') +
' SKIP $skip LIMIT $limit',
{
skip: db.toInt(Math.max(newPage - 1, 0) * perPage),
limit: db.toInt(perPage),
search: search,
id: checkId ? db.toInt(search) : null,
},
props.database
)
.then(response2 => {
setRows(response2.records.map(record => record.get('n')));
setTotal(cnt);
setPage(Math.max(newPage, 1));
})
.catch(err => {
setError('[' + err.name + '] ' + err.message);
})
.finally(() => {
setLoading(false);
});
})
.catch(err => {
setError('[' + err.name + '] ' + err.message);
setLoading(false);
});
if (ac.signal.aborted) return;

const cnt: number = db.fromInt(response1.records[0].get('cnt'));
const newPage: number = Math.min(page, Math.ceil(cnt / perPage));

const response2 = await db.query(
query +
' RETURN n ' +
(sort.length ? 'ORDER BY ' + sort.join(', ') : '') +
' SKIP $skip LIMIT $limit',
{
skip: db.toInt(Math.max(newPage - 1, 0) * perPage),
limit: db.toInt(perPage),
search: search,
id: checkId ? db.toInt(search) : null,
},
props.database
);

if (ac.signal.aborted) return;

setRows(response2.records.map(record => record.get('n')));
setTotal(cnt);
setPage(Math.max(newPage, 1));
setLoading(false);
} catch (err) {
setError('[' + err.name + '] ' + err.message);
setLoading(false);
}
};

useEffect(() => {
Expand All @@ -102,11 +104,12 @@ const Label: React.FC<ILabelProps> = props => {

useEffect(() => {
if (timeout !== null) clearTimeout(timeout);
if (!loading) return;
timeout = setTimeout(() => {
requestData();
timeout = null;
}, 300);
}, [search]);
}, [search, loading]);

const handleChangePage = (newPage: number) => {
setPage(newPage);
Expand Down
131 changes: 71 additions & 60 deletions src/page/Node.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useContext, useActionState } from 'react';
import { useState, useEffect, useContext, useActionState, useRef } from 'react';
import { Button } from '../components/form';
import { Node as _Node, Relationship as _Relationship } from 'neo4j-driver-lite';
import { EPage, EPropertyType } from '../utils/enums';
Expand Down Expand Up @@ -30,72 +30,83 @@ const Node: React.FC<INodeProps> = props => {
const [showAllRels, setShowAllRels] = useState<boolean>(false);
const [rels, setRels] = useState<_Relationship[]>([]);
const [nodes, setNodes] = useState<_Node[]>([]);

const latestRequest = useRef<AbortController>(null);
const copy = useContext(ClipboardContext);
const create: boolean = props.id === null;

const requestData = () => {
const requestData = async () => {
if (create) return;
db.query(
'MATCH (n) WHERE ' +
db.fnId() +
' = $id OPTIONAL MATCH (n)-[r]-(a) RETURN n, collect(DISTINCT r) AS r, collect(DISTINCT a) AS a',
{
id: props.id,
},
props.database
)
.then(response => {
if (response.records.length === 0) {
props.tabManager.close(props.tabId);
return;
}

latestRequest.current?.abort();
const ac: AbortController = new AbortController();
latestRequest.current = ac;

try {
const response = await db.query(
'MATCH (n) WHERE ' +
db.fnId() +
' = $id OPTIONAL MATCH (n)-[r]-(a) RETURN n, collect(DISTINCT r) AS r, collect(DISTINCT a) AS a',
{
id: props.id,
},
props.database
);

const node: _Node = response.records[0].get('n');
const formProps: t_FormProperty[] = [];
const t = new Date().getTime();
for (const key in node.properties) {
const type = resolvePropertyType(node.properties[key]);
if (type === EPropertyType.List) {
const subtype = resolvePropertyType(node.properties[key][0]);
node.properties[key] = (node.properties[key] as []).map(p => {
return {
value: p,
type: subtype,
temp: getPropertyAsTemp(subtype, p),
} as t_FormValue;
});
}
if (type === EPropertyType.Map) {
const mapAsFormValue: t_FormValue[] = [];
for (const k in node.properties[key] as object) {
const subtype = resolvePropertyType(node.properties[key][k]);
mapAsFormValue.push({
key: k,
value: node.properties[key][k],
type: subtype,
temp: getPropertyAsTemp(subtype, node.properties[key][k]),
} as t_FormValue);
}
node.properties[key] = mapAsFormValue;
}
formProps.push({
name: key + t,
key: key,
value: node.properties[key],
type: type,
temp: getPropertyAsTemp(type, node.properties[key]),
if (response.records.length === 0) {
props.tabManager.close(props.tabId);
return;
}

if (ac.signal.aborted) return;

const node: _Node = response.records[0].get('n');
const formProps: t_FormProperty[] = [];
const t = new Date().getTime();
for (const key in node.properties) {
const type = resolvePropertyType(node.properties[key]);
if (type === EPropertyType.List) {
const subtype = resolvePropertyType(node.properties[key][0]);
node.properties[key] = (node.properties[key] as []).map(p => {
return {
value: p,
type: subtype,
temp: getPropertyAsTemp(subtype, p),
} as t_FormValue;
});
}
formProps.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));

setRels(response.records[0].get('r') as _Relationship[]);
setNodes(response.records[0].get('a') as _Node[]);
setNode(node);
setLabels([...node.labels]);
setProperties(formProps);
})
.catch(() => props.tabManager.close(props.tabId));
if (type === EPropertyType.Map) {
const mapAsFormValue: t_FormValue[] = [];
for (const k in node.properties[key] as object) {
const subtype = resolvePropertyType(node.properties[key][k]);
mapAsFormValue.push({
key: k,
value: node.properties[key][k],
type: subtype,
temp: getPropertyAsTemp(subtype, node.properties[key][k]),
} as t_FormValue);
}
node.properties[key] = mapAsFormValue;
}
formProps.push({
name: key + t,
key: key,
value: node.properties[key],
type: type,
temp: getPropertyAsTemp(type, node.properties[key]),
});
}
formProps.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));

if (ac.signal.aborted) return;

setRels(response.records[0].get('r') as _Relationship[]);
setNodes(response.records[0].get('a') as _Node[]);
setNode(node);
setLabels([...node.labels]);
setProperties(formProps);
} catch (err) {

Check failure on line 107 in src/page/Node.tsx

View workflow job for this annotation

GitHub Actions / prettier-linter

'err' is defined but never used
props.tabManager.close(props.tabId);
}
};

useEffect(() => {
Expand Down
Loading

0 comments on commit 003b97b

Please sign in to comment.