Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support creating relationship on canvas by alt-clicking two nodes and supprt relationship type editing from inspector panel #10

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -66,7 +70,9 @@ describe('<DetailsPane />', () => {
item: {
id: '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 @@ -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 @@ -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 @@ -62,7 +62,9 @@ describe('<DetailsPane />', () => {
item: {
id: '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 @@ -70,7 +71,9 @@ describe('<DetailsPane />', () => {
item: {
id: '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 @@ -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