【Neo4j】Dockerで試すNeo4j【第5回/JavaScript編 その2】
今回は、前回実装したターゲット選択用のセレクタを使って、特定のPerson
とFOLLOW
関係があるPerson
を検索するコードを追加していきます。
また前回同様、アプリの作成を通して、JavaScriptとNeo4jの連携について解説していきたいと思います。
↓ 「Dockerで試すNeo4j」シリーズ記事一覧はこちら
目次
はじめに
今回の記事では、以下の記事で追加したデータを使用します。
データを追加していない場合は、上記の記事を参考にデータを追加してください。
注意事項
トライアルという事で、クライアントからNeo4jにアクセスしていますが、アプリを公開する場合などはサーバ経由でアクセスするようにするなど、調整してください。
構成
解説を進める前に、最終的なファイル構成を紹介しておきたいと思います。
最終的なファイル構成は、以下のようになります。
前回紹介したものと同じもので、次回記事で追加するファイルも含んでいます。
また、特にファイルの追加や変更をしない、public
などのディレクトリの中身は表示していません。
グラフ表示用のデータを取得
では早速、フォロー検索機能を実装していきます。
実装は、ここまでのコードを調整・追加する形で進めて行きます。
まずは、特定のPerson
とFOLLOW
関係でつながっているPerson
を検索・取得する関数を追加します。
前回作成したコードに、src/packages/Search/getElements
ファイルを追加して、以下のように記述してください。
import { PathSegment, QueryResult } from 'neo4j-driver' import { Edge, Elements, Node } from "../@types/App"; import { createDriver } from "./createDriver"; export const getElements = async (target: Node<any>): Promise<Elements> => { const driver = await createDriver() const session = await driver.session() const elements: Elements = { nodes: [], edges: [], } const result: QueryResult | void = await session .run( target ? `MATCH p=(s)-[:FOLLOW *]->(e {id:$target}) RETURN p, s, e, ID(s) as sid, ID(e) as eid UNION MATCH p=(s {id:$target})-[:FOLLOW *]->(e) RETURN p, s, e, ID(s) as sid, ID(e) as eid` : `MATCH p=(s)-[:FOLLOW]->(e) RETURN p, s, e, ID(s) as sid, ID(e) as eid`, { target: target?.properties?.id } ) .catch((error) => { console.log(error) }) if (!result) { session.close() await driver.close() return elements } result.records.forEach((record) => { const path = record.get('p') const e = record.get('e') const isTargetEnd = target?.properties?.id === e.properties.id path.segments.forEach((segment: PathSegment<any>) => { const start: Node<any> | any = segment.start const startId = start.identity.toString() const startProperties = start.properties const end: Node<any> | any = segment.end const endId = end.identity.toString() const endProperties = end.properties const duplicate = elements.edges.find( (edge: Edge) => edge.data.source === startId && edge.data.target === endId ) if (duplicate) { return } const color = (nodeId: string): string => { if (nodeId === target?.identity?.toString()) { return '#ea4b4b' } if (!isTargetEnd) { return '#95e76c' } return '#718ef8' } elements.nodes.push({ data: { id: startId, name: startProperties?.name, label: start.labels[0], color: color(startId), }, }) elements.nodes.push({ data: { id: endId, name: endProperties?.name, label: end.labels[0], color: color(endId), }, }) elements.edges.push({ data: { source: startId, target: endId, relationship: path.segments[0].relationship.type, }, }) }) }) session.close() await driver.close() return elements }
少し長いですが、ポイントをまとめると以下のような感じです。
session.run()
の部分で、Neo4jにアクセスして、FOLLOW
関係を取得- 取得した結果をグラフ表示用に整形して
return
session.run()
で使用するクエリは、getElements
関数の引数target
がある場合とない場合で異なります。
target
が指定されている場合は、指定されたターゲットに向かう全てのFOLLOW
リレーションと、指定されたターゲットから他のノードへ向かう全てのFOLLOW
リレーションを検索して結合した結果が返されます。
target
が指定されていない場合は、全てのFOLLOW
リレーションが返されます。
また、整形したデータはグラフ表示に使用しますが、グラフ表示については次回記事で解説したいと思います。
後述しますが、今回は、データをリスト表示するところまでにとどめたいと思います。
「Search」コンポーネントの調整
続いて、前回記事で追加したSearch
コンポーネントを調整して、フォロー検索機能を追加します。
src/packages/Search/Search.tsx
を以下のように書き換えてください。
import Autocomplete from "@material-ui/lab/Autocomplete"; import { Elements, Node } from "../@types/App"; import { Button, TextField } from "@material-ui/core"; import React, { ChangeEvent, useState } from "react"; import { getElements } from "./getElements"; type Props = { persons: Node<any>[] | [] handleSearchButtonClick: (elements: Elements) => void } export default function Search({ persons, handleSearchButtonClick }: Props) { const [target, setTarget] = useState<Node<any>>(null) const handleTargetChange = (event: ChangeEvent<{}>, newTarget: Node<any>) => setTarget(newTarget) return <div> <Autocomplete id="target" value={target} onChange={handleTargetChange} options={persons} getOptionLabel={(person: Node<any>) => person?.properties?.id || ''} style={{ width: 300 }} renderInput={(params) => ( <TextField {...params} label="target" variant="outlined"/> )} /> <Button onClick={async () => handleSearchButtonClick(await getElements(target))} variant={'outlined'}> search </Button> </div> }
主な変更は、以下の検索実行用のボタンの部分です。
<Button onClick={async () => handleSearchButtonClick(await getElements(target))} variant={'outlined'}> search </Button>
ボタンがクリックされると、先ほど定義したgetElements
関数によってフォロー情報が取得されます。
取得されたフォロー情報を引数として、Search
コンポーネントのプロパティhandleSearchButtonClick
が実行されます。
handleSearchButtonClick
は次の項目で実装しますが、引数となるフォロー情報をステートとて保持するような関数です。
仕上げ
最後に、ここまでのコードをsrc/App.tsx
で統合します。
src/App.tsx
を以下のように書き換えてください。
import React, { useEffect, useState } from 'react'; import { Elements, Node } from "./packages/@types/App"; import { getAllPersons } from "./packages/Search/getAllPersons"; import Search from "./packages/Search/Search"; function App() { const [elements, setElements] = useState<Elements>({ nodes: [], edges: [] }) const [persons, setPersons] = useState<Node<any>[] | []>([]) useEffect(() => { (async () => setPersons(await getAllPersons()))() }, []) return ( <div> <div> <Search persons={persons} handleSearchButtonClick={(elements) => setElements(elements)}/> </div> {elements.nodes.length > 0 && <div> <hr/> <h3>nodes</h3> <ul> {elements.nodes.map((node) => <li>{JSON.stringify(node.data)}</li>)} </ul> <hr/> <h3>edges</h3> <ul> {elements.edges.map((edge) => <li>{JSON.stringify(edge.data)}</li>)} </ul> </div>} </div> ) } export default App;
Search
コンポーネントの調整に合わせて、Search
コンポーネントにhandleSearchButtonClick
プロパティの指定を追加しています。
これにあわせて、elements
ステートの定義も追加しています。
また、更新されるelements
の確認用に、elements.nodes
とelements.edges
をリスト表示する部分を追加しています。
動作確認
フォロー検索用のコードの追加・調整が完了したので、実際に動かして動作を確認してみます。
以下のコマンドで、Reactアプリをスタートしてください。
yarn start
アプリを起動したら、http://localhost:3000/にアクセスしてください。
ターゲット入力欄で「Ichiro」を選択、または入力して、「Search」ボタンをクリックしてください。
以下のような、「nodes」一覧と「edges」一覧が表示されれば、実装成功です。
サンプルコード
以下のリポジトリに「Dockerで試すNeo4j」シリーズ全話分のコードを設置しています。
うまく動かない場合は、上記のリポジトリをクローンして試してみてください。
まとめ
以上、Neo4jとReactを使って、フォロー検索機能を追加してみました。
今回は、フォロー検索結果をリスト表示で確認しまいたが、次回はいよいよ、フォロー検索した結果をグラフ表示させてみたいと思います。
↓ 「Dockerで試すNeo4j」シリーズ記事一覧はこちら