Skip to content

Commit b6d7429

Browse files
committed
Now displaying clickable urls in table and graph views
1 parent a66d53e commit b6d7429

File tree

6 files changed

+135
-3
lines changed

6 files changed

+135
-3
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
"d3": "3",
130130
"deepmerge": "^2.1.1",
131131
"dnd-core": "^2.5.1",
132+
"dompurify": "^1.0.11",
132133
"file-saver": "^1.3.8",
133134
"firebase": "^5.8.3",
134135
"isomorphic-fetch": "^2.2.1",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
* This file is part of Neo4j.
5+
* Neo4j is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
*/
17+
18+
import React from 'react'
19+
import { sanitize } from 'dompurify'
20+
21+
export default function ClickableUrls ({ text }) {
22+
return (
23+
<span
24+
dangerouslySetInnerHTML={{
25+
__html: sanitize(convertUrlsToHrefTags(text))
26+
}}
27+
/>
28+
)
29+
}
30+
31+
// credits to https://www.regextester.com/96504
32+
const URL_REGEX = /(([a-zA-Z]+):\/\/)(?:(?:[^\s()<>]+|\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))?\))+(?:\((?:[^\s()<>]+|(?:\(?:[^\s()<>]+\)))?\)|[^\s`!()\[\]{};:'".,<>?«»]))?/gi
33+
34+
/**
35+
* Finds all urls in a string and wraps them in <a target="_blank" />
36+
* @param {string} text
37+
* @return {string}
38+
*/
39+
export function convertUrlsToHrefTags (text = '') {
40+
return text.replace(
41+
URL_REGEX,
42+
match => `<a href="${match}" target="_blank">${match}</a>`
43+
)
44+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
* This file is part of Neo4j.
5+
* Neo4j is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
*/
17+
18+
import { convertUrlsToHrefTags } from './clickable-urls'
19+
20+
describe('clickable-urls', () => {
21+
describe('convertUrlsToHrefTags', () => {
22+
test('handles most common URL formats', () => {
23+
const urls = [
24+
'bolt://127.0.0.1:7687',
25+
'http://foo.com',
26+
'http://goo.gl',
27+
'https://foo.com',
28+
'https://www.foo.com',
29+
'https://www.foo.com/',
30+
'https://www.foo.com/bar',
31+
'http://goo.gl/1',
32+
'http://goo.gl/2',
33+
'http://firstround.com/review/thoughts-on-gender-and-radical-candor/?ct=t(How_Does_Your_Leadership_Team_Rate_12_3_2015)',
34+
'https://google.com',
35+
'http://www.cool.com.au',
36+
'http://www.cool.com.au/ersdfs',
37+
'http://www.cool.com.au/ersdfs?dfd=dfgd@s=1',
38+
'http://www.cool.com:81/index.html'
39+
]
40+
const expected = urls.map(
41+
url => `<a href="${url}" target="_blank">${url}</a>`
42+
)
43+
44+
expect(urls.map(convertUrlsToHrefTags)).toEqual(expected)
45+
})
46+
47+
test('does not catch invalid or missing protocol urls', () => {
48+
expect(convertUrlsToHrefTags('https:google.com')).toBe('https:google.com')
49+
expect(convertUrlsToHrefTags('google.com')).toBe('google.com')
50+
})
51+
52+
test('handles urls inside parentheses', () => {
53+
expect(convertUrlsToHrefTags('(http://goo.gl/1)')).toBe(
54+
'(<a href="http://goo.gl/1" target="_blank">http://goo.gl/1</a>)'
55+
)
56+
})
57+
58+
test('does not include puncuation, comma, exclamation', () => {
59+
expect(convertUrlsToHrefTags('http://foo.com/.')).toBe(
60+
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>.'
61+
)
62+
expect(convertUrlsToHrefTags('http://foo.com/!')).toBe(
63+
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>!'
64+
)
65+
expect(convertUrlsToHrefTags('http://foo.com/,')).toBe(
66+
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>,'
67+
)
68+
})
69+
})
70+
})

src/browser/modules/D3Visualization/components/Inspector.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
} from './styled'
3939
import { GrassEditor } from './GrassEditor'
4040
import { RowExpandToggleComponent } from './RowExpandToggle'
41+
import ClickableUrls from '../../../components/clickable-urls'
4142

4243
const mapItemProperties = itemProperties =>
4344
itemProperties
@@ -51,7 +52,7 @@ const mapItemProperties = itemProperties =>
5152
{prop.key + ': '}
5253
</StyledInspectorFooterRowListKey>
5354
<StyledInspectorFooterRowListValue className='value'>
54-
{optionalToString(prop.value)}
55+
<ClickableUrls text={optionalToString(prop.value)} />
5556
</StyledInspectorFooterRowListValue>
5657
</StyledInspectorFooterRowListPair>
5758
))

src/browser/modules/Stream/CypherFrame/TableView.jsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import React, { Component } from 'react'
2222
import { v4 } from 'uuid'
2323
import { v1 as neo4j } from 'neo4j-driver'
24+
import { sanitize } from 'dompurify'
25+
2426
import {
2527
StyledStatsBar,
2628
PaddedTableViewDiv,
@@ -41,6 +43,9 @@ import {
4143
transformResultRecordsToResultArray
4244
} from './helpers'
4345
import { stringModifier } from 'services/bolt/cypherTypesFormatting'
46+
import ClickableUrls, {
47+
convertUrlsToHrefTags
48+
} from '../../../components/clickable-urls'
4449

4550
const renderCell = entry => {
4651
if (Array.isArray(entry)) {
@@ -54,14 +59,20 @@ const renderCell = entry => {
5459
} else if (typeof entry === 'object') {
5560
return renderObject(entry)
5661
} else {
57-
return stringifyMod(entry, stringModifier, true)
62+
return <ClickableUrls text={stringifyMod(entry, stringModifier, true)} />
5863
}
5964
}
6065
export const renderObject = entry => {
6166
if (neo4j.isInt(entry)) return entry.toString()
6267
if (entry === null) return <em>null</em>
6368
return (
64-
<StyledJsonPre>{stringifyMod(entry, stringModifier, true)}</StyledJsonPre>
69+
<StyledJsonPre
70+
dangerouslySetInnerHTML={{
71+
__html: sanitize(
72+
convertUrlsToHrefTags(stringifyMod(entry, stringModifier, true))
73+
)
74+
}}
75+
/>
6576
)
6677
}
6778
const buildData = entries => {

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3506,6 +3506,11 @@ [email protected]:
35063506
dependencies:
35073507
domelementtype "1"
35083508

3509+
dompurify@^1.0.11:
3510+
version "1.0.11"
3511+
resolved "https://neo.jfrog.io/neo/api/npm/npm/dompurify/-/dompurify-1.0.11.tgz#fe0f4a40d147f7cebbe31a50a1357539cfc1eb4d"
3512+
integrity sha1-/g9KQNFH98674xpQoTV1Oc/B600=
3513+
35093514
35103515
version "1.1.6"
35113516
resolved "https://neo.jfrog.io/neo/api/npm/npm/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"

0 commit comments

Comments
 (0)