diff --git a/pages/search.js b/pages/search.js
index 763875fca..1ef1da629 100644
--- a/pages/search.js
+++ b/pages/search.js
@@ -1,7 +1,7 @@
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import Head from 'next/head'
-import { withRouter } from 'next/router'
+import { useRouter } from 'next/router'
import axios from 'axios'
import styled from 'styled-components'
import {
@@ -24,11 +24,53 @@ import FormattedMarkdown from '../components/FormattedMarkdown'
import { sortByKey } from '../utils'
+export const getServerSideProps = async ({query}) => {
+ // By default, on '/search' show measurements published until today
+ // including the measurements of today (so the date of tomorrow).
+ // This prevents the search page from showing time-travelling future
+ // measurements from showing up
+ query.since = query.since || dayjs(query.until).utc().subtract(30, 'day').format('YYYY-MM-DD')
+ query.until = query.until || dayjs.utc().add(1, 'day').format('YYYY-MM-DD')
+
+ // If there is no 'failure' in query, default to a false
+ if ('failure' in query === false) {
+ query.failure = false
+ } else {
+ // Convert the string param into boolean
+ query.failure = !(query.failure === 'false')
+ }
+
+ const client = axios.create({baseURL: process.env.NEXT_PUBLIC_OONI_API})
+ const [testNamesR, countriesR] = await Promise.all([
+ client.get('/api/_/test_names'),
+ client.get('/api/_/countries')
+ ])
+
+ const testNames = testNamesR.data.test_names
+ testNames.sort(sortByKey('name'))
+
+ const testNamesKeyed = {}
+ testNames.forEach(v => testNamesKeyed[v.id] = v.name)
+
+ const countries = countriesR.data.countries
+ countries.sort(sortByKey('name'))
+
+ return {
+ props: {
+ testNamesKeyed,
+ testNames,
+ countries,
+ query,
+ }
+ }
+}
+
+
const queryToParams = ({ query }) => {
- // XXX do better validation
let params = {},
show = 50
const supportedParams = ['probe_cc', 'domain', 'input','category_code', 'probe_asn', 'test_name', 'since', 'until', 'failure']
+
if (query.show) {
show = parseInt(query.show)
}
@@ -143,292 +185,162 @@ const NoResults = () => (
)
-class Search extends React.Component {
- static async getInitialProps ({ query }) {
- let msmtR, testNamesR, countriesR
- let client = axios.create({baseURL: process.env.NEXT_PUBLIC_OONI_API}) // eslint-disable-line
-
- // By default, on '/search' show measurements published until today
- // including the measurements of today (so the date of tomorrow).
- // This prevents the search page from showing time-travelling future
- // measurements from showing up
- const since = dayjs(query.until).utc().subtract(30, 'day').format('YYYY-MM-DD')
- if (!query.since) {
- query.since = since
- }
+const Search = ({testNames, testNamesKeyed, countries, query: queryProp }) => {
+ const router = useRouter()
+ const { query, replace, isReady } = router
- const until = dayjs.utc().add(1, 'day').format('YYYY-MM-DD')
- if ('until' in query === false) {
- query.until = until
- }
+ const [nextURL, setNextURL] = useState(null)
+ const [loading, setLoading] = useState(false)
+ const [results, setResults] = useState([])
+ const [filters, setFilters] = useState({})
+ const [error, setError] = useState(null)
- // If there is no 'failure' in query, default to a false
- if ('failure' in query === false) {
- query.failure = false
- } else {
- // Convert the string param into boolean
- query.failure = !(query.failure === 'false')
- }
-
- [testNamesR, countriesR] = await Promise.all([
- client.get('/api/_/test_names'),
- client.get('/api/_/countries')
- ])
-
- let testNames = testNamesR.data.test_names
- testNames.sort(sortByKey('name'))
-
- let testNamesKeyed = {}
- testNames.forEach(v => testNamesKeyed[v.id] = v.name)
-
- let countries = countriesR.data.countries
- countries.sort(sortByKey('name'))
-
-
- try {
- msmtR = await getMeasurements(query)
- } catch (err) {
- let error
- if (err.response) {
- error = err.response.data
- } else {
- error = err.message
- }
- return {
- error,
- results: [],
- nextURL: null,
- testNamesKeyed,
- testNames,
- countries
- }
- }
-
- const measurements = msmtR.data
- // drop results with probe_asn === 'AS0'
- const results = measurements.results.filter(item => item.probe_asn !== 'AS0')
-
- return {
- error: null,
- results,
- nextURL: measurements.metadata.next_url,
- testNamesKeyed,
- testNames,
- countries,
- }
- }
-
- constructor(props) {
- super(props)
- this.state = {
- testNameFilter: props.router.query.test_name,
- domainFilter: props.router.query.domain,
- inputFilter: props.router.query.input,
- categoryFilter: props.router.query.category_code,
- countryFilter: props.router.query.probe_cc,
- asnFilter: props.router.query.probe_asn,
- sinceFilter: props.router.query.since,
- untilFilter: props.router.query.until,
- onlyFilter: props.router.query.only || 'all',
- hideFailed: !props.router.query.failure,
- results: props.results,
- nextURL: props.nextURL,
- error: props.error,
-
- loading: false
- }
- this.getFilterQuery = this.getFilterQuery.bind(this)
- this.onApplyFilter = this.onApplyFilter.bind(this)
- this.loadMore = this.loadMore.bind(this)
- }
-
- componentDidMount () {
- const { query, replace } = this.props.router
+ useEffect(() => {
+ const query = query || queryProp
const href = {
pathname: '/search',
- query
+ query: query
}
replace(href, href, { shallow: true })
- }
- shouldComponentUpdate (nextProps, nextState) {
- if (this.props != nextProps) {
- return true
- }
- if (this.state.results != nextState.results) {
- return true
- }
- if (this.state.loading != nextState.loading) {
- return true
- }
- return false
- }
+ setLoading(true)
+ getMeasurements(query)
+ .then(({ data: { results, metadata: { next_url } } }) => {
+ setLoading(false)
+ setResults(results)
+ setNextURL(next_url)
+ })
+ .catch((err) => {
+ console.error(err)
+ const error = serializeError(err)
+ setLoading(false)
+ setError(error)
+ })
+ }, [])
- loadMore() {
- axios.get(this.state.nextURL)
- .then((res) => {
- // XXX update the query
- const nextPageResults = res.data.results.filter(item => item.probe_asn !== 'AS0')
- this.setState({
- results: this.state.results.concat(nextPageResults),
- nextURL: res.data.metadata.next_url,
- show: this.state.show + 50
- })
+ const loadMore = () => {
+ axios.get(nextURL)
+ .then(({ data: { results: nextPageResults, metadata: { next_url } } }) => {
+ setResults(results.concat(nextPageResults))
+ setNextURL(next_url)
})
.catch((err) => {
console.error(err)
const error = serializeError(err)
- this.setState({
- error,
- loading: false
- })
+ setLoading(false)
+ setError(error)
})
}
- onApplyFilter (state) {
- this.setState({
- error: null,
- loading: true,
- ...state
- }, () => {
- const query = this.getFilterQuery()
- const href = {
- pathname: '/search',
- query
- }
- this.props.router.push(href, href, { shallow: true }).then(() => {
- // XXX do error handling
- getMeasurements(query)
- .then((res) => {
- const results = res.data.results.filter(item => item.probe_asn !== 'AS0')
- this.setState({
- loading: false,
- results,
- nextURL: res.data.metadata.next_url
- })
- })
- .catch((err) => {
- console.error(err)
- const error = serializeError(err)
- this.setState({
- error,
- loading: false
- })
- })
+ const onApplyFilter = (state) => {
+ setLoading(true)
+ setResults([])
+ setError(null)
+
+ const query = getFilterQuery(state)
+ const href = {
+ pathname: '/search',
+ query
+ }
+ router.push(href, href, { shallow: true }).then(() => {
+ getMeasurements(query)
+ .then(({ data: { results, metadata: { next_url } } }) => {
+ setLoading(false)
+ setResults(results)
+ setNextURL(next_url)
+ })
+ .catch((err) => {
+ console.error(err)
+ const error = serializeError(err)
+ setError(error)
+ setLoading(false)
+ })
})
- })
}
- getFilterQuery () {
- let query = {...this.props.router.query}
+ const getFilterQuery = (state) => {
+ let query = {...router.query}
const resetValues = [undefined, 'XX', '']
for (const [queryParam, [key]] of Object.entries(queryToFilterMap)) {
// If it's unset or marked as XX, let's be sure the path is clean
- if (resetValues.includes(this.state[key])) {
+ if (resetValues.includes(state[key])) {
if (queryParam in query) {
delete query[queryParam]
}
- } else if (key === 'onlyFilter' && this.state[key] == 'all') {
+ } else if (key === 'onlyFilter' && state[key] == 'all') {
// If the onlyFilter is not set to 'confirmed' or 'anomalies'
// remove it from the path
if (queryParam in query) {
delete query[queryParam]
}
} else if (key === 'hideFailed') {
- if (this.state[key] === true) {
+ if (state[key] === true) {
// When `hideFailure` is true, add `failure=false` in the query
query[queryParam] = false
} else {
query[queryParam] = true
}
} else {
- query[queryParam] = this.state[key]
+ query[queryParam] = state[key]
}
}
return query
}
- render () {
- const {
- testNames,
- testNamesKeyed,
- countries
- } = this.props
-
- const {
- loading,
- error,
- results,
- onlyFilter,
- domainFilter,
- inputFilter,
- categoryFilter,
- testNameFilter,
- countryFilter,
- asnFilter,
- sinceFilter,
- untilFilter,
- hideFailed
- } = this.state
-
- return (
-
-
- Search through millions of Internet censorship measurements | OONI Explorer
-
-
-
-
-
-
-
-
-
-
- {error && }
- {loading && }
-
- {!error && !loading && results.length === 0 && }
- {!error && !loading && results.length > 0 &&
-
- {this.state.nextURL &&
-
-
-
- }
- }
-
-
-
-
- )
- }
+ return (
+
+
+ Search through millions of Internet censorship measurements | OONI Explorer
+
+
+
+
+
+
+
+
+
+
+ {error && }
+ {loading && }
+
+ {!error && !loading && results.length === 0 && }
+ {!error && !loading && results.length > 0 &&
+
+ {nextURL &&
+
+
+
+ }
+ }
+
+
+
+
+ )
}
Search.propTypes = {
- router: PropTypes.object,
- results: PropTypes.array,
testNamesKeyed: PropTypes.object,
testNames: PropTypes.array,
countries: PropTypes.array,
- nextURL: PropTypes.string,
- error: PropTypes.object
+ query: PropTypes.object,
}
-export default withRouter(Search)
+export default Search
\ No newline at end of file