Skip to content

Commit

Permalink
RDFDisplay should get a dataset instead of a string to avoid overload…
Browse files Browse the repository at this point in the history
…ing the browser memory
  • Loading branch information
jgreben committed Sep 17, 2020
1 parent b9bc8c1 commit 5203434
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 73 deletions.
38 changes: 22 additions & 16 deletions __tests__/components/editor/RDFDisplay.test.js
Expand Up @@ -3,16 +3,17 @@
import React from 'react'
import { render, fireEvent, screen } from '@testing-library/react'
import RDFDisplay from 'components/editor/RDFDisplay'
import GraphBuilder from 'GraphBuilder'
import { createState } from 'stateUtils'
import { selectFullSubject, selectCurrentResourceKey } from 'selectors/resources'
import * as dataSetUtils from 'utilities/Utilities'

describe('<RDFDisplay />', () => {
const rdf = `<> <http://id.loc.gov/ontologies/bibframe/mainTitle> "foo"@eng .
<> <http://id.loc.gov/ontologies/bibframe/mainTitle> "bar" .
<> <http://sinopia.io/vocabulary/hasResourceTemplate> "ld4p:RT:bf2:Title:AbbrTitle" .
<> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://id.loc.gov/ontologies/bibframe/AbbreviatedTitle> .
`
const state = createState({ hasTwoLiteralResources: true })
const dataset = new GraphBuilder(selectFullSubject(state, selectCurrentResourceKey(state))).graph

it('renders by default as a table', async () => {
const { container } = render(<RDFDisplay rdf={rdf} />)
const { container } = render(<RDFDisplay dataset={dataset} />)
await screen.findByText(/Format:/)
// Table is selected.
screen.getByDisplayValue('Table')
Expand All @@ -23,34 +24,33 @@ describe('<RDFDisplay />', () => {
screen.getByText('Predicate', 'th')
screen.getByText('Object', 'th')
// And table rows
expect(screen.getAllByText('<>', 'td')).toHaveLength(4)
expect(screen.getAllByText('http://id.loc.gov/ontologies/bibframe/mainTitle', 'td')).toHaveLength(2)
expect(screen.getAllByText('https://api.sinopia.io/resource/0894a8b3', 'td')).toHaveLength(3)
expect(screen.getAllByText('http://id.loc.gov/ontologies/bibframe/mainTitle', 'td')).toHaveLength(1)
screen.getByText('foo [eng]', 'td')
screen.getByText('bar', 'td')
})

it('renders N-Triples', async () => {
render(<RDFDisplay rdf={rdf} />)
render(<RDFDisplay dataset={dataset} />)

await screen.findByText(/Format:/)

fireEvent.change(screen.getByLabelText(/Format/), { target: { value: 'n-triples' } })

await screen.findByText(/<> <http:\/\/id.loc.gov\/ontologies\/bibframe\/mainTitle> "foo"@eng \./)
await screen.findByText(/<https:\/\/api.sinopia.io\/resource\/0894a8b3> <http:\/\/id.loc.gov\/ontologies\/bibframe\/mainTitle> "foo"@eng \./)
})

it('renders Turtle', async () => {
render(<RDFDisplay rdf={rdf} />)
render(<RDFDisplay dataset={dataset} />)

await screen.findByText(/Format:/)

fireEvent.change(screen.getByLabelText(/Format/), { target: { value: 'turtle' } })

await screen.findByText(/<> <http:\/\/id.loc.gov\/ontologies\/bibframe\/mainTitle> "foo"@eng, "bar";/)
await screen.findByText(/<http:\/\/id.loc.gov\/ontologies\/bibframe\/mainTitle> "foo"@eng./)
})

it('renders JSON-LD', async () => {
render(<RDFDisplay rdf={rdf} />)
render(<RDFDisplay dataset={dataset} />)

await screen.findByText(/Format:/)

Expand All @@ -60,8 +60,14 @@ describe('<RDFDisplay />', () => {
}, 10000)

it('displays errors', async () => {
render(<RDFDisplay rdf={`${rdf}x`} />)
jest.spyOn(dataSetUtils, 'jsonldFromDataset').mockRejectedValueOnce(new Error('Alert error'))

await screen.findByText(/Unexpected "x"/)
render(<RDFDisplay dataset={dataset} />)

await screen.findByText(/Format:/)

fireEvent.change(screen.getByLabelText(/Format/), { target: { value: 'jsonld' } })

await screen.findByText(/Alert error/)
})
})
12 changes: 7 additions & 5 deletions __tests__/feature/previewRdf.test.js
Expand Up @@ -8,12 +8,14 @@ global.$ = jest.fn().mockReturnValue({ popover: jest.fn() })

jest.spyOn(Config, 'useResourceTemplateFixtures', 'get').mockReturnValue(true)

const rdf = `<> <http://id.loc.gov/ontologies/bibframe/uber/template1/property20> "Default required literal1", "Default required literal2";
const rdf = `<> <http://sinopia.io/vocabulary/hasResourceTemplate> "resourceTemplate:testing:uber1";
a <http://id.loc.gov/ontologies/bibframe/Uber1>;
<http://id.loc.gov/ontologies/bibframe/uber/template1/property7> "Default literal1", "Default literal2";
<http://id.loc.gov/ontologies/bibframe/uber/template1/property8> <http://sinopia.io/defaultURI1>, <http://sinopia.io/defaultURI2>;
<http://sinopia.io/vocabulary/hasResourceTemplate> "resourceTemplate:testing:uber1";
a <http://id.loc.gov/ontologies/bibframe/Uber1>.
<http://sinopia.io/defaultURI1> <http://www.w3.org/2000/01/rdf-schema#label> "Default URI1".`
<http://id.loc.gov/ontologies/bibframe/uber/template1/property8> <http://sinopia.io/defaultURI1>.
<http://sinopia.io/defaultURI1> <http://www.w3.org/2000/01/rdf-schema#label> "Default URI1".
<> <http://id.loc.gov/ontologies/bibframe/uber/template1/property8> <http://sinopia.io/defaultURI2>;
<http://id.loc.gov/ontologies/bibframe/uber/template1/property20> "Default required literal1", "Default required literal2".
`

describe('preview RDF after editing', () => {
it('adds properties and then displays preview RDF model', async () => {
Expand Down
25 changes: 7 additions & 18 deletions src/components/editor/RDFDisplay.jsx
Expand Up @@ -2,34 +2,26 @@

import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { datasetFromN3, n3FromDataset, jsonldFromDataset } from 'utilities/Utilities'
import { n3FromDataset, jsonldFromDataset } from 'utilities/Utilities'
import Alert from '../Alert'

const RDFDisplay = (props) => {
const [error, setError] = useState(false)
const [dataset, setDataset] = useState(false)
useEffect(() => {
setError(false)
datasetFromN3(props.rdf)
.then((dataset) => setDataset(dataset))
.catch((err) => setError(err.message || err))
}, [props.rdf])

const [format, setFormat] = useState('table')
const [formattedRDF, setFormattedRDF] = useState(false)
useEffect(() => {
if (!dataset || format === 'table') return
if (!props.dataset || format === 'table') return
setError(false)
if (format === 'jsonld') {
jsonldFromDataset(dataset)
jsonldFromDataset(props.dataset)
.then((result) => setFormattedRDF(JSON.stringify(result, null, 2)))
.catch((err) => setError(err.message || err))
} else {
n3FromDataset(dataset, format)
n3FromDataset(props.dataset, format)
.then((result) => setFormattedRDF(result.replace(/<null>/g, '<>')))
.catch((err) => setError(err.message || err))
}
}, [dataset, format])
}, [props.dataset, format])

if (error) {
return (<Alert text={error} />)
Expand All @@ -38,13 +30,10 @@ const RDFDisplay = (props) => {
if (format !== 'table' && !formattedRDF) {
return null
}
if (format === 'table' && !dataset) {
return null
}

let body
if (format === 'table') {
const rows = dataset.toArray().map((quad) => <tr key={[quad.subject.value, quad.predicate.value, quad.object.value].concat('-')}>
const rows = props.dataset.toArray().map((quad) => <tr key={[quad.subject.value, quad.predicate.value, quad.object.value].concat('-')}>
<td>{quad.subject.value || '<>'}</td>
<td>{quad.predicate.value}</td>
<td>{quad.object.value}{quad.object.language && ` [${quad.object.language}]`}</td>
Expand Down Expand Up @@ -88,7 +77,7 @@ const RDFDisplay = (props) => {
}

RDFDisplay.propTypes = {
rdf: PropTypes.string,
dataset: PropTypes.object.isRequired,
id: PropTypes.string,
}

Expand Down
6 changes: 4 additions & 2 deletions src/components/editor/RDFModal.jsx
Expand Up @@ -42,7 +42,7 @@ const RDFModal = (props) => {
</div>
</div>
{ props.show
&& <RDFDisplay rdf={props.rdf()} />
&& <RDFDisplay dataset={props.dataset()} />
}
</div>
</div>
Expand All @@ -55,11 +55,13 @@ const RDFModal = (props) => {
RDFModal.propTypes = {
show: PropTypes.bool,
rdf: PropTypes.func,
dataset: PropTypes.func,
}

const mapStateToProps = (state) => ({
show: selectModalType(state) === 'RDFModal',
rdf: () => new GraphBuilder(selectFullSubject(state, selectCurrentResourceKey(state))).graph.toCanonical(),
dataset: () => new GraphBuilder(selectFullSubject(state, selectCurrentResourceKey(state))).graph,
})


export default connect(mapStateToProps, null)(RDFModal)
64 changes: 32 additions & 32 deletions src/components/editor/ResourceComponent.jsx
@@ -1,69 +1,69 @@
// Copyright 2019 Stanford University see LICENSE for license

import React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import PanelResource from './property/PanelResource'
import { selectErrors } from 'selectors/errors'
import CopyToNewMessage from './CopyToNewMessage'
import ResourceURIMessage from './ResourceURIMessage'
import SaveAlert from './SaveAlert'
import RDFDisplay from './RDFDisplay'
import Alerts from '../Alerts'
import { newResourceErrorKey } from './property/ResourceList'
import { resourceEditErrorKey } from './Editor'
import { selectCurrentResourceKey, selectCurrentResource } from 'selectors/resources'
import { addError } from 'actions/errors'
import { datasetFromN3 } from 'utilities/Utilities'
import { selectCurrentResource } from 'selectors/resources'
import { selectErrors } from 'selectors/errors'
import { selectUnusedRDF } from 'selectors/modals'
import _ from 'lodash'

/**
* This is the root component of the editor on the resource edit page
*/
const ResourceComponent = (props) => {
if (!_.isEmpty(props.errors)) {
return (<Alerts errorKey={resourceEditErrorKey(props.resourceKey)} />)
const ResourceComponent = () => {
const dispatch = useDispatch()
const resource = useSelector((state) => selectCurrentResource(state))
const resourceKey = resource.key
const errors = useSelector((state) => selectErrors(state, resourceEditErrorKey(resourceKey)))
const unusedRDF = useSelector((state) => selectUnusedRDF(state, resourceKey))

const [dataset, setDataset] = useState(null)

useEffect(() => {
if (_.isEmpty(unusedRDF)) return
datasetFromN3(unusedRDF)
.then((dataset) => setDataset(dataset))
.catch((err) => dispatch(addError(resourceEditErrorKey(resourceKey), err.message || err)))
}, [unusedRDF, resourceKey, dispatch])

if (!_.isEmpty(errors)) {
return (<Alerts errorKey={resourceEditErrorKey(resourceKey)} />)
}
if (!props.resource) {
if (!resource) {
return null
}

return (
<div className="ResourceTemplate">
<div id="resourceTemplate">
<Alerts errorKey={resourceEditErrorKey(props.resourceKey)} />
<Alerts errorKey={resourceEditErrorKey(resourceKey)} />
<Alerts errorKey={newResourceErrorKey} />
<section>
<h3>{props.resource.subjectTemplate.label}</h3>
<h3>{resource.subjectTemplate.label}</h3>
<CopyToNewMessage />
<ResourceURIMessage />
<SaveAlert />
</section>
{props.unusedRDF
{dataset
&& <div className="alert alert-warning" role="alert">
<strong>Unable to load the entire resource.</strong> The unused triples are:
<RDFDisplay rdf={props.unusedRDF} />
<RDFDisplay dataset={dataset} />
</div>
}
<PanelResource resource={props.resource} />
<PanelResource resource={resource} />
</div>
</div>
)
}

ResourceComponent.propTypes = {
errors: PropTypes.array,
unusedRDF: PropTypes.string,
resourceKey: PropTypes.string,
resource: PropTypes.object,
}

const mapStateToProps = (state) => {
const resourceKey = selectCurrentResourceKey(state)
return {
resourceKey,
resource: selectCurrentResource(state),
errors: selectErrors(state, resourceEditErrorKey(resourceKey)),
unusedRDF: state.selectorReducer.editor.unusedRDF[resourceKey],
}
}

export default connect(mapStateToProps)(ResourceComponent)
export default ResourceComponent
2 changes: 2 additions & 0 deletions src/selectors/modals.js
Expand Up @@ -3,3 +3,5 @@
export const selectModalType = (state) => state.selectorReducer.editor.modal.name

export const selectModalMessages = (state) => state.selectorReducer.editor.modal.messages

export const selectUnusedRDF = (state, resourceKey) => state.selectorReducer.editor.unusedRDF[resourceKey]

0 comments on commit 5203434

Please sign in to comment.