diff --git a/e2e_tests/integration/editor.spec.ts b/e2e_tests/integration/editor.spec.ts index dfbe5eaa41c..02b7bb7a85c 100644 --- a/e2e_tests/integration/editor.spec.ts +++ b/e2e_tests/integration/editor.spec.ts @@ -42,7 +42,7 @@ describe('Cypher Editor', () => { // It can take a little while for the label meta-data to update in the background cy.getEditor().type(selectAllAndDelete) cy.executeCommand('return extraTimeForMetadataupdate') - cy.resultContains('extraTimeForMetadataupdate') + cy.resultContains('ERROR') cy.wait(5000) cy.getEditor().type(selectAllAndDelete) diff --git a/e2e_tests/integration/multistatements.spec.ts b/e2e_tests/integration/multistatements.spec.ts index d9e9386ab91..94ba1663f83 100644 --- a/e2e_tests/integration/multistatements.spec.ts +++ b/e2e_tests/integration/multistatements.spec.ts @@ -32,6 +32,7 @@ describe('Multi statements', () => { after(() => { cy.disableMultiStatement() }) + it('can connect', () => { const password = Cypress.config('password') cy.connect('neo4j', password) @@ -69,7 +70,7 @@ describe('Multi statements', () => { ) cy.get('[data-testid="frameContents"]', { timeout: 10000 }) .first() - .should('contain', 'Error') + .should('contain', 'ERROR') cy.get('[data-testid="navigationSettings"]').click() cy.get('[data-testid="setting-enableMultiStatementMode"]').click() @@ -94,6 +95,7 @@ describe('Multi statements', () => { .first() .should('contain', 'ERROR') }) + it('Takes any statements (not just valid cypher and client commands)', () => { cy.executeCommand(':clear') const query = 'RETURN 1; hello1; RETURN 2; hello2;' @@ -112,6 +114,7 @@ describe('Multi statements', () => { .first() .should('contain', 'ERROR') }) + if (Cypress.config('serverVersion') >= 4.1) { if (isEnterpriseEdition()) { it('Can use :use command in multi-statements', () => { diff --git a/e2e_tests/integration/params.spec.ts b/e2e_tests/integration/params.spec.ts index 05fa97f205c..8d16b96914c 100644 --- a/e2e_tests/integration/params.spec.ts +++ b/e2e_tests/integration/params.spec.ts @@ -150,8 +150,8 @@ testData.forEach(testData => { it('can generate a set params template to use if query is missing params', () => { cy.executeCommand(':clear') cy.executeCommand('return $test1, $test2') - const expectedMessage = `Expected parameter(s): test1, test2` - cy.get('[data-testid="cypherFrameErrorMessage"]', { timeout: 20000 }) + const expectedMessage = `Use this template to add missing parameter(s):` + cy.get('[data-testid="frameContents"]', { timeout: 20000 }) .first() .should('contain', expectedMessage) diff --git a/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.test.tsx b/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.test.tsx index 7b319b6e3a2..1186e10bc80 100644 --- a/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.test.tsx +++ b/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.test.tsx @@ -38,8 +38,7 @@ const mount = (props: Partial, state?: any) => { params: {}, executeCmd: jest.fn(), setEditorContent: jest.fn(), - neo4jVersion: null, - gqlErrorsEnabled: true + neo4jVersion: null } const combinedProps = { @@ -118,9 +117,6 @@ describe('ErrorsView', () => { server: { version: '5.26.0' } - }, - settings: { - enableGqlErrorsAndNotifications: true } } @@ -161,9 +157,6 @@ describe('ErrorsView', () => { server: { version: '5.26.0' } - }, - settings: { - enableGqlErrorsAndNotifications: true } } diff --git a/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.tsx b/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.tsx index 465bba3b2c3..354d63346f8 100644 --- a/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.tsx +++ b/src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.tsx @@ -57,14 +57,12 @@ import { import { BrowserError } from 'services/exceptions' import { deepEquals } from 'neo4j-arc/common' import { getSemanticVersion } from 'shared/modules/dbMeta/dbMetaDuck' -import { gte, SemVer } from 'semver' +import { SemVer } from 'semver' import { formatError, formatErrorGqlStatusObject, hasPopulatedGqlFields } from '../errorUtils' -import { FIRST_GQL_ERRORS_SUPPORT } from 'shared/modules/features/versionedFeatures' -import { shouldShowGqlErrorsAndNotifications } from 'shared/modules/settings/settingsDuck' export type ErrorsViewProps = { result: BrowserRequestResult @@ -74,7 +72,6 @@ export type ErrorsViewProps = { executeCmd: (cmd: string) => void setEditorContent: (cmd: string) => void depth?: number - gqlErrorsEnabled: boolean } class ErrorsViewComponent extends Component { @@ -92,8 +89,7 @@ class ErrorsViewComponent extends Component { executeCmd, setEditorContent, neo4jVersion, - depth = 0, - gqlErrorsEnabled + depth = 0 } = this.props const error = this.props.result as BrowserError @@ -101,10 +97,9 @@ class ErrorsViewComponent extends Component { return null } - const formattedError = - gqlErrorsEnabled && hasPopulatedGqlFields(error) - ? formatErrorGqlStatusObject(error) - : formatError(error) + const formattedError = hasPopulatedGqlFields(error) + ? formatErrorGqlStatusObject(error) + : formatError(error) if (!formattedError?.title) { return null @@ -176,18 +171,9 @@ class ErrorsViewComponent extends Component { } } -const gqlErrorsEnabled = (state: GlobalState): boolean => { - const featureEnabled = shouldShowGqlErrorsAndNotifications(state) - const version = getSemanticVersion(state) - return version - ? featureEnabled && gte(version, FIRST_GQL_ERRORS_SUPPORT) - : false -} - const mapStateToProps = (state: GlobalState) => ({ params: getParams(state), - neo4jVersion: getSemanticVersion(state), - gqlErrorsEnabled: gqlErrorsEnabled(state) + neo4jVersion: getSemanticVersion(state) }) const mapDispatchToProps = ( diff --git a/src/browser/modules/Stream/CypherFrame/WarningsView.test.tsx b/src/browser/modules/Stream/CypherFrame/WarningsView.test.tsx index c05ca983e77..85d6601596e 100644 --- a/src/browser/modules/Stream/CypherFrame/WarningsView.test.tsx +++ b/src/browser/modules/Stream/CypherFrame/WarningsView.test.tsx @@ -36,8 +36,7 @@ const withProvider = (store: any, children: any) => { const mount = (props: DeepPartial, state?: any) => { const defaultProps: WarningsViewProps = { result: null, - bus: createBus(), - gqlWarningsEnabled: false + bus: createBus() } const combinedProps = { @@ -141,9 +140,6 @@ describe('WarningsView', () => { server: { version: '5.23.0' } - }, - settings: { - enableGqlErrorsAndNotifications: true } } @@ -237,9 +233,6 @@ describe('WarningsView', () => { server: { version: '5.23.0' } - }, - settings: { - enableGqlErrorsAndNotifications: true } } diff --git a/src/browser/modules/Stream/CypherFrame/WarningsView.tsx b/src/browser/modules/Stream/CypherFrame/WarningsView.tsx index 5c554e44272..3f3d9d0955f 100644 --- a/src/browser/modules/Stream/CypherFrame/WarningsView.tsx +++ b/src/browser/modules/Stream/CypherFrame/WarningsView.tsx @@ -37,17 +37,13 @@ import { deepEquals } from 'neo4j-arc/common' import { formatSummaryFromGqlStatusObjects, formatSummaryFromNotifications, - FormattedNotification + FormattedNotification, + hasPopulatedGqlFields } from './warningUtilts' import { NotificationSeverityLevel, QueryResult } from 'neo4j-driver-core' import { connect } from 'react-redux' import { withBus } from 'react-suber' -import { GlobalState } from 'shared/globalState' import { Bus } from 'suber' -import { getSemanticVersion } from 'shared/modules/dbMeta/dbMetaDuck' -import { gte } from 'semver' -import { FIRST_GQL_NOTIFICATIONS_SUPPORT } from 'shared/modules/features/versionedFeatures' -import { shouldShowGqlErrorsAndNotifications } from 'shared/modules/settings/settingsDuck' const getWarningComponent = (severity?: string | NotificationSeverityLevel) => { if (severity === 'ERROR') { @@ -64,7 +60,6 @@ const getWarningComponent = (severity?: string | NotificationSeverityLevel) => { export type WarningsViewProps = { result?: QueryResult | null bus: Bus - gqlWarningsEnabled: boolean } class WarningsViewComponent extends Component { @@ -82,7 +77,7 @@ class WarningsViewComponent extends Component { return null const { summary } = this.props.result - const notifications = this.props.gqlWarningsEnabled + const notifications = hasPopulatedGqlFields(summary) ? formatSummaryFromGqlStatusObjects(summary) : formatSummaryFromNotifications(summary) const { text: cypher = '' } = summary.query @@ -130,21 +125,7 @@ class WarningsViewComponent extends Component { } } -const gqlWarningsEnabled = (state: GlobalState): boolean => { - const featureEnabled = shouldShowGqlErrorsAndNotifications(state) - const version = getSemanticVersion(state) - return version - ? featureEnabled && gte(version, FIRST_GQL_NOTIFICATIONS_SUPPORT) - : false -} - -const mapStateToProps = (state: GlobalState) => ({ - gqlWarningsEnabled: gqlWarningsEnabled(state) -}) - -export const WarningsView = withBus( - connect(mapStateToProps, null)(WarningsViewComponent) -) +export const WarningsView = withBus(connect(null, null)(WarningsViewComponent)) export class WarningsStatusbar extends Component { shouldComponentUpdate() { diff --git a/src/browser/modules/Stream/CypherFrame/errorUtils.ts b/src/browser/modules/Stream/CypherFrame/errorUtils.ts index 8fd6b757157..bad62b1127e 100644 --- a/src/browser/modules/Stream/CypherFrame/errorUtils.ts +++ b/src/browser/modules/Stream/CypherFrame/errorUtils.ts @@ -59,7 +59,10 @@ const mapBrowserErrorToFormattedError = ( ) const title = isNonEmptyString(gqlStatusTitle) ? gqlStatusTitle : description return { - title: isNonEmptyString(title) ? `${gqlStatus}: ${title}` : gqlStatus, + title: + isNonEmptyString(title) && title !== gqlStatus + ? `${gqlStatus}: ${title}` + : gqlStatus, description } } diff --git a/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.test.ts b/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.test.ts index c1f14a0222b..3b260e59786 100644 --- a/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.test.ts +++ b/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.test.ts @@ -58,19 +58,38 @@ describe('gql status formatting', () => { ) }) - test('formats a title from a gql status description with no matches correctly', () => { + test('formats a description with line breaks correctly', () => { + const gqlStatusDescription = `error: general processing exception - unexpected error. The shortest path algorithm does not work when the start and end nodes are the same. This can happen if you +perform a shortestPath search after a cartesian product that might have the same start and end nodes for some +of the rows passed to shortestPath.` + + const result = + formatDescriptionFromGqlStatusDescription(gqlStatusDescription) + + expect(result) + .toEqual(`The shortest path algorithm does not work when the start and end nodes are the same. This can happen if you +perform a shortestPath search after a cartesian product that might have the same start and end nodes for some +of the rows passed to shortestPath.`) + }) + + test('formats a title from a gql status description with no error|info|warn prefix correctly', () => { const gqlStatusDescription = - 'Unfortunately, no one can be told what the Matrix is. You have to see it for yourself' + 'Unfortunately, no one can be told what the Matrix is. You have to see it for yourself.' + const result = formatTitleFromGqlStatusDescription(gqlStatusDescription) - expect(result).toEqual('') + expect(result).toEqual( + 'Unfortunately, no one can be told what the Matrix is' + ) }) - test('formats a description from a gql status description with no matches correctly', () => { - const gqlStatusDescription = 'Believe the unbelievable' + test('formats a description from a gql status description with no error|info|warn correctly', () => { + const gqlStatusDescription = + 'Unfortunately, no one can be told what the Matrix is. You have to see it for yourself.' + const result = formatDescriptionFromGqlStatusDescription(gqlStatusDescription) - expect(result).toEqual('') + expect(result).toEqual('You have to see it for yourself.') }) }) diff --git a/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.ts b/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.ts index 72095e80554..d0026ec69c4 100644 --- a/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.ts +++ b/src/browser/modules/Stream/CypherFrame/gqlStatusUtils.ts @@ -31,7 +31,7 @@ const formatPropertyFromStatusDescripton = ( ): string | undefined => { const matches = gqlStatusDescription?.match( - /^(?:error|info|warn):\s(.+?)(?:\.(.+?))?\.?$/ + /^(?:(?:error|info|warn):\s)?([\s\S]+?)(?:\.([\s\S]+?))?\.?$/ ) ?? [] return matches[index] === undefined diff --git a/src/browser/modules/Stream/CypherFrame/warningUtilts.ts b/src/browser/modules/Stream/CypherFrame/warningUtilts.ts index 6b373180910..df7872ee223 100644 --- a/src/browser/modules/Stream/CypherFrame/warningUtilts.ts +++ b/src/browser/modules/Stream/CypherFrame/warningUtilts.ts @@ -75,6 +75,18 @@ const mapNotificationsToFormattedNotifications = ( const SEVERITY_LEVELS = ['ERROR', 'WARNING', 'INFORMATION'] +export const hasPopulatedGqlFields = ( + resultSummary: Partial +): resultSummary is Partial & { + gqlStatusObjects: GqlStatusObject[] + notifications: Notification[] +} => { + return ( + 'gqlStatusObjects' in resultSummary && + resultSummary.gqlStatusObjects !== undefined + ) +} + export const formatSummaryFromNotifications = ( resultSummary?: Partial ): FormattedNotification[] => { diff --git a/src/shared/modules/features/versionedFeatures.ts b/src/shared/modules/features/versionedFeatures.ts index fa61293b5f7..187e9b121dd 100644 --- a/src/shared/modules/features/versionedFeatures.ts +++ b/src/shared/modules/features/versionedFeatures.ts @@ -32,9 +32,6 @@ export const FIRST_MULTI_DB_SUPPORT = NEO4J_4_0 // compatible bolt server. export const FIRST_NO_MULTI_DB_SUPPORT = '3.4.0' -export const FIRST_GQL_NOTIFICATIONS_SUPPORT = '5.23.0' -export const FIRST_GQL_ERRORS_SUPPORT = '5.26.0' - export const getShowCurrentUserProcedure = (serverVersion: string) => { const serverVersionGuessed = guessSemverVersion(serverVersion) diff --git a/src/shared/modules/settings/settingsDuck.ts b/src/shared/modules/settings/settingsDuck.ts index d9fd71e2641..2b2eaaa4fc3 100644 --- a/src/shared/modules/settings/settingsDuck.ts +++ b/src/shared/modules/settings/settingsDuck.ts @@ -94,8 +94,6 @@ export const getAllowUserStats = (state: GlobalState): boolean => state[NAME].allowUserStats ?? initialState.allowUserStats export const shouldShowWheelZoomInfo = (state: GlobalState) => state[NAME].showWheelZoomInfo -export const shouldShowGqlErrorsAndNotifications = (state: any) => - state[NAME].enableGqlErrorsAndNotifications // Ideally the string | number types would be only numbers // but they're saved as strings in the settings component