From 6c47c57a7668ca353bed5f8af317ae73a5239cef Mon Sep 17 00:00:00 2001 From: esikkala <esko.ikkala@aalto.fi> Date: Wed, 10 Jun 2020 19:21:54 +0300 Subject: [PATCH] Add network actions from AcademySampo --- src/client/actions/index.js | 27 +++++++- .../components/facet_results/Network.js | 67 ++++++++++++++----- src/client/containers/SemanticPortal.js | 14 +++- src/client/reducers/sampo/perspective1.js | 1 + src/client/reducers/sampo/perspective2.js | 1 + src/client/reducers/sampo/perspective3.js | 1 + src/server/index.js | 18 +++++ src/server/openapi.yaml | 32 ++++++--- src/server/sparql/NetworkApi.js | 13 ++-- 9 files changed, 140 insertions(+), 34 deletions(-) diff --git a/src/client/actions/index.js b/src/client/actions/index.js index 0a917b5a..5530b2b4 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 9ecf0973..1fe17c9f 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 39d6bc62..c232f4d9 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 15fdbd42..fedb1d7b 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 1159e8f2..b1ab409a 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 b303797c..a96bf18b 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 4fbe8f2d..08285c92 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 e4107a5a..2dac21ea 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 9273bcfe..dc73f2b5 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' -- GitLab