diff --git a/src/client/actions/index.js b/src/client/actions/index.js index 0a917b5a8dec619975d49d290d733ae86851af26..5530b2b483e916435f53b9c6d56794cd8585c48b 100644 --- a/src/client/actions/index.js +++ b/src/client/actions/index.js @@ -16,8 +16,11 @@ export const FETCH_BY_URI = 'FETCH_BY_URI' export const FETCH_BY_URI_FAILED = 'FETCH_BY_URI_FAILED' export const FETCH_SIMILAR_DOCUMENTS_BY_ID = 'FETCH_SIMILAR_DOCUMENTS_BY_ID' export const FETCH_SIMILAR_DOCUMENTS_BY_ID_FAILED = 'FETCH_SIMILAR_DOCUMENTS_BY_ID_FAILED' +export const FETCH_NETWORK_BY_ID = 'FETCH_NETWORK_BY_ID' +export const FETCH_NETWORK_BY_ID_FAILED = 'FETCH_NETWORK_BY_ID_FAILED' export const UPDATE_INSTANCE = 'UPDATE_INSTANCE' export const UPDATE_INSTANCE_RELATED_DATA = 'UPDATE_INSTANCE_RELATED_DATA' +export const UPDATE_INSTANCE_NETWORK_DATA = 'UPDATE_INSTANCE_NETWORK_DATA' export const FETCH_FACET = 'FETCH_FACET' export const FETCH_FACET_CONSTRAIN_SELF = 'FETCH_FACET_CONSTRAIN_SELF' export const FETCH_FACET_FAILED = 'FETCH_FACET_FAILED' @@ -61,10 +64,12 @@ export const fetchPaginatedResultsFailed = (resultClass, error, message) => ({ error, message }) -export const fetchResults = ({ resultClass, facetClass }) => ({ +export const fetchResults = ({ resultClass, facetClass, limit = null, optimize = null }) => ({ type: FETCH_RESULTS, resultClass, - facetClass + facetClass, + limit, + optimize }) export const fetchResultCount = ({ resultClass, facetClass }) => ({ type: FETCH_RESULT_COUNT, @@ -148,6 +153,19 @@ export const fetchSimilarDocumentsById = ({ resultClass, id, modelName, resultSi modelName, resultSize }) +export const fetchNetworkById = ({ resultClass, id, limit = null, optimize = null }) => ({ + type: FETCH_NETWORK_BY_ID, + resultClass, + id, + limit, + optimize +}) +export const fetchNetworkByIdFailed = ({ resultClass, id, error, message }) => ({ + type: FETCH_NETWORK_BY_ID_FAILED, + resultClass, + error, + message +}) export const fetchSimilarDocumentsByIdFailed = (resultClass, id, error, message) => ({ type: FETCH_SIMILAR_DOCUMENTS_BY_ID_FAILED, resultClass, @@ -166,6 +184,11 @@ export const updateInstanceRelatedData = ({ resultClass, data }) => ({ resultClass, data }) +export const updateInstanceNetworkData = ({ resultClass, data }) => ({ + type: UPDATE_INSTANCE_NETWORK_DATA, + resultClass, + data +}) export const fetchFacet = ({ facetClass, facetID }) => ({ type: FETCH_FACET, facetClass, diff --git a/src/client/components/facet_results/Network.js b/src/client/components/facet_results/Network.js index 9ecf097347b74cc6baba07309be89563567f7876..1fe17c9f207bbd766aba2188d6bc00758585daab 100644 --- a/src/client/components/facet_results/Network.js +++ b/src/client/components/facet_results/Network.js @@ -1,13 +1,14 @@ import React from 'react' import PropTypes from 'prop-types' import { withStyles } from '@material-ui/core/styles' +import history from '../../History' import cytoscape from 'cytoscape' const styles = theme => ({ root: { height: 400, [theme.breakpoints.up('md')]: { - height: 'calc(100% - 72px)' + height: 'calc(100% - 21px)' } }, cyContainer: { @@ -29,7 +30,7 @@ const layout = { edgeElasticity: 100, nestingFactor: 5, gravity: 80, - numIter: 1000, + numIter: 1347, initialTemp: 200, coolingFactor: 0.95, minTemp: 1.0 @@ -42,10 +43,21 @@ class Network extends React.Component { } componentDidMount = () => { - this.props.fetchResults({ - resultClass: this.props.resultClass, - facetClass: this.props.facetClass - }) + if (this.props.pageType === 'instancePage') { + this.props.fetchNetworkById({ + resultClass: this.props.resultClass, + id: this.props.id, + limit: this.props.limit, + optimize: this.props.optimize + }) + } else { + this.props.fetchResults({ + resultClass: this.props.resultClass, + facetClass: this.props.facetClass, + limit: this.props.limit, + optimize: this.props.optimize + }) + } this.cy = cytoscape({ container: this.cyRef.current, @@ -53,37 +65,53 @@ class Network extends React.Component { { selector: 'node', style: { - 'background-color': ele => ele.data('class') === 'http://erlangen-crm.org/efrbroo/F4_Manifestation_Singleton' - ? '#666' : '#000', - label: 'data(prefLabel)' + shape: 'ellipse', + 'font-size': '12', + 'background-color': ele => ele.data('color') || '#666', + label: ' data(prefLabel)', + height: ele => (ele.data('size') || 16 / (ele.data('distance') + 1) || '16px'), + width: ele => (ele.data('size') || 16 / (ele.data('distance') + 1) || '16px') } }, { selector: 'edge', style: { - // 'width': 'data(weight)', - 'line-color': '#999', + width: ele => ele.data('weight') || 1, + 'line-color': ele => ele.data('color') || '#BBB', 'curve-style': 'bezier', - content: 'data(prefLabel)', + content: ' data(prefLabel) ', 'target-arrow-shape': 'triangle', 'target-arrow-color': '#999', color: '#555', - 'font-size': '9', + 'font-size': '6', 'text-valign': 'top', 'text-halign': 'center', 'edge-text-rotation': 'autorotate', 'text-background-opacity': 1, - 'text-background-color': '#FFF', + 'text-background-color': 'white', 'text-background-shape': 'roundrectangle' } } ] }) + + this.cy.on('tap', 'node', function () { + try { + if (this.data('href')) { + // console.log(this.data('href')) + history.push(this.data('href')) + } + } catch (e) { // fall back on url change + console.log('Fail', e) + console.log(this.data()) + } + }) } componentDidUpdate = prevProps => { if (prevProps.resultUpdateID !== this.props.resultUpdateID) { // console.log(this.props.results.elements); + this.cy.elements().remove() this.cy.add(this.props.results.elements) this.cy.layout(layout).run() } @@ -108,11 +136,14 @@ class Network extends React.Component { Network.propTypes = { classes: PropTypes.object.isRequired, results: PropTypes.object, - fetchResults: PropTypes.func.isRequired, + fetchResults: PropTypes.func, + fetchNetworkById: PropTypes.func, resultClass: PropTypes.string.isRequired, - facetClass: PropTypes.string.isRequired, - facetUpdateID: PropTypes.number.isRequired, - resultUpdateID: PropTypes.number.isRequired + facetClass: PropTypes.string, + facetUpdateID: PropTypes.number, + resultUpdateID: PropTypes.number.isRequired, + limit: PropTypes.number.isRequired, + optimize: PropTypes.number.isRequired } export default withStyles(styles)(Network) diff --git a/src/client/containers/SemanticPortal.js b/src/client/containers/SemanticPortal.js index 39d6bc623fb704b9dbc9062b80ac609c64eb45db..c232f4d907744583d83948ec24ca4b6168084039 100644 --- a/src/client/containers/SemanticPortal.js +++ b/src/client/containers/SemanticPortal.js @@ -42,6 +42,7 @@ import { fetchFullTextResults, clearResults, fetchByURI, + fetchNetworkById, fetchFacet, fetchFacetConstrainSelf, clearFacet, @@ -417,10 +418,13 @@ const SemanticPortal = props => { <InstanceHomePage rootUrl={rootUrlWithLang} fetchByURI={props.fetchByURI} + fetchNetworkById={props.fetchNetworkById} resultClass={perspective.id} + resultUpdateID={props[perspective.id].resultUpdateID} properties={props[perspective.id].properties} tabs={perspective.instancePageTabs} data={props[perspective.id].instance} + networkData={props[perspective.id].instanceNetworkData} sparqlQuery={props[perspective.id].instanceSparqlQuery} isLoading={props[perspective.id].fetching} routeProps={routeProps} @@ -441,7 +445,7 @@ const SemanticPortal = props => { {perspectiveConfigOnlyInfoPages.map(perspective => <Switch key={perspective.id}> <Redirect - from={`/${perspective.id}/page/:id`} + from={`${rootUrl}/${perspective.id}/page/:id`} to={`${rootUrlWithLang}/${perspective.id}/page/:id`} /> <Route @@ -466,10 +470,13 @@ const SemanticPortal = props => { <InstanceHomePage rootUrl={rootUrlWithLang} fetchByURI={props.fetchByURI} + fetchNetworkById={props.fetchNetworkById} resultClass={perspective.id} + resultUpdateID={props[perspective.id].resultUpdateID} properties={props[perspective.id].properties} tabs={perspective.instancePageTabs} data={props[perspective.id].instance} + networkData={props[perspective.id].instanceNetworkData} sparqlQuery={props[perspective.id].instanceSparqlQuery} isLoading={props[perspective.id].fetching} routeProps={routeProps} @@ -597,6 +604,7 @@ const mapDispatchToProps = ({ fetchFacetConstrainSelf, clearFacet, fetchGeoJSONLayers, + fetchNetworkById, fetchGeoJSONLayersBackend, clearGeoJSONLayers, sortResults, @@ -686,6 +694,10 @@ SemanticPortal.propTypes = { * Redux action for fetching information about a single entity. */ fetchByURI: PropTypes.func.isRequired, + /** + * Redux action for fetching network of a single entity. + */ + fetchNetworkById: PropTypes.func.isRequired, /** * Redux action for loading external GeoJSON layers. */ diff --git a/src/client/reducers/sampo/perspective1.js b/src/client/reducers/sampo/perspective1.js index 15fdbd4273e05dd4b857bc5d88cd95993ba2b327..fedb1d7b51514f9764959d610df3cb2f9107e61b 100644 --- a/src/client/reducers/sampo/perspective1.js +++ b/src/client/reducers/sampo/perspective1.js @@ -36,6 +36,7 @@ export const INITIAL_STATE = { paginatedResults: [], paginatedResultsSparqlQuery: null, instance: null, + instanceNetworkData: null, instanceSparqlQuery: null, resultCount: 0, page: -1, diff --git a/src/client/reducers/sampo/perspective2.js b/src/client/reducers/sampo/perspective2.js index 1159e8f27720a5d45e740a98938781743633d1ba..b1ab409af143f69f7939ebffede53f3a0cdee855 100644 --- a/src/client/reducers/sampo/perspective2.js +++ b/src/client/reducers/sampo/perspective2.js @@ -35,6 +35,7 @@ export const INITIAL_STATE = { paginatedResults: [], paginatedResultsSparqlQuery: null, instance: null, + instanceNetworkData: null, instanceSparqlQuery: null, resultCount: 0, page: -1, diff --git a/src/client/reducers/sampo/perspective3.js b/src/client/reducers/sampo/perspective3.js index b303797cffeff5254bd0c3283504a5cf04e6ec04..a96bf18b656bfd11b3420ac2ddd1155d784d0efb 100644 --- a/src/client/reducers/sampo/perspective3.js +++ b/src/client/reducers/sampo/perspective3.js @@ -35,6 +35,7 @@ export const INITIAL_STATE = { paginatedResults: [], paginatedResultsSparqlQuery: null, instance: null, + instanceNetworkData: null, instanceSparqlQuery: null, resultCount: 0, page: -1, diff --git a/src/server/index.js b/src/server/index.js index 4fbe8f2d77f1f9351aec1225092a4a4e62bb4548..08285c925a2ce7adf4780fa940149fefbb43fa37 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -168,6 +168,24 @@ new OpenApiValidator({ } }) + app.get(`${apiPath}/:resultClass/network/:id`, async (req, res, next) => { + const { params, query } = req + try { + const data = await getByURI({ + backendSearchConfig, + resultClass: params.resultClass, + uri: params.id, + limit: query.limit, + optimize: query.optimize, + constraints: null, + resultFormat: 'json' + }) + res.json(data) + } catch (error) { + next(error) + } + }) + app.post(`${apiPath}/faceted-search/:facetClass/facet/:id`, async (req, res, next) => { const { params, body } = req try { diff --git a/src/server/openapi.yaml b/src/server/openapi.yaml index e4107a5adb22aca8c0c472f9b09813d22630ba29..2dac21eae8d96841753964bd16ce34b606ca82e4 100644 --- a/src/server/openapi.yaml +++ b/src/server/openapi.yaml @@ -118,15 +118,6 @@ paths: application/json: schema: type: object - properties: - data: - type: array - items: - type: object - description: Results as an array of objects - sparqlQuery: - type: string - description: The SPARQL query that was used for the results get: summary: Return all search results as a CSV file responses: @@ -292,6 +283,29 @@ paths: sparqlQuery: type: string description: The SPARQL query that was used for retrieving the metadata + /{resultClass}/network/{id}: + get: + summary: Return a network of a single resource + parameters: + - in: path + name: resultClass + schema: + type: string + required: true + description: The class of the resource + - in: path + name: id + schema: + type: string + required: true + description: The URI of the resource + responses: + '200': + description: Network data + content: + application/json: + schema: + type: object /full-text-search: get: summary: Full text search diff --git a/src/server/sparql/NetworkApi.js b/src/server/sparql/NetworkApi.js index 9273bcfe4209577f6f31c56f5ed4461119a26cda..dc73f2b59a1bd2a5a5bcb7c91df8604a18177320 100644 --- a/src/server/sparql/NetworkApi.js +++ b/src/server/sparql/NetworkApi.js @@ -4,17 +4,22 @@ export const runNetworkQuery = async ({ endpoint, prefixes, links, - nodes + limit, + nodes, + id, + optimize }) => { const payload = { endpoint, prefixes, links, nodes, - limit: 500 - // id: 'http://ldf.fi/mmm/actor/bodley_person_51697938' + limit, + id, + optimize, + customHttpHeaders: { Authorization: `Basic ${process.env.SPARQL_ENDPOINT_BASIC_AUTH}` } } - const url = 'http://127.0.0.1:5000/query' + const url = 'https://sparql-network.demo.seco.cs.aalto.fi/query' // 'http://127.0.0.1:5000/query' const config = { headers: { 'Content-Type': 'application/json'