diff --git a/docs/modules/ROOT/content-nav.adoc b/docs/modules/ROOT/content-nav.adoc index 2552c8fc64c..15ad11a29e5 100644 --- a/docs/modules/ROOT/content-nav.adoc +++ b/docs/modules/ROOT/content-nav.adoc @@ -25,6 +25,7 @@ ** xref:operations/rest-requests.adoc[] ** xref:operations/browser-rbac-count.adoc[] ** xref:operations/product-analytics.adoc[] +** xref:operations/on-canvas-operations.adoc[] * xref:reference-commands.adoc[Command reference] diff --git a/docs/modules/ROOT/images/enable-apoc.png b/docs/modules/ROOT/images/enable-apoc.png new file mode 100644 index 00000000000..ef26dbda4c1 Binary files /dev/null and b/docs/modules/ROOT/images/enable-apoc.png differ diff --git a/docs/modules/ROOT/images/modifying-node-label-example.png b/docs/modules/ROOT/images/modifying-node-label-example.png new file mode 100644 index 00000000000..861bac45e9b Binary files /dev/null and b/docs/modules/ROOT/images/modifying-node-label-example.png differ diff --git a/docs/modules/ROOT/images/modifying-node-properties-example.png b/docs/modules/ROOT/images/modifying-node-properties-example.png new file mode 100644 index 00000000000..82d044ad61c Binary files /dev/null and b/docs/modules/ROOT/images/modifying-node-properties-example.png differ diff --git a/docs/modules/ROOT/pages/operations/index.adoc b/docs/modules/ROOT/pages/operations/index.adoc index 8dacd13ab43..ee7afb22815 100644 --- a/docs/modules/ROOT/pages/operations/index.adoc +++ b/docs/modules/ROOT/pages/operations/index.adoc @@ -17,4 +17,4 @@ * xref:operations/rest-requests.adoc[] -- HTTP REST commands. * xref:operations/browser-rbac-count.adoc[] -- Manual refresh of counts due to RBAC. * xref:operations/product-analytics.adoc[] -- Configure consent for anonymous usage statistics. - +* xref:operations/on-canvas-operations.adoc[] -- On-Canvas Operations. diff --git a/docs/modules/ROOT/pages/operations/on-canvas-operations.adoc b/docs/modules/ROOT/pages/operations/on-canvas-operations.adoc new file mode 100644 index 00000000000..ac522c8c294 --- /dev/null +++ b/docs/modules/ROOT/pages/operations/on-canvas-operations.adoc @@ -0,0 +1,31 @@ +:description: On-Canvas Operations + + +[[on-canvas-operations]] += On-Canvas Operations + +== Double-Clicking the Canvas + +*Double clicking the white area* on canvas will automatically generate a new node with + +1. an auto-incremented ID, i.e. (the max ID of nodes on canvas) + 1 +2. a label of "Undefined" +3. the node caption of "New Node" + +== Editing the Node Label and Properties in Inspector Panel + +[NOTE] +==== +Make sure APOC plugin is enabled in Neo4J database: + +image:enable-apoc.png[width=600] +==== + +*Clicking the property value* in node inspector panel will make that property editable and un-clicking the text box +(by for example clicking somewhere else) will persist the new value into Neo4J database: + +image:modifying-node-properties-example.png[width=600] + +Similary, we can modify node label: + +image:modifying-node-label-example.png[width=600] \ No newline at end of file diff --git a/e2e_tests/integration/viz.spec.ts b/e2e_tests/integration/viz.spec.ts index c063dadc1b7..4dfa45432ac 100644 --- a/e2e_tests/integration/viz.spec.ts +++ b/e2e_tests/integration/viz.spec.ts @@ -18,6 +18,8 @@ * along with this program. If not, see . */ +import { should } from 'chai' + /* global Cypress, cy, before */ const GREY = 'rgb(165, 171, 182)' // Default color for nodes and relationships @@ -259,4 +261,25 @@ describe('Viz rendering', () => { cy.executeCommand('MATCH (n) DETACH DELETE n') }) + + it('new node by double-clicking the canvas has "description" and "name" property fields', () => { + cy.executeCommand(':clear') + cy.executeCommand(`CREATE (a:TestLabel {name: 'testNode'}) RETURN a`, { + parseSpecialCharSequences: false + }) + + cy.get('[data-testid="graphCanvas"]') + .trigger('click', 200, 200, { force: true }) + .trigger('dblclick', 200, 200, { force: true }) + + cy.get('[data-testid="nodeGroups"]', { timeout: 5000 }) + .contains('New Node') + .trigger('mouseover', { force: true }) + .trigger('mouseenter', { force: true }) + .get('[data-testid="viz-details-pane-properties-table"]') + .find('td:nth-child(1)') + .should('have.text', 'descriptionname') + + cy.executeCommand('MATCH (n) DETACH DELETE n') + }) }) diff --git a/src/browser/modules/Sidebar/__snapshots__/GuideDrawer.test.tsx.snap b/src/browser/modules/Sidebar/__snapshots__/GuideDrawer.test.tsx.snap index fdc724e488a..9816124a6b4 100644 --- a/src/browser/modules/Sidebar/__snapshots__/GuideDrawer.test.tsx.snap +++ b/src/browser/modules/Sidebar/__snapshots__/GuideDrawer.test.tsx.snap @@ -3,15 +3,15 @@ exports[`GuideDrawer renders guide slide when a guide is selected 1`] = `

@@ -48,54 +48,54 @@ exports[`GuideDrawer renders guide slide when a guide is selected 1`] = ` exports[`GuideDrawer renders list view including Remote Guides list 1`] = `

Neo4j Browser Guides

You can also access Browser guides by running :guide [guide name] in the code editor.
Built-in guides
  • :guide intro
    Navigating Neo4j Browser @@ -103,17 +103,17 @@ exports[`GuideDrawer renders list view including Remote Guides list 1`] = `
  • :guide concepts
    Property graph model concepts @@ -121,17 +121,17 @@ exports[`GuideDrawer renders list view including Remote Guides list 1`] = `
  • :guide cypher
    Cypher basics - create, match, delete @@ -139,17 +139,17 @@ exports[`GuideDrawer renders list view including Remote Guides list 1`] = `
  • :guide movie-graph
    Queries and recommendations with Cypher - movie use case} @@ -157,17 +157,17 @@ exports[`GuideDrawer renders list view including Remote Guides list 1`] = `
  • :guide northwind-graph
    Translate and import relation data into graph @@ -176,29 +176,29 @@ exports[`GuideDrawer renders list view including Remote Guides list 1`] = `
  • Remote Guides
  • Title
  • Neo4j Browser Guides

    You can also access Browser guides by running :guide [guide name] in the code editor.
    Built-in guides
  • :guide intro
    Navigating Neo4j Browser @@ -283,17 +283,17 @@ exports[`GuideDrawer renders list view without Remote Guides 1`] = `
  • :guide concepts
    Property graph model concepts @@ -301,17 +301,17 @@ exports[`GuideDrawer renders list view without Remote Guides 1`] = `
  • :guide cypher
    Cypher basics - create, match, delete @@ -319,17 +319,17 @@ exports[`GuideDrawer renders list view without Remote Guides 1`] = `
  • :guide movie-graph
    Queries and recommendations with Cypher - movie use case} @@ -337,17 +337,17 @@ exports[`GuideDrawer renders list view without Remote Guides 1`] = `
  • :guide northwind-graph
    Translate and import relation data into graph @@ -356,10 +356,10 @@ exports[`GuideDrawer renders list view without Remote Guides 1`] = `
  • Browser Settings

    Test åäö settings
    Product Analytics
    diff --git a/src/browser/modules/Stream/CypherFrame/ErrorsView/__snapshots__/ErrorsStatusbar.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/ErrorsView/__snapshots__/ErrorsStatusbar.test.tsx.snap index 3c58f5993c6..f6b1acdda1e 100644 --- a/src/browser/modules/Stream/CypherFrame/ErrorsView/__snapshots__/ErrorsStatusbar.test.tsx.snap +++ b/src/browser/modules/Stream/CypherFrame/ErrorsView/__snapshots__/ErrorsStatusbar.test.tsx.snap @@ -3,14 +3,14 @@ exports[`ErrorsStatusbar displays error 1`] = `
    diff --git a/src/browser/modules/Stream/CypherFrame/RelatableView/__snapshots__/relatable-view.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/RelatableView/__snapshots__/relatable-view.test.tsx.snap index 33007397161..170d78dc2b3 100644 --- a/src/browser/modules/Stream/CypherFrame/RelatableView/__snapshots__/relatable-view.test.tsx.snap +++ b/src/browser/modules/Stream/CypherFrame/RelatableView/__snapshots__/relatable-view.test.tsx.snap @@ -3,10 +3,10 @@ exports[`RelatableViews RelatableView displays bodyMessage if no rows 1`] = `
    (no changes, no records)
    @@ -17,10 +17,10 @@ exports[`RelatableViews RelatableView displays bodyMessage if no rows 1`] = ` exports[`RelatableViews RelatableView does not crash if key is empty string 1`] = `
    "String value" @@ -89,10 +89,10 @@ exports[`RelatableViews RelatableView does not crash if key is empty string 1`] exports[`RelatableViews RelatableView does not display bodyMessage if rows, and escapes HTML 1`] = `
    "String with HTML <strong>in</strong> it" @@ -161,7 +161,7 @@ exports[`RelatableViews RelatableView does not display bodyMessage if rows, and exports[`RelatableViews TableStatusbar displays no statusBarMessage 1`] = `
    `; @@ -169,10 +169,10 @@ exports[`RelatableViews TableStatusbar displays no statusBarMessage 1`] = ` exports[`RelatableViews TableStatusbar displays statusBarMessage 1`] = `
    Started streaming 1 records after 5 ms and completed after 10 ms. diff --git a/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/DetailsPane.tsx b/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/DetailsPane.tsx index 2ce19969662..66afb6d2e5c 100644 --- a/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/DetailsPane.tsx +++ b/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/DetailsPane.tsx @@ -30,7 +30,8 @@ export const DETAILS_PANE_STEP_SIZE = 1000 export function DetailsPane({ vizItem, graphStyle, - nodeInspectorWidth + nodeInspectorWidth, + onGraphInteraction }: DetailsPaneProps): JSX.Element { const [maxPropertiesCount, setMaxPropertiesCount] = useState( DETAILS_PANE_STEP_SIZE @@ -89,6 +90,8 @@ export function DetailsPane({ label, propertyKeys: vizItem.item.propertyList.map(p => p.key) }} + onGraphInteraction={onGraphInteraction} + nodeId={vizItem.item.id} /> ) })} @@ -100,6 +103,7 @@ export function DetailsPane({ moreStep={DETAILS_PANE_STEP_SIZE} totalNumItems={allItemProperties.length} nodeInspectorWidth={nodeInspectorWidth} + onGraphInteraction={onGraphInteraction} /> diff --git a/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/StyleableNodeLabel.tsx b/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/StyleableNodeLabel.tsx index a3f510ab537..ea3fce06304 100644 --- a/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/StyleableNodeLabel.tsx +++ b/src/browser/modules/Stream/CypherFrame/VisualizationView/PropertiesPanelContent/StyleableNodeLabel.tsx @@ -21,7 +21,11 @@ import React from 'react' import { Popup } from 'semantic-ui-react' import { StyledLabelChip } from 'neo4j-arc/common' -import { GraphStyleModel } from 'neo4j-arc/graph-visualization' +import { + GraphInteractionCallBack, + GraphStyleModel, + NODE_LABEL_UPDATE +} from 'neo4j-arc/graph-visualization' import { GrassEditor } from './GrassEditor' @@ -34,11 +38,15 @@ export type StyleableNodeLabelProps = { graphStyle: GraphStyleModel /* The total number of nodes in returned graph */ allNodesCount?: number | null + onGraphInteraction?: GraphInteractionCallBack + nodeId?: string } export function StyleableNodeLabel({ graphStyle, selectedLabel, - allNodesCount + allNodesCount, + onGraphInteraction = () => undefined, + nodeId }: StyleableNodeLabelProps): JSX.Element { const labels = selectedLabel.label === '*' ? [] : [selectedLabel.label] const graphStyleForLabel = graphStyle.forNode({ @@ -48,26 +56,40 @@ export function StyleableNodeLabel({ selectedLabel.label === '*' ? allNodesCount : selectedLabel.count return ( - - {`${selectedLabel.label}${count || count === 0 ? ` (${count})` : ''}`} - +
    + onGraphInteraction(NODE_LABEL_UPDATE, { + nodeId: nodeId, + oldLabel: labels[0], + newLabel: e.currentTarget.textContent + }) } > - - + + {`${selectedLabel.label}${ + count || count === 0 ? ` (${count})` : '' + }`} + + } + > + + +
    ) } diff --git a/src/browser/modules/Stream/CypherFrame/VisualizationView/VisualizationView.tsx b/src/browser/modules/Stream/CypherFrame/VisualizationView/VisualizationView.tsx index e3381dadb3d..18417afc061 100644 --- a/src/browser/modules/Stream/CypherFrame/VisualizationView/VisualizationView.tsx +++ b/src/browser/modules/Stream/CypherFrame/VisualizationView/VisualizationView.tsx @@ -28,7 +28,9 @@ import { GraphInteractionCallBack, GraphModel, GraphVisualizer, - NODE_ON_CANVAS_CREATE + NODE_ON_CANVAS_CREATE, + NODE_PROP_UPDATE, + NODE_LABEL_UPDATE } from 'neo4j-arc/graph-visualization' import { StyledVisContainer } from './VisualizationView.styled' @@ -277,6 +279,61 @@ LIMIT ${maxNewNeighbours}` } onGraphInteraction: GraphInteractionCallBack = (event, properties) => { + if (event == NODE_LABEL_UPDATE) { + if (properties == null) { + throw new Error( + 'A property map with nodeId, oldLabel, and newLabel keys are required' + ) + } + + const nodeId = properties['nodeId'] + const oldLabel = `\`${properties['oldLabel']}\`` + 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, + { + query, + params: { nodeId, oldLabel, newLabel }, + queryType: NEO4J_BROWSER_USER_ACTION_QUERY + }, + (response: any) => { + if (!response.success) { + throw new Error(response.error) + } + } + ) + } + + if (event == NODE_PROP_UPDATE) { + if (properties == null) { + throw new Error('') + } + + const nodeId = properties['nodeId'] + const propKey = properties['propKey'] + const propVal = properties['propVal'] + + const query = `MATCH (n) WHERE ID(n) = ${nodeId} SET n.${propKey} = "${propVal}"` + console.log(query) + + this.props.bus.self( + CYPHER_REQUEST, + { + query, + params: { nodeId, propKey, propVal }, + 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( @@ -286,18 +343,19 @@ LIMIT ${maxNewNeighbours}` const id = properties['id'] const name = properties['name'] + const description = properties['description'] const variableName = `node${id}` const labels = (properties['labels'] as string[]) .map(label => `\`${label}\``) .join(':') - const query = `CREATE (${variableName}:${labels} { id: ${id}, name: "${name}" });` + const query = `CREATE (${variableName}:${labels} { id: ${id}, name: "${name}", description: "${description}" });` this.props.bus.self( CYPHER_REQUEST, { query, - params: { labels, id, name }, + params: { labels, id, name, description }, queryType: NEO4J_BROWSER_USER_ACTION_QUERY }, (response: any) => { diff --git a/src/browser/modules/Stream/CypherFrame/__snapshots__/AsciiView.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/__snapshots__/AsciiView.test.tsx.snap index ec5111e4766..d35300c8287 100644 --- a/src/browser/modules/Stream/CypherFrame/__snapshots__/AsciiView.test.tsx.snap +++ b/src/browser/modules/Stream/CypherFrame/__snapshots__/AsciiView.test.tsx.snap @@ -3,10 +3,10 @@ exports[`AsciiViews AsciiStatusbar displays statusBarMessage if no rows 1`] = `
    Completed after 10 ms.
    @@ -17,17 +17,17 @@ exports[`AsciiViews AsciiStatusbar displays statusBarMessage if no rows 1`] = ` exports[`AsciiViews AsciiStatusbar displays statusBarMessage if no rows 2`] = `
    Max column width:
    (no changes, no records)
    @@ -56,10 +56,10 @@ exports[`AsciiViews AsciiView displays bodyMessage if no rows 1`] = ` exports[`AsciiViews AsciiView does not display bodyMessage if rows 1`] = `
           ╒═══╕
     │x  │
    diff --git a/src/browser/modules/Stream/CypherFrame/__snapshots__/CodeView.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/__snapshots__/CodeView.test.tsx.snap
    index b0e94bbd504..ae872021f9f 100644
    --- a/src/browser/modules/Stream/CypherFrame/__snapshots__/CodeView.test.tsx.snap
    +++ b/src/browser/modules/Stream/CypherFrame/__snapshots__/CodeView.test.tsx.snap
    @@ -14,7 +14,7 @@ exports[`CodeViews CodeStatusbar displays statusBarMessage 1`] = `
         class="sc-jcFjpl cdUBmZ"
       >
         
    Started streaming 1 records after 5 ms and completed after 10 ms. diff --git a/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap b/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap index 96c747d53b3..0fb029bfe80 100644 --- a/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap +++ b/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap @@ -7,16 +7,16 @@ exports[`SchemaFrame renders empty 1`] = ` >
    @@ -24,10 +24,10 @@ exports[`SchemaFrame renders empty 1`] = ` @@ -35,33 +35,33 @@ exports[`SchemaFrame renders empty 1`] = `
    Indexes
    None
    @@ -69,24 +69,24 @@ exports[`SchemaFrame renders empty 1`] = ` @@ -121,46 +121,46 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = ` >
    Constraint Name Type EntityType LabelsOrTypes Properties
    None
    @@ -168,62 +168,62 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = `
    Index Name Type Uniqueness EntityType LabelsOrTypes Properties State
    None
    @@ -231,24 +231,24 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = ` @@ -283,16 +283,16 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` >
    Constraint Name Type EntityType LabelsOrTypes Properties
    None
    @@ -300,10 +300,10 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` @@ -311,13 +311,13 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = `
    Indexes
    ON :Movie(released) ONLINE
    @@ -325,10 +325,10 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` @@ -365,46 +365,46 @@ exports[`SchemaFrame renders results for Neo4j >= 4.2 1`] = ` >
    Constraints
    ON ( book:Book ) ASSERT book.isbn IS UNIQUE
    @@ -412,34 +412,34 @@ exports[`SchemaFrame renders results for Neo4j >= 4.2 1`] = ` @@ -447,33 +447,33 @@ exports[`SchemaFrame renders results for Neo4j >= 4.2 1`] = `
    Index Name Type Uniqueness EntityType LabelsOrTypes Properties State
    node_label_property [ "released" ] ONLINE
    @@ -481,32 +481,32 @@ exports[`SchemaFrame renders results for Neo4j >= 4.2 1`] = `
    Constraint Name Type EntityType LabelsOrTypes Properties
    constraint_550b2518 UNIQUE node [ "Movie" ] [ "released" diff --git a/src/neo4j-arc/common/components/PropertiesTable/PropertiesTable.tsx b/src/neo4j-arc/common/components/PropertiesTable/PropertiesTable.tsx index 7a2455e3a66..0dba941a653 100644 --- a/src/neo4j-arc/common/components/PropertiesTable/PropertiesTable.tsx +++ b/src/neo4j-arc/common/components/PropertiesTable/PropertiesTable.tsx @@ -31,17 +31,31 @@ import { import { ClipboardCopier } from '../ClipboardCopier' import { ShowMoreOrAll } from '../ShowMoreOrAll/ShowMoreOrAll' import { VizItemProperty } from 'neo4j-arc/common' +import { + GraphInteractionCallBack, + NODE_PROP_UPDATE +} from '../../../graph-visualization' export const ELLIPSIS = '\u2026' export const WIDE_VIEW_THRESHOLD = 900 export const MAX_LENGTH_NARROW = 150 export const MAX_LENGTH_WIDE = 300 type ExpandableValueProps = { + nodeId: string + propKey: string value: string width: number type: string + onGraphInteraction: GraphInteractionCallBack } -function ExpandableValue({ value, width, type }: ExpandableValueProps) { +function ExpandableValue({ + nodeId, + propKey, + value, + width, + type, + onGraphInteraction +}: ExpandableValueProps) { const [expanded, setExpanded] = useState(false) const maxLength = @@ -56,7 +70,17 @@ function ExpandableValue({ value, width, type }: ExpandableValueProps) { valueShown += valueIsTrimmed ? ELLIPSIS : '' return ( - <> +
    + onGraphInteraction(NODE_PROP_UPDATE, { + nodeId: nodeId, + propKey: propKey, + propVal: e.currentTarget.textContent + }) + } + > {type.startsWith('Array') && '['} {valueIsTrimmed && ( @@ -65,7 +89,7 @@ function ExpandableValue({ value, width, type }: ExpandableValueProps) { )} {type.startsWith('Array') && ']'} - +
    ) } @@ -75,14 +99,23 @@ type PropertiesViewProps = { totalNumItems: number moreStep: number nodeInspectorWidth: number + onGraphInteraction?: GraphInteractionCallBack } export const PropertiesTable = ({ visibleProperties, totalNumItems, onMoreClick, moreStep, - nodeInspectorWidth + nodeInspectorWidth, + onGraphInteraction }: PropertiesViewProps): JSX.Element => { + let id = '' + for (let i = 0; i < visibleProperties.length; i++) { + if (visibleProperties[i].key == '') { + id = visibleProperties[i].value + } + } + return ( <> @@ -95,9 +128,12 @@ export const PropertiesTable = ({ undefined)} /> diff --git a/src/neo4j-arc/graph-visualization/GraphVisualizer/DefaultPanelContent/DefaultDetailsPane.tsx b/src/neo4j-arc/graph-visualization/GraphVisualizer/DefaultPanelContent/DefaultDetailsPane.tsx index 037d669abe1..3526929c81b 100644 --- a/src/neo4j-arc/graph-visualization/GraphVisualizer/DefaultPanelContent/DefaultDetailsPane.tsx +++ b/src/neo4j-arc/graph-visualization/GraphVisualizer/DefaultPanelContent/DefaultDetailsPane.tsx @@ -26,17 +26,20 @@ import { PaneBody, PaneHeader, PaneTitle, PaneWrapper } from './styled' import { NodeLabel } from './NodeLabel' import { RelType } from './RelType' import { GraphStyleModel } from '../../models/GraphStyle' +import { GraphInteractionCallBack } from '../Graph/GraphEventHandlerModel' export const DETAILS_PANE_STEP_SIZE = 1000 export type DetailsPaneProps = { vizItem: NodeItem | RelationshipItem graphStyle: GraphStyleModel nodeInspectorWidth: number + onGraphInteraction?: GraphInteractionCallBack } export function DefaultDetailsPane({ vizItem, graphStyle, - nodeInspectorWidth + nodeInspectorWidth, + onGraphInteraction }: DetailsPaneProps): JSX.Element { const [maxPropertiesCount, setMaxPropertiesCount] = useState( DETAILS_PANE_STEP_SIZE @@ -106,6 +109,7 @@ export function DefaultDetailsPane({ moreStep={DETAILS_PANE_STEP_SIZE} totalNumItems={allItemProperties.length} nodeInspectorWidth={nodeInspectorWidth} + onGraphInteraction={onGraphInteraction} /> diff --git a/src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/GraphEventHandlerModel.ts b/src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/GraphEventHandlerModel.ts index aa15f6afc54..d64aba685ef 100644 --- a/src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/GraphEventHandlerModel.ts +++ b/src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/GraphEventHandlerModel.ts @@ -31,6 +31,8 @@ import { import { Visualization } from './visualization/Visualization' export const NODE_ON_CANVAS_CREATE = 'NODE_ON_CANVAS_CREATE' +export const NODE_PROP_UPDATE = 'NODE_PROP_UPDATE' +export const NODE_LABEL_UPDATE = 'NODE_LABEL_UPDATE' export type GraphInteraction = | 'NODE_EXPAND' @@ -38,6 +40,8 @@ export type GraphInteraction = | 'NODE_DISMISSED' | 'NODE_ON_CANVAS_CREATE' | typeof NODE_ON_CANVAS_CREATE + | typeof NODE_PROP_UPDATE + | typeof NODE_LABEL_UPDATE export type GraphInteractionCallBack = ( event: GraphInteraction, @@ -239,8 +243,14 @@ export class GraphEventHandlerModel { new NodeModel( newId.toString(), ['Undefined'], - { name: 'New Node' }, - { name: 'string' } + { + name: 'New Node', + description: 'New Node' + }, + { + name: 'string', + description: 'string' + } ) ]) this.visualization.update({ updateNodes: true, updateRelationships: true }) @@ -249,6 +259,7 @@ export class GraphEventHandlerModel { this.onGraphInteraction(NODE_ON_CANVAS_CREATE, { id: newId, name: 'New Node', + description: 'New Node', labels: ['Undefined'] }) } diff --git a/src/neo4j-arc/graph-visualization/GraphVisualizer/GraphVisualizer.tsx b/src/neo4j-arc/graph-visualization/GraphVisualizer/GraphVisualizer.tsx index b67b1360344..87bc6c09ad6 100644 --- a/src/neo4j-arc/graph-visualization/GraphVisualizer/GraphVisualizer.tsx +++ b/src/neo4j-arc/graph-visualization/GraphVisualizer/GraphVisualizer.tsx @@ -297,6 +297,7 @@ export class GraphVisualizer extends Component< }} DetailsPaneOverride={this.props.DetailsPaneOverride} OverviewPaneOverride={this.props.OverviewPaneOverride} + onGraphInteraction={this.props.onGraphInteraction} /> ) diff --git a/src/neo4j-arc/graph-visualization/GraphVisualizer/NodeInspectorPanel.tsx b/src/neo4j-arc/graph-visualization/GraphVisualizer/NodeInspectorPanel.tsx index 734d813b09e..12d9572c1e7 100644 --- a/src/neo4j-arc/graph-visualization/GraphVisualizer/NodeInspectorPanel.tsx +++ b/src/neo4j-arc/graph-visualization/GraphVisualizer/NodeInspectorPanel.tsx @@ -38,6 +38,7 @@ import { Resizable } from 're-resizable' import { GraphStats } from '../utils/mapper' import { GraphStyleModel } from '../models/GraphStyle' import { VizItem } from '../types' +import { GraphInteractionCallBack } from './Graph/GraphEventHandlerModel' interface NodeInspectorPanelProps { expanded: boolean @@ -51,6 +52,7 @@ interface NodeInspectorPanelProps { width: number DetailsPaneOverride?: React.FC OverviewPaneOverride?: React.FC + onGraphInteraction?: GraphInteractionCallBack } export const defaultPanelWidth = (): number => @@ -68,7 +70,8 @@ export class NodeInspectorPanel extends Component { toggleExpanded, width, DetailsPaneOverride, - OverviewPaneOverride + OverviewPaneOverride, + onGraphInteraction } = this.props const relevantItems = ['node', 'relationship'] const hoveringNodeOrRelationship = @@ -120,6 +123,7 @@ export class NodeInspectorPanel extends Component { vizItem={shownEl} graphStyle={graphStyle} nodeInspectorWidth={width} + onGraphInteraction={onGraphInteraction} /> ) : (