From c2d05ce61f7e17e317549e2635094bd2e55c1b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Thu, 17 Oct 2024 16:19:06 +0200 Subject: [PATCH 01/16] Allow search for curators comment --- .../scripts/setup-db/schemas/day0cases.indexes.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/data-serving/scripts/setup-db/schemas/day0cases.indexes.json b/data-serving/scripts/setup-db/schemas/day0cases.indexes.json index a7007c6b6..b55dd708a 100644 --- a/data-serving/scripts/setup-db/schemas/day0cases.indexes.json +++ b/data-serving/scripts/setup-db/schemas/day0cases.indexes.json @@ -8,7 +8,8 @@ "location.admin2": "text", "location.admin3": "text", "caseReference.sourceUrl": "text", - "caseStatus": "text" + "caseStatus": "text", + "comment": "text" } }, { @@ -145,6 +146,16 @@ "strength": 2 } }, + { + "name": "commentIdx", + "key": { + "comment": -1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, { "name": "countryAndDate", "key": { From 8d3f9aac45cd1cb3d27a953c1f4a07e789fb309e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Thu, 17 Oct 2024 16:19:23 +0200 Subject: [PATCH 02/16] Fix highlighting of the text in view case form --- .../ui/src/components/App/index.tsx | 2 +- .../ui/src/components/ViewCase.tsx | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/verification/curator-service/ui/src/components/App/index.tsx b/verification/curator-service/ui/src/components/App/index.tsx index 0cc3fe81d..62be37d7a 100644 --- a/verification/curator-service/ui/src/components/App/index.tsx +++ b/verification/curator-service/ui/src/components/App/index.tsx @@ -371,7 +371,7 @@ export default function App(): JSX.Element { ) return; - dispatch(setSearchQuery(location.search)); + dispatch(setSearchQuery(decodeURI(location.search))); // Save searchQuery to local storage not to lost it when user goes through auth process localStorage.setItem('searchQuery', location.search); diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index edc5eab8d..58614835f 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -970,12 +970,39 @@ function RowContent(props: { const searchQueryArray: any[] = []; function words(s: string) { + if (s.startsWith('?q=')) { + s = s.substring(3) + } + const quoted: string[] = [] + const notQuoted: string[] = [] + if (s.includes('"') && s.replace(/[^"]/g, "").length % 2 !== 1) { + s.split('"').map((subs: string, i: number) => { + if (i % 2) { + if (subs != "") quoted.push(subs) + } else { + if (subs != "") notQuoted.push(subs) + } + + }); + } else { + notQuoted.push(s) + } const regex = /"([^"]+)"|(\w{3,})/g; - let match; - while ((match = regex.exec(s))) { - searchQueryArray.push(match[match[1] ? 1 : 2]); + + for (const quotedEntry of quoted) { + let match; + let accumulator = []; + while ((match = regex.exec(quotedEntry))) { + accumulator.push(match[match[1] ? 1 : 2]); + } + searchQueryArray.push(accumulator.join(' ')) + } + for (const notQuotedEntry of notQuoted) { + let match; + while ((match = regex.exec(notQuotedEntry))) { + searchQueryArray.push(match[match[1] ? 1 : 2]); + } } - return searchQueryArray; } words(searchQuery); From 8c7a573257ace1aa49c98ac281af52687af3341f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Fri, 18 Oct 2024 13:43:16 +0200 Subject: [PATCH 03/16] Update DataGuideDialog.tsx --- .../ui/src/components/DataGuideDialog.tsx | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/verification/curator-service/ui/src/components/DataGuideDialog.tsx b/verification/curator-service/ui/src/components/DataGuideDialog.tsx index d24c7a03c..64c41281c 100644 --- a/verification/curator-service/ui/src/components/DataGuideDialog.tsx +++ b/verification/curator-service/ui/src/components/DataGuideDialog.tsx @@ -1,7 +1,18 @@ import React, { useEffect } from 'react'; -import { Box, Portal, Theme, Typography } from '@mui/material'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Portal, + Theme, + Typography, +} from '@mui/material'; import { withStyles } from 'tss-react/mui'; -import CloseIcon from '@mui/icons-material/Close'; +import { + Close as CloseIcon, + ExpandMore as ExpandMoreIcon, +} from '@mui/icons-material'; import Draggable, { ControlPosition } from 'react-draggable'; // As per this issue from react-draggable library: https://github.com/react-grid-layout/react-draggable/pull/648 @@ -96,16 +107,49 @@ const SearchGuideDialog = ({ the left and choose ascending or descending. - For full-text search, enter any combination of search terms. -
- Full-text search covers: occupation, admin0, admin1, admin2, admin3, sourceUrl and caseStatus. -
- Search terms must be exact (example: "German" will not match "Germany"). -
- Full-text search matches cases that contain any ot the search terms, not a combination. -
- No special characters apart from "." are allowed and if the "." is used in a search term - given search term must be contained within quotation marks. + For full-text search, enter any + combination of search terms. + Rules for full-text search: +
+
    +
  • + Full-text search covers: occupation, admin0, + admin1, admin2, admin3, sourceUrl, comment + and caseStatus. +
  • +
  • + Search terms must be exact (example:{' '} + + German + {' '} + will not match{' '} + + Germany + + ). +
  • +
  • + Full-text search matches cases that contain + any of the search terms, not a combination. +
  • +
  • + To search for a combination of terms, wrap + the combination in quotation marks (example:{' '} + + "Bus driver" + + ). +
  • +
  • + No special characters apart from dot are + allowed. Search terms with dot must be + contained within quotation marks (example:{' '} + + "global.health" + + ). +
  • +
You can use the icons on the right to navigate From cebe3ed1fbea3823168c42fe31d9302bf2ea0803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Fri, 18 Oct 2024 13:47:15 +0200 Subject: [PATCH 04/16] Cleanup --- .../ui/src/components/DataGuideDialog.tsx | 19 ++++--------------- .../ui/src/components/ViewCase.tsx | 2 +- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/verification/curator-service/ui/src/components/DataGuideDialog.tsx b/verification/curator-service/ui/src/components/DataGuideDialog.tsx index 64c41281c..8a3c92801 100644 --- a/verification/curator-service/ui/src/components/DataGuideDialog.tsx +++ b/verification/curator-service/ui/src/components/DataGuideDialog.tsx @@ -1,18 +1,7 @@ import React, { useEffect } from 'react'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Box, - Portal, - Theme, - Typography, -} from '@mui/material'; +import { Box, Portal, Theme, Typography } from '@mui/material'; import { withStyles } from 'tss-react/mui'; -import { - Close as CloseIcon, - ExpandMore as ExpandMoreIcon, -} from '@mui/icons-material'; +import { Close as CloseIcon } from '@mui/icons-material'; import Draggable, { ControlPosition } from 'react-draggable'; // As per this issue from react-draggable library: https://github.com/react-grid-layout/react-draggable/pull/648 @@ -108,8 +97,8 @@ const SearchGuideDialog = ({ For full-text search, enter any - combination of search terms. - Rules for full-text search: + combination of search terms. Rules for full-text + search:
  • diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index 58614835f..895be94ae 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -987,7 +987,7 @@ function RowContent(props: { } else { notQuoted.push(s) } - const regex = /"([^"]+)"|(\w{3,})/g; + const regex = /"([^"]+)"|(\w{1,})/g; for (const quotedEntry of quoted) { let match; From c28b1513676a06dfbf4c320b468619670ec6bcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Fri, 18 Oct 2024 15:39:41 +0200 Subject: [PATCH 05/16] Inform user if there is any not closed quote --- .../ui/src/components/SearchBar.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index a4ebf82e2..14fac3723 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -174,16 +174,25 @@ export default function SearchBar({ return searchStringStrippedOutColon; } + const renderSearchErrorMessage = () => { + if (searchError) { + return 'Incorrect entry. ":" characters have been removed. Please use filters instead.'; + } else { + const quoteCount = decodeURI(searchInput).split('"').length - 1; + console.log(searchInput, quoteCount); + if (quoteCount % 2 !== 0) { + return 'Incorrect entry. Please make sure you have an even number of quotes.'; + } + } + }; + return ( <>
    Date: Fri, 18 Oct 2024 15:52:42 +0200 Subject: [PATCH 06/16] Cleanup code + remove console.log --- .../ui/src/components/SearchBar.tsx | 1 - .../ui/src/components/ViewCase.tsx | 32 +++++++------------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index 14fac3723..d17e67a0c 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -179,7 +179,6 @@ export default function SearchBar({ return 'Incorrect entry. ":" characters have been removed. Please use filters instead.'; } else { const quoteCount = decodeURI(searchInput).split('"').length - 1; - console.log(searchInput, quoteCount); if (quoteCount % 2 !== 0) { return 'Incorrect entry. Please make sure you have an even number of quotes.'; } diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index 895be94ae..d7cc6da37 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -966,36 +966,28 @@ function RowContent(props: { linkComment?: string; }): JSX.Element { const searchQuery = useSelector(selectSearchQuery); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const searchQueryArray: any[] = []; + const searchQueryArray: string[] = []; function words(s: string) { - if (s.startsWith('?q=')) { - s = s.substring(3) - } - const quoted: string[] = [] - const notQuoted: string[] = [] - if (s.includes('"') && s.replace(/[^"]/g, "").length % 2 !== 1) { - s.split('"').map((subs: string, i: number) => { - if (i % 2) { - if (subs != "") quoted.push(subs) - } else { - if (subs != "") notQuoted.push(subs) - } + if (s.startsWith('?q=')) s = s.substring(3); + const quoted: string[] = []; + const notQuoted: string[] = []; + if (s.includes('"') && s.replace(/[^"]/g, '').length % 2 !== 1) { + s.split('"').map((subs: string, i: number) => { + subs != '' && i % 2 ? quoted.push(subs) : notQuoted.push(subs); }); - } else { - notQuoted.push(s) - } - const regex = /"([^"]+)"|(\w{1,})/g; + } else notQuoted.push(s); + const regex = /"([^"]+)"|(\w{1,})/g; + // Make sure that terms in quotes will be highlighted as one search term for (const quotedEntry of quoted) { let match; - let accumulator = []; + let accumulator: string[] = []; while ((match = regex.exec(quotedEntry))) { accumulator.push(match[match[1] ? 1 : 2]); } - searchQueryArray.push(accumulator.join(' ')) + searchQueryArray.push(accumulator.join(' ')); } for (const notQuotedEntry of notQuoted) { let match; From 0527872de1c68fbdacd0570d894c09de35863eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Fri, 18 Oct 2024 16:20:18 +0200 Subject: [PATCH 07/16] Add cypress tests for comments and uneven quote warning --- .../e2e/components/LinelistTableTest.spec.ts | 27 +++++++++++++++++++ .../ui/cypress/support/commands.ts | 2 ++ 2 files changed, 29 insertions(+) diff --git a/verification/curator-service/ui/cypress/e2e/components/LinelistTableTest.spec.ts b/verification/curator-service/ui/cypress/e2e/components/LinelistTableTest.spec.ts index 7cc942796..be520b375 100644 --- a/verification/curator-service/ui/cypress/e2e/components/LinelistTableTest.spec.ts +++ b/verification/curator-service/ui/cypress/e2e/components/LinelistTableTest.spec.ts @@ -501,6 +501,7 @@ describe('Linelist table', function () { sourceUrl: 'www.example.com', caseStatus: CaseStatus.Confirmed, occupation: 'Actor', + comment: 'note', }); cy.addCase({ country: 'Germany', @@ -524,6 +525,7 @@ describe('Linelist table', function () { cy.intercept('GET', getDefaultQuery({ limit: 50 })).as('getCases'); cy.intercept('GET', getDefaultQuery({ limit: 50, query: 'Argentina' })).as('getCasesWithSearch1'); cy.intercept('GET', getDefaultQuery({ limit: 50, query: 'Doctor' })).as('getCasesWithSearch2'); + cy.intercept('GET', getDefaultQuery({ limit: 50, query: 'note' })).as('getCasesWithSearch3'); cy.visit('/cases'); cy.wait('@getCases'); @@ -549,5 +551,30 @@ describe('Linelist table', function () { cy.contains('Argentina').should('not.exist'); cy.contains('France').should('not.exist'); cy.contains('Germany').should('exist'); + + cy.get('#clear-search').click(); + cy.wait('@getCases'); + cy.contains('Argentina').should('exist'); + cy.contains('France').should('exist'); + cy.contains('Germany').should('exist'); + + cy.get('#search-field').type('note'); + cy.wait('@getCasesWithSearch3'); + cy.contains('Argentina').should('not.exist'); + cy.contains('France').should('exist'); + cy.contains('Germany').should('not.exist'); + }); + + it('Informs user when uneven number of quotes is present in free-text search', () => { + cy.intercept('GET', getDefaultQuery({ limit: 50, query: '"Bus driver"' })).as('getCasesWithSearch'); + cy.visit('/cases'); + cy.contains('Please make sure you have an even number of quotes.').should('not.exist'); + + cy.get('#search-field').type('"Bus driver'); + cy.contains('Please make sure you have an even number of quotes.').should('exist'); + + cy.get('#search-field').type('"'); + cy.wait('@getCasesWithSearch'); + cy.contains('Please make sure you have an even number of quotes.').should('not.exist'); }); }); diff --git a/verification/curator-service/ui/cypress/support/commands.ts b/verification/curator-service/ui/cypress/support/commands.ts index 4ddc86446..c951c0be8 100644 --- a/verification/curator-service/ui/cypress/support/commands.ts +++ b/verification/curator-service/ui/cypress/support/commands.ts @@ -43,6 +43,7 @@ interface AddCaseProps { gender?: Gender; outcome?: Outcome; uploadIds?: string[]; + comment?: string; } declare global { @@ -111,6 +112,7 @@ export function addCase(opts: AddCaseProps): void { travelHistory: {}, genomeSequences: {}, vaccination: {}, + comment: opts.comment || '', }, }); } From c9b336a893fed66042bae99acd616140631dde91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Tue, 22 Oct 2024 16:55:49 +0200 Subject: [PATCH 08/16] WIP fixed issues with filters and special characters in free text search --- .../data-service/src/controllers/case.ts | 4 +- .../ui/src/components/App/index.tsx | 12 ++- .../ui/src/components/FiltersDialog/index.tsx | 7 +- .../ui/src/components/SearchBar.tsx | 81 ++++++++++++++----- .../ui/src/components/ViewCase.tsx | 6 +- 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/data-serving/data-service/src/controllers/case.ts b/data-serving/data-service/src/controllers/case.ts index 02ce0eaef..91f48860e 100644 --- a/data-serving/data-service/src/controllers/case.ts +++ b/data-serving/data-service/src/controllers/case.ts @@ -436,12 +436,12 @@ export class CasesController { logger.debug('Got past 422s'); try { const casesQuery = casesMatchingSearchQuery({ - searchQuery: req.query.q || '', + searchQuery: decodeURIComponent(req.query.q || ''), count: false, verificationStatus: verificationStatus, }) as Query; const countQuery = casesMatchingSearchQuery({ - searchQuery: req.query.q || '', + searchQuery: decodeURIComponent(req.query.q || ''), count: true, verificationStatus: verificationStatus, }); diff --git a/verification/curator-service/ui/src/components/App/index.tsx b/verification/curator-service/ui/src/components/App/index.tsx index 62be37d7a..6c3f3b54a 100644 --- a/verification/curator-service/ui/src/components/App/index.tsx +++ b/verification/curator-service/ui/src/components/App/index.tsx @@ -327,13 +327,22 @@ export default function App(): JSX.Element { }; const onModalClose = (): void => { + let parsedSearchQuery: string = ''; + if (searchQuery.includes('?q=')) { + parsedSearchQuery = `?q=${encodeURIComponent(searchQuery.split('?q=')[1])}`; + } else if (searchQuery.includes('&q=')) { + parsedSearchQuery = `${searchQuery.split('&q=')[0]}&q=${encodeURIComponent(searchQuery.split('&q=')[1])}`; + } else { + parsedSearchQuery = searchQuery; + } + navigate( { pathname: location.state && location.state.lastLocation ? location.state.lastLocation : '/cases', - search: searchQuery, + search: parsedSearchQuery, }, { state: { lastLocation: '/case/view' } }, ); @@ -371,7 +380,6 @@ export default function App(): JSX.Element { ) return; - dispatch(setSearchQuery(decodeURI(location.search))); // Save searchQuery to local storage not to lost it when user goes through auth process localStorage.setItem('searchQuery', location.search); diff --git a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx index 810a28c8d..c9baf7a8d 100644 --- a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx +++ b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx @@ -153,7 +153,12 @@ export default function FiltersDialog({ handleSetModalAlert(); dispatch(setModalOpen(false)); - const searchQuery = filtersToURL(values); + let searchQuery = filtersToURL(values); + + if (location.search.includes('?q=')) { + const q = new URLSearchParams(location.search).get('q'); + searchQuery = `${searchQuery}&q=${q}` + } sendCustomGtmEvent('filters_applied', { query: searchQuery }); diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index d17e67a0c..dac2b9f9b 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -86,8 +86,10 @@ export default function SearchBar({ const [isDataGuideOpen, setIsDataGuideOpen] = useState(false); const [searchInput, setSearchInput] = useState( location.search.includes('?q=') - ? URLToSearchQuery(location.search) - : '', + ? location.search.split('?q=')[1] + : location.search?.includes('&q=') + ? location.search.split('&q=')[1] + : '', ); const [modalAlert, setModalAlert] = useState(false); const guideButtonRef = React.useRef(null); @@ -103,28 +105,67 @@ export default function SearchBar({ } }, [filtersBreadcrumb]); + useEffect(() => { + const q = location.search.includes('?q=') + ? location.search.split('?q=')[1] + : location.search?.includes('&q=') + ? location.search.split('&q=')[1] + : ''; + const decodedQ = decodeURIComponent(q); + if (decodedQ !== searchInput) { + setSearchInput(decodedQ); + } + }, [location.search]); + // Set search query debounce to 1000ms const debouncedSearch = useDebounce(searchInput, 2000); - // Update search input based on search query - useEffect(() => { - if (!location.search.includes('?q=')) { - setSearchInput(''); - return; - } - setSearchInput(URLToSearchQuery(location.search)); - }, [location.search]); + const handleNavigating = (q: string) => { + const encodedQ = encodeURIComponent(q); + if (encodedQ === '') { + if (location.search.includes('?q=')) { + navigate({ + pathname: '/cases', + search: '', + }); + } else if(location.search.includes('&q=')) { + navigate({ + pathname: '/cases', + search: location.search.split('&q=')[0], + }); + } else { + navigate({ + pathname: '/cases', + search: location.search, + }); + } + } else { + if (location.search.includes('?q=') || location.search === '') { + navigate({ + pathname: '/cases', + search: `?q=${encodedQ}`, + }); + } else if(location.search.includes('&q=')) { + navigate({ + pathname: '/cases', + search: `${location.search.split('&q=')[0]}&q=${encodedQ}`, + }); + } else { + navigate({ + pathname: '/cases', + search: `${location.search}&q=${encodedQ}`, + }); + } + } + } // Apply filter parameters after delay useEffect(() => { if (!isUserTyping) return; - setIsUserTyping(false); - navigate({ - pathname: '/cases', - search: searchQueryToURL(debouncedSearch), - }); + + handleNavigating(debouncedSearch); //eslint-disable-next-line }, [debouncedSearch]); @@ -136,10 +177,8 @@ export default function SearchBar({ if (ev.key === 'Enter') { ev.preventDefault(); setIsUserTyping(false); - navigate({ - pathname: '/cases', - search: searchQueryToURL(searchInput), - }); + + handleNavigating(searchInput); } }; @@ -178,7 +217,7 @@ export default function SearchBar({ if (searchError) { return 'Incorrect entry. ":" characters have been removed. Please use filters instead.'; } else { - const quoteCount = decodeURI(searchInput).split('"').length - 1; + const quoteCount = decodeURIComponent(searchInput).split('"').length - 1; if (quoteCount % 2 !== 0) { return 'Incorrect entry. Please make sure you have an even number of quotes.'; } @@ -211,7 +250,7 @@ export default function SearchBar({ } }} placeholder="Fulltext search" - value={decodeURI(searchInput)} + value={searchInput} variant="outlined" fullWidth InputProps={{ diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index d7cc6da37..0bc70889d 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -969,7 +969,9 @@ function RowContent(props: { const searchQueryArray: string[] = []; function words(s: string) { - if (s.startsWith('?q=')) s = s.substring(3); + if (s.includes('&q=')) { + s = s.split('&q=')[1]; + } else if (s.includes('?q=')) s = s.substring(3); const quoted: string[] = []; const notQuoted: string[] = []; @@ -979,7 +981,7 @@ function RowContent(props: { }); } else notQuoted.push(s); - const regex = /"([^"]+)"|(\w{1,})/g; + const regex = /"([^"]+)"|(\S{1,})/g; // Make sure that terms in quotes will be highlighted as one search term for (const quotedEntry of quoted) { let match; From e950071c6eb5771a6b7dabbc4be73854f00b490c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Tue, 22 Oct 2024 16:56:05 +0200 Subject: [PATCH 09/16] Update index.tsx --- verification/curator-service/ui/src/components/App/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/verification/curator-service/ui/src/components/App/index.tsx b/verification/curator-service/ui/src/components/App/index.tsx index 6c3f3b54a..dd1eecaab 100644 --- a/verification/curator-service/ui/src/components/App/index.tsx +++ b/verification/curator-service/ui/src/components/App/index.tsx @@ -380,6 +380,7 @@ export default function App(): JSX.Element { ) return; + dispatch(setSearchQuery(decodeURIComponent(location.search))); // Save searchQuery to local storage not to lost it when user goes through auth process localStorage.setItem('searchQuery', location.search); From 5e04403c5e3f92b68eb48eda3e7daaf17d24ec11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 13:34:38 +0200 Subject: [PATCH 10/16] Update SearchBar.tsx --- .../ui/src/components/SearchBar.tsx | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index dac2b9f9b..a58fdf8d5 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -122,42 +122,16 @@ export default function SearchBar({ const handleNavigating = (q: string) => { - const encodedQ = encodeURIComponent(q); - if (encodedQ === '') { - if (location.search.includes('?q=')) { - navigate({ - pathname: '/cases', - search: '', - }); - } else if(location.search.includes('&q=')) { - navigate({ - pathname: '/cases', - search: location.search.split('&q=')[0], - }); - } else { - navigate({ - pathname: '/cases', - search: location.search, - }); - } + const searchParams = new URLSearchParams(location.search); + if (q !== '') { + searchParams.set('q', q); } else { - if (location.search.includes('?q=') || location.search === '') { - navigate({ - pathname: '/cases', - search: `?q=${encodedQ}`, - }); - } else if(location.search.includes('&q=')) { - navigate({ - pathname: '/cases', - search: `${location.search.split('&q=')[0]}&q=${encodedQ}`, - }); - } else { - navigate({ - pathname: '/cases', - search: `${location.search}&q=${encodedQ}`, - }); - } + searchParams.delete('q'); } + navigate({ + pathname: '/cases', + search: searchParams.toString(), + }); } // Apply filter parameters after delay From 75558dcfa836a7eb4ec2c3eed8f799c6a0e2cf9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 13:55:03 +0200 Subject: [PATCH 11/16] Update index.tsx --- .../ui/src/components/App/index.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/verification/curator-service/ui/src/components/App/index.tsx b/verification/curator-service/ui/src/components/App/index.tsx index dd1eecaab..465fcf8db 100644 --- a/verification/curator-service/ui/src/components/App/index.tsx +++ b/verification/curator-service/ui/src/components/App/index.tsx @@ -327,14 +327,7 @@ export default function App(): JSX.Element { }; const onModalClose = (): void => { - let parsedSearchQuery: string = ''; - if (searchQuery.includes('?q=')) { - parsedSearchQuery = `?q=${encodeURIComponent(searchQuery.split('?q=')[1])}`; - } else if (searchQuery.includes('&q=')) { - parsedSearchQuery = `${searchQuery.split('&q=')[0]}&q=${encodeURIComponent(searchQuery.split('&q=')[1])}`; - } else { - parsedSearchQuery = searchQuery; - } + const searchQueryObject = new URLSearchParams(searchQuery); navigate( { @@ -342,7 +335,7 @@ export default function App(): JSX.Element { location.state && location.state.lastLocation ? location.state.lastLocation : '/cases', - search: parsedSearchQuery, + search: searchQueryObject.toString(), }, { state: { lastLocation: '/case/view' } }, ); @@ -380,7 +373,8 @@ export default function App(): JSX.Element { ) return; - dispatch(setSearchQuery(decodeURIComponent(location.search))); + const searchParams = new URLSearchParams(location.search); + dispatch(setSearchQuery(searchParams.toString())); // Save searchQuery to local storage not to lost it when user goes through auth process localStorage.setItem('searchQuery', location.search); From c7caa02413972b2b66448b84a9e6342ac75f406a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 14:14:25 +0200 Subject: [PATCH 12/16] Fix setting filters --- .../curator-service/ui/src/components/App/index.tsx | 1 - .../ui/src/components/FiltersDialog/index.tsx | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/verification/curator-service/ui/src/components/App/index.tsx b/verification/curator-service/ui/src/components/App/index.tsx index 465fcf8db..44c0e58c4 100644 --- a/verification/curator-service/ui/src/components/App/index.tsx +++ b/verification/curator-service/ui/src/components/App/index.tsx @@ -328,7 +328,6 @@ export default function App(): JSX.Element { const onModalClose = (): void => { const searchQueryObject = new URLSearchParams(searchQuery); - navigate( { pathname: diff --git a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx index c9baf7a8d..2161aa807 100644 --- a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx +++ b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx @@ -153,18 +153,18 @@ export default function FiltersDialog({ handleSetModalAlert(); dispatch(setModalOpen(false)); - let searchQuery = filtersToURL(values); - if (location.search.includes('?q=')) { - const q = new URLSearchParams(location.search).get('q'); - searchQuery = `${searchQuery}&q=${q}` + const searchParams = new URLSearchParams(); + for (const [key, value] of Object.entries(values)) { + if (value) searchParams.set(key, value); } + const searchParamsString = searchParams.toString(); - sendCustomGtmEvent('filters_applied', { query: searchQuery }); + sendCustomGtmEvent('filters_applied', { query: searchParamsString }); navigate({ pathname: '/cases', - search: searchQuery, + search: searchParamsString, }); }, }); From 302f99b595a9be234bc6627a1ae7fbb7c7ec90b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 16:40:34 +0200 Subject: [PATCH 13/16] Fix issues with text-search not visible --- .../data-service/src/controllers/case.ts | 2 +- .../ui/src/components/FiltersDialog/index.tsx | 4 +++- .../ui/src/components/SearchBar.tsx | 18 ++++-------------- .../ui/src/components/ViewCase.tsx | 11 +++++------ 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/data-serving/data-service/src/controllers/case.ts b/data-serving/data-service/src/controllers/case.ts index 91f48860e..fe21d9073 100644 --- a/data-serving/data-service/src/controllers/case.ts +++ b/data-serving/data-service/src/controllers/case.ts @@ -436,7 +436,7 @@ export class CasesController { logger.debug('Got past 422s'); try { const casesQuery = casesMatchingSearchQuery({ - searchQuery: decodeURIComponent(req.query.q || ''), + searchQuery: req.query.q || '', count: false, verificationStatus: verificationStatus, }) as Query; diff --git a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx index 2161aa807..23b36e225 100644 --- a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx +++ b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx @@ -153,11 +153,13 @@ export default function FiltersDialog({ handleSetModalAlert(); dispatch(setModalOpen(false)); - const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(values)) { + console.log(key, value) if (value) searchParams.set(key, value); } + const q = (new URLSearchParams(location.search)).get('q') + if (q) searchParams.set('q', q); const searchParamsString = searchParams.toString(); sendCustomGtmEvent('filters_applied', { query: searchParamsString }); diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index a58fdf8d5..e53111d01 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -106,15 +106,8 @@ export default function SearchBar({ }, [filtersBreadcrumb]); useEffect(() => { - const q = location.search.includes('?q=') - ? location.search.split('?q=')[1] - : location.search?.includes('&q=') - ? location.search.split('&q=')[1] - : ''; - const decodedQ = decodeURIComponent(q); - if (decodedQ !== searchInput) { - setSearchInput(decodedQ); - } + const q = (new URLSearchParams(location.search)).get('q') || ''; + if (q !== searchInput) setSearchInput(q); }, [location.search]); // Set search query debounce to 1000ms @@ -123,11 +116,8 @@ export default function SearchBar({ const handleNavigating = (q: string) => { const searchParams = new URLSearchParams(location.search); - if (q !== '') { - searchParams.set('q', q); - } else { - searchParams.delete('q'); - } + q !== '' ? searchParams.set('q', q) : searchParams.delete('q'); + navigate({ pathname: '/cases', search: searchParams.toString(), diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index 0bc70889d..8f63bd3f1 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -969,17 +969,16 @@ function RowContent(props: { const searchQueryArray: string[] = []; function words(s: string) { - if (s.includes('&q=')) { - s = s.split('&q=')[1]; - } else if (s.includes('?q=')) s = s.substring(3); + const q = (new URLSearchParams(s)).get('q'); + if (!q) return; const quoted: string[] = []; const notQuoted: string[] = []; - if (s.includes('"') && s.replace(/[^"]/g, '').length % 2 !== 1) { - s.split('"').map((subs: string, i: number) => { + if (q.includes('"') && q.replace(/[^"]/g, '').length % 2 !== 1) { + q.split('"').map((subs: string, i: number) => { subs != '' && i % 2 ? quoted.push(subs) : notQuoted.push(subs); }); - } else notQuoted.push(s); + } else notQuoted.push(q); const regex = /"([^"]+)"|(\S{1,})/g; // Make sure that terms in quotes will be highlighted as one search term From 005cda71f1f7894ea0a28f066e918de3894c86c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 16:51:35 +0200 Subject: [PATCH 14/16] Cleanup --- .../data-service/src/controllers/case.ts | 2 +- .../ui/src/components/FiltersDialog/index.tsx | 2 +- .../ui/src/components/SearchBar.tsx | 1 - .../ui/src/components/ViewCase.tsx | 41 ++++++++++--------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/data-serving/data-service/src/controllers/case.ts b/data-serving/data-service/src/controllers/case.ts index fe21d9073..02ce0eaef 100644 --- a/data-serving/data-service/src/controllers/case.ts +++ b/data-serving/data-service/src/controllers/case.ts @@ -441,7 +441,7 @@ export class CasesController { verificationStatus: verificationStatus, }) as Query; const countQuery = casesMatchingSearchQuery({ - searchQuery: decodeURIComponent(req.query.q || ''), + searchQuery: req.query.q || '', count: true, verificationStatus: verificationStatus, }); diff --git a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx index 23b36e225..d33c8b26d 100644 --- a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx +++ b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx @@ -17,7 +17,7 @@ import { useMediaQuery, } from '@mui/material'; import { Close as CloseIcon } from '@mui/icons-material'; -import { filtersToURL, URLToFilters } from '../util/searchQuery'; +import { URLToFilters } from '../util/searchQuery'; import { hasAnyRole } from '../util/helperFunctions'; import { useAppSelector, useAppDispatch } from '../../hooks/redux'; import { fetchCountries } from '../../redux/filters/thunk'; diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index e53111d01..2a8ed6789 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -15,7 +15,6 @@ import clsx from 'clsx'; import DataGuideDialog from './DataGuideDialog'; import { useDebounce } from '../hooks/useDebounce'; import FiltersDialog from './FiltersDialog'; -import { searchQueryToURL, URLToSearchQuery } from './util/searchQuery'; import { useLocation, useNavigate } from 'react-router-dom'; import { KeyboardEvent, ChangeEvent } from 'react'; import { useAppSelector, useAppDispatch } from '../hooks/redux'; diff --git a/verification/curator-service/ui/src/components/ViewCase.tsx b/verification/curator-service/ui/src/components/ViewCase.tsx index 8f63bd3f1..15b669087 100644 --- a/verification/curator-service/ui/src/components/ViewCase.tsx +++ b/verification/curator-service/ui/src/components/ViewCase.tsx @@ -1,6 +1,19 @@ +import axios from 'axios'; import React, { useEffect, useState } from 'react'; +import Highlighter from 'react-highlight-words'; +import { useSelector } from 'react-redux'; +import { Link, useParams } from 'react-router-dom'; +import Scroll from 'react-scroll'; +import { makeStyles } from 'tss-react/mui'; import { + CheckCircleOutline as CheckIcon, + Close as CloseIcon, + EditOutlined as EditIcon, +} from '@mui/icons-material'; +import { + Alert, Button, + Chip, Dialog, DialogContent, DialogTitle, @@ -9,30 +22,20 @@ import { LinearProgress, Paper, Typography, + useMediaQuery, } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; + import { Day0Case, Outcome, YesNo } from '../api/models/Day0Case'; +import { Role } from '../api/models/User'; import AppModal from './AppModal'; -import EditIcon from '@mui/icons-material/EditOutlined'; -import CheckIcon from '@mui/icons-material/CheckCircleOutline'; -import { Link, useParams } from 'react-router-dom'; -import MuiAlert from '@mui/material/Alert'; -import Scroll from 'react-scroll'; -import axios from 'axios'; -import createHref from './util/links'; -import { makeStyles } from 'tss-react/mui'; import renderDate from './util/date'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { useTheme } from '@mui/material/styles'; -import Highlighter from 'react-highlight-words'; -import { useSelector } from 'react-redux'; +import createHref from './util/links'; import { selectFilterBreadcrumbs } from '../redux/app/selectors'; +import { selectUser } from '../redux/auth/selectors'; import { selectSearchQuery } from '../redux/linelistTable/selectors'; -import Chip from '@mui/material/Chip'; import { nameCountry } from './util/countryNames'; import { parseAgeRange } from './util/helperFunctions'; -import CloseIcon from '@mui/icons-material/Close'; -import { selectUser } from '../redux/auth/selectors'; -import { Role } from '../api/models/User'; const styles = makeStyles()(() => ({ errorMessage: { @@ -98,14 +101,14 @@ export default function ViewCase(props: Props): JSX.Element { {loading && } {errorMessage && ( - {errorMessage} - + )} {c && ( Date: Wed, 23 Oct 2024 17:01:24 +0200 Subject: [PATCH 15/16] Cleanup SearchBar --- .../curator-service/ui/src/components/SearchBar.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/verification/curator-service/ui/src/components/SearchBar.tsx b/verification/curator-service/ui/src/components/SearchBar.tsx index 2a8ed6789..f45287740 100644 --- a/verification/curator-service/ui/src/components/SearchBar.tsx +++ b/verification/curator-service/ui/src/components/SearchBar.tsx @@ -84,11 +84,7 @@ export default function SearchBar({ const [isUserTyping, setIsUserTyping] = useState(false); const [isDataGuideOpen, setIsDataGuideOpen] = useState(false); const [searchInput, setSearchInput] = useState( - location.search.includes('?q=') - ? location.search.split('?q=')[1] - : location.search?.includes('&q=') - ? location.search.split('&q=')[1] - : '', + new URLSearchParams(location.search).get('q') || '', ); const [modalAlert, setModalAlert] = useState(false); const guideButtonRef = React.useRef(null); @@ -105,14 +101,13 @@ export default function SearchBar({ }, [filtersBreadcrumb]); useEffect(() => { - const q = (new URLSearchParams(location.search)).get('q') || ''; + const q = new URLSearchParams(location.search).get('q') || ''; if (q !== searchInput) setSearchInput(q); }, [location.search]); // Set search query debounce to 1000ms const debouncedSearch = useDebounce(searchInput, 2000); - const handleNavigating = (q: string) => { const searchParams = new URLSearchParams(location.search); q !== '' ? searchParams.set('q', q) : searchParams.delete('q'); @@ -121,7 +116,7 @@ export default function SearchBar({ pathname: '/cases', search: searchParams.toString(), }); - } + }; // Apply filter parameters after delay useEffect(() => { @@ -180,7 +175,7 @@ export default function SearchBar({ if (searchError) { return 'Incorrect entry. ":" characters have been removed. Please use filters instead.'; } else { - const quoteCount = decodeURIComponent(searchInput).split('"').length - 1; + const quoteCount = searchInput.split('"').length - 1; if (quoteCount % 2 !== 0) { return 'Incorrect entry. Please make sure you have an even number of quotes.'; } From 5d7b1260d168d32dbb4ad83e2c0b9bb8541ed636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zakrzewski?= Date: Wed, 23 Oct 2024 17:07:57 +0200 Subject: [PATCH 16/16] Remove console.log --- .../curator-service/ui/src/components/FiltersDialog/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx index d33c8b26d..a18d3c69e 100644 --- a/verification/curator-service/ui/src/components/FiltersDialog/index.tsx +++ b/verification/curator-service/ui/src/components/FiltersDialog/index.tsx @@ -155,7 +155,6 @@ export default function FiltersDialog({ const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(values)) { - console.log(key, value) if (value) searchParams.set(key, value); } const q = (new URLSearchParams(location.search)).get('q')