Skip to content

Commit

Permalink
Support creating relationship on canvas by alt-clicking two nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
QubitPi committed Oct 25, 2023
1 parent 96eb6c2 commit cc07206
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 40 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
name: Release

"on":
pull_request:
push:
branches:
- master
Expand Down
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]
7 changes: 7 additions & 0 deletions e2e_tests/integration/viz.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ describe('Viz rendering', () => {
'Undefined'
)

cy.get('[data-testid="nodeGroups"]')
.contains('New Node')
.trigger('mouseover')
.get('[data-testid="viz-details-pane-properties-table"]')
.find('td:nth-child(4)')
.should('have.text', 'description')

cy.executeCommand('MATCH (n) DETACH DELETE n')
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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 @@ -79,6 +79,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 cc07206

Please sign in to comment.