From 0e9be688e17d411ecfd4f52a6709c19e107152f9 Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Fri, 21 Oct 2022 13:27:23 +0200 Subject: [PATCH] Search page refactoring (#799) * Remove AS0 filter --- pages/search.js | 398 +++++++++++++++++++----------------------------- 1 file changed, 155 insertions(+), 243 deletions(-) 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