diff --git a/locale/en.json b/locale/en.json index 29de78b7c..d21b17c66 100644 --- a/locale/en.json +++ b/locale/en.json @@ -32,8 +32,8 @@ "ASSIGN_ANNOTATIONS": "Assign annotations", "ASSIGN_ANNOTATIONS_TO_INDIVIDUAL": "Assign annotations to individual", "ASSIGN_ANNOTATIONS_TO_INDIVIDUAL_INSTRUCTIONS": "Verify that the annotations below should be assigned to this individual before proceeding.", - "ADD_ANNOTATIONS_TO_CLUSTER_TITLE": "Add annotations to animal", - "ADD_ANNOTATIONS_TO_CLUSTER_BUTTON": "Add annotations", + "ADD_ANNOTATIONS_TO_CLUSTER_TITLE": "Assign annotations to animal", + "ADD_ANNOTATIONS_TO_CLUSTER_BUTTON": "Assign annotations", "SIGHTING_COMMIT_TITLE": "Ready to commit", "REMOVE_FROM_CLUSTER": "Remove from animal", "MOVE_ANNOTATION": "Move annotation", @@ -765,8 +765,6 @@ "SERVER_STATUS_PAGE_TITLE": "Server Status", "MATCH_ANNOTATIONS": "Match annotations", "MATCH_REVIEW": "Match review", - "MARK_SIGHTING_REVIEWED": "Mark sighting reviewed", - "MARK_SIGHTING_REVIEWED_CONFIRMATION": "Are you sure you want to mark this sighting reviewed? This action cannot be undone.", "MATCH_COUNT": "{matchCount} {matchCount, plural, =0 {matches} one {match} other {matches}}", "PHOTO_COUNT": "{photoCount} {photoCount, plural, =0 {photographs} one {photograph} other {photographs}}", "ENCOUNTERS_IMPORTED_COUNT": "{encounterCount} {encounterCount, plural, =0 {encounters} one {encounter} other {encounters}} imported.", @@ -797,7 +795,7 @@ "ENCOUNTER_ID": "Encounter ID", "RESTORE_DELETED_ENCOUNTER_INSTRUCTIONS": "The restored encounter will include all metadata, including the original date of submission.", "RESTORE_ENCOUNTER": "Restore encounter", - "COPYRIGHT_LINE": "Copyright 2020 Wild Me for Whale Sharks", + "COPYRIGHT_LINE": "Copyright {year} Wild Me", "LAST_SEEN_DATE": "Last seen on {date}", "SHOW_FILTERS": "Show filters", "SUCCESS": "Success", @@ -1264,5 +1262,14 @@ "ENCOUNTER_SEX" : "Encounter sex", "ENCOUNTER_SPECIES" : "Encounter species", "LAST_REGION" : "Last region", - "LAST_FREEFORM" : "Last freeform" + "LAST_FREEFORM" : "Last freeform", + "UNREVIEWED" : "Unreviewed", + "REVIEWED" : "Reviewed", + "IN_PROGRESS" : "In progress", + "UNPROCESSED" : "Unprocessed", + "NO_MATCH_DESCRIPTION" : "Can't find any match for the sighting?", + "CONFIRM_NO_MATCH" : "Confirm no match", + "NUMBER_OF_INDIVIDUALS" : "Number of individuals", + "NUMBER_OF_ENCOUNTERS" : "Number of animals", + "ASSIGN" : "Assign" } diff --git a/src/components/AuthenticatedAppHeader/ActionsPane.jsx b/src/components/AuthenticatedAppHeader/ActionsPane.jsx index 79ae5cfb5..518b73238 100644 --- a/src/components/AuthenticatedAppHeader/ActionsPane.jsx +++ b/src/components/AuthenticatedAppHeader/ActionsPane.jsx @@ -20,6 +20,10 @@ const actions = [ { id: 'bulk-import', href: '/bulk-import', + permissionsTest: userData => + userData?.is_admin || + userData?.is_staff || + userData?.is_researcher, messageId: 'BULK_IMPORT', icon: BulkImportIcon, }, @@ -49,26 +53,26 @@ export default function NotificationsPane({ const name = get(userData, 'full_name') || 'Unnamed user'; const profileSrc = get(userData, ['profile_fileupload', 'src']); - const logout = async (event) => { + const logout = async event => { event.preventDefault(); let is_authenticated = true; await fetch('/api/v1/users/me') - .then(response => { - if(response.status == 401) { - is_authenticated = false; - } - }) - .catch(error => { - console.log(error); - }); + .then(response => { + if (response.status === 401) { + is_authenticated = false; + } + }) + .catch(error => { + console.log(error); + }); - if(is_authenticated) { - document.getElementById("logoutForm").submit(); - }else { - window.location.href = '/'; + if (is_authenticated) { + document.getElementById('logoutForm').submit(); + } else { + window.location.href = '/'; } setAnchorEl(null); - }; + }; return ( - + ); diff --git a/src/components/dataDisplays/ElasticsearchSightingsDisplay.jsx b/src/components/dataDisplays/ElasticsearchSightingsDisplay.jsx index 98bea22ec..f01f16583 100644 --- a/src/components/dataDisplays/ElasticsearchSightingsDisplay.jsx +++ b/src/components/dataDisplays/ElasticsearchSightingsDisplay.jsx @@ -14,8 +14,8 @@ export default function ElasticsearchSightingsDisplay({ ...rest }) { const title = `${dataCount || sightings.length} matching sightings`; - - const tableData = sightings.map(sighting => { + + const tableData = sightings.map(sighting => // const encounters = sighting?.encounters || []; // const photoCount = encounters.reduce((memo, e) => { // memo += e.images.length; @@ -27,12 +27,12 @@ export default function ElasticsearchSightingsDisplay({ // return individual ? [...memo, individual] : null; // }, []); - return { + ({ ...sighting, // photoCount, // individuals, - }; - }); + }), + ); const columns = [ { @@ -50,6 +50,24 @@ export default function ElasticsearchSightingsDisplay({ sortable: true, align: 'left', }, + { + name: 'numberIndividuals', + labelId: 'NUMBER_OF_INDIVIDUALS', + sortable: false, + align: 'left', + }, + { + name: 'numberEncounters', + labelId: 'NUMBER_OF_ENCOUNTERS', + sortable: false, + align: 'left', + }, + { + name: 'verbatimLocality', + labelId: 'FREEFORM_LOCATION', + sortable: false, + align: 'left', + }, { name: 'owners', sortName: 'owners.full_name', @@ -58,6 +76,7 @@ export default function ElasticsearchSightingsDisplay({ align: 'left', options: { customBodyRender: owners => { + // eslint-disable-line const ownerName = get( owners, [0, 'full_name'], @@ -81,7 +100,9 @@ export default function ElasticsearchSightingsDisplay({ labelId: 'ACTIONS', sortable: false, options: { - customBodyRender: guid => ( + customBodyRender: ( + guid, // eslint-disable-line + ) => ( { + const operations = [ + { + op: 'replace', + path: '/match_state', + value: status, + }, + ]; + await reviewSighting({ sightingGuid, operations }); + }; + + const buttonActions = [ + { + id: 'mark-sighting-unreviewed', + labelId: 'UNREVIEWED', + onClick: async () => { + sendRequest('unreviewed'); + }, + }, + { + id: 'mark-sighting-in-progress', + labelId: 'IN_PROGRESS', + onClick: async () => { + sendRequest('in_progress'); + }, + }, + { + id: 'mark-sighting-reviewed', + labelId: 'REVIEWED', + onClick: async () => { + sendRequest('reviewed'); + }, + }, + { + id: 'mark-sighting-unidentifiable', + labelId: 'UNIDENTIFIABLE', + onClick: async () => { + sendRequest('unidentifiable'); + }, + }, + ]; + + return ( +
+ + + {error && ( + + {error} + + )} +
+ ); +} diff --git a/src/components/dialogs/ReviewSightingDialog.jsx b/src/components/dialogs/ReviewSightingDialog.jsx deleted file mode 100644 index fe2066d88..000000000 --- a/src/components/dialogs/ReviewSightingDialog.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; - -import DialogContent from '@material-ui/core/DialogContent'; -import DialogActions from '@material-ui/core/DialogActions'; - -import useReviewSighting from '../../models/sighting/useReviewSighting'; -import StandardDialog from '../StandardDialog'; -import Button from '../Button'; -import Text from '../Text'; -import Alert from '../Alert'; - -export default function ReviewSightingDialog({ - sightingGuid, - open, - onClose, -}) { - const { - mutate: reviewSighting, - loading, - error, - clearError, - } = useReviewSighting(); - - function handleClose() { - clearError(); - onClose(); - } - - return ( - - - - {error && ( - - {error} - - )} - - - } > )} {error && ( diff --git a/src/pages/sighting/identification/CustomMatchingSetForm.jsx b/src/pages/sighting/identification/CustomMatchingSetForm.jsx index c4e1d60d8..b3c41422f 100644 --- a/src/pages/sighting/identification/CustomMatchingSetForm.jsx +++ b/src/pages/sighting/identification/CustomMatchingSetForm.jsx @@ -9,6 +9,7 @@ import buildMatchingSetQuery from './buildMatchingSetQuery'; export default function CustomMatchingSetForm({ // idConfig, // use this to get matching set size! setIdConfig, + nested = false, }) { const idConfigSchemas = useIdConfigSchemas(); @@ -25,11 +26,14 @@ export default function CustomMatchingSetForm({ const [algorithms, setAlgorithms] = useState([]); const [region, setRegion] = useState(''); - useEffect(() => { setIdConfig({ algorithms, - matching_set: buildMatchingSetQuery(regionSchema, region), + matching_set: buildMatchingSetQuery( + regionSchema, + region, + nested, + ), }); }, [ setIdConfig, diff --git a/src/pages/sighting/identification/RerunIdentificationDialog.jsx b/src/pages/sighting/identification/RerunIdentificationDialog.jsx index 3eb338ff6..e3df00186 100644 --- a/src/pages/sighting/identification/RerunIdentificationDialog.jsx +++ b/src/pages/sighting/identification/RerunIdentificationDialog.jsx @@ -39,6 +39,7 @@ export default function RerunIdentificationDialog({ {error && ( diff --git a/src/pages/sighting/identification/buildMatchingSetQuery.js b/src/pages/sighting/identification/buildMatchingSetQuery.js index 74db63b1f..6f8c5e1be 100644 --- a/src/pages/sighting/identification/buildMatchingSetQuery.js +++ b/src/pages/sighting/identification/buildMatchingSetQuery.js @@ -30,45 +30,55 @@ function getMatchWithChildren(regions, matchId) { return flattenDeep(matchingRegionTree); } -export default function buildMatchingSetQuery(regionSchema, region) { +export default function buildMatchingSetQuery( + regionSchema, + region, + nested = false, +) { if (region === '' || !regionSchema) return {}; - const regionChoices = get(regionSchema, 'choices', []); const matchWithChildren = getMatchWithChildren( regionChoices, region, ); + const filter = nested + ? { + terms: { + locationId: matchWithChildren.map(r => r.id), + }, + } + : { + match: { + locationId: matchWithChildren.find(r => !!r.id)?.id, + }, + }; + return { - "bool": { - "minimum_should_match": 1, - "should": [ + bool: { + minimum_should_match: 1, + should: [ { - "term": { - "git_store_guid": "_MACRO_annotation_git_store_guid" - } + term: { + git_store_guid: '_MACRO_annotation_git_store_guid', + }, }, { - "bool": { - "filter": [ + bool: { + filter: [ + filter, { - "match": { - "locationId": matchWithChildren.find(r => !!r.id).id - } + exists: { + field: 'encounter_guid', + }, }, - { - "exists": { - "field": "encounter_guid" - } - } - ] - } - } + ], + }, + }, ], - "must": { - "bool": "_MACRO_annotation_neighboring_viewpoints_clause" - } - } - + must: { + bool: '_MACRO_annotation_neighboring_viewpoints_clause', + }, + }, }; } diff --git a/src/pages/sighting/statusCard/CurationStep.jsx b/src/pages/sighting/statusCard/CurationStep.jsx index 34fe91f37..bb9d61deb 100644 --- a/src/pages/sighting/statusCard/CurationStep.jsx +++ b/src/pages/sighting/statusCard/CurationStep.jsx @@ -119,7 +119,7 @@ export default function CurationStep({ sightingData }) { href={ someAssetsHaveAnnotations ? '#individuals' - : '#photographs' + : '#annotations' } display="primary" size="small"