Skip to content

Commit b241700

Browse files
committed
implementing gql errors
1 parent 1f554c5 commit b241700

File tree

17 files changed

+476
-34
lines changed

17 files changed

+476
-34
lines changed

src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const mount = (partOfProps: Partial<ErrorsViewProps>) => {
3333
params: {},
3434
executeCmd: jest.fn(),
3535
setEditorContent: jest.fn(),
36-
neo4jVersion: null
36+
neo4jVersion: null,
37+
protocolVersion: null
3738
}
3839
const props = {
3940
...defaultProps,

src/browser/modules/Stream/CypherFrame/ErrorsView/ErrorsView.tsx

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { Bus } from 'suber'
2525

2626
import { PlayIcon } from 'browser-components/icons/LegacyIcons'
2727

28-
import { errorMessageFormater } from '../../errorMessageFormater'
2928
import {
3029
StyledCypherErrorMessage,
3130
StyledDiv,
@@ -59,14 +58,18 @@ import { BrowserError } from 'services/exceptions'
5958
import { deepEquals } from 'neo4j-arc/common'
6059
import { getSemanticVersion } from 'shared/modules/dbMeta/dbMetaDuck'
6160
import { SemVer } from 'semver'
61+
import { getProtocolVersion } from 'shared/modules/connections/connectionsDuck'
62+
import { formatError } from '../errorUtils'
6263

6364
export type ErrorsViewProps = {
6465
result: BrowserRequestResult
6566
bus: Bus
6667
neo4jVersion: SemVer | null
68+
protocolVersion: number | null
6769
params: Record<string, unknown>
6870
executeCmd: (cmd: string) => void
6971
setEditorContent: (cmd: string) => void
72+
depth?: number
7073
}
7174

7275
class ErrorsViewComponent extends Component<ErrorsViewProps> {
@@ -78,31 +81,50 @@ class ErrorsViewComponent extends Component<ErrorsViewProps> {
7881
}
7982

8083
render(): null | JSX.Element {
81-
const { bus, params, executeCmd, setEditorContent, neo4jVersion } =
82-
this.props
84+
const {
85+
bus,
86+
params,
87+
executeCmd,
88+
setEditorContent,
89+
neo4jVersion,
90+
protocolVersion,
91+
depth = 0
92+
} = this.props
8393

8494
const error = this.props.result as BrowserError
85-
if (!error || !error.code) {
95+
if (!error) {
96+
return null
97+
}
98+
99+
const formattedError = formatError(protocolVersion, error)
100+
101+
if (!formattedError?.title) {
86102
return null
87103
}
88-
const fullError = errorMessageFormater(null, error.message)
89104

90105
const handleSetMissingParamsTemplateHelpMessageClick = () => {
91106
bus.send(GENERATE_SET_MISSING_PARAMS_TEMPLATE, undefined)
92107
}
93108

94109
return (
95-
<StyledHelpFrame>
110+
<StyledHelpFrame nested={depth > 0}>
96111
<StyledHelpContent>
97112
<StyledHelpDescription>
98-
<StyledCypherErrorMessage>ERROR</StyledCypherErrorMessage>
99-
<StyledErrorH4>{error.code}</StyledErrorH4>
113+
{depth === 0 && (
114+
<StyledCypherErrorMessage>ERROR</StyledCypherErrorMessage>
115+
)}
116+
<StyledErrorH4>{formattedError.title}</StyledErrorH4>
100117
</StyledHelpDescription>
101-
<StyledDiv>
102-
<StyledPreformattedArea data-testid={'cypherFrameErrorMessage'}>
103-
{fullError.message}
104-
</StyledPreformattedArea>
105-
</StyledDiv>
118+
{formattedError.description && (
119+
<StyledDiv>
120+
<StyledPreformattedArea data-testid={'cypherFrameErrorMessage'}>
121+
{formattedError?.description}
122+
</StyledPreformattedArea>
123+
</StyledDiv>
124+
)}
125+
{formattedError.innerError && (
126+
<ErrorsView result={formattedError.innerError} depth={depth + 1} />
127+
)}
106128
{isUnknownProcedureError(error) && (
107129
<StyledLinkContainer>
108130
<StyledLink
@@ -149,7 +171,8 @@ class ErrorsViewComponent extends Component<ErrorsViewProps> {
149171
const mapStateToProps = (state: GlobalState) => {
150172
return {
151173
params: getParams(state),
152-
neo4jVersion: getSemanticVersion(state)
174+
neo4jVersion: getSemanticVersion(state),
175+
protocolVersion: getProtocolVersion(state)
153176
}
154177
}
155178
const mapDispatchToProps = (

src/browser/modules/Stream/CypherFrame/ErrorsView/__snapshots__/ErrorsView.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`ErrorsView displays nothing if no errors 1`] = `<div />`;
55
exports[`ErrorsView displays procedure link if unknown procedure 1`] = `
66
<div>
77
<div
8-
class="sc-kfPuZi eetjan"
8+
class="sc-kfPuZi hoSnpS"
99
>
1010
<div
1111
class="sc-fKVqWL hFvVsk"
@@ -14,7 +14,7 @@ exports[`ErrorsView displays procedure link if unknown procedure 1`] = `
1414
class="sc-bBHxTw cOKOWM"
1515
>
1616
<div
17-
class="sc-iJKOTD sc-ezbkAF hhmYSB ftYvYV"
17+
class="sc-iJKOTD sc-ezbkAF hhmYSB fuiMKG"
1818
>
1919
ERROR
2020
</div>
@@ -55,7 +55,7 @@ exports[`ErrorsView displays procedure link if unknown procedure 1`] = `
5555
exports[`ErrorsView does displays an error 1`] = `
5656
<div>
5757
<div
58-
class="sc-kfPuZi eetjan"
58+
class="sc-kfPuZi hoSnpS"
5959
>
6060
<div
6161
class="sc-fKVqWL hFvVsk"
@@ -64,7 +64,7 @@ exports[`ErrorsView does displays an error 1`] = `
6464
class="sc-bBHxTw cOKOWM"
6565
>
6666
<div
67-
class="sc-iJKOTD sc-ezbkAF hhmYSB ftYvYV"
67+
class="sc-iJKOTD sc-ezbkAF hhmYSB fuiMKG"
6868
>
6969
ERROR
7070
</div>

src/browser/modules/Stream/CypherFrame/__snapshots__/WarningsView.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`WarningsViews WarningsView displays nothing if no notifications 1`] = `
55
exports[`WarningsViews WarningsView does display a warning for GQL status codes 1`] = `
66
<div>
77
<div
8-
class="sc-hGPBjI jLpNLK"
8+
class="sc-hGPBjI kJlknP"
99
>
1010
<div
1111
class="sc-dlVxhl fwhZkz"
@@ -55,7 +55,7 @@ exports[`WarningsViews WarningsView does display a warning for GQL status codes
5555
exports[`WarningsViews WarningsView does display multiple warnings 1`] = `
5656
<div>
5757
<div
58-
class="sc-hGPBjI jLpNLK"
58+
class="sc-hGPBjI kJlknP"
5959
>
6060
<div
6161
class="sc-dlVxhl fwhZkz"
@@ -168,7 +168,7 @@ exports[`WarningsViews WarningsView does display multiple warnings 1`] = `
168168
exports[`WarningsViews WarningsView does display multiple warnings for GQL status codes 1`] = `
169169
<div>
170170
<div
171-
class="sc-hGPBjI jLpNLK"
171+
class="sc-hGPBjI kJlknP"
172172
>
173173
<div
174174
class="sc-dlVxhl fwhZkz"
@@ -259,7 +259,7 @@ exports[`WarningsViews WarningsView does display multiple warnings for GQL statu
259259
exports[`WarningsViews WarningsView does displays a warning 1`] = `
260260
<div>
261261
<div
262-
class="sc-hGPBjI jLpNLK"
262+
class="sc-hGPBjI kJlknP"
263263
>
264264
<div
265265
class="sc-dlVxhl fwhZkz"
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
import { ErrorType } from 'services/exceptions'
22+
import { formatError } from './errorUtils'
23+
24+
describe('error formatting', () => {
25+
test('formats an error with no gql fields correctly', () => {
26+
const error = {
27+
type: 'Neo4jError' as ErrorType,
28+
message: 'epochSeconds cannot be selected together with datetime.',
29+
code: 'Neo.ClientError.Statement.ArgumentError'
30+
}
31+
32+
const result = formatError(5.6, error)
33+
expect(result).toEqual({
34+
description: 'epochSeconds cannot be selected together with datetime.',
35+
title: 'Neo.ClientError.Statement.ArgumentError'
36+
})
37+
})
38+
39+
test('formats a long error with no gql fields correctly', () => {
40+
const error = {
41+
type: 'Neo4jError' as ErrorType,
42+
message:
43+
'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. If you would rather not experience this exception, and can accept the possibility of missing results for those rows, disable this in the Neo4j configuration by setting `dbms.cypher.forbid_shortestpath_common_nodes` to false. If you cannot accept missing results, and really want the shortestPath between two common nodes, then re-write the query using a standard Cypher variable length pattern expression followed by ordering by path length and limiting to one result.',
44+
code: 'Neo.DatabaseError.Statement.ExecutionFailed'
45+
}
46+
47+
const result = formatError(5.6, error)
48+
expect(result).toEqual({
49+
description:
50+
'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. If you would rather not experience this exception, and can accept the possibility of missing results for those rows, disable this in the Neo4j configuration by setting `dbms.cypher.forbid_shortestpath_common_nodes` to false. If you cannot accept missing results, and really want the shortestPath between two common nodes, then re-write the query using a standard Cypher variable length pattern expression followed by ordering by path length and limiting to one result.',
51+
title: 'Neo.DatabaseError.Statement.ExecutionFailed'
52+
})
53+
})
54+
55+
test('formats a gql error correctly', () => {
56+
const error = {
57+
type: 'Neo4jError' as ErrorType,
58+
message: 'Expected parameter(s): param',
59+
code: 'Neo.ClientError.Statement.ParameterMissing',
60+
gqlStatus: '42N51',
61+
gqlStatusDescription:
62+
'error: syntax error or access rule violation - invalid parameter. Invalid parameter $`param`.',
63+
cause: {
64+
gqlStatus: '22G03',
65+
gqlStatusDescription: '22G03',
66+
cause: {
67+
gqlStatus: '22N27',
68+
gqlStatusDescription:
69+
"error: data exception - invalid entity type. Invalid input '******' for $`param`. Expected to be STRING."
70+
}
71+
}
72+
}
73+
74+
const result = formatError(5.7, error)
75+
expect(result).toEqual({
76+
description: 'Invalid parameter $`param`.',
77+
innerError: {
78+
cause: {
79+
gqlStatus: '22N27',
80+
gqlStatusDescription:
81+
"error: data exception - invalid entity type. Invalid input '******' for $`param`. Expected to be STRING."
82+
},
83+
gqlStatus: '22G03',
84+
gqlStatusDescription: '22G03'
85+
},
86+
title: '42N51: Syntax error or access rule violation - invalid parameter'
87+
})
88+
})
89+
90+
test('formats a gql error with no description correctly', () => {
91+
const error = {
92+
type: 'Neo4jError' as ErrorType,
93+
message: 'epochSeconds cannot be selected together with datetime.',
94+
code: 'Neo.ClientError.Statement.ArgumentError',
95+
gqlStatus: '22007',
96+
gqlStatusDescription:
97+
'error: data exception - invalid date, time, or datetime format',
98+
cause: {
99+
gqlStatus: '22N14',
100+
gqlStatusDescription:
101+
"error: data exception - invalid temporal value combination. Cannot select both epochSeconds and 'datetime'."
102+
}
103+
}
104+
105+
const result = formatError(5.7, error)
106+
expect(result).toEqual({
107+
description: '',
108+
title: '22007: Data exception - invalid date, time, or datetime format',
109+
innerError: {
110+
gqlStatus: '22N14',
111+
gqlStatusDescription:
112+
"error: data exception - invalid temporal value combination. Cannot select both epochSeconds and 'datetime'."
113+
}
114+
})
115+
})
116+
117+
test('formats a gql error with only a gql status correctly', () => {
118+
const error = {
119+
type: 'Neo4jError' as ErrorType,
120+
message: '',
121+
code: '',
122+
gqlStatus: '22G03',
123+
gqlStatusDescription: '22G03',
124+
cause: undefined
125+
}
126+
127+
const result = formatError(5.7, error)
128+
expect(result).toEqual({
129+
description: '',
130+
title: '22G03',
131+
innerError: undefined
132+
})
133+
})
134+
135+
test('formats a gql error with a cause correctly', () => {
136+
const error = {
137+
type: 'Neo4jError' as ErrorType,
138+
message: '',
139+
code: '',
140+
gqlStatus: '22N27',
141+
gqlStatusDescription:
142+
"error: data exception - invalid entity type. Invalid input '******' for $`param`. Expected to be STRING.",
143+
cause: undefined
144+
}
145+
146+
const result = formatError(5.7, error)
147+
expect(result).toEqual({
148+
description:
149+
"Invalid input '******' for $`param`. Expected to be STRING.",
150+
title: '22N27: Data exception - invalid entity type',
151+
innerError: undefined
152+
})
153+
})
154+
155+
test('formats a long gql error correctly', () => {
156+
const error = {
157+
type: 'Neo4jError' as ErrorType,
158+
message:
159+
'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. If you would rather not experience this exception, and can accept the possibility of missing results for those rows, disable this in the Neo4j configuration by setting `dbms.cypher.forbid_shortestpath_common_nodes` to false. If you cannot accept missing results, and really want the shortestPath between two common nodes, then re-write the query using a standard Cypher variable length pattern expression followed by ordering by path length and limiting to one result.',
160+
code: 'Neo.DatabaseError.Statement.ExecutionFailed',
161+
gqlStatus: '51N23',
162+
gqlStatusDescription:
163+
"error: system configuration or operation exception - cyclic shortest path search disabled. Cannot find the shortest path when the start and end nodes are the same. To enable this behavior, set 'dbms.cypher.forbid_shortestpath_common_nodes' to false.",
164+
cause: undefined
165+
}
166+
167+
const result = formatError(5.7, error)
168+
expect(result).toEqual({
169+
description:
170+
"Cannot find the shortest path when the start and end nodes are the same. To enable this behavior, set 'dbms.cypher.forbid_shortestpath_common_nodes' to false.",
171+
title:
172+
'51N23: System configuration or operation exception - cyclic shortest path search disabled',
173+
innerError: undefined
174+
})
175+
})
176+
})

0 commit comments

Comments
 (0)