From 4f1c1637f9b1821a3e1bc2ca8fb3ab0d8df09bd0 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Wed, 17 Jul 2024 17:06:13 -0600 Subject: [PATCH 001/102] todo/note for future cleanup --- src/main/java/org/ecocean/Encounter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ecocean/Encounter.java b/src/main/java/org/ecocean/Encounter.java index 1ddddee7ac..1861896b2a 100644 --- a/src/main/java/org/ecocean/Encounter.java +++ b/src/main/java/org/ecocean/Encounter.java @@ -2968,7 +2968,10 @@ public Set getAnnotationIAClasses() { for (Annotation ann : annotations) { if (ann.getIAClass() != null) classes.add(ann.getIAClass()); } - classes.remove("____"); // blech + // TODO we should find out how/where bunk iaClass values are getting set + // and stop the via isValidIAClass() or similar + // also should be considered for any data integrity/repair tools + classes.remove("____"); return classes; } From 95b444be52fded8706706b0349ef3b01eb91efea Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Thu, 18 Jul 2024 01:57:53 +0000 Subject: [PATCH 002/102] fitBounds, remove old bounding box when start drawing --- frontend/src/components/Map.jsx | 54 ++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index 6113bede61..a7cb27989a 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -133,15 +133,20 @@ // ); // } -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useContext, useEffect } from 'react'; import GoogleMapReact from 'google-map-react'; import { Button } from 'react-bootstrap'; +import BrutalismButton from './BrutalismButton'; +import ThemeContext from '../ThemeColorProvider'; const MapComponent = ({ center, zoom = 10, - setBounds + bounds, + setBounds, + onChange }) => { + const theme = useContext(ThemeContext); const [rectangle, setRectangle] = useState(null); const drawingRef = useRef(false); @@ -182,13 +187,17 @@ const MapComponent = ({ const moveListener = maps.event.addListener(map, 'mousemove', mouseMoveHandler); const mouseUpHandler = () => { - console.log("mouseup"); drawingRef.current = false; setIsDrawing(false); map.setOptions({ draggable: true }); maps.event.removeListener(moveListener); setBounds(rect.getBounds().toJSON()); - console.log("rect.getBounds().toJSON()",rect.getBounds().toJSON()); + map.fitBounds(rect.getBounds(), { + left: 50, + right: 50, + top: 50, + bottom: 50 + }); }; document.addEventListener('mouseup', mouseUpHandler, { once: true }); } @@ -196,23 +205,52 @@ const MapComponent = ({ }; const toggleDrawing = () => { + if (rectangle) { + rectangle.setMap(null); + } drawingRef.current = !drawingRef.current; }; + + // useEffect(() => { + // if(bounds){ + // onChange({ + // filterId: "locationId", + // term: "terms", + // clause: "filter", + // "query": { + // "geo_bounding_box": { + // "location": { + // "top_left": { + // lat: bounds.north, + // lon: bounds.west + // }, + // "bottom_right": { + // lat: bounds.south, + // lon: bounds.east + // } + // } + // } + // } + + // }) + // }}, [bounds]) return (
- + Date: Thu, 18 Jul 2024 20:26:43 +0000 Subject: [PATCH 003/102] measurements and biomeasurements query update to nested --- frontend/src/AuthenticatedSwitch.jsx | 2 +- frontend/src/UnAuthenticatedSwitch.jsx | 2 +- frontend/src/components/Chip.jsx | 4 +- frontend/src/components/DataTable.jsx | 89 ++++++++--- frontend/src/components/FilterPanel.jsx | 10 +- .../src/components/Form/FormMeasurements.jsx | 58 +++---- .../BiologicalSamplesAndAnalysesFilter.jsx | 81 +++++----- .../components/filterFields/DateFilter.jsx | 20 ++- .../filterFields/LocationFilterMap.jsx | 31 +++- .../ObservationAttributeFilter.jsx | 3 +- .../src/components/filterFields/SideBar.jsx | 22 ++- .../models/encounters/useFilterEncounters.js | 141 ++++++++++++++---- frontend/src/pages/EncounterSearch.jsx | 53 ++++--- 13 files changed, 366 insertions(+), 150 deletions(-) diff --git a/frontend/src/AuthenticatedSwitch.jsx b/frontend/src/AuthenticatedSwitch.jsx index 1e0ff7a36d..cbc71f11ff 100644 --- a/frontend/src/AuthenticatedSwitch.jsx +++ b/frontend/src/AuthenticatedSwitch.jsx @@ -38,7 +38,7 @@ export default function AuthenticatedSwitch({ showAlert, setShowAlert }) {
{ Object.entries(item).forEach(([key, value]) => { diff --git a/frontend/src/components/DataTable.jsx b/frontend/src/components/DataTable.jsx index badc2589aa..2d9cbcb289 100644 --- a/frontend/src/components/DataTable.jsx +++ b/frontend/src/components/DataTable.jsx @@ -4,6 +4,8 @@ import ReactPaginate from "react-paginate"; import { InputGroup, Form, Button, Container, Row, Col } from "react-bootstrap"; import "bootstrap/dist/css/bootstrap.min.css"; import "../css/dataTable.css"; +import { FormattedMessage } from "react-intl"; +import ThemeColorContext from "../ThemeColorProvider"; const customStyles = { rows: { @@ -38,7 +40,9 @@ const MyDataTable = ({ perPage, onPageChange, onPerPageChange, - onSelectedRowsChange = () => {}, + style = {}, + tabs = [], + onSelectedRowsChange = () => { }, }) => { const [data, setData] = useState([]); const [filterText, setFilterText] = useState(""); @@ -48,11 +52,12 @@ const MyDataTable = ({ const wrappedColumns = useMemo( () => columnNames.map((col) => { - return ({ - name: col.name.charAt(0).toUpperCase() + col.name.slice(1), - selector: (row) => row[col.selector], // Accessor function for the column data - sortable: true, // Make the column sortable - })}), + return ({ + name: col.name.charAt(0).toUpperCase() + col.name.slice(1), + selector: (row) => row[col.selector], // Accessor function for the column data + sortable: true, // Make the column sortable + }) + }), [columnNames], ); @@ -102,21 +107,63 @@ const MyDataTable = ({ ), ); + const theme = React.useContext(ThemeColorContext); + return ( -
-

{title}

- - - - - +
+

{title}

+
+
+ + {Object(tabs).map((tab, index) => { + return ( + + ); + })} +
+ + + + + +
+ -
+
Total Items: {totalItems}
diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index 42ed29b046..844bd345ec 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -3,7 +3,7 @@ import React, { Fragment, useEffect, useState, useRef } from 'react'; import Text from './Text'; import { Container } from 'react-bootstrap'; -import { set } from 'lodash-es'; +import { filter, set } from 'lodash-es'; import ThemeContext from "../ThemeColorProvider"; import BrutalismButton from './BrutalismButton'; import useGetSiteSettings from '../models/useGetSiteSettings'; @@ -26,6 +26,7 @@ export default function FilterPanel({ formFilters = [], setFormFilters = () => { }, setFilterPanel, + style={}, }) { const [selectedChoices, setSelectedChoices] = useState({}); const [tempFormFilters, setTempFormFilters] = useState(formFilters); @@ -33,6 +34,7 @@ export default function FilterPanel({ const { data } = useGetSiteSettings(); const handleFilterChange = filter => { + console.log("Filter:", filter); if (filter.selectedChoice) { setSelectedChoices({ ...selectedChoices, @@ -93,8 +95,11 @@ export default function FilterPanel({ // }; // }, [clicked, safeSchemas.length]); + return ( - + input.value !== '').map(input => ([ // { // "term": { @@ -67,7 +67,7 @@ // } // } // ])).flat(); - + // if (must.length > 0) { // onChange({ // filterId, @@ -80,7 +80,7 @@ // }); // } // }; - + // }; // return ( @@ -117,16 +117,23 @@ // export default ObservationInputs; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Form, Col, Row, Container } from 'react-bootstrap'; +import { FormattedMessage } from 'react-intl'; -function ObservationInputs({ +function FormMeasurements({ data, onChange, field, filterId -}) { - const [inputs, setInputs] = useState(data.map(item => ({ type: item, operator: 'gte', value: '' }))); +}) { + const [inputs, setInputs] = useState(data?.map(item => ({ type: item, operator: 'gte', value: '' }))); + useEffect(() => { + if (data) { + const newInputs = data.map(item => ({ type: item, operator: 'gte', value: '' })); + setInputs(newInputs); + } + }, [data]); const handleInputChange = (index, field, value) => { const updatedInputs = inputs.map((input, i) => { @@ -136,38 +143,32 @@ function ObservationInputs({ return input; }); setInputs(updatedInputs); - if (field === 'value' && value !== '') { // Ensure the value is not empty + if (field === 'value' && value !== '') { updateQuery(updatedInputs); } }; const updateQuery = (inputs) => { const must = inputs.filter(input => input.value !== '').map(input => { - // Adjust the query based on the operator - if (input.operator === "equals") { - return { - "term": { - [`measurements.${input.type}`]: input.value - } - }; - } else { - return { - "range": { - [`measurements.${input.type}`]: { - [input.operator]: input.value - } - } - }; - } + return { + "match": { + [`${field}.type`]: input.type + }, + + "range": { + [`${field}.value`]: { [input.operator]: [input.value] } + } + }; }); if (must.length > 0) { onChange({ filterId, - clause: "filter", + clause: "nested", + path: field, query: { "bool": { - "must": must + "filter": must } } }); @@ -176,6 +177,7 @@ function ObservationInputs({ return ( +
{inputs.map((input, index) => ( @@ -189,7 +191,7 @@ function ObservationInputs({ > - + @@ -206,4 +208,4 @@ function ObservationInputs({ ); } -export default ObservationInputs; +export default FormMeasurements; diff --git a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx index 972d57330f..1f17cad882 100644 --- a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx +++ b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx @@ -6,6 +6,7 @@ import Description from '../Form/Description'; import FormGroupText from '../Form/FormGroupText'; import FormMeasurements from '../Form/FormMeasurements'; import { FormGroup, FormLabel, FormControl } from 'react-bootstrap'; +import FormGroupMultiSelect from '../Form/FormGroupMultiSelect'; export default function BiologicalSamplesAndAnalysesFilter({ @@ -14,36 +15,35 @@ export default function BiologicalSamplesAndAnalysesFilter({ }) { const label = const [isChecked, setIsChecked] = React.useState(false); - const bioChemicalOptions = Object.keys(data?.bioMeasurement || {}) || []; - const microSatelliteMarkerLoci = [ - { - "analysisId": "ZAMBONI", - "loci": { - "X": [ - 1, - 2 - ], - "Y": [ - 3, - 4 - ], - "Z": [ - 55, - 66 - ] - } - } - ]; + const bioMeasurementOptions = Object.entries(data?.bioMeasurement || {}).map( + item => item[0] + ) || []; + const microSatelliteMarkerLoci = data?.loci || []; const [checkedState, setCheckedState] = useState({}); const [alleleLength, setAlleleLength] = React.useState(false); const [length, setLength] = React.useState(null); + const haploTypeOptions = data?.haplotype.map(item => { + return { + value: typeof item === "object" ? item.value : item, + label: typeof item === "object" ? item.label : item + } + }) || []; + + const geneticSexOptions = data?.geneticSex.map(item => { + return { + value: typeof item === "object" ? item.value : item, + label: typeof item === "object" ? item.label : item + } + }) || []; + + const [bioChemicalValue, setBioChemicalValue] = React.useState(null); return (
-

+

- +
{ setIsChecked(!isChecked); - // onChange({ - // filterId: "biologicalSampleId", - // clause: "filter", - // query: { - // "exists": { - // "field": "biologicalSampleId" - // } - // } - - // }) onChange({ filterId: "biologicalSampleId", clause: "must_not", @@ -78,16 +68,37 @@ export default function BiologicalSamplesAndAnalysesFilter({ + + + +
diff --git a/frontend/src/components/filterFields/DateFilter.jsx b/frontend/src/components/filterFields/DateFilter.jsx index 648ccfe53b..6a0220132f 100644 --- a/frontend/src/components/filterFields/DateFilter.jsx +++ b/frontend/src/components/filterFields/DateFilter.jsx @@ -2,15 +2,23 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import Description from "../Form/Description"; import { FormGroup, FormLabel, FormControl, Button } from "react-bootstrap"; +import FormGroupText from "../Form/FormGroupText"; +import FormGroupMultiSelect from "../Form/FormGroupMultiSelect"; export default function DateFilter({ onChange, + data, }) { const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [submissionStartDate, setSubmissionStartDate] = useState(''); const [submissionEndDate, setSubmissionEndDate] = useState(''); - const [verbatimDate, setVerbatimDate] = useState(''); + const verbatimeventdateOptions = data?.verbatimeventdate?.map(data => { + return { + value: data, + label: data + } + }) || []; const updateQuery1 = () => { @@ -89,6 +97,16 @@ export default function DateFilter({ + + + <>

diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 9488c4b3b2..59015872b9 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -1,7 +1,7 @@ import { FormattedMessage } from 'react-intl'; import Map from "../Map"; import { FormGroup, FormLabel, FormControl } from 'react-bootstrap'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Description from '../Form/Description'; import FormGroupMultiSelect from '../Form/FormGroupMultiSelect'; import _ from 'lodash'; @@ -11,7 +11,32 @@ export default function LocationFilterMap({ data, }) { const [bounds, setBounds] = useState(); - console.log("location map bounds", bounds); + + useEffect(() => { + if (bounds) { + onChange({ + filterId: "locationId", + clause: "filter", + query: { + // terms: { + // locationId: data.locationData.locationID.map(location => location.id) + // }, + "geo_bounding_box": { + "pin.location": { + "top_left": { + "lat": bounds.north, + "lon": bounds.west + }, + "bottom_right": { + "lat": bounds.south, + "lon": bounds.east + } + } + } + } + }); + } + }, [bounds]); function flattenLocationData(data) { if (!data) { @@ -89,9 +114,11 @@ export default function LocationFilterMap({
diff --git a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx index 3ec4bf91c4..da1d515694 100644 --- a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx +++ b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx @@ -43,7 +43,8 @@ export default function ObservationAttributeFilter( label: item }; }) || []; - const measurementsOptions = data?.measurement || []; + const measurementsOptions = data?.measurement || []; + console.log("Measurements Options:", measurementsOptions); return (
diff --git a/frontend/src/components/filterFields/SideBar.jsx b/frontend/src/components/filterFields/SideBar.jsx index 6cb2576923..d141fb9fa1 100644 --- a/frontend/src/components/filterFields/SideBar.jsx +++ b/frontend/src/components/filterFields/SideBar.jsx @@ -3,12 +3,15 @@ import { Button, Offcanvas } from 'react-bootstrap'; import 'bootstrap/dist/css/bootstrap.min.css'; import { FormattedMessage } from 'react-intl'; import Chip from '../Chip'; +import BrutalismButton from '../BrutalismButton'; +import ThemeContext from '../../ThemeColorProvider'; function Sidebar({ formFilters, setFilterPanel, setFormFilters, }) { + const theme = React.useContext(ThemeContext); const [show, setShow] = useState(false); const sidebarWidth = 400; @@ -76,25 +79,28 @@ function Sidebar({ style={{ padding: '10px 0', }}> - - + +
diff --git a/frontend/src/models/encounters/useFilterEncounters.js b/frontend/src/models/encounters/useFilterEncounters.js index 92b41b4e75..96e19c8116 100644 --- a/frontend/src/models/encounters/useFilterEncounters.js +++ b/frontend/src/models/encounters/useFilterEncounters.js @@ -1,41 +1,122 @@ -import { get, partition } from "lodash-es"; +// import { get, partition } from "lodash-es"; +// import useFetch from "../../hooks/useFetch"; +// import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; + +// export default function useFilterEncounters({ queries, params = {} }) { +// console.log("Queries:", queries); +// const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); +// const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); + +// const nestedQuery = nestedQueries.map(n => ({ +// nested: { +// path: n.path, +// query: { +// bool: { +// filter: n.query.bool.filter.map(f => ({ match: f })) +// } +// } +// } +// })); + +// console.log("Nested Query:", nestedQuery); + +// const boolQuery = { +// filter: filterQueries.map(f => ({ match: f.query })), +// must_not: mustNotQueries.map(f => f.query) +// }; + +// console.log + +// if (nestedQuery.length > 0) { +// boolQuery.filter.push(...nestedQuery); +// } + +// const compositeQuery = { +// query: { +// bool: boolQuery +// } +// }; +// return useFetch({ +// method: "post", +// queryKey: getEncounterFilterQueryKey(queries, params), +// url: "/search/encounter", +// data: compositeQuery, +// params: { +// sort: "date", +// size: 1, +// from: 3, +// ...params, +// }, +// dataAccessor: (result) => { +// const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); +// return { +// resultCount: parseInt(resultCountString, 10), +// results: get(result, ["data", "data", "hits"], []), +// }; +// }, +// queryOptions: { +// retry: 2, +// }, +// }); +// } + +import { get, partition } from "lodash-es"; import useFetch from "../../hooks/useFetch"; import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; export default function useFilterEncounters({ queries, params = {} }) { - const [filters, mustNots] = partition(queries, (q) => q.clause === "filter"); - const filterQueries = filters.map((f) => f.query); - const mustNotQueries = mustNots.map((f) => f.query); + console.log("Queries:", queries); + const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); + const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); + + const nestedQuery = nestedQueries.map(n => ({ + nested: { + path: n.path, + query: { + bool: { + filter: n.query.bool.filter.map(f => ({ match: f })) + } + } + } + })); + + console.log("Nested Query:", nestedQuery); + + const boolQuery = { + filter: filterQueries.map(f => ({ match: f.query })), + must_not: mustNotQueries.map(f => f.query), + must: nestedQuery + }; + + console.log("Bool Query:", boolQuery); const compositeQuery = { - "query": {bool: { filter: filterQueries, must_not: mustNotQueries || [] }}, + query: { + bool: boolQuery + } }; return useFetch({ - method: "post", - queryKey: getEncounterFilterQueryKey(queries, params), - url: "/search/encounter", - data: compositeQuery, - params: { - sort: "date", - //reverse: false, - size:1, - from: 3, - ...params, - }, - dataAccessor: (result) => { - // console.log("result", result); - const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"]); - return { - resultCount: parseInt(resultCountString, 10) || 0, - results: get(result, ["data", "data","hits"], []), - }; - }, - queryOptions: { - retry: 2, - }, - }) - - + method: "post", + queryKey: getEncounterFilterQueryKey(queries, params), + url: "/search/encounter", + data: compositeQuery, + params: { + sort: "date", + size: 1, + from: 3, + ...params, + }, + dataAccessor: (result) => { + const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); + return { + resultCount: parseInt(resultCountString, 10), + results: get(result, ["data", "data", "hits"], []), + }; + }, + queryOptions: { + retry: 2, + }, + }); } diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index 25c0ef79ed..0b03c1fa90 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -12,11 +12,15 @@ export default function EncounterSearch() { const columns = [ { name: "Encounter ID", selector: "id" }, + { name: "Sighting ID", selector: "occurrenceId" }, + { name: "Alternative ID", selector: "otherCatalogNumbers" }, { name: "Created Date", selector: "date" }, - { name: "Individual ID", selector: "individualId" }, { name: "Location ID", selector: "locationId" }, - { name: "Number Annotations", selector: "numberAnnotations" }, { name: "Species", selector: "taxonomy" }, + { name: "Submitter", selector: "submitters" }, + { name: "Date Submitted", selector: "dateSubmitted" }, + { name: "Individual ID", selector: "individualId" }, + { name: "Number Annotations", selector: "numberAnnotations" }, ]; const schemas = useEncounterSearchSchemas(); @@ -35,6 +39,14 @@ export default function EncounterSearch() { const encounters = encounterData?.results || []; const totalEncounters = encounterData?.resultCount || 0; + const tabs = [ + "Project Management : /encounters/projectManagement.jsp", + "Matching Images/Videos : /encounters/thumbnailSearchResults.jsp", + "Mapped Results : /encounters/mappedSearchResults.jsp", + "Results Calendar : /xcalendar/calendar.jsp", + "Analysis : /encounters/searchResultsAnalysis.jsp", + "Export : /encounters/exportSearchResults.jsp", + ]; return ( @@ -48,27 +60,34 @@ export default function EncounterSearch() { padding: "20px", }} > - {filterPanel ? - : { - console.log("Selected Rows: ", selectedRows); - }} - />} + { + console.log("Selected Rows: ", selectedRows); + }} + /> Date: Thu, 18 Jul 2024 20:42:43 +0000 Subject: [PATCH 004/102] set center of map and initial bounds --- frontend/src/components/Map.jsx | 29 ++-------------- .../filterFields/LocationFilterMap.jsx | 34 +++++++++++-------- .../ObservationAttributeFilter.jsx | 1 - .../models/encounters/useFilterEncounters.js | 5 --- 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index a7cb27989a..88511ba82f 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -209,32 +209,7 @@ const MapComponent = ({ rectangle.setMap(null); } drawingRef.current = !drawingRef.current; - }; - - // useEffect(() => { - // if(bounds){ - // onChange({ - // filterId: "locationId", - // term: "terms", - // clause: "filter", - // "query": { - // "geo_bounding_box": { - // "location": { - // "top_left": { - // lat: bounds.north, - // lon: bounds.west - // }, - // "bottom_right": { - // lat: bounds.south, - // lon: bounds.east - // } - // } - // } - // } - - // }) - // }}, [bounds]) - + }; return (
@@ -246,7 +221,7 @@ const MapComponent = ({ backgroundColor= {theme.primaryColors.primary700} borderColor={theme.primaryColors.primary700} color='white' - style={{ position: 'absolute', zIndex: 5, width: "100px" }} + style={{ position: 'absolute', zIndex: 2, width: "100px" }} disabled={isDrawing} > {drawingRef.current ? 'Drawing' : 'Draw'} diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 59015872b9..443ac46b34 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -10,7 +10,13 @@ export default function LocationFilterMap({ onChange, data, }) { - const [bounds, setBounds] = useState(); + const initialBounds = { + north: -1.276389, // Slightly north of Nairobi center + south: -1.296389, // Slightly south of Nairobi center + east: 36.827223, // Slightly east of Nairobi center + west: 36.807223 // Slightly west of Nairobi center + }; + const [bounds, setBounds] = useState(initialBounds); useEffect(() => { if (bounds) { @@ -18,9 +24,6 @@ export default function LocationFilterMap({ filterId: "locationId", clause: "filter", query: { - // terms: { - // locationId: data.locationData.locationID.map(location => location.id) - // }, "geo_bounding_box": { "pin.location": { "top_left": { @@ -77,15 +80,9 @@ export default function LocationFilterMap({ + - +
- +
); diff --git a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx index da1d515694..9bfdf57b07 100644 --- a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx +++ b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx @@ -44,7 +44,6 @@ export default function ObservationAttributeFilter( }; }) || []; const measurementsOptions = data?.measurement || []; - console.log("Measurements Options:", measurementsOptions); return (
diff --git a/frontend/src/models/encounters/useFilterEncounters.js b/frontend/src/models/encounters/useFilterEncounters.js index 96e19c8116..7b7714063d 100644 --- a/frontend/src/models/encounters/useFilterEncounters.js +++ b/frontend/src/models/encounters/useFilterEncounters.js @@ -66,7 +66,6 @@ import useFetch from "../../hooks/useFetch"; import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; export default function useFilterEncounters({ queries, params = {} }) { - console.log("Queries:", queries); const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); @@ -81,16 +80,12 @@ export default function useFilterEncounters({ queries, params = {} }) { } })); - console.log("Nested Query:", nestedQuery); - const boolQuery = { filter: filterQueries.map(f => ({ match: f.query })), must_not: mustNotQueries.map(f => f.query), must: nestedQuery }; - console.log("Bool Query:", boolQuery); - const compositeQuery = { query: { bool: boolQuery From cdb9c1514eaeea7585484e3e32d32d0d774eba6d Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Fri, 19 Jul 2024 00:27:50 +0000 Subject: [PATCH 005/102] remove confirm buttons, real time update query --- .../components/filterFields/DateFilter.jsx | 274 +++++++++++++----- 1 file changed, 201 insertions(+), 73 deletions(-) diff --git a/frontend/src/components/filterFields/DateFilter.jsx b/frontend/src/components/filterFields/DateFilter.jsx index 6a0220132f..e34211f56a 100644 --- a/frontend/src/components/filterFields/DateFilter.jsx +++ b/frontend/src/components/filterFields/DateFilter.jsx @@ -1,74 +1,211 @@ -import React, { useState } from "react"; +// import React, { useState, useContext, useEffect } from "react"; +// import { FormattedMessage } from "react-intl"; +// import Description from "../Form/Description"; +// import FormGroupText from "../Form/FormGroupText"; +// import FormGroupMultiSelect from "../Form/FormGroupMultiSelect"; +// import { FormLabel, FormGroup, FormControl } from "react-bootstrap"; + +// export default function DateFilter({ +// onChange, +// data, +// }) { +// const [startDate, setStartDate] = useState(""); +// const [endDate, setEndDate] = useState(""); +// const [submissionStartDate, setSubmissionStartDate] = useState(''); +// const [submissionEndDate, setSubmissionEndDate] = useState(''); +// const verbatimeventdateOptions = data?.verbatimeventdate?.map(data => { +// return { +// value: data, +// label: data +// } +// }) || []; + +// useEffect(() => { +// updateQuery1("startDate", startDate); +// }, [startDate]); +// useEffect(() => { +// updateQuery1("endDate", endDate); +// }, [endDate]); + +// useEffect(() => { +// updateQuery2("submissionStartDate", submissionStartDate); +// }, [submissionStartDate]); + +// useEffect(() => { +// updateQuery2("submissionEndDate", submissionEndDate); +// }, [submissionEndDate]); + +// const updateQuery1 = () => { +// if (startDate || endDate) { +// const query = { +// range: { +// sightingDate: {} +// } +// }; + +// if (startDate) { +// query.range.sightingDate.gte = startDate + "T00:00:00Z"; +// } + +// if (endDate) { +// query.range.sightingDate.lte = endDate + "T23:59:59Z"; +// } +// onChange({ +// filterId: "date", +// clause: "filter", +// query: query +// } +// ) +// } + +// } + +// const updateQuery2 = () => { +// if (submissionStartDate || submissionEndDate) { +// const query = { +// range: { +// submissionDate: {} +// } +// }; + +// if (submissionStartDate) { +// query.range.submissionDate.gte = submissionStartDate + "T00:00:00Z"; +// } + +// if (submissionEndDate) { +// query.range.submissionDate.lte = submissionEndDate + "T23:59:59Z"; +// } +// onChange({ +// filterId: "dateSubmitted", +// clause: "filter", +// query: query +// } +// ) +// } +// } + +// return ( +//
+//

+// +// +// +// <> +// +//
+// +//

+// +//

+// { +// setStartDate(e.target.value); +// updateQuery1(); +// }} /> +//
+// +//

+// +//

+// { +// setEndDate(e.target.value); +// updateQuery1(); +// }} /> +//
+//
+// + +// + +// <>

+ +//
+ +// +//

+// +//

+// { +// setSubmissionStartDate(e.target.value); +// }} /> +//
+// +//

+// +//

+// { +// setSubmissionEndDate(e.target.value); +// }} /> +//
+//
+// + +//
+// ); +// } + +import React, { useState, useEffect } from "react"; import { FormattedMessage } from "react-intl"; import Description from "../Form/Description"; -import { FormGroup, FormLabel, FormControl, Button } from "react-bootstrap"; -import FormGroupText from "../Form/FormGroupText"; import FormGroupMultiSelect from "../Form/FormGroupMultiSelect"; +import { FormLabel, FormGroup, FormControl } from "react-bootstrap"; -export default function DateFilter({ - onChange, - data, -}) { - const [startDate, setStartDate] = useState(''); - const [endDate, setEndDate] = useState(''); - const [submissionStartDate, setSubmissionStartDate] = useState(''); - const [submissionEndDate, setSubmissionEndDate] = useState(''); - const verbatimeventdateOptions = data?.verbatimeventdate?.map(data => { - return { - value: data, - label: data - } - }) || []; - - const updateQuery1 = () => { - - if (startDate || endDate) { - const query = { - range: { - sightingDate: {} - } - }; - - if (startDate) { - query.range.sightingDate.gte = startDate + "T00:00:00Z"; - } +export default function DateFilter({ onChange, data }) { + const loadInitialValue = () => { + const savedData = localStorage.getItem("formData"); + return savedData ? JSON.parse(savedData) : { + startDate: "", + endDate: "", + submissionStartDate: "", + submissionEndDate: "", + verbatimEventDates: [] + }; + }; - if (endDate) { - query.range.sightingDate.lte = endDate + "T23:59:59Z"; - } - onChange({ - filterId: "date", - clause: "filter", - query: query - } - ) - } + const [formData, setFormData] = useState(loadInitialValue()); + + const handleInputChange = (field, value) => { + const newFormData = { ...formData, [field]: value }; + setFormData(newFormData); + localStorage.setItem("formData", JSON.stringify(newFormData)); + }; - } + const verbatimEventDateOptions = data?.verbatimeventdate?.map(data => ({ + value: data, + label: data + })) || []; - const updateQuery2 = () => { - if (submissionStartDate || submissionEndDate) { - const query = { - range: { - submissionDate: {} - } - }; + useEffect(() => { + updateQuery("sightingDate", formData.startDate, formData.endDate, "date"); + updateQuery("submissionDate", formData.submissionStartDate, formData.submissionEndDate, "dateSubmitted"); + }, [formData.startDate, formData.endDate, formData.submissionStartDate, formData.submissionEndDate]); - if (submissionStartDate) { - query.range.submissionDate.gte = submissionStartDate + "T00:00:00Z"; + const updateQuery = (dateType, start, end, filterId) => { + if (start || end) { + const query = { range: {} }; + query.range[dateType] = {}; + + if (start) { + query.range[dateType].gte = start + "T00:00:00Z"; } - if (submissionEndDate) { - query.range.submissionDate.lte = submissionEndDate + "T23:59:59Z"; + if (end) { + query.range[dateType].lte = end + "T23:59:59Z"; } + onChange({ - filterId: "dateSubmitted", + filterId: filterId, clause: "filter", query: query - } - ) + }); } - } + }; return (
@@ -83,52 +220,43 @@ export default function DateFilter({

- setStartDate(e.target.value)} /> + handleInputChange("startDate", e.target.value)} />

- setEndDate(e.target.value)} /> + handleInputChange("endDate", e.target.value)} />
- - handleInputChange("verbatimEventDates", e)} // Adjust as necessary for multi-select handling term="terms" field="verbatimEventDate" /> - <>

- + <> +

-

- setSubmissionStartDate(e.target.value)} /> + handleInputChange("submissionStartDate", e.target.value)} />

- setSubmissionEndDate(e.target.value)} /> + handleInputChange("submissionEndDate", e.target.value)} />
- -
); } From 03737b3682dde9c5a3e92939bf57c42fe49131ed Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Fri, 19 Jul 2024 00:30:48 +0000 Subject: [PATCH 006/102] update select options background --- .../src/components/filterFields/ImageLabelFilter.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/filterFields/ImageLabelFilter.jsx b/frontend/src/components/filterFields/ImageLabelFilter.jsx index d705880846..051125ef55 100644 --- a/frontend/src/components/filterFields/ImageLabelFilter.jsx +++ b/frontend/src/components/filterFields/ImageLabelFilter.jsx @@ -12,11 +12,11 @@ import Select from 'react-select'; const colourStyles = { option: (styles) => ({ ...styles, - color: 'black', + color: 'black', }), control: (styles) => ({ ...styles, backgroundColor: 'white' }), - singleValue: (styles) => ({ ...styles, color: 'black' }), - }; + singleValue: (styles) => ({ ...styles, color: 'black' }), +}; export default function ImageLabelFilter({ data, @@ -76,7 +76,7 @@ export default function ImageLabelFilter({ const [isChecked_photo, setIsChecked_photo] = React.useState(false); const [isChecked_keyword, setIsChecked_keyword] = React.useState(false); - const term = isChecked_keyword? "terms" : "match"; + const term = isChecked_keyword ? "terms" : "match"; const field = "keywords"; const filterId = "keywords"; @@ -98,7 +98,7 @@ export default function ImageLabelFilter({ query: { "range": { "numberMediaAssets": { - "gte": 1 + "gte": 1 } } }, @@ -150,6 +150,7 @@ export default function ImageLabelFilter({
{ onChange({ filterId: "labelledKeywords", From 826ecfc3112e49357e614b3efd6e02fab8463a18 Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Fri, 19 Jul 2024 13:47:55 +0000 Subject: [PATCH 007/102] interComponent states update --- frontend/package-lock.json | 22 + frontend/package.json | 1 + frontend/src/App.jsx | 17 + frontend/src/FilterContextProvider.jsx | 31 ++ frontend/src/components/FilterPanel.jsx | 23 +- .../components/filterFields/DateFilter.jsx | 396 +++++++++--------- .../filterFields/IdentityFilter.jsx | 18 +- .../filterFields/ImageLabelFilter.jsx | 10 + .../filterFields/LocationFilterMap.jsx | 95 ++--- .../src/components/filterFields/SideBar.jsx | 4 +- .../components/filterFields/SocialFilter.jsx | 8 +- .../models/encounters/useFilterEncounters.js | 2 +- frontend/src/pages/EncounterSearch.jsx | 3 + 13 files changed, 365 insertions(+), 265 deletions(-) create mode 100644 frontend/src/FilterContextProvider.jsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d160d2f858..4a7c83c8dc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,6 +25,7 @@ "react-burger-menu": "^3.0.9", "react-data-table-component": "^7.6.2", "react-dom": "^18.2.0", + "react-hook-form": "^7.52.1", "react-intl": "^6.6.2", "react-paginate": "^8.2.0", "react-router-bootstrap": "^0.26.2", @@ -16475,6 +16476,21 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-hook-form": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.1.tgz", + "integrity": "sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-intl": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.2.tgz", @@ -31907,6 +31923,12 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-hook-form": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.1.tgz", + "integrity": "sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg==", + "requires": {} + }, "react-intl": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index df28700f0b..9cb7030120 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "react-burger-menu": "^3.0.9", "react-data-table-component": "^7.6.2", "react-dom": "^18.2.0", + "react-hook-form": "^7.52.1", "react-intl": "^6.6.2", "react-paginate": "^8.2.0", "react-router-bootstrap": "^0.26.2", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2a326eb080..483f180edc 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -12,6 +12,7 @@ import { BrowserRouter, useLocation, useRoutes } from "react-router-dom"; import LocaleContext from "./IntlProvider"; import FooterVisibilityContext from "./FooterVisibilityContext"; import Cookies from "js-cookie"; +import FilterContext from "./FilterContextProvider"; function App() { const messageMap = { @@ -37,6 +38,20 @@ function App() { Cookies.set("wildbookLangCode", newLocale); }; + const [filters, setFilters] = useState({}); + const updateFilter = (filterName, value) => { + console.log("1111111111111111FilterName:", filterName); + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: value, + })); + + } + + const resetFilters = () => { + setFilters({}); + }; + return ( + + diff --git a/frontend/src/FilterContextProvider.jsx b/frontend/src/FilterContextProvider.jsx new file mode 100644 index 0000000000..58cb686c7c --- /dev/null +++ b/frontend/src/FilterContextProvider.jsx @@ -0,0 +1,31 @@ +// import React, { createContext, useState } from 'react'; + +// const FilterContext = createContext(); + +// const FilterProvider = ({ children }) => { +// const [filters, setFilters] = useState({}); + +// const updateFilter = (filterName, value) => { +// console.log("1111111111111111FilterName:", filterName); +// console.log("2222222222222222Value:", value); +// setFilters(prevFilters => ({ +// ...prevFilters, +// [filterName]: value +// })); +// }; + + +// return ( +// +// {children} +// +// ); +// }; + +// export { FilterContext, FilterProvider }; + +import { createContext } from "react"; + +const FilterContext = createContext(true); +export default FilterContext; + diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index 844bd345ec..7bd38075d7 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -1,5 +1,5 @@ -import React, { Fragment, useEffect, useState, useRef } from 'react'; +import React, { Fragment, useEffect, useState, useRef, useContext } from 'react'; import Text from './Text'; import { Container } from 'react-bootstrap'; @@ -7,6 +7,7 @@ import { filter, set } from 'lodash-es'; import ThemeContext from "../ThemeColorProvider"; import BrutalismButton from './BrutalismButton'; import useGetSiteSettings from '../models/useGetSiteSettings'; +import FilterContext from '../FilterContextProvider'; function setFilter(newFilter, formFilters, setFormFilters) { const matchingFilterIndex = formFilters.findIndex( @@ -27,20 +28,22 @@ export default function FilterPanel({ setFormFilters = () => { }, setFilterPanel, style={}, + setRefresh, }) { const [selectedChoices, setSelectedChoices] = useState({}); const [tempFormFilters, setTempFormFilters] = useState(formFilters); + const { filters, updateFilter, resetFilters } = useContext(FilterContext); const { data } = useGetSiteSettings(); const handleFilterChange = filter => { console.log("Filter:", filter); - if (filter.selectedChoice) { - setSelectedChoices({ - ...selectedChoices, - [filter.filterId]: filter.selectedChoice, - }); - } + // if (filter.selectedChoice) { + // setSelectedChoices({ + // ...selectedChoices, + // [filter.filterId]: filter.selectedChoice, + // }); + // } setFilter(filter, tempFormFilters, setTempFormFilters); }; const clearFilter = filterId => { @@ -178,7 +181,9 @@ export default function FilterPanel({ onClick={() => { setFormFilters([]); setTempFormFilters([]); - setFilterPanel(false); + // setFilterPanel(false); + // localStorage.removeItem("formData"); + window.location.reload(); }}> RESET @@ -209,7 +214,7 @@ export default function FilterPanel({ // onClearFilter={clearFilter} // {...schema.filterComponentProps} // data={data} - // tempFormFilters={tempFormFilters} + // filters={filters} // />
{ -// return { -// value: data, -// label: data -// } -// }) || []; - -// useEffect(() => { -// updateQuery1("startDate", startDate); -// }, [startDate]); -// useEffect(() => { -// updateQuery1("endDate", endDate); -// }, [endDate]); - -// useEffect(() => { -// updateQuery2("submissionStartDate", submissionStartDate); -// }, [submissionStartDate]); - -// useEffect(() => { -// updateQuery2("submissionEndDate", submissionEndDate); -// }, [submissionEndDate]); - -// const updateQuery1 = () => { -// if (startDate || endDate) { -// const query = { -// range: { -// sightingDate: {} -// } -// }; - -// if (startDate) { -// query.range.sightingDate.gte = startDate + "T00:00:00Z"; -// } - -// if (endDate) { -// query.range.sightingDate.lte = endDate + "T23:59:59Z"; -// } -// onChange({ -// filterId: "date", -// clause: "filter", -// query: query -// } -// ) -// } - -// } - -// const updateQuery2 = () => { -// if (submissionStartDate || submissionEndDate) { -// const query = { -// range: { -// submissionDate: {} -// } -// }; - -// if (submissionStartDate) { -// query.range.submissionDate.gte = submissionStartDate + "T00:00:00Z"; -// } - -// if (submissionEndDate) { -// query.range.submissionDate.lte = submissionEndDate + "T23:59:59Z"; -// } -// onChange({ -// filterId: "dateSubmitted", -// clause: "filter", -// query: query -// } -// ) -// } -// } - -// return ( -//
-//

-// -// -// -// <> -// -//
-// -//

-// -//

-// { -// setStartDate(e.target.value); -// updateQuery1(); -// }} /> -//
-// -//

-// -//

-// { -// setEndDate(e.target.value); -// updateQuery1(); -// }} /> -//
-//
-// - -// - -// <>

- -//
- -// -//

-// -//

-// { -// setSubmissionStartDate(e.target.value); -// }} /> -//
-// -//

-// -//

-// { -// setSubmissionEndDate(e.target.value); -// }} /> -//
-//
-// - -//
-// ); -// } - -import React, { useState, useEffect } from "react"; +import React, { useState, useContext, useEffect } from "react"; import { FormattedMessage } from "react-intl"; import Description from "../Form/Description"; +import FormGroupText from "../Form/FormGroupText"; import FormGroupMultiSelect from "../Form/FormGroupMultiSelect"; import { FormLabel, FormGroup, FormControl } from "react-bootstrap"; -export default function DateFilter({ onChange, data }) { - const loadInitialValue = () => { - const savedData = localStorage.getItem("formData"); - return savedData ? JSON.parse(savedData) : { - startDate: "", - endDate: "", - submissionStartDate: "", - submissionEndDate: "", - verbatimEventDates: [] - }; - }; - - const [formData, setFormData] = useState(loadInitialValue()); +export default function DateFilter({ + onChange, + data, +}) { + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [submissionStartDate, setSubmissionStartDate] = useState(''); + const [submissionEndDate, setSubmissionEndDate] = useState(''); + const verbatimeventdateOptions = data?.verbatimeventdate?.map(data => { + return { + value: data, + label: data + } + }) || []; - const handleInputChange = (field, value) => { - const newFormData = { ...formData, [field]: value }; - setFormData(newFormData); - localStorage.setItem("formData", JSON.stringify(newFormData)); - }; - - const verbatimEventDateOptions = data?.verbatimeventdate?.map(data => ({ - value: data, - label: data - })) || []; + useEffect(() => { + updateQuery1("startDate", startDate); + }, [startDate]); + useEffect(() => { + updateQuery1("endDate", endDate); + }, [endDate]); + + useEffect(() => { + updateQuery2("submissionStartDate", submissionStartDate); + }, [submissionStartDate]); useEffect(() => { - updateQuery("sightingDate", formData.startDate, formData.endDate, "date"); - updateQuery("submissionDate", formData.submissionStartDate, formData.submissionEndDate, "dateSubmitted"); - }, [formData.startDate, formData.endDate, formData.submissionStartDate, formData.submissionEndDate]); + updateQuery2("submissionEndDate", submissionEndDate); + }, [submissionEndDate]); - const updateQuery = (dateType, start, end, filterId) => { - if (start || end) { - const query = { range: {} }; - query.range[dateType] = {}; + const updateQuery1 = () => { + if (startDate || endDate) { + const query = { + range: { + sightingDate: {} + } + }; - if (start) { - query.range[dateType].gte = start + "T00:00:00Z"; + if (startDate) { + query.range.sightingDate.gte = startDate + "T00:00:00Z"; } - if (end) { - query.range[dateType].lte = end + "T23:59:59Z"; + if (endDate) { + query.range.sightingDate.lte = endDate + "T23:59:59Z"; + } + onChange({ + filterId: "date", + clause: "filter", + query: query + } + ) + } + + } + + const updateQuery2 = () => { + if (submissionStartDate || submissionEndDate) { + const query = { + range: { + submissionDate: {} + } + }; + + if (submissionStartDate) { + query.range.submissionDate.gte = submissionStartDate + "T00:00:00Z"; } + if (submissionEndDate) { + query.range.submissionDate.lte = submissionEndDate + "T23:59:59Z"; + } onChange({ - filterId: filterId, + filterId: "dateSubmitted", clause: "filter", query: query - }); + } + ) } - }; + } return (
@@ -220,13 +97,19 @@ export default function DateFilter({ onChange, data }) {

- handleInputChange("startDate", e.target.value)} /> + { + setStartDate(e.target.value); + updateQuery1(); + }} />

- handleInputChange("endDate", e.target.value)} /> + { + setEndDate(e.target.value); + updateQuery1(); + }} />
@@ -234,29 +117,146 @@ export default function DateFilter({ onChange, data }) { handleInputChange("verbatimEventDates", e)} // Adjust as necessary for multi-select handling + options={verbatimeventdateOptions} + onChange={onChange} term="terms" field="verbatimEventDate" /> - <> -

+ <>

+
+

- handleInputChange("submissionStartDate", e.target.value)} /> + { + setSubmissionStartDate(e.target.value); + }} />

- handleInputChange("submissionEndDate", e.target.value)} /> + { + setSubmissionEndDate(e.target.value); + }} />
+
); } + +// import React, { useState, useEffect } from "react"; +// import { FormattedMessage } from "react-intl"; +// import Description from "../Form/Description"; +// import FormGroupMultiSelect from "../Form/FormGroupMultiSelect"; +// import { FormLabel, FormGroup, FormControl } from "react-bootstrap"; + +// export default function DateFilter({ onChange, data }) { +// const loadInitialValue = () => { +// const savedData = localStorage.getItem("formData"); +// return savedData ? JSON.parse(savedData) : { +// startDate: "", +// endDate: "", +// submissionStartDate: "", +// submissionEndDate: "", +// verbatimEventDates: [] +// }; +// }; + +// const [formData, setFormData] = useState(loadInitialValue()); + +// const handleInputChange = (field, value) => { +// const newFormData = { ...formData, [field]: value }; +// setFormData(newFormData); +// localStorage.setItem("formData", JSON.stringify(newFormData)); +// }; + +// const verbatimEventDateOptions = data?.verbatimeventdate?.map(data => ({ +// value: data, +// label: data +// })) || []; + +// useEffect(() => { +// updateQuery("sightingDate", formData.startDate, formData.endDate, "date"); +// updateQuery("submissionDate", formData.submissionStartDate, formData.submissionEndDate, "dateSubmitted"); +// }, [formData.startDate, formData.endDate, formData.submissionStartDate, formData.submissionEndDate]); + +// const updateQuery = (dateType, start, end, filterId) => { +// if (start || end) { +// const query = { range: {} }; +// query.range[dateType] = {}; + +// if (start) { +// query.range[dateType].gte = start + "T00:00:00Z"; +// } + +// if (end) { +// query.range[dateType].lte = end + "T23:59:59Z"; +// } + +// onChange({ +// filterId: filterId, +// clause: "filter", +// query: query +// }); +// } +// }; + +// return ( +//
+//

+// +// +// +// <> +// +//
+// +//

+// +//

+// handleInputChange("startDate", e.target.value)} /> +//
+// +//

+// +//

+// handleInputChange("endDate", e.target.value)} /> +//
+//
+// + +// handleInputChange("verbatimEventDates", e)} // Adjust as necessary for multi-select handling +// term="terms" +// field="verbatimEventDate" +// /> + +// <> +//

+//
+// +//

+// +//

+// handleInputChange("submissionStartDate", e.target.value)} /> +//
+// +//

+// +//

+// handleInputChange("submissionEndDate", e.target.value)} /> +//
+//
+// +//
+// ); +// } diff --git a/frontend/src/components/filterFields/IdentityFilter.jsx b/frontend/src/components/filterFields/IdentityFilter.jsx index c586733f27..288020b9f8 100644 --- a/frontend/src/components/filterFields/IdentityFilter.jsx +++ b/frontend/src/components/filterFields/IdentityFilter.jsx @@ -5,6 +5,8 @@ import Form from "react-bootstrap/Form"; import Description from "../Form/Description"; import FormGroupText from "../Form/FormGroupText"; import FormControl from "react-bootstrap/FormControl"; +import { useContext } from "react"; +import FilterContext from "../../FilterContextProvider"; export default function IdentityFilter({ onChange, @@ -13,6 +15,8 @@ export default function IdentityFilter({ const includeEncounters = const [isChecked1, setIsChecked1] = React.useState(false); const [isChecked2, setIsChecked2] = React.useState(false); + const { filters, updateFilter } = useContext(FilterContext); + return (

@@ -28,8 +32,9 @@ export default function IdentityFilter({ type="checkbox" id="custom-checkbox" checked={isChecked1} - onChange={() => { + onChange={(e) => { setIsChecked1(!isChecked1); + // onChange({ // filterId: "individualNumberEncounters", // clause: "filter", @@ -49,9 +54,13 @@ export default function IdentityFilter({ marginLeft: "10px", marginRight: "10px" }} + checked={filters.individualNumberEncounters} placeholder="Type Here" onChange={(e) => { - if(isChecked1){ + setIsChecked1(!isChecked1); + console.log("e.target.value", e.target.value); + // updateFilter("individualNumberEncounters", isChecked1); + if(isChecked1){ onChange({ filterId: "individualNumberEncounters", clause: "filter", @@ -75,9 +84,10 @@ export default function IdentityFilter({ label={includeEncounters} type="checkbox" id="custom-checkbox" - checked={isChecked2} - onChange={() => { + checked={filters.hello} + onChange={(e) => { setIsChecked2(!isChecked2); + // updateFilter("hello", isChecked2); if(isChecked2){ onChange({ filterId: "individualId", diff --git a/frontend/src/components/filterFields/ImageLabelFilter.jsx b/frontend/src/components/filterFields/ImageLabelFilter.jsx index 051125ef55..ecf350437e 100644 --- a/frontend/src/components/filterFields/ImageLabelFilter.jsx +++ b/frontend/src/components/filterFields/ImageLabelFilter.jsx @@ -8,6 +8,8 @@ import Description from '../Form/Description'; import { filter } from 'lodash-es'; import FormGroupText from '../Form/FormGroupText'; import Select from 'react-select'; +import { useContext } from 'react'; +import FilterContext from '../../FilterContextProvider'; const colourStyles = { option: (styles) => ({ @@ -22,6 +24,12 @@ export default function ImageLabelFilter({ data, onChange, }) { + + const { filters, updateFilter } = useContext(FilterContext); + const handleChange = (event) => { + const { name, value } = event.target; + updateFilter(name, value); + }; const keywordsOptions = data?.keyword?.map(item => { return { value: item, @@ -167,6 +175,8 @@ export default function ImageLabelFilter({ onChange({ filterId: "labelledKeywords", clause: "filter", + name: e.name, + value: e.value, query: { "match": { [labelledKeyword]: e.value diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 443ac46b34..075e7d802f 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -10,36 +10,36 @@ export default function LocationFilterMap({ onChange, data, }) { - const initialBounds = { - north: -1.276389, // Slightly north of Nairobi center - south: -1.296389, // Slightly south of Nairobi center - east: 36.827223, // Slightly east of Nairobi center - west: 36.807223 // Slightly west of Nairobi center - }; + const initialBounds = {}; + // north: -1.276389, // Slightly north of Nairobi center + // south: -1.296389, // Slightly south of Nairobi center + // east: 36.827223, // Slightly east of Nairobi center + // west: 36.807223 // Slightly west of Nairobi center + // }; const [bounds, setBounds] = useState(initialBounds); - useEffect(() => { - if (bounds) { - onChange({ - filterId: "locationId", - clause: "filter", - query: { - "geo_bounding_box": { - "pin.location": { - "top_left": { - "lat": bounds.north, - "lon": bounds.west - }, - "bottom_right": { - "lat": bounds.south, - "lon": bounds.east - } - } - } - } - }); - } - }, [bounds]); + // useEffect(() => { + // if (bounds) { + // onChange({ + // filterId: "locationId", + // clause: "filter", + // query: { + // "geo_bounding_box": { + // "pin.location": { + // "top_left": { + // "lat": bounds.north, + // "lon": bounds.west + // }, + // "bottom_right": { + // "lat": bounds.south, + // "lon": bounds.east + // } + // } + // } + // } + // }); + // } + // }, [bounds]); function flattenLocationData(data) { if (!data) { @@ -80,33 +80,30 @@ export default function LocationFilterMap({ - - -
- {bounds ? [{"Northeast_Latitude" : "north"}, - {"Northeast_Longitude" : "east"}, - {"Southwest_Latitude" : "south"}, - {"Southwest_Longitude" : "west"}].map((item, index) => { - - return ( - - - - - ); - }) : null + {bounds ? [{ "Northeast_Latitude": "north" }, + { "Northeast_Longitude": "east" }, + { "Southwest_Latitude": "south" }, + { "Southwest_Longitude": "west" }].map((item, index) => { + + return ( + + + + + ); + }) : null }
diff --git a/frontend/src/components/filterFields/SideBar.jsx b/frontend/src/components/filterFields/SideBar.jsx index d141fb9fa1..43594c397e 100644 --- a/frontend/src/components/filterFields/SideBar.jsx +++ b/frontend/src/components/filterFields/SideBar.jsx @@ -96,7 +96,9 @@ function Sidebar({ onClick={() => { setFormFilters([]); handleClose(); - setFilterPanel(false); + // setFilterPanel(false); + // localStorage.removeItem("formData"); + window.location.reload(); }} > diff --git a/frontend/src/components/filterFields/SocialFilter.jsx b/frontend/src/components/filterFields/SocialFilter.jsx index 0fad54bcf5..c6da30ba69 100644 --- a/frontend/src/components/filterFields/SocialFilter.jsx +++ b/frontend/src/components/filterFields/SocialFilter.jsx @@ -36,8 +36,10 @@ export default function SocialFilter({ }) || []; const term = isChecked ? "terms" : "match"; - const filterId = "socialUnitRole"; - const field = "socialUnitRole"; + const filterId = "individualSocialUnits"; + const field = "individualSocialUnits"; + + const [ socialUnitName, setSocialUnitName ] = React.useState(null); return (
@@ -49,7 +51,7 @@ export default function SocialFilter({ ({ match: f.query })), + filter: filterQueries.map(f => f.query), must_not: mustNotQueries.map(f => f.query), must: nestedQuery }; diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index 0b03c1fa90..3352790f45 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -9,6 +9,7 @@ export default function EncounterSearch() { const [formFilters, setFormFilters] = useState([]); const [filterPanel, setFilterPanel] = useState(true); + const [refresh, setRefresh] = useState(false); const columns = [ { name: "Encounter ID", selector: "id" }, @@ -69,6 +70,7 @@ export default function EncounterSearch() { setFilterPanel={setFilterPanel} updateFilters={Function.prototype} schemas={schemas} + setRefresh={setRefresh} />
); From 806d1715ca213099cb27668089a3238c87e1c0fe Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 19 Jul 2024 13:08:40 -0600 Subject: [PATCH 008/102] first draft, but its a start --- search.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 search.md diff --git a/search.md b/search.md new file mode 100644 index 0000000000..b65ce0dca4 --- /dev/null +++ b/search.md @@ -0,0 +1,41 @@ +# Search in Wildbook + +## Overview + +Searching on Wildbook is handled by [OpenSearch](https://opensearch.org), which runs on its own docker container. Wildbook uses the +[OpenSearch.java](src/main/java/org/ecocean/OpenSearch.java) class and methods within the [Base.java](src/main/java/org/ecocean/Base.java) class (and subclasses) to +handle the indexing. + +Searching on Wildbook indices is done through the [Search API servlet](src/main/java/org/ecocean/api/SearchApi.java) by POSTing OpenSearch Query JSON. + +## Indexing details + +Indexing of classes (currently only Encounters) are done in a variety of ways, with the goal of index syncing being fully automatic. + +1. Manual index creation / syncing - primarily intended for first pass at indexing on legacy data +2. Indexing as object persistence happens, via hooks on the DataNucleus internals using [LifecycleListener](src/main/java/org/ecocean/WildbookLifecycleListener.java), as well as +hooks within deletion methods to un-index removed objects +3. A background indexing mechanism which regularly compares indices to actual data to catch mis-indexed objects + +### Caveats and considerations + +- Class indexes contain data which rely on _other classes_ for information; for example, indexing an Encounter will reference that encounter's MarkedIndividual to get search fields. +This necessitates the need for _cascading index triggers_ -- in this example, when a MarkedIindividual changes, it must also trigger re-indexing of all Encounters which reference that individual. This is to be handled by the "deep" methods (e.g. `indexDeep()`) on objects. **It is currently incomplete and will develop as other classes gain support for indexing.** This means that some values will not change within indexes until the background indexing happens. (In our example, the encounter index will not reflect the Individual change until the background index catches up.) + +- Background indexing is built to rely heavily on a `version` value within the class, in order to index only what "has changed". Most code in Wildbook is already making the change to this +field, but there are places where it is neglected. These will be fixed as discovered. Note: this _does not_ affect the persistence-triggered indexing (2 above). + +## Querying the API and pagination + +Getting results back from the search API will be "paged" with a subset of results when there are a large number of matches. This paging is controlled with the `from` and `size` +parameters. Notably, paging is done in OpenSearch via [PIT (point-in-time) functionality](https://opensearch.org/docs/latest/search-plugins/searching-data/point-in-time/), which is +the preferred method of paginating. + +The query (and thus results) are done against a sort of "snapshot" of the index, that is frozen in time. This has numerous benefits including efficiency and preventing "drifting" +pages and results (if the index changes while the user is paging through the results). It also means the PIT snapshot is showing data as it was at the time of being frozen. This can +mean stale results, but that is really more of a benefit, as it means the results *do not change* as the user pages. + +The way Wildbook handles PIT create and expiration is a little experimental right now. It can also be controlled via parameters on the query itself. The details and process should +be monitored in real-world use so default parameters can be tweaked as needed. + +The _total results count_ can be found in the header on any search results as `X-Wildbook-Total-Hits: 12345`. From ad57966137c494d976437a13509d49edaf6f3a24 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 19 Jul 2024 17:52:56 -0600 Subject: [PATCH 009/102] store search queries --- src/main/java/org/ecocean/OpenSearch.java | 41 ++++++++++++++++++++ src/main/java/org/ecocean/api/SearchApi.java | 15 +++---- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/ecocean/OpenSearch.java b/src/main/java/org/ecocean/OpenSearch.java index 66e1352450..4320a494ad 100644 --- a/src/main/java/org/ecocean/OpenSearch.java +++ b/src/main/java/org/ecocean/OpenSearch.java @@ -62,6 +62,7 @@ public class OpenSearch { public static String[] VALID_INDICES = { "encounter", "individual", "occurrence" }; public static int BACKGROUND_DELAY_MINUTES = 20; public static int BACKGROUND_SLICE_SIZE = 1000; + public static String QUERY_STORAGE_DIR = "/tmp"; // FIXME private int pitRetry = 0; @@ -525,6 +526,19 @@ public Long getIndexTimestamp(Shepherd myShepherd, String indexName) { return SystemValue.getLong(myShepherd, INDEX_TIMESTAMP_PREFIX + indexName); } + public static JSONObject querySanitize(JSONObject query, User user) { + if ((query == null) || (user == null)) return query; + JSONObject newQuery = new JSONObject(query.toString()); + try { + JSONArray filter = newQuery.getJSONObject("query").getJSONObject("bool").getJSONArray( + "filter"); + filter.put(new JSONObject("{\"match\": {\"viewUsers\": \"" + user.getId() + "\"}}")); + } catch (Exception ex) { + System.out.println("OpenSearch.querySanitize() failed to find filter element: " + ex); + } + return newQuery; + } + // TODO right now this respects index timestamp and only indexes objects with versions > timestamp. // probably want to make an option to index everything and ignore version/timestamp. public void indexAll(Shepherd myShepherd, Base obj) @@ -572,4 +586,31 @@ public void indexAll(Shepherd myShepherd, Base obj) System.out.println("OpenSearch.indexAll() [" + (System.currentTimeMillis() - initTime) + "] completed indexing " + indexName); } + + public static String queryStoragePath(String id) { + return QUERY_STORAGE_DIR + "/OpenSearch-query-" + id + ".json"; + } + + public static String queryStore(JSONObject query) { + if (query == null) return null; + String id = Util.generateUUID(); + try { + Util.writeToFile(query.toString(), queryStoragePath(id)); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + return id; + } + + public static JSONObject queryLoad(String id) { + if (id == null) return null; + try { + String jsonData = Util.readFromFile(queryStoragePath(id)); + return new JSONObject(jsonData); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } } diff --git a/src/main/java/org/ecocean/api/SearchApi.java b/src/main/java/org/ecocean/api/SearchApi.java index 8e64a4c1f9..0a8e6f6279 100644 --- a/src/main/java/org/ecocean/api/SearchApi.java +++ b/src/main/java/org/ecocean/api/SearchApi.java @@ -50,15 +50,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) try { numFrom = Integer.parseInt(fromStr); } catch (Exception ex) {} try { pageSize = Integer.parseInt(sizeStr); } catch (Exception ex) {} JSONObject query = ServletUtilities.jsonFromHttpServletRequest(request); - try { - JSONArray filter = query.getJSONObject("query").getJSONObject( - "bool").getJSONArray("filter"); - filter.put(new JSONObject("{\"match\": {\"viewUsers\": \"" + - currentUser.getId() + "\"}}")); - } catch (Exception ex) { - System.out.println("SearchApi failed to find filter element: " + ex); - } - System.out.println("SearchApi query=" + query); + // we store this *before* we sanitize + String searchQueryId = OpenSearch.queryStore(query); + query = OpenSearch.querySanitize(query, currentUser); + System.out.println("SearchApi (sanitized) query=" + query); OpenSearch os = new OpenSearch(); try { @@ -86,9 +81,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) hitsArr.put(doc); } response.setHeader("X-Wildbook-Total-Hits", Integer.toString(totalHits)); + response.setHeader("X-Wildbook-Search-Query-Id", searchQueryId); // response.setHeader("X-Wildbook-Scroll-Id", scrollId); response.setStatus(200); res.put("success", true); + res.put("searchQueryId", searchQueryId); res.put("hits", hitsArr); } catch (IOException ex) { response.setStatus(500); From 457d0091b90257590f264c58bdc1fce66ba39baf Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 19 Jul 2024 17:53:59 -0600 Subject: [PATCH 010/102] use stored search query (alpha) --- .../org/ecocean/EncounterQueryProcessor.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/main/java/org/ecocean/EncounterQueryProcessor.java b/src/main/java/org/ecocean/EncounterQueryProcessor.java index ee61005bc1..4258dbf612 100644 --- a/src/main/java/org/ecocean/EncounterQueryProcessor.java +++ b/src/main/java/org/ecocean/EncounterQueryProcessor.java @@ -27,6 +27,8 @@ import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; +import org.json.JSONArray; +import org.json.JSONObject; public class EncounterQueryProcessor extends QueryProcessor { private static final String SELECT_FROM_ORG_ECOCEAN_ENCOUNTER_WHERE = @@ -1620,6 +1622,63 @@ public static EncounterQueryResult processQuery(Shepherd myShepherd, HttpServlet // Query query=myShepherd.getPM().newQuery(encClass); // if(!order.equals("")){query.setOrdering(order);} + String searchQueryId = request.getParameter("searchQueryId"); + if (searchQueryId != null) { + User user = myShepherd.getUser(currentUser); + if (user == null) + return new EncounterQueryResult(rEncounters, "must be logged in", + "OpenSearch id " + searchQueryId); + JSONObject searchQuery = OpenSearch.queryLoad(searchQueryId); + if (searchQuery == null) + return new EncounterQueryResult(rEncounters, "searchQuery not found", + "OpenSearch id " + searchQueryId); + JSONObject sanitized = OpenSearch.querySanitize(searchQuery, user); + OpenSearch os = new OpenSearch(); + String indexName = "encounter"; + int numFrom = 0; + int pageSize = 1000; + String sort = "_id"; // TODO make this respect param? + String sortOrder = "asc"; + try { + os.deletePit(indexName); + JSONObject queryRes = os.queryPit(indexName, sanitized, numFrom, pageSize, sort, + sortOrder); + JSONObject outerHits = queryRes.optJSONObject("hits"); + if (outerHits == null) { + System.out.println("could not find (outer) hits"); + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } + JSONArray hits = outerHits.optJSONArray("hits"); + if (hits == null) { + System.out.println("could not find hits"); + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } + for (int i = 0; i < hits.length(); i++) { + JSONObject h = hits.optJSONObject(i); + if (h == null) { + System.out.println("failed to parse hits[" + i + "]"); + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } + String hId = h.optString("_id", null); + if (hId == null) { + System.out.println("failed to parse _id from hits[" + i + "]"); + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } + Encounter enc = myShepherd.getEncounter(hId); + if (enc != null) rEncounters.add(enc); + } + } catch (Exception ex) { + ex.printStackTrace(); + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } + return new EncounterQueryResult(rEncounters, searchQuery.toString(), + "OpenSearch id " + searchQueryId); + } String filter = ""; StringBuffer prettyPrint = new StringBuffer(""); Map paramMap = new HashMap(); From 0545a59f78450457ab7d468281b068ef5951ca6a Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Fri, 19 Jul 2024 23:58:06 +0000 Subject: [PATCH 011/102] fix encounter data table links issue --- frontend/src/AuthenticatedSwitch.jsx | 6 +- frontend/src/UnAuthenticatedSwitch.jsx | 9 +- frontend/src/components/Chip.jsx | 16 +- frontend/src/components/DataTable.jsx | 22 +- frontend/src/components/FilterPanel.jsx | 255 +++++++++--------- .../src/components/Form/FormMeasurements.jsx | 72 +++-- frontend/src/components/Map.jsx | 1 - .../BiologicalSamplesAndAnalysesFilter.jsx | 16 +- .../components/filterFields/DateFilter.jsx | 16 +- .../filterFields/IdentityFilter.jsx | 84 +++--- .../filterFields/ImageLabelFilter.jsx | 55 ++-- .../filterFields/LocationFilter.jsx | 4 - .../filterFields/LocationFilterMap.jsx | 60 ++--- .../components/filterFields/SocialFilter.jsx | 21 +- frontend/src/pages/EncounterSearch.jsx | 21 +- frontend/src/pages/errorPages/NotFound.jsx | 6 +- .../src/pages/errorPages/Unauthorized.jsx | 5 +- 17 files changed, 390 insertions(+), 279 deletions(-) diff --git a/frontend/src/AuthenticatedSwitch.jsx b/frontend/src/AuthenticatedSwitch.jsx index cbc71f11ff..d6e67a66e0 100644 --- a/frontend/src/AuthenticatedSwitch.jsx +++ b/frontend/src/AuthenticatedSwitch.jsx @@ -15,6 +15,7 @@ export default function AuthenticatedSwitch({ showAlert, setShowAlert }) { const { isFetched, data, error } = useGetMe(); const username = data?.username; const avatar = data?.imageURL || "/react/images/Avatar.png"; + const [header, setHeader] = React.useState(true); return (
@@ -38,11 +39,12 @@ export default function AuthenticatedSwitch({ showAlert, setShowAlert }) {
@@ -51,7 +53,7 @@ export default function AuthenticatedSwitch({ showAlert, setShowAlert }) { } /> } /> } /> - } /> + } />
diff --git a/frontend/src/UnAuthenticatedSwitch.jsx b/frontend/src/UnAuthenticatedSwitch.jsx index 95a35e70f7..01db8a41f3 100644 --- a/frontend/src/UnAuthenticatedSwitch.jsx +++ b/frontend/src/UnAuthenticatedSwitch.jsx @@ -13,9 +13,11 @@ import ServerError from "./pages/errorPages/ServerError"; import BadRequest from "./pages/errorPages/BadRequest"; import About from "./About"; import EncounterSearch from "./pages/EncounterSearch"; +import { set } from "date-fns"; export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) { console.log("UnAuthenticatedSwitch", showAlert); + const [header, setHeader] = React.useState(true); return (
@@ -37,20 +39,21 @@ export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) {
} /> - } /> + } /> } /> } /> } /> - } /> + } />
diff --git a/frontend/src/components/Chip.jsx b/frontend/src/components/Chip.jsx index b472b20aad..eead7b44bf 100644 --- a/frontend/src/components/Chip.jsx +++ b/frontend/src/components/Chip.jsx @@ -1,9 +1,18 @@ import React from 'react'; function Chip({ text, children }) { + console.log('children', children); function renderFilter(filter) { const entries = []; - const { filterId, query } = filter; + const { clause, filterId, query } = filter; + if(clause === "nested") { + entries.push(`Nested filter: ${filterId}`); + } + if (query?.geo_bounding_box) { + const { top_left, bottom_right } = query.geo_bounding_box['pin.location']; + entries.push(`Location within bounding box: top_left: ${top_left.lat}, ${top_left.lon}, bottom_right: ${bottom_right.lat}, ${bottom_right.lon}`); + } + if (query?.range) { Object.entries(query.range).forEach(([key, range]) => { @@ -18,6 +27,11 @@ function Chip({ text, children }) { entries.push(`"${key}" matches "${value}"`); }); } + if (query?.exists) { + Object.entries(query.exists).forEach(([key, value]) => { + entries.push(`"${value}" exists`); + }); + } if (query?.term) { Object.entries(query.term).forEach(([key, value]) => { entries.push(`${key} is "${value}"`); diff --git a/frontend/src/components/DataTable.jsx b/frontend/src/components/DataTable.jsx index 2d9cbcb289..65a6b38b12 100644 --- a/frontend/src/components/DataTable.jsx +++ b/frontend/src/components/DataTable.jsx @@ -43,6 +43,7 @@ const MyDataTable = ({ style = {}, tabs = [], onSelectedRowsChange = () => { }, + onRowClicked = () => { }, }) => { const [data, setData] = useState([]); const [filterText, setFilterText] = useState(""); @@ -52,11 +53,28 @@ const MyDataTable = ({ const wrappedColumns = useMemo( () => columnNames.map((col) => { + if (col.selector === 'occurrenceId') { + return { + name: col.name.charAt(0).toUpperCase() + col.name.slice(1), + cell: (row) => {row[col.selector]}, + sortable: true, + }; + } else if (col.selector === 'individualId') { + return { + name: col.name.charAt(0).toUpperCase() + col.name.slice(1), + cell: (row) => {row[col.selector]}, + sortable: true, + }; + } else { return ({ name: col.name.charAt(0).toUpperCase() + col.name.slice(1), selector: (row) => row[col.selector], // Accessor function for the column data sortable: true, // Make the column sortable - }) + })} }), [columnNames], ); @@ -172,6 +190,8 @@ const MyDataTable = ({ conditionalRowStyles={conditionalRowStyles} selectableRows onSelectedRowsChange={onSelectedRowsChange} + pointerOnHover + onRowClicked={onRowClicked} selectableRowsHighlight /> diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index 7bd38075d7..18adbe0462 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -8,6 +8,7 @@ import ThemeContext from "../ThemeColorProvider"; import BrutalismButton from './BrutalismButton'; import useGetSiteSettings from '../models/useGetSiteSettings'; import FilterContext from '../FilterContextProvider'; +import { Col, Row } from 'react-bootstrap'; function setFilter(newFilter, formFilters, setFormFilters) { const matchingFilterIndex = formFilters.findIndex( @@ -27,15 +28,15 @@ export default function FilterPanel({ formFilters = [], setFormFilters = () => { }, setFilterPanel, - style={}, - setRefresh, + style = {}, + }) { const [selectedChoices, setSelectedChoices] = useState({}); const [tempFormFilters, setTempFormFilters] = useState(formFilters); - const { filters, updateFilter, resetFilters } = useContext(FilterContext); + // const { filters, updateFilter, resetFilters } = useContext(FilterContext); const { data } = useGetSiteSettings(); - + const handleFilterChange = filter => { console.log("Filter:", filter); // if (filter.selectedChoice) { @@ -100,146 +101,134 @@ export default function FilterPanel({ return ( - + -
- -
- - {safeSchemas.map(schema => { - return
{ - setClicked(schema.id); - }} - > - + + +
+ + {safeSchemas.map(schema => { + return
{ + setClicked(schema.id); }} > - - {" > "} -
- })} -
- { - setFormFilters(tempFormFilters); - setFilterPanel(false); - }} - > - APPLY - - { - setFormFilters([]); - setTempFormFilters([]); - // setFilterPanel(false); - // localStorage.removeItem("formData"); - window.location.reload(); - }}> - - RESET - -
- -
-
- { - safeSchemas.map(schema => { - return ( - - // schema.id === clicked && -
- + {" > "} +
+ })} +
+ { + setFormFilters(tempFormFilters); + setFilterPanel(false); + }} + > + APPLY + + { + setFormFilters([]); + setTempFormFilters([]); + // setFilterPanel(false); + // localStorage.removeItem("formData"); + window.location.reload(); + }}> + + RESET + +
+ +
+ + +
+ { + safeSchemas.map(schema => { + return ( + + // schema.id === clicked && +
-
- ); - } - )} - -
-
+ style={{ + display: schema.id === clicked ? 'block' : 'none', + width: '100%', + + }} + > + +
+ ); + } + )} + +
+ + {/*
*/} + ); } \ No newline at end of file diff --git a/frontend/src/components/Form/FormMeasurements.jsx b/frontend/src/components/Form/FormMeasurements.jsx index 578178f955..85e0f76af4 100644 --- a/frontend/src/components/Form/FormMeasurements.jsx +++ b/frontend/src/components/Form/FormMeasurements.jsx @@ -120,20 +120,21 @@ import React, { useState, useEffect } from 'react'; import { Form, Col, Row, Container } from 'react-bootstrap'; import { FormattedMessage } from 'react-intl'; +import { FormControl } from 'react-bootstrap'; function FormMeasurements({ data, onChange, field, filterId -}) { +}) { const [inputs, setInputs] = useState(data?.map(item => ({ type: item, operator: 'gte', value: '' }))); useEffect(() => { if (data) { const newInputs = data.map(item => ({ type: item, operator: 'gte', value: '' })); setInputs(newInputs); } - }, [data]); + }, [data]); const handleInputChange = (index, field, value) => { const updatedInputs = inputs.map((input, i) => { @@ -150,6 +151,27 @@ function FormMeasurements({ const updateQuery = (inputs) => { const must = inputs.filter(input => input.value !== '').map(input => { + const id = `${filterId}.${input.type}`; + onChange({ + filterId : id, + clause: "nested", + path: field, + query: { + "bool": { + "filter": [ + { + "match": { + [`${field}.type`]: input.type + }, + + "range": { + [`${field}.value`]: { [input.operator]: [input.value] } + } + } + ] + } + } + }) return { "match": { [`${field}.type`]: input.type @@ -161,18 +183,18 @@ function FormMeasurements({ }; }); - if (must.length > 0) { - onChange({ - filterId, - clause: "nested", - path: field, - query: { - "bool": { - "filter": must - } - } - }); - } + // if (must.length > 0) { + // onChange({ + // filterId, + // clause: "nested", + // path: field, + // query: { + // "bool": { + // "filter": must + // } + // } + // }); + // } }; return ( @@ -183,7 +205,7 @@ function FormMeasurements({ {input.type.charAt(0).toUpperCase() + input.type.slice(1)} - + = - - + handleInputChange(index, 'value', e.target.value)} + style={{ + width: "100px", + marginLeft: "10px", + marginRight: "10px" + }} + placeholder="Type Here" + onChange={(e) => { + console.log(e.target.value); + handleInputChange(index, 'value', e.target.value); + } + } + /> diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index 88511ba82f..7eb0181c35 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -144,7 +144,6 @@ const MapComponent = ({ zoom = 10, bounds, setBounds, - onChange }) => { const theme = useContext(ThemeContext); diff --git a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx index 1f17cad882..1202f830c3 100644 --- a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx +++ b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx @@ -18,6 +18,7 @@ export default function BiologicalSamplesAndAnalysesFilter({ const bioMeasurementOptions = Object.entries(data?.bioMeasurement || {}).map( item => item[0] ) || []; + const microSatelliteMarkerLoci = data?.loci || []; const [checkedState, setCheckedState] = useState({}); const [alleleLength, setAlleleLength] = React.useState(false); @@ -54,7 +55,7 @@ export default function BiologicalSamplesAndAnalysesFilter({ onChange={() => { setIsChecked(!isChecked); onChange({ - filterId: "biologicalSampleId", + filterId: `biologicalSampleId`, clause: "must_not", query: { "exists": { @@ -113,17 +114,8 @@ export default function BiologicalSamplesAndAnalysesFilter({ } } /> - { - setLength(e.target.value); - }} - value={length} - - />
+ +
diff --git a/frontend/src/components/filterFields/DateFilter.jsx b/frontend/src/components/filterFields/DateFilter.jsx index 9b8d416844..79e8d78266 100644 --- a/frontend/src/components/filterFields/DateFilter.jsx +++ b/frontend/src/components/filterFields/DateFilter.jsx @@ -13,7 +13,7 @@ export default function DateFilter({ const [endDate, setEndDate] = useState(""); const [submissionStartDate, setSubmissionStartDate] = useState(''); const [submissionEndDate, setSubmissionEndDate] = useState(''); - const verbatimeventdateOptions = data?.verbatimeventdate?.map(data => { + const verbatimeventdateOptions = data?.verbatimEventDate?.map(data => { return { value: data, label: data @@ -39,16 +39,16 @@ export default function DateFilter({ if (startDate || endDate) { const query = { range: { - sightingDate: {} + date: {} } }; if (startDate) { - query.range.sightingDate.gte = startDate + "T00:00:00Z"; + query.range.date.gte = startDate + "T00:00:00Z"; } if (endDate) { - query.range.sightingDate.lte = endDate + "T23:59:59Z"; + query.range.date.lte = endDate + "T23:59:59Z"; } onChange({ filterId: "date", @@ -64,16 +64,16 @@ export default function DateFilter({ if (submissionStartDate || submissionEndDate) { const query = { range: { - submissionDate: {} + dateSubmitted: {} } }; if (submissionStartDate) { - query.range.submissionDate.gte = submissionStartDate + "T00:00:00Z"; + query.range.dateSubmitted.gte = submissionStartDate + "T00:00:00Z"; } if (submissionEndDate) { - query.range.submissionDate.lte = submissionEndDate + "T23:59:59Z"; + query.range.dateSubmitted.lte = submissionEndDate + "T23:59:59Z"; } onChange({ filterId: "dateSubmitted", @@ -85,7 +85,7 @@ export default function DateFilter({ } return ( -
+

diff --git a/frontend/src/components/filterFields/IdentityFilter.jsx b/frontend/src/components/filterFields/IdentityFilter.jsx index 288020b9f8..e68804ae72 100644 --- a/frontend/src/components/filterFields/IdentityFilter.jsx +++ b/frontend/src/components/filterFields/IdentityFilter.jsx @@ -16,17 +16,13 @@ export default function IdentityFilter({ const [isChecked1, setIsChecked1] = React.useState(false); const [isChecked2, setIsChecked2] = React.useState(false); const { filters, updateFilter } = useContext(FilterContext); - + return (

-
- - -
{ setIsChecked1(!isChecked1); - + // onChange({ // filterId: "individualNumberEncounters", // clause: "filter", @@ -47,60 +43,88 @@ export default function IdentityFilter({ }} /> - { - setIsChecked1(!isChecked1); - console.log("e.target.value", e.target.value); - // updateFilter("individualNumberEncounters", isChecked1); - if(isChecked1){ + if (isChecked1 && e.target.value) { + console.log("checked and ", e.target.value); onChange({ - filterId: "individualNumberEncounters", - clause: "filter", - query: { - "range": { - "individualNumberEncounters": {"gte" :e.target.value} - } - }, - }); + filterId: "individualNumberEncounters", + clause: "filter", + query: { + "range": { + "individualNumberEncounters": { "gte": e.target.value } + } + }, + }); } }} disabled={!isChecked1} /> - - - + + +
+ { setIsChecked2(!isChecked2); - // updateFilter("hello", isChecked2); - if(isChecked2){ + if (e.target.checked) { + console.log("checked and ", e.target.value); onChange({ filterId: "individualId", clause: "filter", query: { - "match": { - "individualId": null + "exists": { + "field": "individualId" } }, }) - - }}} + + } + }} /> + + {/* { + const newChecked = e.target.checked; + if(newChecked){ + onChange({ + filterId: "individualId", + clause: "filter", + query: { + "match": { + "individualId": null + } + } + }); + } else { + onChange({ + filterId: "individualId", + clause: "filter", + query: {} + }); + } + }} +/> */} + { + console.log("isChecked_photo", isChecked_photo); + if(isChecked_photo){ + onChange({ + filterId: "numberMediaAssets", + clause: "filter", + query: { + "range": { + "numberMediaAssets": { + "gte": 1 + } + } + }, + }) } + // else { + // onChange({ + // filterId: "numberMediaAssets", + // clause: "filter", + // query: {} + // }); + // } + }, [isChecked_photo]); + return (
@@ -100,17 +123,7 @@ export default function ImageLabelFilter({ checked={isChecked_photo} onChange={() => { setIsChecked_photo(!isChecked_photo); - onChange({ - filterId: "numberMediaAssets", - clause: "filter", - query: { - "range": { - "numberMediaAssets": { - "gte": 1 - } - } - }, - }) + }} /> @@ -132,9 +145,17 @@ export default function ImageLabelFilter({ />
+ - + /> */}
); } \ No newline at end of file diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index 3352790f45..efe96b4026 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -12,16 +12,17 @@ export default function EncounterSearch() { const [refresh, setRefresh] = useState(false); const columns = [ - { name: "Encounter ID", selector: "id" }, + { name: "Individual ID", selector: "individualId" }, + // { name: "Encounter ID", selector: "id" }, { name: "Sighting ID", selector: "occurrenceId" }, - { name: "Alternative ID", selector: "otherCatalogNumbers" }, + { name: "Alternative ID", selector: "otherCatalogNumbers" }, { name: "Created Date", selector: "date" }, { name: "Location ID", selector: "locationId" }, { name: "Species", selector: "taxonomy" }, { name: "Submitter", selector: "submitters" }, - { name: "Date Submitted", selector: "dateSubmitted" }, - { name: "Individual ID", selector: "individualId" }, - { name: "Number Annotations", selector: "numberAnnotations" }, + { name: "Date Submitted", selector: "dateSubmitted" }, + + { name: "Number Annotations", selector: "numberAnnotations" }, ]; const schemas = useEncounterSearchSchemas(); @@ -41,7 +42,7 @@ export default function EncounterSearch() { const encounters = encounterData?.results || []; const totalEncounters = encounterData?.resultCount || 0; const tabs = [ - "Project Management : /encounters/projectManagement.jsp", + "Project Management : /encounters/projectManagement.jsp", "Matching Images/Videos : /encounters/thumbnailSearchResults.jsp", "Mapped Results : /encounters/mappedSearchResults.jsp", "Results Calendar : /xcalendar/calendar.jsp", @@ -78,7 +79,7 @@ export default function EncounterSearch() { }} title="Encounters Search Results" columnNames={columns} - tabs = {tabs} + tabs={tabs} tableData={encounters} totalItems={totalEncounters} page={page} @@ -86,8 +87,14 @@ export default function EncounterSearch() { onPageChange={setPage} onPerPageChange={setPerPage} loading={false} + onRowClicked={(row) => { + console.log("Row Clicked: ", row); + const url = `/encounters/encounter.jsp?number=${row.id}`; + window.location.href = url; + }} onSelectedRowsChange={(selectedRows) => { console.log("Selected Rows: ", selectedRows); + }} /> Date: Mon, 22 Jul 2024 18:39:45 +0000 Subject: [PATCH 012/102] get key from property file, cancel checkbox filters --- frontend/src/components/Chip.jsx | 2 +- frontend/src/components/FilterPanel.jsx | 16 +++++--- frontend/src/components/Map.jsx | 37 ++++++++++--------- .../BiologicalSamplesAndAnalysesFilter.jsx | 4 +- .../filterFields/IdentityFilter.jsx | 32 +++++++++------- .../filterFields/ImageLabelFilter.jsx | 13 ++----- .../filterFields/LocationFilterMap.jsx | 2 +- .../ecocean/servlet/JavascriptGlobals.java | 4 +- 8 files changed, 61 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/Chip.jsx b/frontend/src/components/Chip.jsx index eead7b44bf..685eed16ad 100644 --- a/frontend/src/components/Chip.jsx +++ b/frontend/src/components/Chip.jsx @@ -9,7 +9,7 @@ function Chip({ text, children }) { entries.push(`Nested filter: ${filterId}`); } if (query?.geo_bounding_box) { - const { top_left, bottom_right } = query.geo_bounding_box['pin.location']; + const { top_left, bottom_right } = query.geo_bounding_box['locationGeoPoint']; entries.push(`Location within bounding box: top_left: ${top_left.lat}, ${top_left.lon}, bottom_right: ${bottom_right.lat}, ${bottom_right.lon}`); } diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index 18adbe0462..76d6132683 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -36,16 +36,22 @@ export default function FilterPanel({ // const { filters, updateFilter, resetFilters } = useContext(FilterContext); const { data } = useGetSiteSettings(); - - const handleFilterChange = filter => { + + const handleFilterChange = (filter = null, remove) => { console.log("Filter:", filter); + + if (remove) { + console.log("Remove:", remove); + const updatedFilters = tempFormFilters.filter(filter => filter.filterId !== remove); + console.log("Updated Filters:", updatedFilters); + setTempFormFilters(updatedFilters); + } else setFilter(filter, tempFormFilters, setTempFormFilters); // if (filter.selectedChoice) { // setSelectedChoices({ // ...selectedChoices, // [filter.filterId]: filter.selectedChoice, // }); // } - setFilter(filter, tempFormFilters, setTempFormFilters); }; const clearFilter = filterId => { const newFormFilters = formFilters.filter( @@ -110,7 +116,7 @@ export default function FilterPanel({ variant="h1" id="ENCOUNTER_SEARCH_FILTERS" /> - +
- {/*
*/} + {/*
*/} ); diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index 7eb0181c35..fa37e41528 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -137,16 +137,16 @@ import React, { useState, useRef, useContext, useEffect } from 'react'; import GoogleMapReact from 'google-map-react'; import { Button } from 'react-bootstrap'; import BrutalismButton from './BrutalismButton'; -import ThemeContext from '../ThemeColorProvider'; +import ThemeContext from '../ThemeColorProvider'; -const MapComponent = ({ - center, +const MapComponent = ({ + center, zoom = 10, bounds, setBounds, }) => { const theme = useContext(ThemeContext); - + const [rectangle, setRectangle] = useState(null); const drawingRef = useRef(false); const [isDrawing, setIsDrawing] = useState(false); @@ -159,9 +159,9 @@ const MapComponent = ({ fillColor: '#FF0000', fillOpacity: 0.35, }); - + setRectangle(rect); - + maps.event.addListener(map, 'mousedown', (e) => { if (drawingRef.current) { const initialBounds = { @@ -173,7 +173,7 @@ const MapComponent = ({ rect.setMap(map); rect.setBounds(initialBounds); map.setOptions({ draggable: false }); - + const mouseMoveHandler = (ev) => { const updatedBounds = { north: Math.max(initialBounds.north, ev.latLng.lat()), @@ -184,7 +184,7 @@ const MapComponent = ({ rect.setBounds(updatedBounds); }; const moveListener = maps.event.addListener(map, 'mousemove', mouseMoveHandler); - + const mouseUpHandler = () => { drawingRef.current = false; setIsDrawing(false); @@ -192,23 +192,26 @@ const MapComponent = ({ maps.event.removeListener(moveListener); setBounds(rect.getBounds().toJSON()); map.fitBounds(rect.getBounds(), { - left: 50, - right: 50, - top: 50, - bottom: 50 + left: 50, + right: 50, + top: 50, + bottom: 50 }); }; document.addEventListener('mouseup', mouseUpHandler, { once: true }); } }); }; - + const toggleDrawing = () => { if (rectangle) { - rectangle.setMap(null); + rectangle.setMap(null); } drawingRef.current = !drawingRef.current; - }; + }; + + const key = window?.wildbookGlobals?.gtmKey || ""; + console.log("Key:", key); return (
@@ -217,7 +220,7 @@ const MapComponent = ({ toggleDrawing(); setIsDrawing(!isDrawing); }} - backgroundColor= {theme.primaryColors.primary700} + backgroundColor={theme.primaryColors.primary700} borderColor={theme.primaryColors.primary700} color='white' style={{ position: 'absolute', zIndex: 2, width: "100px" }} @@ -226,7 +229,7 @@ const MapComponent = ({ {drawingRef.current ? 'Drawing' : 'Draw'} { setIsChecked(!isChecked); + + onChange({ filterId: `biologicalSampleId`, - clause: "must_not", + clause: "must", query: { "exists": { "field": "tissueSampleIds" diff --git a/frontend/src/components/filterFields/IdentityFilter.jsx b/frontend/src/components/filterFields/IdentityFilter.jsx index e68804ae72..f0a08deb10 100644 --- a/frontend/src/components/filterFields/IdentityFilter.jsx +++ b/frontend/src/components/filterFields/IdentityFilter.jsx @@ -15,7 +15,7 @@ export default function IdentityFilter({ const includeEncounters = const [isChecked1, setIsChecked1] = React.useState(false); const [isChecked2, setIsChecked2] = React.useState(false); - const { filters, updateFilter } = useContext(FilterContext); + const [times, setTimes] = React.useState(0); return (
@@ -30,16 +30,19 @@ export default function IdentityFilter({ checked={isChecked1} onChange={(e) => { setIsChecked1(!isChecked1); - - // onChange({ - // filterId: "individualNumberEncounters", - // clause: "filter", - // query: { - // "match": { - // "individualID": null - // } - // }, - // }) + if (e.target.checked && times) { + onChange({ + filterId: "individualNumberEncounters", + clause: "filter", + query: { + "range": { + "individualNumberEncounters": { "gte": times } + } + }, + }); + }else { + onChange(null, "individualNumberEncounters"); + } }} /> @@ -50,11 +53,10 @@ export default function IdentityFilter({ marginLeft: "10px", marginRight: "10px" }} - // checked={filters.individualNumberEncounters} placeholder="Type Here" onChange={(e) => { + setTimes(e.target.value); if (isChecked1 && e.target.value) { - console.log("checked and ", e.target.value); onChange({ filterId: "individualNumberEncounters", clause: "filter", @@ -65,6 +67,7 @@ export default function IdentityFilter({ }, }); } + }} disabled={!isChecked1} /> @@ -93,7 +96,8 @@ export default function IdentityFilter({ } }, }) - + }else { + onChange(null, "individualId"); } }} /> diff --git a/frontend/src/components/filterFields/ImageLabelFilter.jsx b/frontend/src/components/filterFields/ImageLabelFilter.jsx index 08f40e90a7..a1e5268d28 100644 --- a/frontend/src/components/filterFields/ImageLabelFilter.jsx +++ b/frontend/src/components/filterFields/ImageLabelFilter.jsx @@ -56,14 +56,6 @@ export default function ImageLabelFilter({ } } )) - const testOptions = (data?.labeledKeyword[labelledKeyword] || []).map( - item => { - return { - value: item, - label: item - } - } - ) }, [labelledKeyword]) const viewPointOptions = data?.annotationViewpoint?.map(item => { @@ -91,7 +83,7 @@ export default function ImageLabelFilter({ useEffect(() => { console.log("isChecked_photo", isChecked_photo); if(isChecked_photo){ - onChange({ + onChange({ filterId: "numberMediaAssets", clause: "filter", query: { @@ -102,6 +94,9 @@ export default function ImageLabelFilter({ } }, }) } + else { + onChange(null, "numberMediaAssets"); + } // else { // onChange({ // filterId: "numberMediaAssets", diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 78422828f9..9cc097289a 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -19,7 +19,7 @@ export default function LocationFilterMap({ clause: "filter", query: { "geo_bounding_box": { - "pin.location": { + "locationGeoPoint": { "top_left": { "lat": bounds.north, "lon": bounds.west diff --git a/src/main/java/org/ecocean/servlet/JavascriptGlobals.java b/src/main/java/org/ecocean/servlet/JavascriptGlobals.java index bf9c82c993..7552ae1580 100644 --- a/src/main/java/org/ecocean/servlet/JavascriptGlobals.java +++ b/src/main/java/org/ecocean/servlet/JavascriptGlobals.java @@ -15,7 +15,7 @@ import java.util.*; import com.google.gson.Gson; -import org.ecocean.CommonConfiguration; + import org.ecocean.identity.IBEISIA; import org.ecocean.security.SocialAuth; @@ -48,6 +48,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) String langCode = ServletUtilities.getLanguageCode(request); String gtmKey = CommonConfiguration.getGoogleTagManagerKey(context); String gaId = CommonConfiguration.getGoogleAnalyticsId(context); + String gMapKey = CommonConfiguration.getGoogleMapsKey(context); // Properties props = new Properties(); // props = ShepherdProperties.getProperties("collaboration.properties", langCode, context); HashMap rtn = new HashMap(); @@ -132,6 +133,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) rtn.put("keywords", kw); rtn.put("gtmKey", gtmKey); rtn.put("gaId", gaId); + rtn.put("gMapKey", gMapKey); // this might throw an exception in various ways, so we swallow them here try { From 0f84a5de18d5ad3b9973fa15f5619395858109b9 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Mon, 22 Jul 2024 12:45:56 -0600 Subject: [PATCH 013/102] nested for metalTags --- src/main/java/org/ecocean/Encounter.java | 1 + src/main/webapp/appadmin/opensearchSync.jsp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ecocean/Encounter.java b/src/main/java/org/ecocean/Encounter.java index 1861896b2a..802492f6e4 100644 --- a/src/main/java/org/ecocean/Encounter.java +++ b/src/main/java/org/ecocean/Encounter.java @@ -4518,6 +4518,7 @@ public org.json.JSONObject opensearchMapping() { map.put("organizations", new org.json.JSONObject("{\"type\": \"keyword\"}")); // https://stackoverflow.com/questions/68760699/matching-documents-where-multiple-fields-match-in-an-array-of-objects map.put("measurements", new org.json.JSONObject("{\"type\": \"nested\"}")); + map.put("metalTags", new org.json.JSONObject("{\"type\": \"nested\"}")); map.put("locationGeoPoint", new org.json.JSONObject("{\"type\": \"geo_point\"}")); return map; } diff --git a/src/main/webapp/appadmin/opensearchSync.jsp b/src/main/webapp/appadmin/opensearchSync.jsp index 928a3bf2a5..ca9e6cfbea 100644 --- a/src/main/webapp/appadmin/opensearchSync.jsp +++ b/src/main/webapp/appadmin/opensearchSync.jsp @@ -7,6 +7,7 @@ org.ecocean.* <% +System.out.println("opensearchSync.jsp begun..."); boolean resetIndex = Util.requestParameterSet("resetIndex"); @@ -46,7 +47,7 @@ if (forceNum > 0) { if (!Util.stringExists(enc.getId())) continue; //System.out.println(enc.getId() + ": " + enc.getVersion()); enc.opensearchIndex(); - if (ct % 100 == 0) System.out.println("count " + ct); + if (ct % 100 == 0) System.out.println("opensearchSync.jsp: count " + ct); ct++; if (ct > forceNum) break; } From 598e4419f9003028e23782b33984fbef6199ee79 Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Tue, 23 Jul 2024 00:25:24 +0000 Subject: [PATCH 014/102] fix map display issue, add cancel function to select filters --- frontend/src/components/BrutalismButton.jsx | 6 +- frontend/src/components/Chip.jsx | 1 - frontend/src/components/FilterPanel.jsx | 8 +- .../src/components/Form/DynamicInputs.jsx | 1 + .../src/components/Form/FormMeasurements.jsx | 136 ++--------------- frontend/src/components/Map.jsx | 144 +----------------- frontend/src/components/MultiSelect.jsx | 25 +-- .../filterFields/ImageLabelFilter.jsx | 4 - .../filterFields/LocationFilterMap.jsx | 10 +- .../filterFields/LocationFilterText.jsx | 17 ++- .../ObservationAttributeFilter.jsx | 6 +- .../components/filterFields/TagsFilter.jsx | 30 ++-- .../models/encounters/useFilterEncounters.js | 94 +++++++++--- frontend/src/pages/EncounterSearch.jsx | 4 +- 14 files changed, 165 insertions(+), 321 deletions(-) diff --git a/frontend/src/components/BrutalismButton.jsx b/frontend/src/components/BrutalismButton.jsx index aecaa31a31..44c96bc023 100644 --- a/frontend/src/components/BrutalismButton.jsx +++ b/frontend/src/components/BrutalismButton.jsx @@ -12,6 +12,7 @@ export default function BrutalismButton({ className = "", children, style, + noArrow, ...rest }) { const [isHovered, setIsHovered] = useState(false); @@ -67,7 +68,9 @@ export default function BrutalismButton({ > {children} - + } ); diff --git a/frontend/src/components/Chip.jsx b/frontend/src/components/Chip.jsx index 685eed16ad..fd556770c7 100644 --- a/frontend/src/components/Chip.jsx +++ b/frontend/src/components/Chip.jsx @@ -1,7 +1,6 @@ import React from 'react'; function Chip({ text, children }) { - console.log('children', children); function renderFilter(filter) { const entries = []; const { clause, filterId, query } = filter; diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index 76d6132683..d1541ed04e 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -116,9 +116,9 @@ export default function FilterPanel({ variant="h1" id="ENCOUNTER_SEARCH_FILTERS" /> - + - +
{ safeSchemas.map(schema => { diff --git a/frontend/src/components/Form/DynamicInputs.jsx b/frontend/src/components/Form/DynamicInputs.jsx index c60ece8d21..b1ce39967d 100644 --- a/frontend/src/components/Form/DynamicInputs.jsx +++ b/frontend/src/components/Form/DynamicInputs.jsx @@ -65,6 +65,7 @@ function DynamicInputs({ }} borderColor="#fff" color="white" + noArrow backgroundColor="transparent" onClick={addInput} > diff --git a/frontend/src/components/Form/FormMeasurements.jsx b/frontend/src/components/Form/FormMeasurements.jsx index 85e0f76af4..4929d794e7 100644 --- a/frontend/src/components/Form/FormMeasurements.jsx +++ b/frontend/src/components/Form/FormMeasurements.jsx @@ -1,121 +1,3 @@ -// import React, { useState } from 'react'; -// import { Form, Col, Row, Container } from 'react-bootstrap'; - -// function ObservationInputs({ -// data, -// onChange, -// field, -// filterId -// }) { -// const [inputs, setInputs] = useState(data.map(item => ({ type: item, operator: 'gte', value: '' }))); - -// const handleInputChange = (index, field, value) => { -// const updatedInputs = inputs.map((input, i) => { -// if (i === index) { -// return { ...input, [field]: value }; -// } -// return input; -// }); -// setInputs(updatedInputs); -// if (field === 'value' && value) { -// updateQuery(updatedInputs); -// } -// }; - -// const updateQuery = (inputs) => { -// console.log("Query Updated:", inputs.filter(input => input.value)); -// // const must = inputs.filter(input => input.value).map(input => ({ -// // range: { -// // [`${field}.${input.type}`]: { -// // [input.operator]: input.value -// // } -// // } -// // })); -// // console.log("Must:", must); -// // inputs.forEach(input => { -// // if (!input.value) { -// // return; -// // } -// // onChange({ -// // filterId: `${filterId}.${input.type}`, -// // clause: "filter", -// // query: { -// // // "range": { -// // // [`${field}.${input.type}`]: { -// // // [input.operator]: input.value -// // // } -// // // } -// // "bool": { -// // "must": must -// // } -// // } -// // }); -// // }) - - -// const must = inputs.filter(input => input.value !== '').map(input => ([ -// { -// "term": { -// [`measurements.type`]: input.type -// } -// }, -// { -// "range": { -// "measurements.value": { -// [input.operator]: input.value -// } -// } -// } -// ])).flat(); - -// if (must.length > 0) { -// onChange({ -// filterId, -// clause: "filter", -// query: { -// "bool": { -// "must": must -// } -// } -// }); -// } -// }; - -// }; - -// return ( -// -// {inputs.map((input, index) => ( -// -// -// {input.type.charAt(0).toUpperCase() + input.type.slice(1)} -// -// -// handleInputChange(index, 'operator', e.target.value)} -// > -// -// -// -// -// -// -// handleInputChange(index, 'value', e.target.value)} -// /> -// -// -// ))} -// -// ); -// } - -// export default ObservationInputs; import React, { useState, useEffect } from 'react'; import { Form, Col, Row, Container } from 'react-bootstrap'; @@ -150,22 +32,30 @@ function FormMeasurements({ }; const updateQuery = (inputs) => { - const must = inputs.filter(input => input.value !== '').map(input => { + const must = inputs.filter(input => input.value !== ''); + must.map(input => { + console.log("Input:", input); + const type_field = `${filterId}.type`; + const value_field = `${filterId}.value`; const id = `${filterId}.${input.type}`; + console.log(type_field, value_field, id); onChange({ - filterId : id, + filterId: id, clause: "nested", path: field, query: { "bool": { "filter": [ + { "match": { - [`${field}.type`]: input.type + [`${filterId}.type`]: input.type }, - + }, + + { "range": { - [`${field}.value`]: { [input.operator]: [input.value] } + [`${filterId}.value`]: { [input.operator]: input.value } } } ] diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index fa37e41528..3360dcde10 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -1,143 +1,9 @@ -// import React, { useState, useRef } from "react"; -// import GoogleMapReact from 'google-map-react'; -// import { Button } from 'react-bootstrap'; - -// export default function Map({ -// setBounds -// }) { -// const defaultProps = { -// center: { -// lat: 59.95, -// lng: 30.33 -// }, -// zoom: 5 -// }; - -// const [draggable, setDraggable] = useState(true); -// const rectangleRef = useRef(null); - -// let myMap; - -// const handleGoogleMapApi = ({ map, maps }) => { -// myMap = map; -// let moveListener; - -// console.log("draggable", draggable); - -// const clearRectangle = () => { -// if (rectangleRef.current) { -// rectangleRef.current.setMap(null); -// rectangleRef.current = null; -// } -// }; - -// const drawing = (e) => { -// if (!map.enableDrawing) { -// return; -// } -// let finalBounds = null; -// clearRectangle(); - -// if (map.isMoving) { -// console.log(`second click.`); -// map.isMoving = false; -// maps.event.removeListener(moveListener); -// setDraggable(true); -// return; -// } - -// map.isMoving = true; - -// const initialBounds = { -// north: e.latLng.lat(), -// south: e.latLng.lat(), -// east: e.latLng.lng(), -// west: e.latLng.lng() -// }; - -// const newRectangle = new maps.Rectangle({ -// bounds: initialBounds, -// fillColor: '#FF0000', -// fillOpacity: 0.35, -// strokeColor: '#FF0000', -// strokeWeight: 2, -// map: map, -// }); - -// rectangleRef.current = newRectangle; - -// if (newRectangle) { -// newRectangle.addListener('mouseup', () => { -// map.isMoving = false; -// map.enableDrawing = false; -// maps.event.removeListener(moveListener); -// setDraggable(true); -// const ne = finalBounds?.getNorthEast(); -// const sw = finalBounds?.getSouthWest(); -// setBounds({ -// north: ne?.lat(), -// south: sw?.lat(), -// east: ne?.lng(), -// west: sw?.lng() -// }); -// }); -// } - -// setDraggable(false); - -// const moveHandler = (e) => { -// if (!map.isMoving) return; -// if (!rectangleRef.current) return; -// const currentBounds = rectangleRef.current.getBounds(); -// currentBounds.extend({ -// lat: e.latLng.lat(), -// lng: e.latLng.lng(), -// }); -// finalBounds = currentBounds; -// rectangleRef.current.setBounds(currentBounds); -// }; -// moveListener = maps.event.addListener(map, 'mousemove', moveHandler); -// } - -// maps.event.addListener(map, 'mousedown', drawing); -// }; - -// return ( -//
- -// -// -// - -//
-// ); -// } - import React, { useState, useRef, useContext, useEffect } from 'react'; import GoogleMapReact from 'google-map-react'; -import { Button } from 'react-bootstrap'; import BrutalismButton from './BrutalismButton'; import ThemeContext from '../ThemeColorProvider'; +import { set } from 'lodash-es'; const MapComponent = ({ center, @@ -211,7 +77,6 @@ const MapComponent = ({ }; const key = window?.wildbookGlobals?.gtmKey || ""; - console.log("Key:", key); return (
@@ -219,14 +84,15 @@ const MapComponent = ({ onClick={() => { toggleDrawing(); setIsDrawing(!isDrawing); + setBounds(null); }} + noArrow backgroundColor={theme.primaryColors.primary700} borderColor={theme.primaryColors.primary700} color='white' - style={{ position: 'absolute', zIndex: 2, width: "100px" }} - disabled={isDrawing} + style={{ position: 'absolute', zIndex: 2, width: "100px", marginLeft: "10px" }} > - {drawingRef.current ? 'Drawing' : 'Draw'} + {drawingRef.current ? 'Cancel' : 'Draw'} - onChange({ - filterId: field, - clause: "filter", - query:{ - [term]: { - [field]: isMulti? e.map(item=> item.value) : e.value - } - } - }) + onChange={(e) =>{ + if(e?.target?.value || e.length > 0){ + onChange({ + filterId: field, + clause: "filter", + query:{ + [term]: { + [field]: isMulti? e.map(item=> item.value) : e.value + } + } + }) + }else { + onChange(null, field); + } + } } /> ); diff --git a/frontend/src/components/filterFields/ImageLabelFilter.jsx b/frontend/src/components/filterFields/ImageLabelFilter.jsx index a1e5268d28..5c6f7dc767 100644 --- a/frontend/src/components/filterFields/ImageLabelFilter.jsx +++ b/frontend/src/components/filterFields/ImageLabelFilter.jsx @@ -76,10 +76,6 @@ export default function ImageLabelFilter({ const [isChecked_photo, setIsChecked_photo] = React.useState(false); const [isChecked_keyword, setIsChecked_keyword] = React.useState(false); - const term = isChecked_keyword ? "terms" : "match"; - const field = "keywords"; - const filterId = "keywords"; - useEffect(() => { console.log("isChecked_photo", isChecked_photo); if(isChecked_photo){ diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 9cc097289a..055fc57caa 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -32,6 +32,8 @@ export default function LocationFilterMap({ } } }); + }else { + onChange(null, "locationId"); } }, [bounds]); @@ -92,8 +94,14 @@ export default function LocationFilterMap({ { + setBounds({ + ...bounds, + [Object.values(item)[0]]: e.target.value + }); + }} /> ); diff --git a/frontend/src/components/filterFields/LocationFilterText.jsx b/frontend/src/components/filterFields/LocationFilterText.jsx index f4b4d59402..059cb301cb 100644 --- a/frontend/src/components/filterFields/LocationFilterText.jsx +++ b/frontend/src/components/filterFields/LocationFilterText.jsx @@ -12,12 +12,19 @@ export default function LocationFilterText({ } ) { const { data } = useGetSiteSettings(); - const countries = data?.country.map(data => { + const countries = ["china", "canada"].map((item) => { return { - value: data, - label: data - } - }) || []; + value: item, + label: item + }; + } + ) || []; + // data?.country.map(data => { + // return { + // value: data, + // label: data + // } + // }) || []; return (
diff --git a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx index 9bfdf57b07..7a3ea4392d 100644 --- a/frontend/src/components/filterFields/ObservationAttributeFilter.jsx +++ b/frontend/src/components/filterFields/ObservationAttributeFilter.jsx @@ -43,7 +43,11 @@ export default function ObservationAttributeFilter( label: item }; }) || []; - const measurementsOptions = data?.measurement || []; + const measurementsOptions = [ + "WaterTemperature", + "Salinity" + ] + // data?.measurement || []; return (
diff --git a/frontend/src/components/filterFields/TagsFilter.jsx b/frontend/src/components/filterFields/TagsFilter.jsx index cad70885c3..bd28035674 100644 --- a/frontend/src/components/filterFields/TagsFilter.jsx +++ b/frontend/src/components/filterFields/TagsFilter.jsx @@ -2,8 +2,6 @@ import React from "react"; import Description from "../Form/Description"; import { FormattedMessage } from "react-intl"; import FormGroupText from "../Form/FormGroupText"; -import { Form, Row, Col } from "react-bootstrap"; -import FormDualInputs from "../Form/FormDualInputs"; import { FormGroup, FormLabel, FormControl } from "react-bootstrap"; @@ -36,18 +34,26 @@ export default function TagsFilter({ placeholder="Type Here" onChange={(e) => { onChange({ - filterId: "metalTag", - clause: "filter", + filterId: `metalTag.${location.label}`, + clause: "nested", + path: "metalTags", query: { - "bool" : { - "must": [ - {[field1]: location.label}, - {[field2]: e.target.value,} - ] - } + "bool": { + "filter": [ + { + "match": { + "metalTags.location": location.label + }, + }, + { + "match": { + "metalTags.number": e.target.value + } + } + ] + } } - - }); + }) }} /> diff --git a/frontend/src/models/encounters/useFilterEncounters.js b/frontend/src/models/encounters/useFilterEncounters.js index 1076f778ad..f7637f3cf0 100644 --- a/frontend/src/models/encounters/useFilterEncounters.js +++ b/frontend/src/models/encounters/useFilterEncounters.js @@ -61,36 +61,94 @@ // }); // } +// import { get, partition } from "lodash-es"; +// import useFetch from "../../hooks/useFetch"; +// import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; + +// export default function useFilterEncounters({ queries, params = {} }) { +// console.log("Queries:", queries); +// const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); +// const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); + +// const nestedQuery = nestedQueries.map(n => ({ +// nested: { +// path: n.path, +// query: { +// bool: { +// filter: n.query.bool.filter.map(f => ({ match: f })) +// } +// } +// } +// })); + +// const mustQueries = nonNestedQueries.filter(q => q.clause === "must"); + +// const boolQuery = { +// filter: filterQueries.map(f => f.query), +// must_not: mustNotQueries.map(f => f.query), +// must: mustQueries.map(f => f.query) , +// }; + +// const compositeQuery = { +// query: { +// bool: boolQuery, +// nested: nestedQuery +// } +// }; + +// return useFetch({ +// method: "post", +// queryKey: getEncounterFilterQueryKey(queries, params), +// url: "/search/encounter", +// data: compositeQuery, +// params: { +// sort: "date", +// size: 1, +// from: 3, +// ...params, +// }, +// dataAccessor: (result) => { +// const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); +// return { +// resultCount: parseInt(resultCountString, 10), +// results: get(result, ["data", "data", "hits"], []), +// }; +// }, +// queryOptions: { +// retry: 2, +// }, +// }); +// } + + import { get, partition } from "lodash-es"; import useFetch from "../../hooks/useFetch"; import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; -export default function useFilterEncounters({ queries, params = {} }) { +function buildQuery(queries) { const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); + const mustQueries = nonNestedQueries.filter(q => q.clause === "must"); const nestedQuery = nestedQueries.map(n => ({ nested: { path: n.path, - query: { - bool: { - filter: n.query.bool.filter.map(f => ({ match: f })) - } - } + query: n.query, } - })); + })); - const boolQuery = { + return { filter: filterQueries.map(f => f.query), must_not: mustNotQueries.map(f => f.query), - must: nestedQuery + must: [ ...nestedQuery] }; +} - const compositeQuery = { - query: { - bool: boolQuery - } - }; +export default function useFilterEncounters({ queries, params = {} }) { + // console.log("Queries:", queries); + + const boolQuery = buildQuery(queries); + const compositeQuery = { query: { bool: boolQuery } }; return useFetch({ method: "post", @@ -99,14 +157,14 @@ export default function useFilterEncounters({ queries, params = {} }) { data: compositeQuery, params: { sort: "date", - size: 1, - from: 3, + // size: 1, + // from: 3, ...params, }, dataAccessor: (result) => { - const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); + const resultCount = parseInt(get(result, ["data", "headers", "x-wildbook-total-hits"], "0"), 10); return { - resultCount: parseInt(resultCountString, 10), + resultCount, results: get(result, ["data", "data", "hits"], []), }; }, diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index efe96b4026..38c6d23a48 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -56,10 +56,10 @@ export default function EncounterSearch() { style={{ backgroundImage: "url('/react/images/encounter_search_background.png')", backgroundSize: "cover", - height: "800px", + minHeight: "800px", width: "100%", - overflow: "auto", padding: "20px", + backgroundAttachment: "fixed", }} > Date: Tue, 23 Jul 2024 13:16:23 -0600 Subject: [PATCH 015/102] bugfix --- src/main/java/org/ecocean/Encounter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/ecocean/Encounter.java b/src/main/java/org/ecocean/Encounter.java index 802492f6e4..485851ec32 100644 --- a/src/main/java/org/ecocean/Encounter.java +++ b/src/main/java/org/ecocean/Encounter.java @@ -3359,7 +3359,7 @@ public Measurement getMeasurement(String type) { public Map getBiologicalMeasurementsByType() { Map meas = new HashMap(); - for (MeasurementDesc mdesc : Util.findMeasurementDescs("en", "context0")) { + for (MeasurementDesc mdesc : Util.findBiologicalMeasurementDescs("en", "context0")) { BiologicalMeasurement bm = getBiologicalMeasurement(mdesc.getType()); if (bm != null) meas.put(mdesc.getType(), bm); } From 342616b5d2b0107c2ef5fa147ad4a2ca6ba7f8e2 Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Tue, 23 Jul 2024 20:42:49 +0000 Subject: [PATCH 016/102] update queries --- frontend/config-overrides.js | 1 - frontend/src/App.jsx | 1 - frontend/src/FilterContextProvider.jsx | 25 ---- frontend/src/FrontDesk.jsx | 1 - frontend/src/UnAuthenticatedSwitch.jsx | 7 - frontend/src/components/Chip.jsx | 17 +-- frontend/src/components/FilterPanel.jsx | 1 - .../src/components/Form/BioMeasurements.jsx | 100 ++++++++++++++ .../src/components/Form/DynamicInputs.jsx | 16 ++- .../src/components/Form/FormGroupText.jsx | 4 + .../src/components/Form/FormMeasurements.jsx | 69 +++++----- frontend/src/components/Map.jsx | 2 + .../src/components/fields/filters/AreaMap.jsx | 1 - .../BiologicalSamplesAndAnalysesFilter.jsx | 36 +++-- .../components/filterFields/DateFilter.jsx | 5 + .../filterFields/IdentityFilter.jsx | 1 - .../filterFields/ImageLabelFilter.jsx | 1 - .../filterFields/LocationFilterMap.jsx | 21 ++- .../filterFields/LocationFilterText.jsx | 19 +-- .../ObservationAttributeFilter.jsx | 8 +- .../src/components/filterFields/SideBar.jsx | 3 +- .../components/filterFields/TagsFilter.jsx | 21 ++- .../src/components/navBar/MergeMessages.jsx | 3 - .../models/encounters/useFilterEncounters.js | 123 +----------------- .../getCollaborationNotifications.js | 1 - frontend/src/pages/EncounterSearch.jsx | 1 - frontend/src/pages/Login.jsx | 1 - 27 files changed, 233 insertions(+), 256 deletions(-) create mode 100644 frontend/src/components/Form/BioMeasurements.jsx diff --git a/frontend/config-overrides.js b/frontend/config-overrides.js index 89255c059a..3ce1986cad 100644 --- a/frontend/config-overrides.js +++ b/frontend/config-overrides.js @@ -3,7 +3,6 @@ const webpack = require('webpack'); module.exports = function override(config, env) { if(env === 'production'){ config.devtool = 'source-map'; - console.log('source-map'); // config.devtool = 'cheap-module-source-map'; } config.plugins.push( diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 483f180edc..1d22bae8ab 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -40,7 +40,6 @@ function App() { const [filters, setFilters] = useState({}); const updateFilter = (filterName, value) => { - console.log("1111111111111111FilterName:", filterName); setFilters((prevFilters) => ({ ...prevFilters, [filterName]: value, diff --git a/frontend/src/FilterContextProvider.jsx b/frontend/src/FilterContextProvider.jsx index 58cb686c7c..aae9c3e807 100644 --- a/frontend/src/FilterContextProvider.jsx +++ b/frontend/src/FilterContextProvider.jsx @@ -1,28 +1,3 @@ -// import React, { createContext, useState } from 'react'; - -// const FilterContext = createContext(); - -// const FilterProvider = ({ children }) => { -// const [filters, setFilters] = useState({}); - -// const updateFilter = (filterName, value) => { -// console.log("1111111111111111FilterName:", filterName); -// console.log("2222222222222222Value:", value); -// setFilters(prevFilters => ({ -// ...prevFilters, -// [filterName]: value -// })); -// }; - - -// return ( -// -// {children} -// -// ); -// }; - -// export { FilterContext, FilterProvider }; import { createContext } from "react"; diff --git a/frontend/src/FrontDesk.jsx b/frontend/src/FrontDesk.jsx index 3b03685310..ea27e901e9 100644 --- a/frontend/src/FrontDesk.jsx +++ b/frontend/src/FrontDesk.jsx @@ -21,7 +21,6 @@ export default function FrontDesk() { const [loading, setLoading] = useState(true); const checkLoginStatus = () => { - console.log("Polling API..."); axios .head("/api/v3/user") .then((response) => { diff --git a/frontend/src/UnAuthenticatedSwitch.jsx b/frontend/src/UnAuthenticatedSwitch.jsx index 01db8a41f3..02934694eb 100644 --- a/frontend/src/UnAuthenticatedSwitch.jsx +++ b/frontend/src/UnAuthenticatedSwitch.jsx @@ -1,22 +1,15 @@ import React from "react"; import { Routes, Route } from "react-router-dom"; -import ErrorPage from "./pages/errorPages/ErrorPage"; import Login from "./pages/Login"; import Footer from "./components/Footer"; -import Home from "./pages/Home"; import AlertBanner from "./components/AlertBanner"; import UnAuthenticatedAppHeader from "./components/UnAuthenticatedAppHeader"; import NotFound from "./pages/errorPages/NotFound"; -import Forbidden from "./pages/errorPages/Forbidden"; import Unauthorized from "./pages/errorPages/Unauthorized"; -import ServerError from "./pages/errorPages/ServerError"; -import BadRequest from "./pages/errorPages/BadRequest"; import About from "./About"; import EncounterSearch from "./pages/EncounterSearch"; -import { set } from "date-fns"; export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) { - console.log("UnAuthenticatedSwitch", showAlert); const [header, setHeader] = React.useState(true); return ( diff --git a/frontend/src/components/Chip.jsx b/frontend/src/components/Chip.jsx index fd556770c7..c06a0dac2a 100644 --- a/frontend/src/components/Chip.jsx +++ b/frontend/src/components/Chip.jsx @@ -1,6 +1,7 @@ import React from 'react'; function Chip({ text, children }) { + console.log("Chip children", children); function renderFilter(filter) { const entries = []; const { clause, filterId, query } = filter; @@ -20,23 +21,19 @@ function Chip({ text, children }) { if (range.lte) parts.push(`to "${range.lte}"`); entries.push(`${key} ${parts.join(' ')}`); }); - } - if (query?.match) { + }else if (query?.match) { Object.entries(query.match).forEach(([key, value]) => { entries.push(`"${key}" matches "${value}"`); }); - } - if (query?.exists) { + }else if(query?.exists) { Object.entries(query.exists).forEach(([key, value]) => { entries.push(`"${value}" exists`); }); - } - if (query?.term) { + }else if (query?.term) { Object.entries(query.term).forEach(([key, value]) => { entries.push(`${key} is "${value}"`); }); - } - if (query?.terms) { + }else if (query?.terms) { Object.entries(query.terms).forEach(([key, values]) => { if (Array.isArray(values)) { entries.push(`${key} is any of [${values.join(', ')}]`); @@ -44,6 +41,10 @@ function Chip({ text, children }) { entries.push(`${key} is "${values}"`); } }); + }else { + Object.entries(query).forEach(([key, value]) => { + entries.push(`${key} filter"`); + }); } if(query?.bool) { diff --git a/frontend/src/components/FilterPanel.jsx b/frontend/src/components/FilterPanel.jsx index d1541ed04e..55028ef1e1 100644 --- a/frontend/src/components/FilterPanel.jsx +++ b/frontend/src/components/FilterPanel.jsx @@ -43,7 +43,6 @@ export default function FilterPanel({ if (remove) { console.log("Remove:", remove); const updatedFilters = tempFormFilters.filter(filter => filter.filterId !== remove); - console.log("Updated Filters:", updatedFilters); setTempFormFilters(updatedFilters); } else setFilter(filter, tempFormFilters, setTempFormFilters); // if (filter.selectedChoice) { diff --git a/frontend/src/components/Form/BioMeasurements.jsx b/frontend/src/components/Form/BioMeasurements.jsx new file mode 100644 index 0000000000..186d862a37 --- /dev/null +++ b/frontend/src/components/Form/BioMeasurements.jsx @@ -0,0 +1,100 @@ + +import React, { useState, useEffect } from 'react'; +import { Form, Col, Row, Container } from 'react-bootstrap'; +import { FormattedMessage } from 'react-intl'; +import { FormControl } from 'react-bootstrap'; + +function FormMeasurements({ + data, + onChange, + field, + filterId +}) { + const [inputs, setInputs] = useState(data?.map(item => ({ type: item, operator: 'gte', value: '' }))); + useEffect(() => { + if (data) { + const newInputs = data.map(item => ({ type: item, operator: 'gte', value: '' })); + setInputs(newInputs); + } + }, [data]); + + const handleInputChange = (index, field, value) => { + const updatedInputs = inputs.map((input, i) => { + if (i === index) { + return { ...input, [field]: value }; + } + return input; + }); + setInputs(updatedInputs); + const id = `${filterId}.${updatedInputs[index].type}`; + if (field === 'value') { + if (value !== '') { + updateQuery(updatedInputs); + } else { + onChange(null, id); + } + } + }; + + const updateQuery = (inputs) => { + + inputs.map(input => { + const id = `${filterId}.${input.type}`; + if (input.value) { + onChange({ + filterId: id, + clause: "filter", + query: { + "biologicalMeasurements": { + [input.type]: input.value + }, + } + } + ) + } + }); + }; + + return ( + +
+ {inputs.map((input, index) => ( + + + {input.type.charAt(0).toUpperCase() + input.type.slice(1)} + + + handleInputChange(index, 'operator', e.target.value)} + > + + + + + + + { + handleInputChange(index, 'value', e.target.value); + } + } + + /> + + + ))} +
+ ); +} + +export default FormMeasurements; diff --git a/frontend/src/components/Form/DynamicInputs.jsx b/frontend/src/components/Form/DynamicInputs.jsx index b1ce39967d..a4d9114cf0 100644 --- a/frontend/src/components/Form/DynamicInputs.jsx +++ b/frontend/src/components/Form/DynamicInputs.jsx @@ -7,7 +7,7 @@ import { FormattedMessage } from 'react-intl'; function DynamicInputs({ onChange, }) { - const [inputs, setInputs] = useState([{name: '', value: ''}]); + const [inputs, setInputs] = useState([{ name: '', value: '' }]); const addInput = () => { setInputs([...inputs, { name: '', value: '' }]); @@ -16,7 +16,14 @@ function DynamicInputs({ const handleInputChange = (index, event) => { const newInputs = inputs.map((input, i) => { if (i === index) { - const updatedInput = { ...input, [event.target.name]: event.target.value }; + // const updatedInput = { ...input, [event.target.name]: event.target.value }; + const nameField = event.target.name === 'name' ? event.target.value : input.name; + const valueField = event.target.name === 'value' ? event.target.value : input.value; + const originalName = (event.target.name === 'name' && event.target.value !== '') ? event.target.value : input.originalName || input.name; + + const updatedInput = { ...input, name: nameField, value: valueField, originalName }; + + console.log("updatedInput", updatedInput); if (updatedInput.name && updatedInput.value) { onChange({ filterId: `dynamicProperties.${updatedInput.name}`, @@ -29,6 +36,11 @@ function DynamicInputs({ term: "match", }); + } else { + if (updatedInput.originalName) { + onChange(null, `dynamicProperties.${updatedInput.originalName}`); + } + // onChange(null, `dynamicProperties.${updatedInput.name}`); } return updatedInput; } diff --git a/frontend/src/components/Form/FormGroupText.jsx b/frontend/src/components/Form/FormGroupText.jsx index b58c7508a8..2d0b759abb 100644 --- a/frontend/src/components/Form/FormGroupText.jsx +++ b/frontend/src/components/Form/FormGroupText.jsx @@ -20,6 +20,10 @@ export default function FormGroupText({ type="text" placeholder="Type Here" onChange={(e) => { + if (e.target.value === "") { + onChange(null, field); + return; + } onChange({ filterId: filterId, clause: "filter", diff --git a/frontend/src/components/Form/FormMeasurements.jsx b/frontend/src/components/Form/FormMeasurements.jsx index 4929d794e7..c9aef0bee1 100644 --- a/frontend/src/components/Form/FormMeasurements.jsx +++ b/frontend/src/components/Form/FormMeasurements.jsx @@ -26,42 +26,46 @@ function FormMeasurements({ return input; }); setInputs(updatedInputs); - if (field === 'value' && value !== '') { - updateQuery(updatedInputs); + const id = `${filterId}.${updatedInputs[index].type}`; + if (field === 'value') { + if (value !== '') { + updateQuery(updatedInputs); + } else { + onChange(null, id); + } } }; const updateQuery = (inputs) => { - const must = inputs.filter(input => input.value !== ''); - must.map(input => { - console.log("Input:", input); - const type_field = `${filterId}.type`; - const value_field = `${filterId}.value`; + + inputs.map(input => { const id = `${filterId}.${input.type}`; - console.log(type_field, value_field, id); - onChange({ - filterId: id, - clause: "nested", - path: field, - query: { - "bool": { - "filter": [ + if (input.value) { + onChange({ + filterId: id, + clause: "nested", + path: field, + query: { + "bool": { + "filter": [ - { - "match": { - [`${filterId}.type`]: input.type + { + "match": { + [`${filterId}.type`]: input.type + }, }, - }, - { - "range": { - [`${filterId}.value`]: { [input.operator]: input.value } + { + "range": { + [`${filterId}.value`]: { [input.operator]: input.value } + } } - } - ] + ] + } } - } - }) + }) + } + return { "match": { [`${field}.type`]: input.type @@ -73,18 +77,6 @@ function FormMeasurements({ }; }); - // if (must.length > 0) { - // onChange({ - // filterId, - // clause: "nested", - // path: field, - // query: { - // "bool": { - // "filter": must - // } - // } - // }); - // } }; return ( @@ -117,7 +109,6 @@ function FormMeasurements({ }} placeholder="Type Here" onChange={(e) => { - console.log(e.target.value); handleInputChange(index, 'value', e.target.value); } } diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index 3360dcde10..82c3bcaef4 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -10,6 +10,7 @@ const MapComponent = ({ zoom = 10, bounds, setBounds, + setTempBounds, }) => { const theme = useContext(ThemeContext); @@ -85,6 +86,7 @@ const MapComponent = ({ toggleDrawing(); setIsDrawing(!isDrawing); setBounds(null); + setTempBounds(null); }} noArrow backgroundColor={theme.primaryColors.primary700} diff --git a/frontend/src/components/fields/filters/AreaMap.jsx b/frontend/src/components/fields/filters/AreaMap.jsx index d8b8b41c65..434c53ef81 100644 --- a/frontend/src/components/fields/filters/AreaMap.jsx +++ b/frontend/src/components/fields/filters/AreaMap.jsx @@ -30,7 +30,6 @@ export default function LatLngMap({ onChange, rest }) { marker.setMap(mapObject); lastMarker = marker; - console.log("lat, lng: ", lat, lng) }} yesIWantToUseGoogleMapApiInternals onGoogleApiLoaded={({ map, maps }) => { diff --git a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx index 37694bbd7c..bebbdec1ed 100644 --- a/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx +++ b/frontend/src/components/filterFields/BiologicalSamplesAndAnalysesFilter.jsx @@ -7,7 +7,7 @@ import FormGroupText from '../Form/FormGroupText'; import FormMeasurements from '../Form/FormMeasurements'; import { FormGroup, FormLabel, FormControl } from 'react-bootstrap'; import FormGroupMultiSelect from '../Form/FormGroupMultiSelect'; - +import BioMeasurements from '../Form/BioMeasurements'; export default function BiologicalSamplesAndAnalysesFilter({ onChange, @@ -15,10 +15,18 @@ export default function BiologicalSamplesAndAnalysesFilter({ }) { const label = const [isChecked, setIsChecked] = React.useState(false); - const bioMeasurementOptions = Object.entries(data?.bioMeasurement || {}).map( + const bioMeasurementOptions = Object.entries( { + "weight": "Weight", + "length": "Length", + "height": "Height", + "width": "Width", + "age": "Age", + }|| {}).map( item => item[0] ) || []; + // data?.bioMeasurement + const microSatelliteMarkerLoci = data?.loci || []; const [checkedState, setCheckedState] = useState({}); const [alleleLength, setAlleleLength] = React.useState(false); @@ -52,20 +60,24 @@ export default function BiologicalSamplesAndAnalysesFilter({ id="custom-checkbox" label={label} checked={isChecked} - onChange={() => { + onChange={(e) => { setIsChecked(!isChecked); - - + if(!e.target.checked) { + console.log(1); + onChange(null, `biologicalSampleId`); + return; + } else { + console.log(2); onChange({ filterId: `biologicalSampleId`, - clause: "must", + clause: "filter", query: { "exists": { "field": "tissueSampleIds" } } }) - } + }} } /> @@ -97,12 +109,20 @@ export default function BiologicalSamplesAndAnalysesFilter({ filterId={"geneticSex"} /> - */} + + +
diff --git a/frontend/src/components/filterFields/DateFilter.jsx b/frontend/src/components/filterFields/DateFilter.jsx index 79e8d78266..20f321ffa4 100644 --- a/frontend/src/components/filterFields/DateFilter.jsx +++ b/frontend/src/components/filterFields/DateFilter.jsx @@ -56,6 +56,8 @@ export default function DateFilter({ query: query } ) + }else { + onChange(null, "date"); } } @@ -82,6 +84,9 @@ export default function DateFilter({ } ) } + else { + onChange(null, "dateSubmitted"); + } } return ( diff --git a/frontend/src/components/filterFields/IdentityFilter.jsx b/frontend/src/components/filterFields/IdentityFilter.jsx index f0a08deb10..b6ddb25818 100644 --- a/frontend/src/components/filterFields/IdentityFilter.jsx +++ b/frontend/src/components/filterFields/IdentityFilter.jsx @@ -86,7 +86,6 @@ export default function IdentityFilter({ onChange={(e) => { setIsChecked2(!isChecked2); if (e.target.checked) { - console.log("checked and ", e.target.value); onChange({ filterId: "individualId", clause: "filter", diff --git a/frontend/src/components/filterFields/ImageLabelFilter.jsx b/frontend/src/components/filterFields/ImageLabelFilter.jsx index 5c6f7dc767..69d90f5656 100644 --- a/frontend/src/components/filterFields/ImageLabelFilter.jsx +++ b/frontend/src/components/filterFields/ImageLabelFilter.jsx @@ -77,7 +77,6 @@ export default function ImageLabelFilter({ const [isChecked_keyword, setIsChecked_keyword] = React.useState(false); useEffect(() => { - console.log("isChecked_photo", isChecked_photo); if(isChecked_photo){ onChange({ filterId: "numberMediaAssets", diff --git a/frontend/src/components/filterFields/LocationFilterMap.jsx b/frontend/src/components/filterFields/LocationFilterMap.jsx index 055fc57caa..593427be75 100644 --- a/frontend/src/components/filterFields/LocationFilterMap.jsx +++ b/frontend/src/components/filterFields/LocationFilterMap.jsx @@ -15,7 +15,7 @@ export default function LocationFilterMap({ useEffect(() => { if (bounds) { onChange({ - filterId: "locationId", + filterId: "locationMap", clause: "filter", query: { "geo_bounding_box": { @@ -33,7 +33,7 @@ export default function LocationFilterMap({ } }); }else { - onChange(null, "locationId"); + onChange(null, "locationMap"); } }, [bounds]); @@ -69,6 +69,7 @@ export default function LocationFilterMap({ label: _.repeat("-", location.depth) + " " + location.name } }) || []; + const [tempBounds, setTempBounds] = useState(bounds); return (
@@ -95,12 +96,19 @@ export default function LocationFilterMap({ { - setBounds({ - ...bounds, + const newTempBounds = { + ...tempBounds, [Object.values(item)[0]]: e.target.value - }); + }; + setTempBounds(newTempBounds); + + // Check if all fields have values + const allFieldsFilled = Object.values(newTempBounds).length === 4 && Object.values(newTempBounds).every(value => value !== undefined && value !== ""); + if (allFieldsFilled) { + setBounds(newTempBounds); // Update the main state if all fields are filled + } }} /> @@ -112,6 +120,7 @@ export default function LocationFilterMap({ { + const countries = data?.country.map(data => { return { - value: item, - label: item - }; - } - ) || []; - // data?.country.map(data => { - // return { - // value: data, - // label: data - // } - // }) || []; + value: data, + label: data + } + }) || []; return (
@@ -37,7 +30,7 @@ export default function LocationFilterText({ onChange={onChange} term="match" field="verbatimLocality" - filterId={"Verbatim Location"} + filterId={"verbatimLocality"} /> @@ -100,7 +96,7 @@ export default function ObservationAttributeFilter( onChange={onChange} term="match" field="occurrenceRemarks" - filterId={"Observation Comments Include"} + filterId={"occurrenceRemarks"} /> setShow(true); const num = formFilters.length; - - + return ( <>
); diff --git a/frontend/src/components/navBar/MergeMessages.jsx b/frontend/src/components/navBar/MergeMessages.jsx index c941b29d40..12755c91aa 100644 --- a/frontend/src/components/navBar/MergeMessages.jsx +++ b/frontend/src/components/navBar/MergeMessages.jsx @@ -9,8 +9,6 @@ export default function MergeMessages({ getAllNotifications, setModalOpen, }) { - console.log('CollaborationMessages mergeData:', mergeData); - const handleClick = (action, taskId) => { const result = changeIndividualMergeState(action, taskId); // setError('Error: ' + result); @@ -28,7 +26,6 @@ export default function MergeMessages({ const mergeDenied = data.notificationType === 'mergeDenied'; const ownedByMe = data.ownedByMe === 'true'; - console.log('each data:', data.ownedByMe); return
q.clause === "nested"); -// const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); - -// const nestedQuery = nestedQueries.map(n => ({ -// nested: { -// path: n.path, -// query: { -// bool: { -// filter: n.query.bool.filter.map(f => ({ match: f })) -// } -// } -// } -// })); - -// console.log("Nested Query:", nestedQuery); - -// const boolQuery = { -// filter: filterQueries.map(f => ({ match: f.query })), -// must_not: mustNotQueries.map(f => f.query) -// }; - -// console.log - -// if (nestedQuery.length > 0) { -// boolQuery.filter.push(...nestedQuery); -// } - -// const compositeQuery = { -// query: { -// bool: boolQuery -// } -// }; - -// return useFetch({ -// method: "post", -// queryKey: getEncounterFilterQueryKey(queries, params), -// url: "/search/encounter", -// data: compositeQuery, -// params: { -// sort: "date", -// size: 1, -// from: 3, -// ...params, -// }, -// dataAccessor: (result) => { -// const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); -// return { -// resultCount: parseInt(resultCountString, 10), -// results: get(result, ["data", "data", "hits"], []), -// }; -// }, -// queryOptions: { -// retry: 2, -// }, -// }); -// } - -// import { get, partition } from "lodash-es"; -// import useFetch from "../../hooks/useFetch"; -// import { getEncounterFilterQueryKey } from "../../constants/queryKeys"; - -// export default function useFilterEncounters({ queries, params = {} }) { -// console.log("Queries:", queries); -// const [nestedQueries, nonNestedQueries] = partition(queries, q => q.clause === "nested"); -// const [filterQueries, mustNotQueries] = partition(nonNestedQueries, q => q.clause === "filter"); - -// const nestedQuery = nestedQueries.map(n => ({ -// nested: { -// path: n.path, -// query: { -// bool: { -// filter: n.query.bool.filter.map(f => ({ match: f })) -// } -// } -// } -// })); - -// const mustQueries = nonNestedQueries.filter(q => q.clause === "must"); - -// const boolQuery = { -// filter: filterQueries.map(f => f.query), -// must_not: mustNotQueries.map(f => f.query), -// must: mustQueries.map(f => f.query) , -// }; - -// const compositeQuery = { -// query: { -// bool: boolQuery, -// nested: nestedQuery -// } -// }; - -// return useFetch({ -// method: "post", -// queryKey: getEncounterFilterQueryKey(queries, params), -// url: "/search/encounter", -// data: compositeQuery, -// params: { -// sort: "date", -// size: 1, -// from: 3, -// ...params, -// }, -// dataAccessor: (result) => { -// const resultCountString = get(result, ["data", "headers", "x-wildbook-total-hits"], "0"); -// return { -// resultCount: parseInt(resultCountString, 10), -// results: get(result, ["data", "data", "hits"], []), -// }; -// }, -// queryOptions: { -// retry: 2, -// }, -// }); -// } import { get, partition } from "lodash-es"; @@ -139,7 +18,7 @@ function buildQuery(queries) { return { filter: filterQueries.map(f => f.query), - must_not: mustNotQueries.map(f => f.query), + // must_not: mustNotQueries.map(f => f.query), must: [ ...nestedQuery] }; } diff --git a/frontend/src/models/notifications/getCollaborationNotifications.js b/frontend/src/models/notifications/getCollaborationNotifications.js index 2a2e5793d7..aaa0109a4b 100644 --- a/frontend/src/models/notifications/getCollaborationNotifications.js +++ b/frontend/src/models/notifications/getCollaborationNotifications.js @@ -11,7 +11,6 @@ const getCollaborationNotifications = async () => { const invites = [...doc.querySelectorAll('.collaboration-invite-notification')]; return { collaborationTitle: title, collaborationData: invites }; } else { - console.log('No title found'); return { collaborationTitle: '', collaborationData: [] }; } } catch (error) { diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index 38c6d23a48..e6c297d6e8 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -88,7 +88,6 @@ export default function EncounterSearch() { onPerPageChange={setPerPage} loading={false} onRowClicked={(row) => { - console.log("Row Clicked: ", row); const url = `/encounters/encounter.jsp?number=${row.id}`; window.location.href = url; }} diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index b0ed64cc9a..9f85a7f39e 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -29,7 +29,6 @@ function LoginPage() { const handleSubmit = async (event) => { event.preventDefault(); - console.log("Form submitted"); setError(null); setShow(false); authenticate(username, password); From 8b71c49c00cc7cdef21738c6a14cd580a322545d Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Tue, 23 Jul 2024 23:55:02 +0000 Subject: [PATCH 017/102] fix banner overlap with header issue --- frontend/src/UnAuthenticatedSwitch.jsx | 24 +++++++++++++++++++----- frontend/src/components/AlertBanner.jsx | 12 +++++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/frontend/src/UnAuthenticatedSwitch.jsx b/frontend/src/UnAuthenticatedSwitch.jsx index 02934694eb..ce42f9d9e0 100644 --- a/frontend/src/UnAuthenticatedSwitch.jsx +++ b/frontend/src/UnAuthenticatedSwitch.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Routes, Route } from "react-router-dom"; import Login from "./pages/Login"; import Footer from "./components/Footer"; @@ -11,6 +11,17 @@ import EncounterSearch from "./pages/EncounterSearch"; export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) { const [header, setHeader] = React.useState(true); + const [headerTop, setHeaderTop] = React.useState("60px"); + const alertBannerRef = React.useRef(null); + + // useEffect(() => { + // if (showAlert && alertBannerRef.current) { + // setHeaderTop(alertBannerRef.current.offsetHeight + "px"); + // } else { + // setHeaderTop("60px"); + // } + // }, [showAlert, alertBannerRef?.current?.offsetHeight]); + return (
@@ -23,10 +34,13 @@ export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) { maxWidth: "1440px", }} > - {showAlert && } + {showAlert && }
@@ -37,16 +51,16 @@ export default function UnAuthenticatedSwitch({ showAlert, setShowAlert }) { boxSizing: "border-box", maxWidth: "1440px", overflow: "hidden", - paddingTop: header? "48px" : "0", + paddingTop: header ? "48px" : "0", }} > } /> - } /> + } /> } /> } /> } /> - } /> + } />