diff --git a/__mocks__/neo4j.js b/__mocks__/neo4j.js deleted file mode 100644 index 9bde1c7e084..00000000000 --- a/__mocks__/neo4j.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo4j, Inc" - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -function integerFn (val) { - this.val = val -} -integerFn.prototype.toString = function () { - return this.val.toString() -} - -var out = { - v1: { - isInt: function (val) { - return val instanceof out.v1.Integer - }, - types: { - Node: function Node (id, labels, properties) { - this.identity = id - this.labels = labels - this.properties = properties - }, - Relationship: function Relationship (id, start, end, type, properties) { - this.identity = id - this.start = start - this.end = end - this.type = type - this.properties = properties - }, - Path: function Path (start, end, segments) { - this.start = start - this.end = end - this.segments = segments - this.length = segments.length - }, - PathSegment: function PathSegment (start, relationship, end) { - this.start = start - this.relationship = relationship - this.end = end - } - }, - Integer: function Integer (low, high) { - this.low = low - this.high = high - } - } -} - -out.v1.types.Node.prototype.toString = function () { - return 'node' -} -out.v1.types.Relationship.prototype.toString = function () { - return 'rel' -} -out.v1.types.Path.prototype.toString = function () { - return 'path' -} -out.v1.types.PathSegment.prototype.toString = function () { - return 'pathsegment' -} -out.v1.Integer.prototype.toInt = function () { - return this.low -} -out.v1.Integer.prototype.toString = function () { - return this.low -} -out.v1.Int = out.v1.Integer -out.v1.int = val => { - if (val /* is compatible */ instanceof out.v1.Integer) return val - if (typeof val === 'number') return new out.v1.Integer(val) - if (typeof val === 'string') return new out.v1.Integer(val) - // Throws for non-objects, converts non-instanceof Integer: - return new out.v1.Integer(val.low, val.high) -} - -module.exports = out diff --git a/e2e_tests/integration/types.spec.js b/e2e_tests/integration/types.spec.js new file mode 100644 index 00000000000..e033f41f3e5 --- /dev/null +++ b/e2e_tests/integration/types.spec.js @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2002-2018 "Neo4j, Inc" + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* global Cypress, cy, test, expect */ + +describe('Types in Browser', () => { + it('can connect', () => { + cy.executeCommand(':server disconnect') + cy.executeCommand(':clear') + cy.executeCommand(':server connect') + const password = Cypress.env('BROWSER_NEW_PASSWORD') || 'newpassword' + cy.connect(password) + }) + it('presents the point type correctly', () => { + cy.executeCommand(':clear') + const query = + "WITH point({{}crs: 'wgs-84', longitude: 12.78, latitude: 56.7}) as p1 RETURN p1" + cy.executeCommand(query) + cy.waitForCommandResult() + + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for point type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('point({srid:4326, x:12.78, y:56.7})') + + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│point({srid:4326, x:12.78, y:56.7})') + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents datetime type correctly', () => { + cy.executeCommand(':clear') + const query = + 'RETURN datetime({{}year:2015, month:7, day:20, hour:15, minute:11, second:42, timezone:"Europe/Stockholm"}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"2015-07-20T15:11:42.000000000[Europe/Stockholm]"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains( + '│"2015-07-20T15:11:42.000000000[Europe/Stockholm]"' + ) + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents local datetime type correctly', () => { + cy.executeCommand(':clear') + const query = + 'RETURN localdatetime({{}year:2015, month:7, day:20, hour:15, minute:11, second:42}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"2015-07-20T15:11:42.000000000"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│"2015-07-20T15:11:42.000000000"') + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents date type correctly', () => { + cy.executeCommand(':clear') + const query = 'RETURN date({{}year:2015, month:7, day:20}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"2015-07-20"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│"2015-07-20"') + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents duration type correctly', () => { + cy.executeCommand(':clear') + const query = + 'RETURN duration({{}months:14, days:3, seconds:14706, nanoseconds:0}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"P14M3DT14706.000000000S"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│"P14M3DT14706.000000000S"') + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents time type correctly', () => { + cy.executeCommand(':clear') + const query = + 'RETURN time({{}hour:14, minute:3, second:4, timezone: "Europe/Stockholm"}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"14:03:04.000000000+02:00"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│"14:03:04.000000000+02:00"') + } else { + cy.resultContains('ERROR') + } + }) + }) + it('presents localtime type correctly', () => { + cy.executeCommand(':clear') + const query = 'RETURN localtime({{}hour:14, minute:3, second:4}) AS t1' + cy.executeCommand(query) + cy.waitForCommandResult() + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .then(contents => { + // Check for type support + if (contents.find('.table-row').length > 0) { + cy.resultContains('"14:03:04.000000000"') + // Go to ascii view + cy + .get('[data-test-id="cypherFrameSidebarAscii"') + .first() + .click() + cy.resultContains('│"14:03:04.000000000"') + } else { + cy.resultContains('ERROR') + } + }) + }) +}) diff --git a/e2e_tests/support/commands.js b/e2e_tests/support/commands.js index 0613021c6a5..935bb45f4e4 100644 --- a/e2e_tests/support/commands.js +++ b/e2e_tests/support/commands.js @@ -74,7 +74,6 @@ Cypress.Commands.add('disconnect', () => { Cypress.Commands.add('executeCommand', query => { cy.get(ClearEditorButton).click() cy.get(Editor).type(query, { force: true }) - cy.get(Editor).should('have.value', query) cy.get(SubmitQueryButton).click() }) Cypress.Commands.add('waitForCommandResult', () => { @@ -82,3 +81,9 @@ Cypress.Commands.add('waitForCommandResult', () => { .get('[data-test-id="frame-loaded-contents"]', { timeout: 40000 }) .should('be.visible') }) +Cypress.Commands.add('resultContains', str => { + cy + .get('[data-test-id="frameContents"]', { timeout: 10000 }) + .first() + .should('contain', str) +}) diff --git a/package.json b/package.json index a110472f831..7e838c50162 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,8 @@ "apiVersion": "^1.2.0" }, "scripts": { - "start": - "webpack-dashboard -t 'Neo4j Browser' -- webpack-dev-server --colors --no-info", - "starts": - "webpack-dashboard -t 'Neo4j Browser' -- webpack-dev-server --https --colors --no-info", + "start": "webpack-dashboard -t 'Neo4j Browser' -- webpack-dev-server --colors --no-info", + "starts": "webpack-dashboard -t 'Neo4j Browser' -- webpack-dev-server --https --colors --no-info", "startnodash": "webpack-dev-server --colors --no-info", "precommit": "lint-staged", "format": "prettier-eslint 'src/**/!(*.min).js' 'src/**/*.jsx' --write", @@ -31,23 +29,27 @@ "build": "rm -rf ./dist && NODE_ENV=\"production\" webpack", "prepare-jar": "node ./scripts/prepare-mvn-package.js", "jar": "yarn build && mvn package", - "version-pom": - "node ./scripts/set-pom-version.js -f ./pom.xml -v $npm_package_version", + "version-pom": "node ./scripts/set-pom-version.js -f ./pom.xml -v $npm_package_version", "version": "npm run version-pom && git add ./pom.xml", "update-licenses": "sh ./scripts/generate_licenses.sh" }, "lint-staged": { "linters": { - "*.{js,jsx}": ["prettier-eslint --write", "git add"] + "*.{js,jsx}": [ + "prettier-eslint --write", + "git add" + ] } }, "jest": { - "testPathIgnorePatterns": [".jsx$", "/e2e_tests/"], + "testPathIgnorePatterns": [ + ".jsx$", + "/e2e_tests/" + ], "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|html)$": - "/__mocks__/fileMock.js", + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|html)$": "/__mocks__/fileMock.js", "\\.(css|less)$": "/__mocks__/styleMock.js", - "neo4j": "/__mocks__/neo4j.js", + "^neo4j-driver-alias$": "neo4j-driver", "^react-dom/server$": "preact-render-to-string", "^react-addons-test-utils$": "preact-test-utils", "^react-addons-transition-group$": "preact-transition-group", @@ -57,7 +59,10 @@ "^browser-components(.*)$": "/src/browser/components$1", "worker-loader": "/__mocks__/workerLoaderMock.js" }, - "modulePaths": ["/src", "/src/shared"] + "modulePaths": [ + "/src", + "/src/shared" + ] }, "devDependencies": { "autoprefixer": "^7.1.4", @@ -136,7 +141,7 @@ "isomorphic-fetch": "^2.2.1", "jsonic": "^0.3.0", "lodash.debounce": "^4.0.8", - "neo4j-driver": "^1.5.3", + "neo4j-driver": "1.6.0-rc1", "node-noop": "^1.0.0", "preact": "^8.2.5", "preact-compat": "^3.17.0", diff --git a/src/browser/modules/Stream/CypherFrame/TableView.jsx b/src/browser/modules/Stream/CypherFrame/TableView.jsx index dcf5bbfad6e..1ad509e06bb 100644 --- a/src/browser/modules/Stream/CypherFrame/TableView.jsx +++ b/src/browser/modules/Stream/CypherFrame/TableView.jsx @@ -20,6 +20,7 @@ import { Component } from 'preact' import { v4 } from 'uuid' +import { v1 as neo4j } from 'neo4j-driver-alias' import { StyledStatsBar, PaddedTableViewDiv, @@ -34,16 +35,12 @@ import { StyledJsonPre } from 'browser-components/DataTables' import { deepEquals, shallowEquals, stringifyMod } from 'services/utils' -import { v1 as neo4j } from 'neo4j-driver-alias' import { getBodyAndStatusBarMessages, getRecordsToDisplayInTable, transformResultRecordsToResultArray } from './helpers' - -const intToString = val => { - if (neo4j.isInt(val)) return val.toString() -} +import { stringFormat } from 'services/bolt/cypherTypesFormatting' const renderCell = entry => { if (Array.isArray(entry)) { @@ -57,13 +54,15 @@ const renderCell = entry => { } else if (typeof entry === 'object') { return renderObject(entry) } else { - return stringifyMod(entry, intToString, true) + return stringifyMod(entry, stringFormat, true) } } export const renderObject = entry => { if (neo4j.isInt(entry)) return entry.toString() if (entry === null) return null - return {stringifyMod(entry, intToString, true)} + return ( + {stringifyMod(entry, stringFormat, true)} + ) } const buildData = entries => { return entries.map(entry => { diff --git a/src/browser/modules/Stream/CypherFrame/__snapshots__/TableView.test.js.snap b/src/browser/modules/Stream/CypherFrame/__snapshots__/TableView.test.js.snap index 9bda28d30e8..4c498f72751 100644 --- a/src/browser/modules/Stream/CypherFrame/__snapshots__/TableView.test.js.snap +++ b/src/browser/modules/Stream/CypherFrame/__snapshots__/TableView.test.js.snap @@ -3,7 +3,7 @@ exports[`TableViews TableView renderObject handles null values 1`] = ` { - "x": 1 + "x": 1.0 } `; diff --git a/src/browser/modules/Stream/CypherFrame/helpers.js b/src/browser/modules/Stream/CypherFrame/helpers.js index c7001270e24..e09cc10b493 100644 --- a/src/browser/modules/Stream/CypherFrame/helpers.js +++ b/src/browser/modules/Stream/CypherFrame/helpers.js @@ -26,6 +26,7 @@ import { flattenArray } from 'services/bolt/boltMappings' import { stringifyMod } from 'services/utils' +import { stringFormat } from 'services/bolt/cypherTypesFormatting' export function getBodyAndStatusBarMessages (result, maxRows) { if (!result || !result.summary || !result.summary.resultAvailableAfter) { @@ -167,9 +168,7 @@ export const stringifyResultArray = (intChecker = neo4j.isInt, arr = []) => { return arr.map(col => { if (!col) return col return col.map(fVal => { - return stringifyMod(fVal, val => { - if (intChecker(val)) return val.toString() - }) + return stringifyMod(fVal, stringFormat) }) }) } @@ -247,7 +246,14 @@ export const isGraphItem = (types = neo4j.types, item) => { item instanceof types.Node || item instanceof types.Relationship || item instanceof types.Path || - item instanceof types.PathSegment + item instanceof types.PathSegment || + item instanceof neo4j.types.Date || + item instanceof neo4j.types.DateTime || + item instanceof neo4j.types.Duration || + item instanceof neo4j.types.LocalDateTime || + item instanceof neo4j.types.LocalTime || + item instanceof neo4j.types.Time || + item instanceof neo4j.types.Point ) } diff --git a/src/browser/modules/Stream/CypherFrame/helpers.test.js b/src/browser/modules/Stream/CypherFrame/helpers.test.js index 659776cf95d..37f7f4313a4 100644 --- a/src/browser/modules/Stream/CypherFrame/helpers.test.js +++ b/src/browser/modules/Stream/CypherFrame/helpers.test.js @@ -19,6 +19,7 @@ */ /* global describe, test, expect */ +/* eslint-disable new-cap */ import { v1 as neo4j } from 'neo4j-driver-alias' import * as viewTypes from 'shared/modules/stream/frameViewTypes' import { @@ -517,7 +518,7 @@ describe('helpers', () => { test('extractRecordsToResultArray handles regular records', () => { // Given const start = new neo4j.types.Node(1, ['X'], { x: 1 }) - const end = new neo4j.types.Node(2, ['Y'], { y: new neo4j.Int(1) }) + const end = new neo4j.types.Node(2, ['Y'], { y: new neo4j.int(1) }) const rel = new neo4j.types.Relationship(3, 1, 2, 'REL', { rel: 1 }) const segments = [new neo4j.types.PathSegment(start, rel, end)] const path = new neo4j.types.Path(start, end, segments) @@ -590,11 +591,11 @@ describe('helpers', () => { const records = [ { keys: ['"neoInt"', '"int"', '"any"', '"backslash"'], - _fields: [new neo4j.Int('882573709873217509'), 100, 0.5, '"\\"'] + _fields: [new neo4j.int('882573709873217509'), 100, 0.5, '"\\"'] }, { keys: ['"neoInt"', '"int"', '"any"'], - _fields: [new neo4j.Int(300), 100, 'string'] + _fields: [new neo4j.int(300), 100, 'string'] } ] @@ -609,15 +610,17 @@ describe('helpers', () => { // Then expect(res).toEqual([ ['""neoInt""', '""int""', '""any""', '""backslash""'], - ['882573709873217509', '100', '0.5', '""\\""'], - ['300', '100', '"string"'] + ['882573709873217509', '100.0', '0.5', '""\\""'], + ['300', '100.0', '"string"'] ]) }) test('stringifyResultArray handles neo4j integers nested within graph items', () => { // Given - const start = new neo4j.types.Node(1, ['X'], { x: 1 }) - const end = new neo4j.types.Node(2, ['Y'], { y: new neo4j.Int(2) }) // <-- Neo4j integer - const rel = new neo4j.types.Relationship(3, 1, 2, 'REL', { rel: 1 }) + const start = new neo4j.types.Node(1, ['X'], { x: new neo4j.int(1) }) + const end = new neo4j.types.Node(2, ['Y'], { y: new neo4j.int(2) }) + const rel = new neo4j.types.Relationship(3, 1, 2, 'REL', { + rel: new neo4j.int(1) + }) const segments = [new neo4j.types.PathSegment(start, rel, end)] const path = new neo4j.types.Path(start, end, segments) diff --git a/src/browser/modules/Stream/CypherFrame/index.jsx b/src/browser/modules/Stream/CypherFrame/index.jsx index 25d4e5d92a0..627903a8b3f 100644 --- a/src/browser/modules/Stream/CypherFrame/index.jsx +++ b/src/browser/modules/Stream/CypherFrame/index.jsx @@ -157,6 +157,7 @@ export class CypherFrame extends Component { { this.changeView(viewTypes.TABLE) @@ -172,6 +173,7 @@ export class CypherFrame extends Component { } > { this.changeView(viewTypes.TEXT) diff --git a/src/shared/services/bolt/applyGraphTypes.test.js b/src/shared/services/bolt/applyGraphTypes.test.js index 20c95350bb0..ad583e1e76f 100644 --- a/src/shared/services/bolt/applyGraphTypes.test.js +++ b/src/shared/services/bolt/applyGraphTypes.test.js @@ -125,7 +125,7 @@ describe('applyGraphTypes', () => { test('should apply integer type', () => { const rawNumber = nativeTypesToCustom(neo4j.int(5)) const typedNumber = applyGraphTypes(rawNumber) - expect(typedNumber).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNumber)).toBeTruthy() }) test('should apply node type', () => { @@ -134,7 +134,7 @@ describe('applyGraphTypes', () => { const typedNode = applyGraphTypes(rawNode) expect(typedNode).toBeInstanceOf(neo4j.types.Node) - expect(typedNode.identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNode.identity)).toBeTruthy() }) test('should not false positive on fake node object type', () => { @@ -148,7 +148,8 @@ describe('applyGraphTypes', () => { const obj = applyGraphTypes(rawObject) expect(obj).toBeInstanceOf(Object) - expect(obj.identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(obj.identity)).toBeTruthy() + expect(neo4j.isInt(obj.identity2)).toBeFalsy() expect(obj.identity2).toBeInstanceOf(Object) expect(obj[reservedTypePropertyName]).toEqual('Node') }) @@ -180,7 +181,7 @@ describe('applyGraphTypes', () => { const typedNode = applyGraphTypes(rawNode) expect(typedNode).toBeInstanceOf(neo4j.types.Node) - expect(typedNode.identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNode.identity)).toBeTruthy() expect(typedNode.properties.prop1).toBeNull() expect(typedNode.properties.prop2).toEqual(33) @@ -191,7 +192,7 @@ describe('applyGraphTypes', () => { expect(typedNode.properties.prop6.prop1).toEqual(1) expect(typedNode.properties.prop6.prop2).toEqual('test') - expect(typedNode.properties.prop7.prop1).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNode.properties.prop7.prop1)).toBeTruthy() expect(typedNode.properties.prop7.prop1.toInt()).toEqual(3) expect(typedNode.properties.prop7.prop2).toEqual('test') @@ -223,9 +224,9 @@ describe('applyGraphTypes', () => { const typedNodes = applyGraphTypes(rawNodes, neo4j.types) expect(typedNodes.length).toEqual(2) expect(typedNodes[0]).toBeInstanceOf(neo4j.types.Node) - expect(typedNodes[0].identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNodes[0].identity)).toBeTruthy() expect(typedNodes[1]).toBeInstanceOf(neo4j.types.Node) - expect(typedNodes[1].identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedNodes[1].identity)).toBeTruthy() }) test('should apply relationship type', () => { @@ -241,7 +242,7 @@ describe('applyGraphTypes', () => { const typedRelationship = applyGraphTypes(rawRelationship) expect(typedRelationship).toBeInstanceOf(neo4j.types.Relationship) - expect(typedRelationship.identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedRelationship.identity)).toBeTruthy() expect(typedRelationship.type).toEqual('TESTED_WITH') }) @@ -268,13 +269,13 @@ describe('applyGraphTypes', () => { const typedRelationships = applyGraphTypes(rawRelationships) expect(typedRelationships.length).toEqual(2) expect(typedRelationships[0]).toBeInstanceOf(neo4j.types.Relationship) - expect(typedRelationships[0].identity).toBeInstanceOf(neo4j.Integer) - expect(typedRelationships[0].start).toBeInstanceOf(neo4j.Integer) - expect(typedRelationships[0].end).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedRelationships[0].identity)).toBeTruthy() + expect(neo4j.isInt(typedRelationships[0].start)).toBeTruthy() + expect(neo4j.isInt(typedRelationships[0].end)).toBeTruthy() expect(typedRelationships[1]).toBeInstanceOf(neo4j.types.Relationship) - expect(typedRelationships[1].identity).toBeInstanceOf(neo4j.Integer) - expect(typedRelationships[1].start).toBeInstanceOf(neo4j.Integer) - expect(typedRelationships[1].end).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedRelationships[1].identity)).toBeTruthy() + expect(neo4j.isInt(typedRelationships[1].start)).toBeTruthy() + expect(neo4j.isInt(typedRelationships[1].end)).toBeTruthy() }) test('should apply to custom object properties', () => { @@ -286,7 +287,7 @@ describe('applyGraphTypes', () => { const typedObject = applyGraphTypes(rawData) expect(typedObject.node).toBeInstanceOf(neo4j.types.Node) - expect(typedObject.num).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedObject.num)).toBeTruthy() }) test('should apply to array of custom object properties', () => { @@ -304,9 +305,9 @@ describe('applyGraphTypes', () => { const typedObjects = applyGraphTypes(rawObj) expect(typedObjects.length).toEqual(2) expect(typedObjects[0].node).toBeInstanceOf(neo4j.types.Node) - expect(typedObjects[0].num).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedObjects[0].num)).toBeTruthy() expect(typedObjects[1].node).toBeInstanceOf(neo4j.types.Node) - expect(typedObjects[1].num).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedObjects[1].num)).toBeTruthy() }) test('should apply PathSegment type', () => { @@ -315,7 +316,7 @@ describe('applyGraphTypes', () => { expect(typedPathSegment).toBeTruthy() expect(typedPathSegment).toBeInstanceOf(neo4j.types.PathSegment) expect(typedPathSegment.start).toBeInstanceOf(neo4j.types.Node) - expect(typedPathSegment.start.identity).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedPathSegment.start.identity)).toBeTruthy() expect(typedPathSegment.end).toBeInstanceOf(neo4j.types.Node) expect(typedPathSegment.relationship).toBeInstanceOf( neo4j.types.Relationship @@ -388,7 +389,7 @@ describe('applyGraphTypes', () => { }) const typedObject = applyGraphTypes(complexObj) expect(typedObject).toBeTruthy() - expect(typedObject.rawNum).toBeInstanceOf(neo4j.Integer) + expect(neo4j.isInt(typedObject.rawNum)).toBeTruthy() expect(typedObject.rawNode).toBeInstanceOf(neo4j.types.Node) expect(typedObject.rawRelationship).toBeInstanceOf(neo4j.types.Relationship) expect(typedObject.rawPath).toBeInstanceOf(neo4j.types.Path) @@ -400,6 +401,65 @@ describe('applyGraphTypes', () => { neo4j.types.PathSegment ) }) + test('should apply Point type', () => { + const point = new neo4j.types.Point('test-ref', 57.734588, 12.908685) + const rawPoint = nativeTypesToCustom(point) + + const typedPoint = applyGraphTypes(rawPoint) + expect(typedPoint).toBeInstanceOf(neo4j.types.Point) + }) + test('should apply Date type', () => { + const date = new neo4j.types.Date(2012, 5, 11) + const rawDate = nativeTypesToCustom(date) + + const typedDate = applyGraphTypes(rawDate) + expect(typedDate).toBeInstanceOf(neo4j.types.Date) + }) + test('should apply DateTime type', () => { + const dateTime = new neo4j.types.DateTime( + 2012, + 5, + 11, + 9, + 12, + 44, + 0, + null, + 'Europe/Stockholm' + ) + const rawDateTime = nativeTypesToCustom(dateTime) + + const typedDateTime = applyGraphTypes(rawDateTime) + expect(typedDateTime).toBeInstanceOf(neo4j.types.DateTime) + }) + test('should apply Duration type', () => { + const date = new neo4j.types.Duration(1, 2, 0, 0) + const rawDate = nativeTypesToCustom(date) + + const typedDate = applyGraphTypes(rawDate) + expect(typedDate).toBeInstanceOf(neo4j.types.Duration) + }) + test('should apply LocalDateTime type', () => { + const date = new neo4j.types.LocalDateTime(2012, 5, 11, 1, 12, 2) + const rawDate = nativeTypesToCustom(date) + + const typedDate = applyGraphTypes(rawDate) + expect(typedDate).toBeInstanceOf(neo4j.types.LocalDateTime) + }) + test('should apply LocalTime type', () => { + const date = new neo4j.types.LocalTime(11, 1, 12, 2) + const rawDate = nativeTypesToCustom(date) + + const typedDate = applyGraphTypes(rawDate) + expect(typedDate).toBeInstanceOf(neo4j.types.LocalTime) + }) + test('should apply Time type', () => { + const date = new neo4j.types.Time(11, 1, 12, 3600) + const rawDate = nativeTypesToCustom(date) + + const typedDate = applyGraphTypes(rawDate) + expect(typedDate).toBeInstanceOf(neo4j.types.Time) + }) }) const nativeTypesToCustom = x => { diff --git a/src/shared/services/bolt/boltMappings.js b/src/shared/services/bolt/boltMappings.js index 10a57108e3f..7e3aa88b649 100644 --- a/src/shared/services/bolt/boltMappings.js +++ b/src/shared/services/bolt/boltMappings.js @@ -323,6 +323,63 @@ export const applyGraphTypes = (item, types = neo4j.types) => { applyGraphTypes(item.end, types), item.segments.map(x => applyGraphTypes(x, types)) ) + case 'Point': + return new types[className]( + applyGraphTypes(item.srid), + applyGraphTypes(item.x), + applyGraphTypes(item.y), + applyGraphTypes(item.z) + ) + case 'Date': + return new types[className]( + applyGraphTypes(item.year), + applyGraphTypes(item.month), + applyGraphTypes(item.day) + ) + case 'DateTime': + return new types[className]( + applyGraphTypes(item.year), + applyGraphTypes(item.month), + applyGraphTypes(item.day), + applyGraphTypes(item.hour), + applyGraphTypes(item.minute), + applyGraphTypes(item.second), + applyGraphTypes(item.nanosecond), + applyGraphTypes(item.timeZoneOffsetSeconds), + applyGraphTypes(item.timeZoneId) + ) + case 'Duration': + return new types[className]( + applyGraphTypes(item.months), + applyGraphTypes(item.days), + applyGraphTypes(item.seconds), + applyGraphTypes(item.nanoseconds) + ) + case 'LocalDateTime': + return new types[className]( + applyGraphTypes(item.year), + applyGraphTypes(item.month), + applyGraphTypes(item.day), + applyGraphTypes(item.hour), + applyGraphTypes(item.minute), + applyGraphTypes(item.second), + applyGraphTypes(item.nanosecond) + ) + case 'LocalTime': + return new types[className]( + applyGraphTypes(item.hour), + applyGraphTypes(item.minute), + applyGraphTypes(item.second), + applyGraphTypes(item.nanosecond) + ) + case 'Time': + return new types[className]( + applyGraphTypes(item.hour), + applyGraphTypes(item.minute), + applyGraphTypes(item.second), + applyGraphTypes(item.nanosecond), + applyGraphTypes(item.timeZoneOffsetSeconds) + ) case 'Integer': return neo4j.int(tmpItem) default: @@ -396,6 +453,69 @@ export const recursivelyTypeGraphItems = (item, types = neo4j.types) => { item.properties = props return item } + if (item instanceof types.Point) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'Point') + return tmp + } + if (item instanceof types.Date) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'Date') + return tmp + } + if (item instanceof types.DateTime) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'DateTime') + return tmp + } + if (item instanceof types.Duration) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'Duration') + return tmp + } + if (item instanceof types.LocalDateTime) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'LocalDateTime') + return tmp + } + if (item instanceof types.LocalTime) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'LocalTime') + return tmp + } + if (item instanceof types.Time) { + const keys = Object.keys(item) + let tmp = {} + keys.forEach( + key => (tmp[key] = recursivelyTypeGraphItems(item[key], types)) + ) + safetlyAddObjectProp(tmp, reservedTypePropertyName, 'Time') + return tmp + } if (neo4j.isInt(item)) { return safetlyAddObjectProp(item, reservedTypePropertyName, 'Integer') } diff --git a/src/shared/services/bolt/cypherTypesFormatting.js b/src/shared/services/bolt/cypherTypesFormatting.js new file mode 100644 index 00000000000..cc469341e25 --- /dev/null +++ b/src/shared/services/bolt/cypherTypesFormatting.js @@ -0,0 +1,28 @@ +import { v1 as neo4j } from 'neo4j-driver-alias' + +export const stringFormat = anything => { + if (typeof anything === 'number') { + if (Math.floor(anything) === anything) { + return `${anything}.0` + } + return undefined + } + if (neo4j.isInt(anything)) { + return anything.toString() + } + if (anything instanceof neo4j.types.Point) { + const zString = anything.z ? `, z:${anything.z}` : '' + return `point({srid:${anything.srid}, x:${anything.x}, y:${anything.y}${zString}})` + } + if ( + anything instanceof neo4j.types.Date || + anything instanceof neo4j.types.DateTime || + anything instanceof neo4j.types.Duration || + anything instanceof neo4j.types.LocalDateTime || + anything instanceof neo4j.types.LocalTime || + anything instanceof neo4j.types.Time + ) { + return `"${anything.toString()}"` + } + return undefined +} diff --git a/yarn.lock b/yarn.lock index de64864ea7d..ee4297b1ab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5660,9 +5660,9 @@ negotiator@0.6.1: version "0.6.1" resolved "https://neo.jfrog.io/neo/api/npm/npm/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -neo4j-driver@^1.5.3: - version "1.5.3" - resolved "https://neo.jfrog.io/neo/api/npm/npm/neo4j-driver/-/neo4j-driver-1.5.3.tgz#aa7cec63ed793dd5a51c98ab0477edc35c293248" +neo4j-driver@1.6.0-rc1: + version "1.6.0-rc1" + resolved "https://neo.jfrog.io/neo/api/npm/npm/neo4j-driver/-/neo4j-driver-1.6.0-rc1.tgz?dl=https://registry.npmjs.org/neo4j-driver/-/neo4j-driver-1.6.0-rc1.tgz#8590f05a5e23d2057e898c4209f2f1b4ccc77ae0" dependencies: babel-runtime "^6.18.0" url-parse "^1.2.0"