From d021037bf4ca60f48206e95b898940d5f5aa52c4 Mon Sep 17 00:00:00 2001 From: esikkala <esko.ikkala@aalto.fi> Date: Thu, 11 Apr 2019 15:37:37 +0300 Subject: [PATCH] Building text search perspective --- src/client/actions/index.js | 15 +++ .../components/facet_results/ResultTable2.js | 28 ++++ .../components/main_layout/SearchField.js | 124 ------------------ src/client/components/main_layout/TopBar.js | 22 +--- .../main_layout/TopBarSearchField.js | 115 ++++++++++++++++ src/client/components/perspectives/All.js | 50 +++++++ src/client/containers/SemanticPortal.js | 33 ++++- src/client/epics/index.js | 39 +++++- .../reducers/clientSideFacetedSearch.js | 89 +++++++++++++ src/client/reducers/index.js | 2 + src/server/index.js | 31 +++++ src/server/sparql/JenaQuery.js | 19 +++ src/server/sparql/SparqlQueriesGeneral.js | 9 ++ 13 files changed, 435 insertions(+), 141 deletions(-) create mode 100644 src/client/components/facet_results/ResultTable2.js delete mode 100644 src/client/components/main_layout/SearchField.js create mode 100644 src/client/components/main_layout/TopBarSearchField.js create mode 100644 src/client/components/perspectives/All.js create mode 100644 src/client/reducers/clientSideFacetedSearch.js create mode 100644 src/server/sparql/JenaQuery.js diff --git a/src/client/actions/index.js b/src/client/actions/index.js index eb01a975..27bf7759 100644 --- a/src/client/actions/index.js +++ b/src/client/actions/index.js @@ -1,9 +1,11 @@ export const FETCH_PAGINATED_RESULTS = 'FETCH_PAGINATED_RESULTS'; export const FETCH_PAGINATED_RESULTS_FAILED = 'FETCH_PAGINATED_RESULTS_FAILED'; export const FETCH_RESULTS = 'FETCH_RESULTS'; +export const FETCH_RESULTS_CLIENT_SIDE = 'FETCH_RESULTS_CLIENT_SIDE'; export const FETCH_RESULTS_FAILED = 'FETCH_RESULTS_FAILED'; export const UPDATE_PAGINATED_RESULTS = 'UPDATE_PAGINATED_RESULTS'; export const UPDATE_RESULTS = 'UPDATE_RESULTS'; +export const CLEAR_RESULTS = 'CLEAR_RESULTS'; export const SORT_RESULTS = 'SORT_RESULTS'; export const UPDATE_PAGE = 'UPDATE_PAGE'; export const FETCH_BY_URI = 'FETCH_BY_URI'; @@ -13,6 +15,7 @@ export const FETCH_FACET = 'FETCH_FACET'; export const FETCH_FACET_FAILED = 'FETCH_FACET_FAILED'; export const UPDATE_FACET_VALUES = 'UPDATE_FACET_VALUES'; export const UPDATE_FACET_OPTION = 'UPDATE_FACET_OPTION'; +export const UPDATE_CLIENT_SIDE_FILTER = 'UPDATE_CLIENT_SIDE_FILTER'; export const OPEN_MARKER_POPUP = 'OPEN_MARKER_POPUP'; export const SHOW_ERROR = 'SHOW_ERROR'; @@ -28,6 +31,10 @@ export const fetchResults = ({ resultClass, facetClass, sortBy, variant }) => ({ type: FETCH_RESULTS, resultClass, facetClass, sortBy, variant }); +export const fetchResultsClientSide = ({ jenaIndex, query }) => ({ + type: FETCH_RESULTS_CLIENT_SIDE, + jenaIndex, query +}); export const fetchResultsFailed = (resultClass, error, message) => ({ type: FETCH_RESULTS_FAILED, resultClass, error, message @@ -44,6 +51,10 @@ export const sortResults = (resultClass, sortBy) => ({ type: SORT_RESULTS, resultClass, sortBy }); +export const clearResults = resultClass => ({ + type: CLEAR_RESULTS, + resultClass +}); export const updatePage = (resultClass, page) => ({ type: UPDATE_PAGE, resultClass, page @@ -76,6 +87,10 @@ export const updateFacetOption = ({ facetClass, facetID, option, value }) => ({ type: UPDATE_FACET_OPTION, facetClass, facetID, option, value }); +export const updateClientSideFilter = filterObj => ({ + type: UPDATE_CLIENT_SIDE_FILTER, + filterObj +}); export const openMarkerPopup = uri => ({ type: OPEN_MARKER_POPUP, uri diff --git a/src/client/components/facet_results/ResultTable2.js b/src/client/components/facet_results/ResultTable2.js new file mode 100644 index 00000000..8065033b --- /dev/null +++ b/src/client/components/facet_results/ResultTable2.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import MaterialTable from 'material-table'; + +class ResultTable2 extends React.Component { + render() { + return ( + <div style={{ maxWidth: '100%' }}> + <MaterialTable + columns={[ + { title: 'Adı', field: 'name' }, + { title: 'Soyadı', field: 'surname' }, + { title: 'Doğum Yılı', field: 'birthYear', type: 'numeric' }, + { title: 'Doğum Yeri', field: 'birthCity', lookup: { 34: 'İstanbul', 63: 'Şanlıurfa' } } + ]} + data={[{ name: 'Mehmet', surname: 'Baran', birthYear: 1987, birthCity: 63 }]} + title="Demo Title" + /> + </div> + ); + } +} + +ResultTable2.propTypes = { + data: PropTypes.object, +}; + +export default ResultTable2; diff --git a/src/client/components/main_layout/SearchField.js b/src/client/components/main_layout/SearchField.js deleted file mode 100644 index 5743237d..00000000 --- a/src/client/components/main_layout/SearchField.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import IconButton from '@material-ui/core/IconButton'; -import SearchIcon from '@material-ui/icons/Search'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; -import InputAdornment from '@material-ui/core/InputAdornment'; -import FormControl from '@material-ui/core/FormControl'; -import CircularProgress from '@material-ui/core/CircularProgress'; - -const styles = theme => ({ - textSearch: { - margin: theme.spacing.unit, - }, -}); - -class SearchField extends React.Component { - state = { - value: '', - }; - - componentDidUpdate = prevProps => { - if (prevProps.search.query != this.props.search.query) { - this.setState({ - value: this.props.search.query - }); - } - } - - handleChange = (event) => { - this.setState({ value: event.target.value }); - }; - - handleMouseDown = (event) => { - event.preventDefault(); - }; - - handleOnKeyDown = (event) => { - if (event.key === 'Enter' && this.hasDatasets() && this.hasValidQuery()) { - this.props.clearResults(); - this.props.updateQuery(this.state.value); - this.props.fetchResults('text', this.state.value); - } - }; - - handleClick = () => { - if (this.hasDatasets() && this.hasValidQuery()) { - this.props.clearResults(); - this.props.updateQuery(this.state.value); - this.props.fetchResults('text', this.state.value); - } - }; - - hasDatasets = () => { - let hasDs = false; - Object.values(this.props.datasets).forEach(value => { - if (value.selected) { - hasDs = true; - } - }); - return hasDs; - } - - hasValidQuery = () => { - return this.state.value.length > 2; - } - - render() { - const { classes, strings } = this.props; - let searchButton = null; - if (this.props.search.textResultsFetching) { - searchButton = ( - <IconButton - aria-label="Search places" - > - <CircularProgress size={24} /> - </IconButton> - ); - } else { - searchButton = ( - <IconButton - aria-label="Search" - onClick={this.handleClick} - onMouseDown={this.handleMouseDown} - > - <SearchIcon /> - </IconButton> - ); - } - - return ( - <div className={classes.root}> - <FormControl className={classes.textSearch}> - <InputLabel htmlFor="adornment-search">{strings.searchPlaceNames}</InputLabel> - <Input - id="adornment-search" - type='text' - value={this.state.value} - onChange={this.handleChange} - onKeyDown={this.handleOnKeyDown} - endAdornment={ - <InputAdornment position="end"> - {searchButton} - </InputAdornment> - } - /> - </FormControl> - </div> - ); - } -} - -SearchField.propTypes = { - classes: PropTypes.object.isRequired, - search: PropTypes.object.isRequired, - fetchResults: PropTypes.func.isRequired, - clearResults: PropTypes.func.isRequired, - updateQuery: PropTypes.func.isRequired, - datasets: PropTypes.object.isRequired, - strings: PropTypes.object.isRequired -}; - -export default withStyles(styles)(SearchField); diff --git a/src/client/components/main_layout/TopBar.js b/src/client/components/main_layout/TopBar.js index 994b1663..4ee67288 100644 --- a/src/client/components/main_layout/TopBar.js +++ b/src/client/components/main_layout/TopBar.js @@ -4,15 +4,14 @@ import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; import MenuItem from '@material-ui/core/MenuItem'; import Menu from '@material-ui/core/Menu'; import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; -import SearchIcon from '@material-ui/icons/Search'; import MoreIcon from '@material-ui/icons/MoreVert'; import Button from '@material-ui/core/Button'; import { Link, NavLink } from 'react-router-dom'; +import TopBarSearchField from './TopBarSearchField'; const styles = theme => ({ root: { @@ -154,19 +153,10 @@ class TopBar extends React.Component { MMM </Typography> </Button> - <div className={classes.search}> - <div className={classes.searchIcon}> - <SearchIcon /> - </div> - <InputBase - disabled - placeholder="Search…" - classes={{ - root: classes.inputRoot, - input: classes.inputInput, - }} - /> - </div> + <TopBarSearchField + fetchResultsClientSide={this.props.fetchResultsClientSide} + clearResults={this.props.clearResults} + /> <div className={classes.grow} /> <div className={classes.sectionDesktop}> {perspectives.map(perspective => @@ -197,6 +187,8 @@ class TopBar extends React.Component { TopBar.propTypes = { classes: PropTypes.object.isRequired, + fetchResultsClientSide: PropTypes.func.isRequired, + clearResults: PropTypes.func.isRequired, }; export default withStyles(styles)(TopBar); diff --git a/src/client/components/main_layout/TopBarSearchField.js b/src/client/components/main_layout/TopBarSearchField.js new file mode 100644 index 00000000..5b2dba72 --- /dev/null +++ b/src/client/components/main_layout/TopBarSearchField.js @@ -0,0 +1,115 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { fade } from '@material-ui/core/styles/colorManipulator'; +import SearchIcon from '@material-ui/icons/Search'; +import InputBase from '@material-ui/core/InputBase'; +//import CircularProgress from '@material-ui/core/CircularProgress'; + +const styles = theme => ({ + search: { + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: fade(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: fade(theme.palette.common.white, 0.25), + }, + marginRight: theme.spacing.unit * 2, + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing.unit * 3, + width: 'auto', + }, + }, + searchIcon: { + width: theme.spacing.unit * 9, + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + inputRoot: { + color: 'inherit', + width: '100%', + }, + inputInput: { + paddingTop: theme.spacing.unit, + paddingRight: theme.spacing.unit, + paddingBottom: theme.spacing.unit, + paddingLeft: theme.spacing.unit * 10, + transition: theme.transitions.create('width'), + width: '100%', + [theme.breakpoints.up('md')]: { + width: 200, + }, + }, +}); + +class TopBarSearchField extends React.Component { + state = { + value: '', + }; + + handleChange = (event) => { + this.setState({ value: event.target.value }); + }; + + handleMouseDown = (event) => { + event.preventDefault(); + }; + + handleOnKeyDown = (event) => { + if (event.key === 'Enter' && this.hasValidQuery()) { + this.props.clearResults(); + this.props.fetchResultsClientSide({ + jenaIndex: 'text', + query: this.state.value + }); + } + }; + + handleClick = () => { + if (this.hasValidQuery()) { + this.props.clearResults(); + this.props.fetchResultsClientSide({ + jenaIndex: 'text', + query: this.state.value + }); + } + }; + + hasValidQuery = () => { + return this.state.value.length > 2; + } + + render() { + const { classes } = this.props; + return ( + <div className={classes.search}> + <div className={classes.searchIcon}> + <SearchIcon /> + </div> + <InputBase + placeholder="Search everything" + classes={{ + root: classes.inputRoot, + input: classes.inputInput, + }} + onChange={this.handleChange} + onKeyDown={this.handleOnKeyDown} + /> + </div> + ); + } +} + +TopBarSearchField.propTypes = { + classes: PropTypes.object.isRequired, + fetchResultsClientSide: PropTypes.func.isRequired, + clearResults: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(TopBarSearchField); diff --git a/src/client/components/perspectives/All.js b/src/client/components/perspectives/All.js new file mode 100644 index 00000000..74bbc432 --- /dev/null +++ b/src/client/components/perspectives/All.js @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Route, Redirect } from 'react-router-dom'; +import PerspectiveTabs from '../main_layout/PerspectiveTabs'; +//import ResultTable2 from '../facet_results/ResultTable2'; +import Typography from '@material-ui/core/Typography'; + + +let All = props => { + const perspectiveUrl = '/all'; + return ( + <React.Fragment> + <PerspectiveTabs + routeProps={props.routeProps} + tabs={{ + [`${perspectiveUrl}/table`]: { + label: 'table', + value: 0, + icon: 'CalendarViewDay', + }, + [`${perspectiveUrl}/map`]: { + label: 'map', + value: 1, + icon: 'AddLocation', + }, + }} + /> + <Route + exact path={perspectiveUrl} + render={() => <Redirect to={`${perspectiveUrl}/table`} />} + /> + <Route + path={`${perspectiveUrl}/table`} + render={() => + <Typography>Test</Typography> + //<ResultTable2 /> + } + /> + </React.Fragment> + ); +}; + +All.propTypes = { + results: PropTypes.object, + updatePage: PropTypes.func, + sortResults: PropTypes.func, + routeProps: PropTypes.object.isRequired, +}; + +export default All; diff --git a/src/client/containers/SemanticPortal.js b/src/client/containers/SemanticPortal.js index 0d140a08..9eaf174b 100644 --- a/src/client/containers/SemanticPortal.js +++ b/src/client/containers/SemanticPortal.js @@ -18,9 +18,12 @@ import Works from '../components/perspectives/Works'; import Places from '../components//perspectives/Places'; import People from '../components//perspectives/People'; import Organizations from '../components/perspectives/Organizations'; +import All from '../components/perspectives/All'; import { fetchPaginatedResults, fetchResults, + fetchResultsClientSide, + clearResults, fetchByURI, fetchFacet, sortResults, @@ -82,7 +85,11 @@ let SemanticPortal = (props) => { <div className={classes.appFrame}> <Message error={error} /> <React.Fragment> - <TopBar /> + <TopBar + search={props.clientSideFacetedSearch} + fetchResultsClientSide={props.fetchResultsClientSide} + clearResults={props.clearResults} + /> <Grid container spacing={8} className={classes.mainContainer}> <Route exact path="/" component={Main} /> <Route @@ -243,6 +250,24 @@ let SemanticPortal = (props) => { </React.Fragment> } /> + <Route + path="/all" + render={routeProps => + <React.Fragment> + <Grid item sm={12} md={3} className={classes.facetBarContainer}> + + </Grid> + <Grid item sm={12} md={9} className={classes.resultsContainer}> + <Paper className={classes.resultsContainerPaper}> + <All + results={props.clientSideFacetedSearch.results} + routeProps={routeProps} + /> + </Paper> + </Grid> + </React.Fragment> + } + /> </Grid> </React.Fragment> <Footer /> @@ -266,6 +291,7 @@ const mapStateToProps = state => { organizationsFacets: state.organizationsFacets, places: state.places, placesFacets: state.placesFacets, + clientSideFacetedSearch: state.clientSideFacetedSearch, error: state.error //browser: state.browser, }; @@ -274,9 +300,11 @@ const mapStateToProps = state => { const mapDispatchToProps = ({ fetchPaginatedResults, fetchResults, + fetchResultsClientSide, fetchByURI, fetchFacet, sortResults, + clearResults, updateFacetOption, updatePage, showError @@ -297,10 +325,13 @@ SemanticPortal.propTypes = { organizationsFacets: PropTypes.object.isRequired, places: PropTypes.object.isRequired, placesFacets: PropTypes.object.isRequired, + clientSideFacetedSearch: PropTypes.object.isRequired, fetchResults: PropTypes.func.isRequired, + fetchResultsClientSide: PropTypes.func.isRequired, fetchPaginatedResults: PropTypes.func.isRequired, fetchByURI: PropTypes.func.isRequired, sortResults: PropTypes.func.isRequired, + clearResults: PropTypes.func.isRequired, updatePage: PropTypes.func.isRequired, updateFacetOption: PropTypes.func.isRequired, fetchFacet: PropTypes.func.isRequired, diff --git a/src/client/epics/index.js b/src/client/epics/index.js index e965a8b5..4c33e4fd 100644 --- a/src/client/epics/index.js +++ b/src/client/epics/index.js @@ -1,6 +1,13 @@ import { of } from 'rxjs'; import { ajax } from 'rxjs/ajax'; -import { mergeMap, map, withLatestFrom, catchError } from 'rxjs/operators'; +import { + mergeMap, + switchMap, + map, + withLatestFrom, + debounceTime, + catchError +} from 'rxjs/operators'; import { combineEpics, ofType } from 'redux-observable'; import querystring from 'querystring'; import { has } from 'lodash'; @@ -8,6 +15,7 @@ import { FETCH_PAGINATED_RESULTS, FETCH_PAGINATED_RESULTS_FAILED, FETCH_RESULTS, + FETCH_RESULTS_CLIENT_SIDE, FETCH_RESULTS_FAILED, FETCH_BY_URI, FETCH_BY_URI_FAILED, @@ -89,6 +97,34 @@ const fetchResultsEpic = (action$, state$) => action$.pipe( }) ); +const fetchResultsClientSideEpic = (action$, state$) => action$.pipe( + ofType(FETCH_RESULTS_CLIENT_SIDE), + withLatestFrom(state$), + debounceTime(500), + switchMap(([action, state]) => { + const searchUrl = apiUrl + 'search'; + let requestUrl = ''; + if (action.jenaIndex === 'text') { + requestUrl = `${searchUrl}?q=${action.query}`; + } else if (action.jenaIndex === 'spatial') { + const { latMin, longMin, latMax, longMax } = state.map; + requestUrl = `${searchUrl}?latMin=${latMin}&longMin=${longMin}&latMax=${latMax}&longMax=${longMax}`; + } + return ajax.getJSON(requestUrl).pipe( + map(response => updateResults({ resultClass: 'all', data: response })), + catchError(error => of({ + type: FETCH_RESULTS_FAILED, + resultClass: 'all', + error: error, + message: { + text: backendErrorText, + title: 'Error' + } + })) + ); + }) +); + const fetchByURIEpic = (action$, state$) => action$.pipe( ofType(FETCH_BY_URI), withLatestFrom(state$), @@ -214,6 +250,7 @@ const boundsToValues = bounds => { const rootEpic = combineEpics( fetchPaginatedResultsEpic, fetchResultsEpic, + fetchResultsClientSideEpic, fetchByURIEpic, fetchFacetEpic, ); diff --git a/src/client/reducers/clientSideFacetedSearch.js b/src/client/reducers/clientSideFacetedSearch.js new file mode 100644 index 00000000..7fbe5710 --- /dev/null +++ b/src/client/reducers/clientSideFacetedSearch.js @@ -0,0 +1,89 @@ +import { + FETCH_RESULTS_CLIENT_SIDE, + UPDATE_RESULTS, + CLEAR_RESULTS, + UPDATE_CLIENT_SIDE_FILTER, + SORT_RESULTS +} from '../actions'; + +export const INITIAL_STATE = { + results: null, + latestFilter: { + id: '', + }, + latestFilterValues: [], + resultsFilter: { + prefLabel: new Set(), + type: new Set() + }, + sortBy: 'prefLabel', + sortDirection: 'asc', + //groupBy: 'broaderTypeLabel', + //groupByLabel: 'Paikanlaji', + textResultsFetching: false, + spatialResultsFetching: false, +}; + +const clientSideFacetedSearch = (state = INITIAL_STATE, action) => { + if (action.resultClass === 'all') { + switch (action.type) { + case FETCH_RESULTS_CLIENT_SIDE: + return { + ...state, + //[`${action.jenaIndex}ResultsFetching`]: true + }; + case UPDATE_RESULTS: + console.log(action.data) + return { + ...state, + results: action.data, + //[`${action.jenaIndex}ResultsFetching`]: false + }; + case CLEAR_RESULTS: + return { + ...state, + results: null, + fetchingResults: false, + query: '', + resultsFilter: { + prefLabel: new Set(), + type: new Set() + }, + }; + case UPDATE_CLIENT_SIDE_FILTER: + return updateResultsFilter(state, action); + case SORT_RESULTS: + return { + ...state, + sortBy: action.options.sortBy, + sortDirection: action.options.sortDirection, + }; + default: + return state; + } + } else return state; +}; + +const updateResultsFilter = (state, action) => { + const { property, value, latestValues } = action.filterObj; + let nSet = state.resultsFilter[property]; + if (nSet.has(value)) { + nSet.delete(value); + } else { + nSet.add(value); + } + const newFilter = { + ...state.resultsFilter, + [property]: nSet + }; + return { + ...state, + resultsFilter: newFilter, + latestFilter: { + id: property, + }, + latestFilterValues: latestValues + }; +}; + +export default clientSideFacetedSearch; diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js index caeb7462..c6d61b5d 100644 --- a/src/client/reducers/index.js +++ b/src/client/reducers/index.js @@ -12,6 +12,7 @@ import worksFacets from './worksFacets'; import peopleFacets from './peopleFacets'; import organizationsFacets from './organizationsFacets'; import placesFacets from './placesFacets'; +import clientSideFacetedSearch from './clientSideFacetedSearch'; const reducer = combineReducers({ manuscripts, @@ -24,6 +25,7 @@ const reducer = combineReducers({ organizationsFacets, places, placesFacets, + clientSideFacetedSearch, error, toastr: toastrReducer, browser: createResponsiveStateReducer({ diff --git a/src/server/index.js b/src/server/index.js index 973cc6b8..7b70aa56 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,8 +1,10 @@ import express from 'express'; import path from 'path'; import bodyParser from 'body-parser'; +import { has } from 'lodash'; import { getPaginatedResults, getAllResults, getByURI } from './sparql/FacetResults'; import { getFacet } from './sparql/FacetValues'; +import { queryJenaIndex } from './sparql/JenaQuery'; const DEFAULT_PORT = 3001; const app = express(); app.set('port', process.env.PORT || DEFAULT_PORT); @@ -91,6 +93,35 @@ app.get(`${apiPath}/:facetClass/facet/:id`, async (req, res, next) => { } }); +app.get(`${apiPath}/search`, async (req, res, next) => { + let queryTerm = ''; + let latMin = 0; + let longMin = 0; + let latMax = 0; + let longMax = 0; + if (has(req.query, 'q')) { + queryTerm = req.query.q; + } + if (has(req.query, 'latMin')) { + latMin = req.query.latMin; + longMin = req.query.longMin; + latMax = req.query.latMax; + longMax = req.query.longMax; + } + try { + const data = await queryJenaIndex({ + queryTerm: queryTerm, + latMin: latMin, + longMin: longMin, + latMax: latMax, + longMax: longMax, + }); + res.json(data); + } catch(error) { + next(error); + } +}); + /* Routes are matched to a url in order of their definition Redirect all the the rest for react-router to handle */ app.get('*', function(request, response) { diff --git a/src/server/sparql/JenaQuery.js b/src/server/sparql/JenaQuery.js new file mode 100644 index 00000000..d8082f23 --- /dev/null +++ b/src/server/sparql/JenaQuery.js @@ -0,0 +1,19 @@ +import { runSelectQuery } from './SparqlApi'; +import { prefixes } from './SparqlQueriesPrefixes'; +import { endpoint, jenaQuery } from './SparqlQueriesGeneral'; +import { makeObjectList } from './SparqlObjectMapper'; + +export const queryJenaIndex = async ({ + queryTerm, + latMin, + longMin, + latMax, + longMax, +}) => { + let q = jenaQuery; + q = q.replace('<QUERY>', ` + ?id text:query ('${queryTerm.toLowerCase()}' 10000) . + `); + const results = await runSelectQuery(prefixes + q, endpoint, makeObjectList); + return results; +}; diff --git a/src/server/sparql/SparqlQueriesGeneral.js b/src/server/sparql/SparqlQueriesGeneral.js index 00e1ec5e..fb74a535 100644 --- a/src/server/sparql/SparqlQueriesGeneral.js +++ b/src/server/sparql/SparqlQueriesGeneral.js @@ -9,6 +9,15 @@ export const countQuery = ` } `; +export const jenaQuery = ` + SELECT ?id ?prefLabel ?type + WHERE { + <QUERY> + ?id skos:prefLabel ?prefLabel . + ?id a ?type . + } +`; + export const facetResultSetQuery = ` SELECT * WHERE { -- GitLab