From a52e02fe65cd5e7abcdeba2c9c59fc6ebac03b40 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Wed, 21 Jun 2017 18:44:29 +0100 Subject: [PATCH 1/7] Fixes mapping issue when using :sysinfo with ha --- src/browser/modules/Stream/SysInfoFrame.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/modules/Stream/SysInfoFrame.jsx b/src/browser/modules/Stream/SysInfoFrame.jsx index 2f018c48386..47a0cfe381f 100644 --- a/src/browser/modules/Stream/SysInfoFrame.jsx +++ b/src/browser/modules/Stream/SysInfoFrame.jsx @@ -27,7 +27,7 @@ import FrameTemplate from '../Stream/FrameTemplate' import FrameError from '../Stream/FrameError' import { SysInfoTableContainer, SysInfoTable, SysInfoTableEntry } from 'browser-components/Tables' import bolt from 'services/bolt/bolt' -import { itemIntToString } from 'services/bolt/boltMappings' +import { itemIntToString, extractFromNeoObjects } from 'services/bolt/boltMappings' import { toHumanReadableBytes } from 'services/utils' import Render from 'browser-components/Render' @@ -42,7 +42,7 @@ export class SysInfoFrame extends Component { } flattenAttributes (a) { if (a && a.attributes) { - return Object.assign({}, ...a.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString()}) }))) + return Object.assign({}, ...a.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString(), objectConverter: extractFromNeoObjects}) }))) } else { return null } From cd0b305d3c78e571b935e28c7db32832a19b19a8 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Mon, 26 Jun 2017 17:28:23 +0100 Subject: [PATCH 2/7] Extract out logic from sysinfo frame component --- .../modules/Stream/Queries/QueriesFrame.jsx | 5 +- src/browser/modules/Stream/SysInfoFrame.jsx | 171 +++++++----------- .../modules/Stream/SysInfoFrame.test.js | 34 ++++ .../modules/commands/helpers/sysinfo.js | 82 +++++++++ src/shared/modules/features/featuresDuck.js | 1 + .../modules/features/featuresDuck.test.js | 17 +- 6 files changed, 195 insertions(+), 115 deletions(-) create mode 100644 src/browser/modules/Stream/SysInfoFrame.test.js create mode 100644 src/shared/modules/commands/helpers/sysinfo.js diff --git a/src/browser/modules/Stream/Queries/QueriesFrame.jsx b/src/browser/modules/Stream/Queries/QueriesFrame.jsx index 04379fb02fe..cb5a9d137e3 100644 --- a/src/browser/modules/Stream/Queries/QueriesFrame.jsx +++ b/src/browser/modules/Stream/Queries/QueriesFrame.jsx @@ -65,16 +65,13 @@ export class QueriesFrame extends Component { } } - isCC () { - return this.props.availableProcedures.includes('dbms.cluster.overview') - } canListQueries () { return this.props.availableProcedures.includes('dbms.listQueries') } getRunningQueries (suppressQuerySuccessMessage = false) { this.props.bus.self( - (this.isCC()) ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, + (this.props.isACausalCluster) ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, {query: listQueriesProcedure()}, (response) => { if (response.success) { diff --git a/src/browser/modules/Stream/SysInfoFrame.jsx b/src/browser/modules/Stream/SysInfoFrame.jsx index 47a0cfe381f..c8990e412c2 100644 --- a/src/browser/modules/Stream/SysInfoFrame.jsx +++ b/src/browser/modules/Stream/SysInfoFrame.jsx @@ -22,13 +22,12 @@ import { Component } from 'preact' import { connect } from 'preact-redux' import { withBus } from 'preact-suber' import { CYPHER_REQUEST } from 'shared/modules/cypher/cypherDuck' -import { getAvailableProcedures } from 'shared/modules/features/featuresDuck' +import { isACausalCluster } from 'shared/modules/features/featuresDuck' import FrameTemplate from '../Stream/FrameTemplate' import FrameError from '../Stream/FrameError' import { SysInfoTableContainer, SysInfoTable, SysInfoTableEntry } from 'browser-components/Tables' -import bolt from 'services/bolt/bolt' -import { itemIntToString, extractFromNeoObjects } from 'services/bolt/boltMappings' import { toHumanReadableBytes } from 'services/utils' +import { mapSysInfoRecords, getTableDataFromRecords } from 'shared/modules/commands/helpers/sysinfo' import Render from 'browser-components/Render' export class SysInfoFrame extends Component { @@ -36,31 +35,17 @@ export class SysInfoFrame extends Component { super(props) this.state = { error: '', - cc: [], - haInstances: [] - } - } - flattenAttributes (a) { - if (a && a.attributes) { - return Object.assign({}, ...a.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString(), objectConverter: extractFromNeoObjects}) }))) - } else { - return null + results: false } } + clusterResponseHandler () { return (res) => { if (!res.success) { this.setState({error: 'No causal cluster results'}) return } - const mappedResult = res.result.records.map((record) => { - return { - id: record.get('id'), - addresses: record.get('addresses'), - role: record.get('role'), - groups: record.get('groups') - } - }) + const mappedResult = mapSysInfoRecords(res.result.records) const mappedTableHeader = const mappedTableComponents = mappedResult.map((ccRecord) => { const httpUrlForMember = ccRecord.addresses.filter((address) => { @@ -82,54 +67,26 @@ export class SysInfoFrame extends Component { this.setState({error: 'No results'}) return } + const tableData = getTableDataFromRecords(res.result.records) + const {ha, kernel, cache, tx, primitive} = tableData - const mappedJMXresult = res.result.records.map((record) => { - const origAttributes = record.get('attributes') - return { - name: record.get('name'), - description: record.get('description'), - attributes: Object.keys(record.get('attributes')).map((attributeName) => { - return { - name: attributeName, - description: origAttributes[attributeName].description, - value: origAttributes[attributeName].value - } - }) - } - }) - - const jmxQueryPrefix = mappedJMXresult[0].name.split(',')[0] - const result = Object.assign({}, ...mappedJMXresult.map((item) => { - return { [item.name]: item } - })) - const cache = this.flattenAttributes(result[`${jmxQueryPrefix},name=Page cache`]) || {} - const primitive = this.flattenAttributes(result[`${jmxQueryPrefix},name=Primitive count`]) - const tx = this.flattenAttributes(result[`${jmxQueryPrefix},name=Transactions`]) || {} - const kernel = Object.assign({}, - this.flattenAttributes(result[`${jmxQueryPrefix},name=Configuration`]), - this.flattenAttributes(result[`${jmxQueryPrefix},name=Kernel`]), - this.flattenAttributes(result[`${jmxQueryPrefix},name=Store file sizes`])) - - if (result[`${jmxQueryPrefix},name=High Availability`]) { - const ha = this.flattenAttributes(result[`${jmxQueryPrefix},name=High Availability`]) + if (ha) { const haInstancesHeader = - const haInstances = [haInstancesHeader].concat(ha.InstancesInCluster.map(({properties}) => { + this.haInstances = [haInstancesHeader].concat(ha.InstancesInCluster.map(({properties}) => { const haInstancePropertyValues = [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] return })) - this.setState({ha: [ + this.ha = [ , , , , , - ], - 'haInstances': haInstances - }) + ] } - this.setState({'storeSizes': [ + this.storeSizes = [ , , , @@ -137,35 +94,33 @@ export class SysInfoFrame extends Component { , , - ], - 'idAllocation': [ - , - , - , - - ], - 'pageCache': [ - , - , - , - , - , - , - , - - ], - 'transactions': [ - , - , - , - , - - ]}) + ] + this.idAllocation = [ + , + , + , + + ] + this.pageCache = [ + , + , + , + , + , + , + , + + ] + this.transactions = [ + , + , + , + , + + ] + this.setState({results: true}) } } - isCC () { - return this.props.availableProcedures.includes('dbms.cluster.overview') - } componentDidMount () { if (this.props.bus) { this.props.bus.self( @@ -175,49 +130,49 @@ export class SysInfoFrame extends Component { }, this.responseHandler() ) - } - if (this.isCC()) { - this.props.bus.self( - CYPHER_REQUEST, - { - query: 'CALL dbms.cluster.overview' - }, - this.clusterResponseHandler() - ) + if (this.props.isACausalCluster) { + this.props.bus.self( + CYPHER_REQUEST, + { + query: 'CALL dbms.cluster.overview' + }, + this.clusterResponseHandler() + ) + } } } render () { - const content = ( - + const content = (this.state.results) + ? ( - {this.state.storeSizes || null} + {this.storeSizes || null} - {this.state.idAllocation || null} + {this.idAllocation || null} - {this.state.pageCache || null} + {this.pageCache || null} - {this.state.transactions || null} + {this.transactions || null} - - - {this.state.cc || null} + + + {this.cc || null} - + - {this.state.ha || null} + {this.ha || null} - - - {this.state.haInstances || null} + + + {this.haInstances || null} - - ) + ) + : null return ( { return { - availableProcedures: getAvailableProcedures(state) || [] + isACausalCluster: isACausalCluster(state) } } diff --git a/src/browser/modules/Stream/SysInfoFrame.test.js b/src/browser/modules/Stream/SysInfoFrame.test.js new file mode 100644 index 00000000000..b158fdfeba4 --- /dev/null +++ b/src/browser/modules/Stream/SysInfoFrame.test.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * 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 test, expect */ +import { mount } from 'services/testUtils' +import { SysInfoFrame } from './SysInfoFrame' + +describe('SysInfoFrame', () => { + // const SysInfoFrameFn = (props) => { + // return + // } + test('should render', () => { + mount(SysInfoFrame).withProps({availableProcedures: []}).then(wrapper => { + expect(wrapper.html()).not.toBe(null) + }) + }) +}) diff --git a/src/shared/modules/commands/helpers/sysinfo.js b/src/shared/modules/commands/helpers/sysinfo.js new file mode 100644 index 00000000000..7589e336020 --- /dev/null +++ b/src/shared/modules/commands/helpers/sysinfo.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * 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 . + */ + +import bolt from 'services/bolt/bolt' +import { itemIntToString, extractFromNeoObjects } from 'services/bolt/boltMappings' + +export const getTableDataFromRecords = (records) => { + const mappedJMXresults = mappedJMXresult(records) + const jmxQueryPrefix = mappedJMXresults[0].name.split(',')[0] + const result = Object.assign({}, ...mappedJMXresults.map((item) => { + return { [item.name]: item } + })) + const cache = flattenAttributes(result[`${jmxQueryPrefix},name=Page cache`]) || {} + const primitive = flattenAttributes(result[`${jmxQueryPrefix},name=Primitive count`]) + const tx = flattenAttributes(result[`${jmxQueryPrefix},name=Transactions`]) || {} + const kernel = Object.assign({}, + flattenAttributes(result[`${jmxQueryPrefix},name=Configuration`]), + flattenAttributes(result[`${jmxQueryPrefix},name=Kernel`]), + flattenAttributes(result[`${jmxQueryPrefix},name=Store file sizes`])) + const ha = (result[`${jmxQueryPrefix},name=High Availability`]) + ? flattenAttributes(result[`${jmxQueryPrefix},name=High Availability`]) + : null + + return { + cache, + primitive, + tx, + kernel, + ha + } +} +const mappedJMXresult = (records) => { + return records.map((record) => { + const origAttributes = record.get('attributes') + return { + name: record.get('name'), + description: record.get('description'), + attributes: Object.keys(record.get('attributes')).map((attributeName) => { + return { + name: attributeName, + description: origAttributes[attributeName].description, + value: origAttributes[attributeName].value + } + }) + } + }) +} + +export const mapSysInfoRecords = (records) => { + return records.map((record) => { + return { + id: record.get('id'), + addresses: record.get('addresses'), + role: record.get('role'), + groups: record.get('groups') + } + }) +} +const flattenAttributes = (a) => { + if (a && a.attributes) { + return Object.assign({}, ...a.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString(), objectConverter: extractFromNeoObjects}) }))) + } else { + return null + } +} diff --git a/src/shared/modules/features/featuresDuck.js b/src/shared/modules/features/featuresDuck.js index 2e66baf6df6..598995c0bff 100644 --- a/src/shared/modules/features/featuresDuck.js +++ b/src/shared/modules/features/featuresDuck.js @@ -27,6 +27,7 @@ export const RESET = 'features/RESET' export const UPDATE_ALL_FEATURES = 'features/UPDATE_ALL_FEATURES' export const getAvailableProcedures = (state) => state[NAME].availableProcedures +export const isACausalCluster = (state) => getAvailableProcedures(state).includes('dbms.cluster.overview') const initialState = { availableProcedures: [] diff --git a/src/shared/modules/features/featuresDuck.test.js b/src/shared/modules/features/featuresDuck.test.js index ef016e58558..bac2e03952e 100644 --- a/src/shared/modules/features/featuresDuck.test.js +++ b/src/shared/modules/features/featuresDuck.test.js @@ -19,7 +19,7 @@ */ /* global describe, test, expect */ -import reducer, { UPDATE_ALL_FEATURES } from './featuresDuck' +import reducer, * as features from './featuresDuck' import { dehydrate } from 'services/duckUtils' describe('features reducer', () => { @@ -30,7 +30,7 @@ describe('features reducer', () => { test('handles UPDATE_ALL_FEATURES without initial state', () => { const action = { - type: UPDATE_ALL_FEATURES, + type: features.UPDATE_ALL_FEATURES, availableProcedures: ['proc'] } const nextState = reducer(undefined, action) @@ -40,10 +40,21 @@ describe('features reducer', () => { test('handles UPDATE_ALL_FEATURES', () => { const initialState = { availableProcedures: ['a', 'b'] } const action = { - type: UPDATE_ALL_FEATURES, + type: features.UPDATE_ALL_FEATURES, availableProcedures: ['c'] } const nextState = reducer(initialState, action) expect(nextState.availableProcedures).toEqual(['c']) }) }) + +describe('feature getters', () => { + test('should not be a causal cluster', () => { + const nextState = reducer(undefined, {type: ''}) + expect(features.isACausalCluster(nextState)).toBe(false) + }) + test('should be in a causal cluster', () => { + const nextState = reducer({availableProcedures: ['dbms.cluster.overview']}, {type: ''}) + expect(features.isACausalCluster(nextState)).toBe(true) + }) +}) From 074f914d275006f797318c1a756291bc6c37e306 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Tue, 27 Jun 2017 13:30:13 +0100 Subject: [PATCH 3/7] Refactor to store result data in state --- __mocks__/neo4j.js | 3 +- .../index.jsx} | 121 +++++++++--------- .../modules/Stream/SysInfoFrame}/sysinfo.js | 10 +- .../sysinfo.test.js} | 26 ++-- .../modules/features/featuresDuck.test.js | 12 +- 5 files changed, 95 insertions(+), 77 deletions(-) rename src/browser/modules/Stream/{SysInfoFrame.jsx => SysInfoFrame/index.jsx} (55%) rename src/{shared/modules/commands/helpers => browser/modules/Stream/SysInfoFrame}/sysinfo.js (87%) rename src/browser/modules/Stream/{SysInfoFrame.test.js => SysInfoFrame/sysinfo.test.js} (53%) diff --git a/__mocks__/neo4j.js b/__mocks__/neo4j.js index 998ff141428..0a64b037ac3 100644 --- a/__mocks__/neo4j.js +++ b/__mocks__/neo4j.js @@ -28,7 +28,8 @@ var out = { this.identity = id }, Path: function Path () {} - } + }, + isInt: () => {} } } diff --git a/src/browser/modules/Stream/SysInfoFrame.jsx b/src/browser/modules/Stream/SysInfoFrame/index.jsx similarity index 55% rename from src/browser/modules/Stream/SysInfoFrame.jsx rename to src/browser/modules/Stream/SysInfoFrame/index.jsx index c8990e412c2..72d5c2b725d 100644 --- a/src/browser/modules/Stream/SysInfoFrame.jsx +++ b/src/browser/modules/Stream/SysInfoFrame/index.jsx @@ -23,11 +23,11 @@ import { connect } from 'preact-redux' import { withBus } from 'preact-suber' import { CYPHER_REQUEST } from 'shared/modules/cypher/cypherDuck' import { isACausalCluster } from 'shared/modules/features/featuresDuck' -import FrameTemplate from '../Stream/FrameTemplate' -import FrameError from '../Stream/FrameError' +import FrameTemplate from 'browser/modules/Stream/FrameTemplate' +import FrameError from 'browser/modules/Stream/FrameError' import { SysInfoTableContainer, SysInfoTable, SysInfoTableEntry } from 'browser-components/Tables' import { toHumanReadableBytes } from 'services/utils' -import { mapSysInfoRecords, getTableDataFromRecords } from 'shared/modules/commands/helpers/sysinfo' +import { mapSysInfoRecords, getTableDataFromRecords } from './sysinfo' import Render from 'browser-components/Render' export class SysInfoFrame extends Component { @@ -76,49 +76,46 @@ export class SysInfoFrame extends Component { const haInstancePropertyValues = [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] return })) - this.ha = [ - , - , - , - , - , - - ] + + this.setState({ha: [ + {label: 'InstanceId', value: ha.InstanceId}, + {label: 'Role', value: ha.Role}, + {label: 'Alive', value: ha.Alive.toString()}, + {label: 'Available', value: ha.Available.toString()}, + {label: 'Last Committed Tx Id', value: ha.LastCommittedTxId}, + {label: 'Last Update Time', value: ha.LastUpdateTime} + ]}) } - this.storeSizes = [ - , - , - , - , - , - , - - ] - this.idAllocation = [ - , - , - , - - ] - this.pageCache = [ - , - , - , - , - , - , - , - - ] - this.transactions = [ - , - , - , - , - - ] - this.setState({results: true}) + this.setState({storeSizes: [ + {label: 'Array Store', value: toHumanReadableBytes(kernel.ArrayStoreSize)}, + {label: 'Logical Log', value: toHumanReadableBytes(kernel.LogicalLogSize)}, + {label: 'Node Store', value: toHumanReadableBytes(kernel.NodeStoreSize)}, + {label: 'Property Store', value: toHumanReadableBytes(kernel.PropertyStoreSize)}, + {label: 'Relationship Store', value: toHumanReadableBytes(kernel.RelationshipStoreSize)}, + {label: 'String Store', value: toHumanReadableBytes(kernel.StringStoreSize)}, + {label: 'Total Store Size', value: toHumanReadableBytes(kernel.TotalStoreSize)} + ], idAllocation: [ + {label: 'Node ID', value: primitive.NumberOfNodeIdsInUse}, + {label: 'Property ID', value: primitive.NumberOfPropertyIdsInUse}, + {label: 'Relationship ID', value: primitive.NumberOfRelationshipIdsInUse}, + {label: 'Relationship Type ID', value: primitive.NumberOfRelationshipTypeIdsInUse} + ], pageCache: [ + {label: 'Faults', value: cache.Faults}, + {label: 'Evictions', value: cache.Evictions}, + {label: 'File Mappings', value: cache.FileMappings}, + {label: 'Bytes Read', value: cache.BytesRead}, + {label: 'Flushes', value: cache.Flushes}, + {label: 'Eviction Exceptions', value: cache.EvictionExceptions}, + {label: 'File Unmappings', value: cache.FileUnmappings}, + {label: 'Bytes Written', value: cache.BytesWritten} + ], transactions: [ + {label: 'Last Tx Id', value: tx.LastCommittedTxId}, + {label: 'Current', value: tx.NumberOfOpenTransactions}, + {label: 'Peak', value: tx.PeakNumberOfConcurrentTransactions}, + {label: 'Opened', value: tx.NumberOfOpenedTransactions}, + {label: 'Committed', value: tx.NumberOfCommittedTransactions} + ]}) } } componentDidMount () { @@ -141,38 +138,44 @@ export class SysInfoFrame extends Component { } } } + buildTableData (data) { + if (!data) return null + return data.map(({label, value}) => { + return + }) + } render () { - const content = (this.state.results) - ? ( + const content = ( + - {this.storeSizes || null} + {this.buildTableData(this.state.storeSizes)} - {this.idAllocation || null} + {this.buildTableData(this.state.idAllocation)} - {this.pageCache || null} + {this.buildTableData(this.state.pageCache)} - {this.transactions || null} + {this.buildTableData(this.state.transactions)} - - {this.cc || null} + + {this.buildTableData(this.state.cc)} - + - {this.ha || null} + {this.buildTableData(this.state.ha)} - - - {this.haInstances || null} + + + {this.state.haInstances} - ) - : null + + ) return ( . */ @@ -46,6 +46,7 @@ export const getTableDataFromRecords = (records) => { ha } } + const mappedJMXresult = (records) => { return records.map((record) => { const origAttributes = record.get('attributes') @@ -73,9 +74,10 @@ export const mapSysInfoRecords = (records) => { } }) } -const flattenAttributes = (a) => { - if (a && a.attributes) { - return Object.assign({}, ...a.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString(), objectConverter: extractFromNeoObjects}) }))) + +export const flattenAttributes = (data) => { + if (data && data.attributes) { + return Object.assign({}, ...data.attributes.map(({name, value}) => ({ [name]: itemIntToString(value, {intChecker: bolt.neo4j.isInt, intConverter: (val) => val.toString(), objectConverter: extractFromNeoObjects}) }))) } else { return null } diff --git a/src/browser/modules/Stream/SysInfoFrame.test.js b/src/browser/modules/Stream/SysInfoFrame/sysinfo.test.js similarity index 53% rename from src/browser/modules/Stream/SysInfoFrame.test.js rename to src/browser/modules/Stream/SysInfoFrame/sysinfo.test.js index b158fdfeba4..22b2d6c5a49 100644 --- a/src/browser/modules/Stream/SysInfoFrame.test.js +++ b/src/browser/modules/Stream/SysInfoFrame/sysinfo.test.js @@ -18,17 +18,21 @@ * along with this program. If not, see . */ -/* global test, expect */ -import { mount } from 'services/testUtils' -import { SysInfoFrame } from './SysInfoFrame' +/* global, describe, test, expect, afterEach */ -describe('SysInfoFrame', () => { - // const SysInfoFrameFn = (props) => { - // return - // } - test('should render', () => { - mount(SysInfoFrame).withProps({availableProcedures: []}).then(wrapper => { - expect(wrapper.html()).not.toBe(null) - }) +import { flattenAttributes } from './sysinfo' + +describe('sysinfo attribute types', () => { + test('should handle string value', () => { + const attributeData = {attributes: [{ name: 'foo', value: 'bar' }]} + expect(flattenAttributes(attributeData)).toEqual({foo: 'bar'}) + }) + test('should handle int value', () => { + const attributeData = {attributes: [{ name: 'foo', value: 0 }]} + expect(flattenAttributes(attributeData)).toEqual({foo: 0}) + }) + test('should handle object value', () => { + const attributeData = {attributes: [{ name: 'foo', value: {bar: 'baz'} }]} + expect(flattenAttributes(attributeData)).toEqual({foo: {bar: 'baz'}}) }) }) diff --git a/src/shared/modules/features/featuresDuck.test.js b/src/shared/modules/features/featuresDuck.test.js index bac2e03952e..3e8c69c5d11 100644 --- a/src/shared/modules/features/featuresDuck.test.js +++ b/src/shared/modules/features/featuresDuck.test.js @@ -49,12 +49,20 @@ describe('features reducer', () => { }) describe('feature getters', () => { + test('should return empty list of availableProcedures by default', () => { + const nextState = reducer(undefined, {type: ''}) + expect(features.getAvailableProcedures({features: nextState})).toEqual([]) + }) + test('should return list of availableProcedures', () => { + const nextState = reducer({availableProcedures: ['foo.bar']}, {type: ''}) + expect(features.getAvailableProcedures({features: nextState})).toContain('foo.bar') + }) test('should not be a causal cluster', () => { const nextState = reducer(undefined, {type: ''}) - expect(features.isACausalCluster(nextState)).toBe(false) + expect(features.isACausalCluster({features: nextState})).toBe(false) }) test('should be in a causal cluster', () => { const nextState = reducer({availableProcedures: ['dbms.cluster.overview']}, {type: ''}) - expect(features.isACausalCluster(nextState)).toBe(true) + expect(features.isACausalCluster({features: nextState})).toBe(true) }) }) From 856b96b455287f4fb9f062b8cd55a8dda0c0a421 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Tue, 27 Jun 2017 15:50:26 +0100 Subject: [PATCH 4/7] Change cluster info is mapped in state --- .../modules/Stream/SysInfoFrame/index.jsx | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/browser/modules/Stream/SysInfoFrame/index.jsx b/src/browser/modules/Stream/SysInfoFrame/index.jsx index 72d5c2b725d..79378414059 100644 --- a/src/browser/modules/Stream/SysInfoFrame/index.jsx +++ b/src/browser/modules/Stream/SysInfoFrame/index.jsx @@ -46,19 +46,17 @@ export class SysInfoFrame extends Component { return } const mappedResult = mapSysInfoRecords(res.result.records) - const mappedTableHeader = const mappedTableComponents = mappedResult.map((ccRecord) => { const httpUrlForMember = ccRecord.addresses.filter((address) => { return address.startsWith('http://') && !address.includes(window.location.href) }) - const arrayOfValue = [ + return [ ccRecord.role, ccRecord.addresses.join(', '), Open ] - return }) - this.setState({cc: [mappedTableHeader].concat(mappedTableComponents)}) + this.setState({cc: [{ value: mappedTableComponents }]}) } } responseHandler () { @@ -71,11 +69,9 @@ export class SysInfoFrame extends Component { const {ha, kernel, cache, tx, primitive} = tableData if (ha) { - const haInstancesHeader = - this.haInstances = [haInstancesHeader].concat(ha.InstancesInCluster.map(({properties}) => { - const haInstancePropertyValues = [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] - return - })) + const instancesInCluster = ha.InstancesInCluster.map(({properties}) => { + return [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] + }) this.setState({ha: [ {label: 'InstanceId', value: ha.InstanceId}, @@ -84,6 +80,8 @@ export class SysInfoFrame extends Component { {label: 'Available', value: ha.Available.toString()}, {label: 'Last Committed Tx Id', value: ha.LastCommittedTxId}, {label: 'Last Update Time', value: ha.LastUpdateTime} + ], haInstances: [ + { value: instancesInCluster } ]}) } @@ -141,6 +139,9 @@ export class SysInfoFrame extends Component { buildTableData (data) { if (!data) return null return data.map(({label, value}) => { + if (value instanceof Array) { + return value.map(v => ) + } return }) } @@ -160,7 +161,8 @@ export class SysInfoFrame extends Component { {this.buildTableData(this.state.transactions)} - + + {this.buildTableData(this.state.cc)} @@ -170,8 +172,9 @@ export class SysInfoFrame extends Component { - - {this.state.haInstances} + + + {this.buildTableData(this.state.haInstances)} From 913631a139e7b28648d03f36d45b33b9db077f68 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Tue, 27 Jun 2017 17:16:05 +0100 Subject: [PATCH 5/7] Add SysInfoFrame component tests --- .../modules/Stream/SysInfoFrame/index.jsx | 20 ++++--- .../modules/Stream/SysInfoFrame/index.test.js | 55 +++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 src/browser/modules/Stream/SysInfoFrame/index.test.js diff --git a/src/browser/modules/Stream/SysInfoFrame/index.jsx b/src/browser/modules/Stream/SysInfoFrame/index.jsx index 79378414059..1916767ca51 100644 --- a/src/browser/modules/Stream/SysInfoFrame/index.jsx +++ b/src/browser/modules/Stream/SysInfoFrame/index.jsx @@ -65,8 +65,7 @@ export class SysInfoFrame extends Component { this.setState({error: 'No results'}) return } - const tableData = getTableDataFromRecords(res.result.records) - const {ha, kernel, cache, tx, primitive} = tableData + const {ha, kernel, cache, tx, primitive} = getTableDataFromRecords(res.result.records) if (ha) { const instancesInCluster = ha.InstancesInCluster.map(({properties}) => { @@ -80,9 +79,10 @@ export class SysInfoFrame extends Component { {label: 'Available', value: ha.Available.toString()}, {label: 'Last Committed Tx Id', value: ha.LastCommittedTxId}, {label: 'Last Update Time', value: ha.LastUpdateTime} - ], haInstances: [ + ], + haInstances: [ { value: instancesInCluster } - ]}) + ]}) } this.setState({storeSizes: [ @@ -93,12 +93,14 @@ export class SysInfoFrame extends Component { {label: 'Relationship Store', value: toHumanReadableBytes(kernel.RelationshipStoreSize)}, {label: 'String Store', value: toHumanReadableBytes(kernel.StringStoreSize)}, {label: 'Total Store Size', value: toHumanReadableBytes(kernel.TotalStoreSize)} - ], idAllocation: [ + ], + idAllocation: [ {label: 'Node ID', value: primitive.NumberOfNodeIdsInUse}, {label: 'Property ID', value: primitive.NumberOfPropertyIdsInUse}, {label: 'Relationship ID', value: primitive.NumberOfRelationshipIdsInUse}, {label: 'Relationship Type ID', value: primitive.NumberOfRelationshipTypeIdsInUse} - ], pageCache: [ + ], + pageCache: [ {label: 'Faults', value: cache.Faults}, {label: 'Evictions', value: cache.Evictions}, {label: 'File Mappings', value: cache.FileMappings}, @@ -107,13 +109,14 @@ export class SysInfoFrame extends Component { {label: 'Eviction Exceptions', value: cache.EvictionExceptions}, {label: 'File Unmappings', value: cache.FileUnmappings}, {label: 'Bytes Written', value: cache.BytesWritten} - ], transactions: [ + ], + transactions: [ {label: 'Last Tx Id', value: tx.LastCommittedTxId}, {label: 'Current', value: tx.NumberOfOpenTransactions}, {label: 'Peak', value: tx.PeakNumberOfConcurrentTransactions}, {label: 'Opened', value: tx.NumberOfOpenedTransactions}, {label: 'Committed', value: tx.NumberOfCommittedTransactions} - ]}) + ]}) } } componentDidMount () { @@ -179,6 +182,7 @@ export class SysInfoFrame extends Component { ) + return ( . + */ + +/* global jest, describe, test, expect */ + +import uuid from 'uuid' +import { mount } from 'services/testUtils' +import { SysInfoFrame } from './index' + +jest.mock('browser/modules/Stream/FrameTemplate', + () => ({contents, children}) =>
{contents}{children}
) + +describe('sysinfo component', () => { + test('should render causal cluster table', () => { + return mount(SysInfoFrame).withProps({ isACausalCluster: true }).then((wrapper) => { + expect(wrapper.html()).toContain('Causal Cluster Members') + }) + }) + test('should not render causal cluster table', () => { + return mount(SysInfoFrame).withProps({ isACausalCluster: false }).then((wrapper) => { + expect(wrapper.html()).not.toContain('Causal Cluster Members') + }) + }) + test('should not render ha table', () => { + const value = uuid.v4() + const label = 'InstanceId' + return mount(SysInfoFrame).withProps({}).then((wrapper) => { + expect(wrapper.html()).not.toContain(label) + expect(wrapper.html()).not.toContain(value) + + wrapper.setState({ha: [{ label, value }]}) + wrapper.update() + + expect(wrapper.html()).toContain(label) + expect(wrapper.html()).toContain(value) + }) + }) +}) From 0481734fdc25e4038d5aa7b8ef5151af80e9a6f5 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Thu, 29 Jun 2017 07:25:17 +0100 Subject: [PATCH 6/7] Revert changes in QueriesFrame --- src/browser/modules/Stream/Queries/QueriesFrame.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/browser/modules/Stream/Queries/QueriesFrame.jsx b/src/browser/modules/Stream/Queries/QueriesFrame.jsx index cb5a9d137e3..04379fb02fe 100644 --- a/src/browser/modules/Stream/Queries/QueriesFrame.jsx +++ b/src/browser/modules/Stream/Queries/QueriesFrame.jsx @@ -65,13 +65,16 @@ export class QueriesFrame extends Component { } } + isCC () { + return this.props.availableProcedures.includes('dbms.cluster.overview') + } canListQueries () { return this.props.availableProcedures.includes('dbms.listQueries') } getRunningQueries (suppressQuerySuccessMessage = false) { this.props.bus.self( - (this.props.isACausalCluster) ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, + (this.isCC()) ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, {query: listQueriesProcedure()}, (response) => { if (response.success) { From 73b8ec6f35c6885a937d5693b03ce92c61b17c35 Mon Sep 17 00:00:00 2001 From: Mark Peace Date: Thu, 29 Jun 2017 07:58:59 +0100 Subject: [PATCH 7/7] Remove nested function in response handlers --- .../modules/Stream/SysInfoFrame/index.jsx | 145 +++++++++--------- 1 file changed, 70 insertions(+), 75 deletions(-) diff --git a/src/browser/modules/Stream/SysInfoFrame/index.jsx b/src/browser/modules/Stream/SysInfoFrame/index.jsx index 1916767ca51..190fa504a9b 100644 --- a/src/browser/modules/Stream/SysInfoFrame/index.jsx +++ b/src/browser/modules/Stream/SysInfoFrame/index.jsx @@ -38,86 +38,81 @@ export class SysInfoFrame extends Component { results: false } } - - clusterResponseHandler () { - return (res) => { - if (!res.success) { - this.setState({error: 'No causal cluster results'}) - return - } - const mappedResult = mapSysInfoRecords(res.result.records) - const mappedTableComponents = mappedResult.map((ccRecord) => { - const httpUrlForMember = ccRecord.addresses.filter((address) => { - return address.startsWith('http://') && !address.includes(window.location.href) - }) - return [ - ccRecord.role, - ccRecord.addresses.join(', '), - Open - ] - }) - this.setState({cc: [{ value: mappedTableComponents }]}) + clusterResponseHandler (res) { + if (!res.success) { + this.setState({error: 'No causal cluster results'}) + return } + const mappedResult = mapSysInfoRecords(res.result.records) + const mappedTableComponents = mappedResult.map((ccRecord) => { + const httpUrlForMember = ccRecord.addresses.filter((address) => { + return address.startsWith('http://') && !address.includes(window.location.href) + }) + return [ + ccRecord.role, + ccRecord.addresses.join(', '), + Open + ] + }) + this.setState({cc: [{ value: mappedTableComponents }]}) } - responseHandler () { - return (res) => { - if (!res.success) { - this.setState({error: 'No results'}) - return - } - const {ha, kernel, cache, tx, primitive} = getTableDataFromRecords(res.result.records) - - if (ha) { - const instancesInCluster = ha.InstancesInCluster.map(({properties}) => { - return [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] - }) + responseHandler (res) { + if (!res.success) { + this.setState({error: 'No results'}) + return + } + const {ha, kernel, cache, tx, primitive} = getTableDataFromRecords(res.result.records) - this.setState({ha: [ - {label: 'InstanceId', value: ha.InstanceId}, - {label: 'Role', value: ha.Role}, - {label: 'Alive', value: ha.Alive.toString()}, - {label: 'Available', value: ha.Available.toString()}, - {label: 'Last Committed Tx Id', value: ha.LastCommittedTxId}, - {label: 'Last Update Time', value: ha.LastUpdateTime} - ], - haInstances: [ - { value: instancesInCluster } - ]}) - } + if (ha) { + const instancesInCluster = ha.InstancesInCluster.map(({properties}) => { + return [properties.instanceId, properties.alive.toString(), properties.available.toString(), (properties.haRole === 'master') ? 'yes' : '-'] + }) - this.setState({storeSizes: [ - {label: 'Array Store', value: toHumanReadableBytes(kernel.ArrayStoreSize)}, - {label: 'Logical Log', value: toHumanReadableBytes(kernel.LogicalLogSize)}, - {label: 'Node Store', value: toHumanReadableBytes(kernel.NodeStoreSize)}, - {label: 'Property Store', value: toHumanReadableBytes(kernel.PropertyStoreSize)}, - {label: 'Relationship Store', value: toHumanReadableBytes(kernel.RelationshipStoreSize)}, - {label: 'String Store', value: toHumanReadableBytes(kernel.StringStoreSize)}, - {label: 'Total Store Size', value: toHumanReadableBytes(kernel.TotalStoreSize)} + this.setState({ha: [ + {label: 'InstanceId', value: ha.InstanceId}, + {label: 'Role', value: ha.Role}, + {label: 'Alive', value: ha.Alive.toString()}, + {label: 'Available', value: ha.Available.toString()}, + {label: 'Last Committed Tx Id', value: ha.LastCommittedTxId}, + {label: 'Last Update Time', value: ha.LastUpdateTime} ], - idAllocation: [ - {label: 'Node ID', value: primitive.NumberOfNodeIdsInUse}, - {label: 'Property ID', value: primitive.NumberOfPropertyIdsInUse}, - {label: 'Relationship ID', value: primitive.NumberOfRelationshipIdsInUse}, - {label: 'Relationship Type ID', value: primitive.NumberOfRelationshipTypeIdsInUse} - ], - pageCache: [ - {label: 'Faults', value: cache.Faults}, - {label: 'Evictions', value: cache.Evictions}, - {label: 'File Mappings', value: cache.FileMappings}, - {label: 'Bytes Read', value: cache.BytesRead}, - {label: 'Flushes', value: cache.Flushes}, - {label: 'Eviction Exceptions', value: cache.EvictionExceptions}, - {label: 'File Unmappings', value: cache.FileUnmappings}, - {label: 'Bytes Written', value: cache.BytesWritten} - ], - transactions: [ - {label: 'Last Tx Id', value: tx.LastCommittedTxId}, - {label: 'Current', value: tx.NumberOfOpenTransactions}, - {label: 'Peak', value: tx.PeakNumberOfConcurrentTransactions}, - {label: 'Opened', value: tx.NumberOfOpenedTransactions}, - {label: 'Committed', value: tx.NumberOfCommittedTransactions} + haInstances: [ + { value: instancesInCluster } ]}) } + + this.setState({storeSizes: [ + {label: 'Array Store', value: toHumanReadableBytes(kernel.ArrayStoreSize)}, + {label: 'Logical Log', value: toHumanReadableBytes(kernel.LogicalLogSize)}, + {label: 'Node Store', value: toHumanReadableBytes(kernel.NodeStoreSize)}, + {label: 'Property Store', value: toHumanReadableBytes(kernel.PropertyStoreSize)}, + {label: 'Relationship Store', value: toHumanReadableBytes(kernel.RelationshipStoreSize)}, + {label: 'String Store', value: toHumanReadableBytes(kernel.StringStoreSize)}, + {label: 'Total Store Size', value: toHumanReadableBytes(kernel.TotalStoreSize)} + ], + idAllocation: [ + {label: 'Node ID', value: primitive.NumberOfNodeIdsInUse}, + {label: 'Property ID', value: primitive.NumberOfPropertyIdsInUse}, + {label: 'Relationship ID', value: primitive.NumberOfRelationshipIdsInUse}, + {label: 'Relationship Type ID', value: primitive.NumberOfRelationshipTypeIdsInUse} + ], + pageCache: [ + {label: 'Faults', value: cache.Faults}, + {label: 'Evictions', value: cache.Evictions}, + {label: 'File Mappings', value: cache.FileMappings}, + {label: 'Bytes Read', value: cache.BytesRead}, + {label: 'Flushes', value: cache.Flushes}, + {label: 'Eviction Exceptions', value: cache.EvictionExceptions}, + {label: 'File Unmappings', value: cache.FileUnmappings}, + {label: 'Bytes Written', value: cache.BytesWritten} + ], + transactions: [ + {label: 'Last Tx Id', value: tx.LastCommittedTxId}, + {label: 'Current', value: tx.NumberOfOpenTransactions}, + {label: 'Peak', value: tx.PeakNumberOfConcurrentTransactions}, + {label: 'Opened', value: tx.NumberOfOpenedTransactions}, + {label: 'Committed', value: tx.NumberOfCommittedTransactions} + ]}) } componentDidMount () { if (this.props.bus) { @@ -126,7 +121,7 @@ export class SysInfoFrame extends Component { { query: 'CALL dbms.queryJmx("org.neo4j:*")' }, - this.responseHandler() + this.responseHandler.bind(this) ) if (this.props.isACausalCluster) { this.props.bus.self( @@ -134,7 +129,7 @@ export class SysInfoFrame extends Component { { query: 'CALL dbms.cluster.overview' }, - this.clusterResponseHandler() + this.clusterResponseHandler.bind(this) ) } }