Skip to content

Commit

Permalink
Support creating relationship on canvas by alt-clicking two nodes and…
Browse files Browse the repository at this point in the history
… supprt relationship type editing from inspector panel (#10)
  • Loading branch information
QubitPi committed Jun 11, 2024
1 parent 80b0856 commit 948d0ba
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ image:modifying-node-properties-example.png[width=600]

Similary, we can modify node label:

image:modifying-node-label-example.png[width=600]
image:modifying-node-label-example.png[width=600]
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import React from 'react'

import { DETAILS_PANE_STEP_SIZE, DetailsPane } from './DetailsPane'
import { VizItemProperty } from 'neo4j-arc/common'
import { GraphStyleModel, VizItem } from 'neo4j-arc/graph-visualization'
import {
GraphStyleModel,
VizItem,
NodeModel
} from 'neo4j-arc/graph-visualization'

describe('<DetailsPane />', () => {
const mockGraphStyle = new GraphStyleModel()
Expand Down Expand Up @@ -68,7 +72,9 @@ describe('<DetailsPane />', () => {
id: 'abc',
elementId: 'abc',
type: 'abc2',
propertyList
propertyList,
source: new NodeModel('1', [], {}, {}),
target: new NodeModel('2', [], {}, {})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export function DetailsPane({
relType: vizItem.item.type
}}
graphStyle={graphStyle}
onGraphInteraction={onGraphInteraction}
sourceNodeId={vizItem.item.source.id}
targetNodeId={vizItem.item.target.id}
/>
)}
{vizItem.type === 'node' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,67 @@ import React from 'react'
import { Popup } from 'semantic-ui-react'

import { StyledRelationshipChip } from 'neo4j-arc/common'
import { GraphStyleModel } from 'neo4j-arc/graph-visualization'
import {
GraphInteractionCallBack,
GraphStyleModel,
REL_TYPE_UPDATE
} from 'neo4j-arc/graph-visualization'

import { GrassEditor } from './GrassEditor'

export type StyleableRelTypeProps = {
graphStyle: GraphStyleModel
selectedRelType: { relType: string; propertyKeys: string[]; count?: number }
onGraphInteraction?: GraphInteractionCallBack
sourceNodeId?: string
targetNodeId?: string
}
export function StyleableRelType({
selectedRelType,
graphStyle
graphStyle,
onGraphInteraction = () => undefined,
sourceNodeId,
targetNodeId
}: StyleableRelTypeProps): JSX.Element {
const styleForRelType = graphStyle.forRelationship({
type: selectedRelType.relType
})
return (
<Popup
on="click"
basic
key={selectedRelType.relType}
position="left center"
offset={[0, 0]}
trigger={
<StyledRelationshipChip
style={{
backgroundColor: styleForRelType.get('color'),
color: styleForRelType.get('text-color-internal')
}}
data-testid={`property-details-overview-relationship-type-${selectedRelType.relType}`}
>
{selectedRelType.count !== undefined
? `${selectedRelType.relType} (${selectedRelType.count})`
: `${selectedRelType.relType}`}
</StyledRelationshipChip>
<div
suppressContentEditableWarning={true}
contentEditable="true"
onInput={e =>
onGraphInteraction(REL_TYPE_UPDATE, {
sourceNodeId: sourceNodeId,
targetNodeId: targetNodeId,
oldType: selectedRelType.relType,
newType: e.currentTarget.textContent
})
}
wide
>
<GrassEditor selectedRelType={selectedRelType} />
</Popup>
<Popup
on="click"
basic
key={selectedRelType.relType}
position="left center"
offset={[0, 0]}
trigger={
<StyledRelationshipChip
style={{
backgroundColor: styleForRelType.get('color'),
color: styleForRelType.get('text-color-internal')
}}
data-testid={`property-details-overview-relationship-type-${selectedRelType.relType}`}
>
{selectedRelType.count !== undefined
? `${selectedRelType.relType} (${selectedRelType.count})`
: `${selectedRelType.relType}`}
</StyledRelationshipChip>
}
wide
>
<GrassEditor selectedRelType={selectedRelType} />
</Popup>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import {
GraphInteractionCallBack,
GraphModel,
GraphVisualizer,
REL_ON_CANVAS_CREATE,
NODE_ON_CANVAS_CREATE,
NODE_PROP_UPDATE,
NODE_LABEL_UPDATE
NODE_LABEL_UPDATE,
REL_TYPE_UPDATE
} from 'neo4j-arc/graph-visualization'

import { StyledVisContainer } from './VisualizationView.styled'
Expand Down Expand Up @@ -279,6 +281,37 @@ LIMIT ${maxNewNeighbours}`
}

onGraphInteraction: GraphInteractionCallBack = (event, properties) => {
if (event == REL_TYPE_UPDATE) {
if (properties == null) {
throw new Error(
'A property map with sourceNodeId, targetNodeId, oldType, and newType keys are required'
)
}

const sourceNodeId = properties['sourceNodeId']
const targetNodeId = properties['targetNodeId']
const oldType = properties['oldType']
const newType = properties['newType']

const type = `\`${oldType}\``

const query = `MATCH (source)-[rel:${type}]->(target) WHERE ID(source) = ${sourceNodeId} AND ID(target) = ${targetNodeId} CALL apoc.refactor.setType(rel, '${newType}') YIELD input, output RETURN input, output;`

this.props.bus.self(
CYPHER_REQUEST,
{
query,
params: { sourceNodeId, targetNodeId, oldType, newType },
queryType: NEO4J_BROWSER_USER_ACTION_QUERY
},
(response: any) => {
if (!response.success) {
throw new Error(response.error)
}
}
)
}

if (event == NODE_LABEL_UPDATE) {
if (properties == null) {
throw new Error(
Expand All @@ -291,7 +324,7 @@ LIMIT ${maxNewNeighbours}`
const newLabel = `\`${properties['newLabel']}\``

const query = `MATCH(n) WHERE ID(n) = ${nodeId} REMOVE n:${oldLabel} SET n:${newLabel}`
console.log(query)

this.props.bus.self(
CYPHER_REQUEST,
{
Expand All @@ -309,7 +342,9 @@ LIMIT ${maxNewNeighbours}`

if (event == NODE_PROP_UPDATE) {
if (properties == null) {
throw new Error('')
throw new Error(
'A property map with nodeId, propKey, and propVal keys are required'
)
}

const nodeId = properties['nodeId']
Expand All @@ -334,6 +369,34 @@ LIMIT ${maxNewNeighbours}`
)
}

if (event == REL_ON_CANVAS_CREATE) {
if (properties == null) {
throw new Error(
'A property map with sourceNodeId, targetNodeId, and type keys are required'
)
}

const sourceNodeId = properties['sourceNodeId']
const targetNodeId = properties['targetNodeId']
const type = properties['type']

const query = `MATCH (source), (target) WHERE ID(source) = ${sourceNodeId} AND ID(target) = ${targetNodeId} CREATE (source)-[r:\`${type}\` {name: "new link"}]->(target) RETURN type(r);`

this.props.bus.self(
CYPHER_REQUEST,
{
query,
params: { sourceNodeId, targetNodeId, type },
queryType: NEO4J_BROWSER_USER_ACTION_QUERY
},
(response: any) => {
if (!response.success) {
throw new Error(response.error)
}
}
)
}

if (event == NODE_ON_CANVAS_CREATE) {
if (properties == null) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { render, screen, waitFor } from '@testing-library/react'
import React from 'react'

import { VizItem } from 'neo4j-arc/graph-visualization'
import { VizItem, NodeModel } from 'neo4j-arc/graph-visualization'

import {
PropertiesTable,
Expand Down Expand Up @@ -64,7 +64,9 @@ describe('<DetailsPane />', () => {
id: 'abc',
elementId: 'abc',
type: 'abc2',
propertyList
propertyList,
source: new NodeModel('1', [], {}, {}),
target: new NodeModel('2', [], {}, {})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { VizItemProperty } from 'neo4j-arc/common'
import { GraphStyleModel } from '../../models/GraphStyle'
import { VizItem } from '../../types'
import { NodeModel } from 'neo4j-arc/graph-visualization'

describe('<DetailsPane />', () => {
const mockGraphStyle = new GraphStyleModel()
Expand Down Expand Up @@ -72,7 +73,9 @@ describe('<DetailsPane />', () => {
id: 'abc',
elementId: 'elementId',
type: 'abc2',
propertyList
propertyList,
source: new NodeModel('1', [], {}, {}),
target: new NodeModel('2', [], {}, {})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export function DefaultDetailsPane({
relType: vizItem.item.type
}}
graphStyle={graphStyle}
onGraphInteraction={onGraphInteraction}
sourceNodeId={vizItem.item.source.id}
targetNodeId={vizItem.item.target.id}
/>
)}
{vizItem.type === 'node' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,54 @@ import React from 'react'

import { GraphStyleModel } from '../../models/GraphStyle'
import { NonClickableRelTypeChip } from './styled'
import {
GraphInteractionCallBack,
REL_TYPE_UPDATE
} from '../Graph/GraphEventHandlerModel'

export type RelTypeProps = {
graphStyle: GraphStyleModel
selectedRelType: { relType: string; propertyKeys: string[]; count?: number }
onGraphInteraction?: GraphInteractionCallBack
sourceNodeId?: string
targetNodeId?: string
}
RelType.defaultProps = {
onGraphInteraction: () => undefined
}
export function RelType({
selectedRelType,
graphStyle
graphStyle,
onGraphInteraction = () => undefined,
sourceNodeId,
targetNodeId
}: RelTypeProps): JSX.Element {
const styleForRelType = graphStyle.forRelationship({
type: selectedRelType.relType
})
return (
<NonClickableRelTypeChip
style={{
backgroundColor: styleForRelType.get('color'),
color: styleForRelType.get('text-color-internal')
}}
<div
suppressContentEditableWarning={true}
contentEditable="true"
onInput={e =>
onGraphInteraction(REL_TYPE_UPDATE, {
sourceNodeId: sourceNodeId,
targetNodeId: targetNodeId,
oldType: selectedRelType.relType,
newType: e.currentTarget.textContent
})
}
>
{selectedRelType.count !== undefined
? `${selectedRelType.relType} (${selectedRelType.count})`
: `${selectedRelType.relType}`}
</NonClickableRelTypeChip>
<NonClickableRelTypeChip
style={{
backgroundColor: styleForRelType.get('color'),
color: styleForRelType.get('text-color-internal')
}}
>
{selectedRelType.count !== undefined
? `${selectedRelType.relType} (${selectedRelType.count})`
: `${selectedRelType.relType}`}
</NonClickableRelTypeChip>
</div>
)
}
Loading

0 comments on commit 948d0ba

Please sign in to comment.