diff --git a/e2e_tests/integration/auto-prefix.spec.ts b/e2e_tests/integration/auto-prefix.spec.ts index 91ae45027be..bf7af8403cb 100644 --- a/e2e_tests/integration/auto-prefix.spec.ts +++ b/e2e_tests/integration/auto-prefix.spec.ts @@ -47,7 +47,7 @@ describe(':auto prefix in browser', () => { if (Cypress.config('serverVersion') >= 4.4) { it('shows help link when running CALL IN TRANSACTIONS without :auto', () => { cy.executeCommand( - `MATCH (n) WITH n CALL {{} CALL db.ping() YIELD SUCCESS {}} IN TRANSACTIONS` + `MATCH (n) WITH n CALL {{} CALL db.ping() YIELD success {}} IN TRANSACTIONS` ) cy.getFrames().contains('ERROR') cy.getFrames().contains(':auto') @@ -55,7 +55,7 @@ describe(':auto prefix in browser', () => { it('adding :auto enables running CALL IN TRANSACTIONS', () => { cy.executeCommand( - `:auto MATCH (n) WITH n CALL {{} CALL db.ping() YIELD SUCCESS {}} IN TRANSACTIONS` + `:auto MATCH (n) WITH n CALL {{} CALL db.ping() YIELD success {}} IN TRANSACTIONS` ) cy.getFrames().should('not.contain', 'ERROR') cy.getFrames().contains('(no changes, no records)') diff --git a/e2e_tests/integration/loadcsv.spec.ts b/e2e_tests/integration/loadcsv.spec.ts index fbd5b16e6b4..643503cc7e7 100644 --- a/e2e_tests/integration/loadcsv.spec.ts +++ b/e2e_tests/integration/loadcsv.spec.ts @@ -30,48 +30,46 @@ describe('LOAD CSV', () => { cy.connect('neo4j', password) }) it('imports without periodic commit', () => { - if (!Cypress.config('includeImportTests')) { - return - } - cy.executeCommand(':clear') - cy.executeCommand('MATCH (n) DETACH DELETE n') - cy.executeCommand(`LOAD CSV WITH HEADERS FROM 'file:///import.csv' AS row{shift}{enter} + if (Cypress.config('includeImportTests')) { + cy.executeCommand(':clear') + cy.executeCommand('MATCH (n) DETACH DELETE n') + cy.executeCommand(`LOAD CSV WITH HEADERS FROM 'file:///import.csv' AS row{shift}{enter} CREATE (p:Person {{}name: row.name, born: toInteger(row.born), city: row.city, comment:row.comment});`) - cy.resultContains('Added 3 labels, created 3 nodes, set 11 properties,') + cy.resultContains('Added 3 labels, created 3 nodes, set 11 properties,') - cy.executeCommand( - 'MATCH (n:Person {{}born: 2012}) RETURN n.city, n.comment' - ) - cy.resultContains('"Borås"') - cy.resultContains('"I like unicorns, and "flying unicorns""') + cy.executeCommand( + 'MATCH (n:Person {{}born: 2012}) RETURN n.city, n.comment' + ) + cy.resultContains('"Borås"') + cy.resultContains('"I like unicorns, and "flying unicorns""') + } }) it('imports with periodic commit', () => { if ( - !Cypress.config('includeImportTests') && + Cypress.config('includeImportTests') && Cypress.config('serverVersion') < 5 ) { - return - } - const periodicQuery = `USING PERIODIC COMMIT 1{shift}{enter} + const periodicQuery = `USING PERIODIC COMMIT 1{shift}{enter} LOAD CSV WITH HEADERS FROM 'file:///import.csv' AS row CREATE (p:Person {{}name: row.name, born: toInteger(row.born), city: row.city, comment:row.comment});` - // Let's see it fail when not using auto-committed tx's first - cy.executeCommand(':clear') - cy.executeCommand(periodicQuery) - cy.resultContains('Neo.ClientError.Statement.SemanticError') + // Let's see it fail when not using auto-committed tx's first + cy.executeCommand(':clear') + cy.executeCommand(periodicQuery) + cy.resultContains('Neo.ClientError.Statement.SemanticError') - cy.executeCommand(':clear') - cy.executeCommand('MATCH (n) DETACH DELETE n') - cy.executeCommand(`:auto ${periodicQuery}`) + cy.executeCommand(':clear') + cy.executeCommand('MATCH (n) DETACH DELETE n') + cy.executeCommand(`:auto ${periodicQuery}`) - cy.resultContains('Added 3 labels, created 3 nodes, set 11 properties,') + cy.resultContains('Added 3 labels, created 3 nodes, set 11 properties,') - cy.executeCommand( - 'MATCH (n:Person {{}born: 2012}) RETURN n.city, n.comment' - ) - cy.resultContains('"Borås"') - cy.resultContains('"I like unicorns, and "flying unicorns""') + cy.executeCommand( + 'MATCH (n:Person {{}born: 2012}) RETURN n.city, n.comment' + ) + cy.resultContains('"Borås"') + cy.resultContains('"I like unicorns, and "flying unicorns""') + } }) }) diff --git a/src/browser/modules/Stream/Queries/QueriesFrame.test.tsx b/src/browser/modules/Stream/Queries/LegacyQueriesFrame.test.tsx similarity index 90% rename from src/browser/modules/Stream/Queries/QueriesFrame.test.tsx rename to src/browser/modules/Stream/Queries/LegacyQueriesFrame.test.tsx index 2cc7f34eddf..e54b4911641 100644 --- a/src/browser/modules/Stream/Queries/QueriesFrame.test.tsx +++ b/src/browser/modules/Stream/Queries/LegacyQueriesFrame.test.tsx @@ -21,7 +21,7 @@ import { fireEvent, render } from '@testing-library/react' import React from 'react' import { createBus } from 'suber' -import { QueriesFrame } from './QueriesFrame' +import { LegacyQueriesFrame } from './LegacyQueriesFrame' import { CONNECTED_STATE, DISCONNECTED_STATE @@ -41,7 +41,7 @@ it('shows error message in statusBar when not connected', () => { const props = { connectionState: DISCONNECTED_STATE } as any - const { getByText } = render() + const { getByText } = render() expect(getByText(/Unable to connect to bolt server/i)).not.toBeNull() }) @@ -82,10 +82,12 @@ it('can list and kill queries', () => { isFullscreen: false, isCollapsed: false, isOnCausalCluster: false, - canListQueries: true + hasListQueriesProcedure: true, + versionOverFive: false, + frame: null } - const { getByText, getByTestId } = render() + const { getByText, getByTestId } = render() // Check that it's listed expect(getByText('neo4j://testhost.test')).not.toBeNull() diff --git a/src/browser/modules/Stream/Queries/LegacyQueriesFrame.tsx b/src/browser/modules/Stream/Queries/LegacyQueriesFrame.tsx new file mode 100644 index 00000000000..a347b0134fb --- /dev/null +++ b/src/browser/modules/Stream/Queries/LegacyQueriesFrame.tsx @@ -0,0 +1,379 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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 React, { Component } from 'react' +import { Bus } from 'suber' + +import FrameAside from '../../Frame/FrameAside' +import FrameBodyTemplate from '../../Frame/FrameBodyTemplate' +import FrameError from '../../Frame/FrameError' +import { + AutoRefreshSpan, + AutoRefreshToggle, + StatusbarWrapper, + StyledStatusBar +} from '../AutoRefresh/styled' +import { + Code, + StyledHeaderRow, + StyledTable, + StyledTableWrapper, + StyledTd, + StyledTh +} from './styled' +import { EnterpriseOnlyFrame } from 'browser-components/EditionView' +import { ConfirmationButton } from 'browser-components/buttons/ConfirmationButton' +import bolt from 'services/bolt/bolt' +import { NEO4J_BROWSER_USER_ACTION_QUERY } from 'services/bolt/txMetadata' +import { CONNECTED_STATE } from 'shared/modules/connections/connectionsDuck' +import { + AD_HOC_CYPHER_REQUEST, + CLUSTER_CYPHER_REQUEST, + CYPHER_REQUEST +} from 'shared/modules/cypher/cypherDuck' +import { + killQueriesProcedure, + listQueriesProcedure +} from 'shared/modules/cypher/queriesProcedureHelper' + +import { getDefaultBoltScheme } from 'shared/modules/features/versionedFeatures' + +type LegacyQueriesFrameState = { + queries: any[] + autoRefresh: boolean + autoRefreshInterval: number + success: null | boolean | string + errors: any[] +} + +export type LegacyQueriesFrameProps = { + frame: any + bus: Bus + hasListQueriesProcedure: boolean + connectionState: number + neo4jVersion: string | null + isFullscreen: boolean + versionOverFive: boolean + isCollapsed: boolean + isOnCausalCluster: boolean +} +export class LegacyQueriesFrame extends Component< + LegacyQueriesFrameProps, + LegacyQueriesFrameState +> { + timer: any + state = { + queries: [], + autoRefresh: false, + autoRefreshInterval: 20, // seconds + success: null, + errors: [] + } + + componentDidMount() { + if (this.props.connectionState === CONNECTED_STATE) { + this.getRunningQueries() + } else { + this.setState({ errors: [new Error('Unable to connect to bolt server')] }) + } + } + + componentDidUpdate(prevProps: any, prevState: LegacyQueriesFrameState) { + if (prevState.autoRefresh !== this.state.autoRefresh) { + if (this.state.autoRefresh) { + this.timer = setInterval( + this.getRunningQueries.bind(this), + this.state.autoRefreshInterval * 1000 + ) + } else { + clearInterval(this.timer) + } + } + if ( + (this.props.frame && + this.props.frame.ts !== prevProps.frame.ts && + this.props.frame.isRerun) || + this.props.isOnCausalCluster !== prevProps.isOnCausalCluster + ) { + this.getRunningQueries() + } + } + + getRunningQueries(suppressQuerySuccessMessage = false) { + this.props.bus.self( + this.props.isOnCausalCluster ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, + { + query: listQueriesProcedure(), + queryType: NEO4J_BROWSER_USER_ACTION_QUERY + }, + (response: any) => { + if (response.success) { + const queries = this.extractQueriesFromBoltResult(response.result) + const errors = queries + .filter((_: any) => _.error) + .map((e: any) => ({ + ...e.error + })) + const validQueries = queries.filter((_: any) => !_.error) + const resultMessage = this.constructOverviewMessage( + validQueries, + errors + ) + + this.setState((prevState: any) => { + return { + queries: validQueries, + errors, + success: suppressQuerySuccessMessage + ? prevState.success + : resultMessage + } + }) + } else { + const errors: any[] = this.state.errors || [] + this.setState({ + errors: errors.concat([response.error]), + success: false + }) + } + } + ) + } + + killQueries(host: any, queryIdList: any) { + this.props.bus.self( + this.props.isOnCausalCluster ? AD_HOC_CYPHER_REQUEST : CYPHER_REQUEST, + { host, query: killQueriesProcedure(queryIdList) }, + (response: any) => { + if (response.success) { + this.setState({ + success: 'Query successfully cancelled', + errors: [] + }) + this.getRunningQueries(true) + } else { + const errors: any[] = this.state.errors || [] + this.setState({ + errors: errors.concat([response.error]), + success: false + }) + } + } + ) + } + + extractQueriesFromBoltResult(result: any) { + return result.records.map(({ keys, _fields, host, error }: any) => { + if (error) { + return { error } + } + const queryInfo: any = {} + keys.forEach((key: any, idx: any) => { + queryInfo[key] = bolt.itemIntToNumber(_fields[idx]) + }) + if (host) { + queryInfo.host = getDefaultBoltScheme(this.props.neo4jVersion) + host + } else { + queryInfo.host = + getDefaultBoltScheme(this.props.neo4jVersion) + + result.summary.server.address + } + return queryInfo + }) + } + + onCancelQuery(host: any, queryId: any) { + this.killQueries(host, [queryId]) + } + + constructOverviewMessage(queries: any, errors: any) { + const clusterCount = new Set(queries.map((query: any) => query.host)).size + + const numMachinesMsg = + clusterCount > 1 + ? `running on ${clusterCount} cluster servers` + : 'running on one server' + + const numQueriesMsg = queries.length > 1 ? 'queries' : 'query' + + const successMessage = `Found ${queries.length} ${numQueriesMsg} ${numMachinesMsg}` + + return errors.length > 0 ? ( + + {successMessage} ({errors.length} unsuccessful) + + ) : ( + successMessage + ) + } + + constructViewFromQueryList(queries: any, errors: any) { + if (queries.length === 0) { + return null + } + const tableHeaderSizes = [ + ['Database URI', '20%'], + ['User', '8%'], + ['Query', 'auto'], + ['Params', '7%'], + ['Meta', 'auto'], + ['Elapsed time', '95px'], + ['Kill', '95px'] + ] + const tableRows = queries.map((query: any, i: any) => { + return ( + + + {query.host} + + + {query.username} + + + {query.query} + + + {JSON.stringify(query.parameters, null, 2)} + + + {JSON.stringify(query.metaData, null, 2)} + + + {query.elapsedTimeMillis} ms + + + + + + ) + }) + + const errorRows = errors.map((error: any, i: any) => ( + + + Error connecting to: {error.host} + + + )) + + const tableHeaders = tableHeaderSizes.map(heading => { + return ( + + {heading[0]} + + ) + }) + return ( + + + + {tableHeaders} + + + {tableRows} + {errorRows} + + + + ) + } + + setAutoRefresh(autoRefresh: any) { + this.setState({ autoRefresh: autoRefresh }) + + if (autoRefresh) { + this.getRunningQueries() + } + } + + render() { + let frameContents + let aside + let statusBar + + if ( + this.props.hasListQueriesProcedure || + this.props.connectionState !== CONNECTED_STATE + ) { + frameContents = this.constructViewFromQueryList( + this.state.queries, + this.state.errors + ) + statusBar = ( + + {this.state.errors && !this.state.success && ( + `${e.host}: ${e.message}`) + .join(', ')} + /> + )} + {this.state.success && ( + + {this.state.success} + + this.setAutoRefresh(e.target.checked)} + /> + + + )} + + ) + } else { + aside = ( + + ) + frameContents = + } + return ( + + ) + } +} + +export default LegacyQueriesFrame diff --git a/src/browser/modules/Stream/Queries/QueriesFrame.tsx b/src/browser/modules/Stream/Queries/QueriesFrame.tsx index 2401242d498..9389079989f 100644 --- a/src/browser/modules/Stream/Queries/QueriesFrame.tsx +++ b/src/browser/modules/Stream/Queries/QueriesFrame.tsx @@ -22,7 +22,6 @@ import { connect } from 'react-redux' import { withBus } from 'react-suber' import { Bus } from 'suber' -import FrameAside from '../../Frame/FrameAside' import FrameBodyTemplate from '../../Frame/FrameBodyTemplate' import FrameError from '../../Frame/FrameError' import { @@ -39,75 +38,83 @@ import { StyledTd, StyledTh } from './styled' -import { EnterpriseOnlyFrame } from 'browser-components/EditionView' import { ConfirmationButton } from 'browser-components/buttons/ConfirmationButton' import { GlobalState } from 'project-root/src/shared/globalState' -import bolt from 'services/bolt/bolt' import { NEO4J_BROWSER_USER_ACTION_QUERY } from 'services/bolt/txMetadata' import { CONNECTED_STATE, getConnectionState } from 'shared/modules/connections/connectionsDuck' -import { - AD_HOC_CYPHER_REQUEST, - CLUSTER_CYPHER_REQUEST, - CYPHER_REQUEST -} from 'shared/modules/cypher/cypherDuck' -import { - killQueriesProcedure, - listQueriesProcedure -} from 'shared/modules/cypher/queriesProcedureHelper' +import { CYPHER_REQUEST } from 'shared/modules/cypher/cypherDuck' +import { durationFormat } from 'services/bolt/cypherTypesFormatting' +import { Frame } from 'shared/modules/frames/framesDuck' +import LegacyQueriesFrame, { + LegacyQueriesFrameProps +} from './LegacyQueriesFrame' import { getRawVersion, + getSemanticVersion, hasProcedure, isOnCausalCluster } from 'shared/modules/dbMeta/dbMetaDuck' -import { getDefaultBoltScheme } from 'shared/modules/features/versionedFeatures' +import { gte } from 'semver' type QueriesFrameState = { queries: any[] autoRefresh: boolean autoRefreshInterval: number - success: null | boolean | string - errors: any[] + successMessage: null | string + errorMessages: string[] } type QueriesFrameProps = { - frame?: any + frame?: Frame bus: Bus - canListQueries: boolean connectionState: number - neo4jVersion: string | null isFullscreen: boolean isCollapsed: boolean - isOnCausalCluster: boolean } + +function constructOverviewMessage(queries: any, errors: string[]) { + const numQueriesMsg = queries.length > 1 ? 'queries' : 'query' + const successMessage = `Found ${queries.length} ${numQueriesMsg} on one server (neo4j 5.0 clusters not yet supported).` + + return errors.length > 0 + ? `${successMessage} (${errors.length} unsuccessful)` + : successMessage +} + export class QueriesFrame extends Component< QueriesFrameProps, QueriesFrameState > { - timer: any - state = { + timer: number | undefined + state: QueriesFrameState = { queries: [], autoRefresh: false, autoRefreshInterval: 20, // seconds - success: null, - errors: [] + successMessage: null, + errorMessages: [] } - componentDidMount() { + componentDidMount(): void { if (this.props.connectionState === CONNECTED_STATE) { this.getRunningQueries() } else { - this.setState({ errors: [new Error('Unable to connect to bolt server')] }) + this.setState({ + errorMessages: ['Unable to connect to neo4j'] + }) } } - componentDidUpdate(prevProps: any, prevState: QueriesFrameState) { + componentDidUpdate( + prevProps: QueriesFrameProps, + prevState: QueriesFrameState + ): void { if (prevState.autoRefresh !== this.state.autoRefresh) { if (this.state.autoRefresh) { this.timer = setInterval( - this.getRunningQueries.bind(this), + this.getRunningQueries, this.state.autoRefreshInterval * 1000 ) } else { @@ -115,129 +122,100 @@ export class QueriesFrame extends Component< } } if ( - (this.props.frame && - this.props.frame.ts !== prevProps.frame.ts && - this.props.frame.isRerun) || - this.props.isOnCausalCluster !== prevProps.isOnCausalCluster + this.props.frame && + this.props.frame.ts !== prevProps.frame?.ts && + this.props.frame.isRerun ) { this.getRunningQueries() } } - getRunningQueries(suppressQuerySuccessMessage = false) { + getRunningQueries = (suppressQuerySuccessMessage = false): void => { this.props.bus.self( - this.props.isOnCausalCluster ? CLUSTER_CYPHER_REQUEST : CYPHER_REQUEST, + CYPHER_REQUEST, { - query: listQueriesProcedure(), + query: + 'SHOW TRANSACTIONS YIELD currentQuery, username, metaData, parameters, status, elapsedTime, database, transactionId', queryType: NEO4J_BROWSER_USER_ACTION_QUERY }, - (response: any) => { - if (response.success) { - const queries = this.extractQueriesFromBoltResult(response.result) + resp => { + if (resp.success) { + const queries = resp.result.records.map( + ({ host, keys, _fields, error }: any) => { + if (error) return { error } + const nonNullHost = host ?? resp.result.summary.server.address + const data: any = {} + keys.forEach((key: string, idx: number) => { + data[key] = _fields[idx] + }) + + return { + ...data, + host: `neo4j://${nonNullHost}`, + query: data.currentQuery, + elapsedTimeMillis: durationFormat(data.elapsedTime), + queryId: data.transactionId + } + } + ) + const errors = queries .filter((_: any) => _.error) .map((e: any) => ({ ...e.error })) const validQueries = queries.filter((_: any) => !_.error) - const resultMessage = this.constructOverviewMessage( - validQueries, - errors - ) + const resultMessage = constructOverviewMessage(validQueries, errors) - this.setState((prevState: any) => { - return { - queries: validQueries, - errors, - success: suppressQuerySuccessMessage - ? prevState.success - : resultMessage - } - }) - } else { - const errors: any[] = this.state.errors || [] - this.setState({ - errors: errors.concat([response.error]), - success: false - }) + this.setState((prevState: QueriesFrameState) => ({ + queries: validQueries, + errorMessages: errors, + successMessage: suppressQuerySuccessMessage + ? prevState.successMessage + : resultMessage + })) } } ) } - killQueries(host: any, queryIdList: any) { + killQueries(queryIdList: string[]): void { this.props.bus.self( - this.props.isOnCausalCluster ? AD_HOC_CYPHER_REQUEST : CYPHER_REQUEST, - { host, query: killQueriesProcedure(queryIdList) }, + CYPHER_REQUEST, + { + query: `TERMINATE TRANSACTIONS ${queryIdList + .map(q => `"${q}"`) + .join(',')}`, + queryType: NEO4J_BROWSER_USER_ACTION_QUERY + }, (response: any) => { if (response.success) { this.setState({ - success: 'Query successfully cancelled', - errors: [] + successMessage: 'Query successfully cancelled', + errorMessages: [] }) this.getRunningQueries(true) } else { - const errors: any[] = this.state.errors || [] - this.setState({ - errors: errors.concat([response.error]), - success: false - }) + this.setState(state => ({ + errorMessages: state.errorMessages.concat([response.error.message]), + successMessage: null + })) } } ) } - extractQueriesFromBoltResult(result: any) { - return result.records.map(({ keys, _fields, host, error }: any) => { - if (error) { - return { error } - } - const queryInfo: any = {} - keys.forEach((key: any, idx: any) => { - queryInfo[key] = bolt.itemIntToNumber(_fields[idx]) - }) - if (host) { - queryInfo.host = getDefaultBoltScheme(this.props.neo4jVersion) + host - } else { - queryInfo.host = - getDefaultBoltScheme(this.props.neo4jVersion) + - result.summary.server.address - } - return queryInfo - }) - } - - onCancelQuery(host: any, queryId: any) { - this.killQueries(host, [queryId]) - } - - constructOverviewMessage(queries: any, errors: any) { - const clusterCount = new Set(queries.map((query: any) => query.host)).size - - const numMachinesMsg = - clusterCount > 1 - ? `running on ${clusterCount} cluster servers` - : 'running on one server' - - const numQueriesMsg = queries.length > 1 ? 'queries' : 'query' - - const successMessage = `Found ${queries.length} ${numQueriesMsg} ${numMachinesMsg}` - - return errors.length > 0 ? ( - - {successMessage} ({errors.length} unsuccessful) - - ) : ( - successMessage - ) + killQuery(queryId: string): void { + this.killQueries([queryId]) } - constructViewFromQueryList(queries: any, errors: any) { + constructViewFromQueryList = (): JSX.Element | null => { + const { queries, errorMessages: errors } = this.state if (queries.length === 0) { return null } const tableHeaderSizes = [ - ['Database URI', '20%'], + ['Database', '8%'], ['User', '8%'], ['Query', 'auto'], ['Params', '7%'], @@ -245,151 +223,133 @@ export class QueriesFrame extends Component< ['Elapsed time', '95px'], ['Kill', '95px'] ] - const tableRows = queries.map((query: any, i: any) => { - return ( - - - {query.host} - - - {query.username} - - - {query.query} - - - {JSON.stringify(query.parameters, null, 2)} - - - {JSON.stringify(query.metaData, null, 2)} - - - {query.elapsedTimeMillis} ms - - - - - - ) - }) - const errorRows = errors.map((error: any, i: any) => ( - - - Error connecting to: {error.host} - - - )) - - const tableHeaders = tableHeaderSizes.map(heading => { - return ( - - {heading[0]} - - ) - }) return ( - {tableHeaders} + + {tableHeaderSizes.map(heading => ( + + {heading[0]} + + ))} + - {tableRows} - {errorRows} + {queries.map((query: any, i: number) => ( + + + {query.database} + + + {query.username} + + + {query.query} + + + {JSON.stringify(query.parameters, null, 2)} + + + {JSON.stringify(query.metaData, null, 2)} + + + {query.elapsedTimeMillis} ms + + + this.killQuery(query.queryId)} + /> + + + ))} + + {errors.map((error: any, i: number) => ( + + + Error connecting to: {error.host} + + + ))} ) } - setAutoRefresh(autoRefresh: any) { - this.setState({ autoRefresh: autoRefresh }) + setAutoRefresh(autoRefresh: boolean): void { + this.setState({ autoRefresh }) if (autoRefresh) { this.getRunningQueries() } } - render() { - let frameContents - let aside - let statusBar + render(): JSX.Element { + const { isCollapsed, isFullscreen } = this.props + const { errorMessages, successMessage, autoRefresh } = this.state - if ( - this.props.canListQueries || - this.props.connectionState !== CONNECTED_STATE - ) { - frameContents = this.constructViewFromQueryList( - this.state.queries, - this.state.errors - ) - statusBar = ( - - {this.state.errors && !this.state.success && ( - `${e.host}: ${e.message}`) - .join(', ')} - /> - )} - {this.state.success && ( - - {this.state.success} - - this.setAutoRefresh(e.target.checked)} - /> - - - )} - - ) - } else { - aside = ( - - ) - frameContents = - } return ( + {successMessage ? ( + + {successMessage} + + this.setAutoRefresh(e.target.checked)} + /> + + + ) : ( + errorMessages && + )} + + } /> ) } } -const mapStateToProps = (state: GlobalState) => ({ - canListQueries: hasProcedure(state, 'dbms.listQueries'), - connectionState: getConnectionState(state), - neo4jVersion: getRawVersion(state), - isOnCausalCluster: isOnCausalCluster(state) -}) +const mapStateToProps = (state: GlobalState) => { + const version = getSemanticVersion(state) + const versionOverFive = version + ? gte(version, '5.0.0') + : true /* assume we're 5.0 */ + + return { + hasListQueriesProcedure: hasProcedure(state, 'dbms.listQueries'), + versionOverFive, + connectionState: getConnectionState(state), + neo4jVersion: getRawVersion(state), + isOnCausalCluster: isOnCausalCluster(state) + } +} -export default withBus(connect(mapStateToProps, null)(QueriesFrame)) +export default withBus( + connect(mapStateToProps)((props: LegacyQueriesFrameProps) => { + return props.versionOverFive ? ( + + ) : ( + + ) + }) +)