Skip to content

Commit 1127cbc

Browse files
authored
Merge branch 'main' into remove-enablePagination-check-from-editableTable
2 parents 5bda3f3 + e124f15 commit 1127cbc

File tree

11 files changed

+157
-22
lines changed

11 files changed

+157
-22
lines changed

backend/src/api-tests/locality/create.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeEach, beforeAll, afterAll, describe, it, expect } from '@jest/globals'
2-
import { LocalityDetailsType, SpeciesDetailsType } from '../../../../frontend/src/shared/types'
2+
import { Locality, LocalityDetailsType, SpeciesDetailsType } from '../../../../frontend/src/shared/types'
33
import { LogRow } from '../../services/write/writeOperations/types'
44
import { newLocalityBasis } from './data'
55
import { login, logout, resetDatabase, send, testLogRows, resetDatabaseTimeout, noPermError } from '../utils'
@@ -42,6 +42,15 @@ describe('Creating new locality works', () => {
4242
expect(!!oldSpecies).toEqual(true) // 'Old species not found'
4343
})
4444

45+
it('Locality list exposes synonym data for filtering', async () => {
46+
const { body } = await send<Locality[]>('locality/all', 'GET')
47+
const created = body.find(locality => locality.lid === createdLocality!.lid)
48+
49+
expect(created).toBeDefined()
50+
expect(created!.has_synonym).toEqual(true)
51+
expect(created!.synonyms).toEqual(expect.arrayContaining(['Shuijiazui', 'Bahe']))
52+
})
53+
4554
it('Creation fails without permissions', async () => {
4655
logout()
4756
const resultNoPerm = await send('locality', 'PUT', {

backend/src/services/locality.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,19 @@ type LocalityPreFilter = {
5959
now_plr: {
6060
pid: number
6161
}[]
62+
now_syn_loc: {
63+
synonym: string | null
64+
}[]
6265
}
6366

6467
export const getAllLocalities = async (user?: User) => {
6568
const showAll = user && [Role.Admin, Role.EditUnrestricted].includes(user.role)
66-
const removeProjects: (loc: LocalityPreFilter) => Omit<LocalityPreFilter, 'now_plr'> = loc => {
69+
const removeProjects = (
70+
loc: Omit<LocalityPreFilter, 'now_syn_loc'> & {
71+
synonyms: string[]
72+
has_synonym: boolean
73+
}
74+
) => {
6775
const { now_plr, ...rest } = loc
6876
return rest
6977
}
@@ -123,20 +131,24 @@ export const getAllLocalities = async (user?: User) => {
123131
now_plr: {
124132
select: { pid: true },
125133
},
134+
now_syn_loc: {
135+
select: { synonym: true },
136+
},
126137
},
127138
})) as LocalityPreFilter[]
128139

129-
const synonyms: { lid: number }[] = await nowDb.now_syn_loc.findMany({
130-
select: { lid: true },
131-
distinct: ['lid'],
140+
const result = localityResult.map(loc => {
141+
const synonyms = loc.now_syn_loc
142+
.map(({ synonym }) => synonym)
143+
.filter((syn): syn is string => typeof syn === 'string' && syn.trim().length > 0)
144+
const { now_syn_loc, ...rest } = loc
145+
return {
146+
...rest,
147+
synonyms,
148+
has_synonym: synonyms.length > 0,
149+
}
132150
})
133151

134-
const synonymIdSet = new Set(synonyms.map(s => s.lid))
135-
const result = localityResult.map(loc => ({
136-
...loc,
137-
has_synonym: synonymIdSet.has(loc.lid),
138-
}))
139-
140152
if (showAll) return result.map(removeProjects)
141153

142154
if (!user) return result.filter(loc => loc.loc_status === false).map(removeProjects)

backend/src/services/museum.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,81 @@ export const getMuseumDetails = async (id: string) => {
2323

2424
const localityIds = localityLinks.map(link => link.lid)
2525

26-
const localities = (await nowDb.now_loc.findMany({
26+
const localitiesResult = await nowDb.now_loc.findMany({
2727
where: { lid: { in: localityIds } },
28-
})) as Locality[]
28+
select: {
29+
lid: true,
30+
loc_name: true,
31+
bfa_max: true,
32+
bfa_min: true,
33+
max_age: true,
34+
min_age: true,
35+
bfa_max_abs: true,
36+
bfa_min_abs: true,
37+
frac_max: true,
38+
frac_min: true,
39+
chron: true,
40+
age_comm: true,
41+
basin: true,
42+
subbasin: true,
43+
dms_lat: true,
44+
dms_long: true,
45+
dec_lat: true,
46+
dec_long: true,
47+
altitude: true,
48+
country: true,
49+
state: true,
50+
county: true,
51+
appr_num_spm: true,
52+
site_area: true,
53+
gen_loc: true,
54+
plate: true,
55+
formation: true,
56+
member: true,
57+
bed: true,
58+
loc_status: true,
59+
estimate_precip: true,
60+
estimate_temp: true,
61+
estimate_npp: true,
62+
pers_woody_cover: true,
63+
pers_pollen_ap: true,
64+
pers_pollen_nap: true,
65+
pers_pollen_other: true,
66+
hominin_skeletal_remains: true,
67+
bipedal_footprints: true,
68+
stone_tool_cut_marks_on_bones: true,
69+
stone_tool_technology: true,
70+
technological_mode_1: true,
71+
technological_mode_2: true,
72+
technological_mode_3: true,
73+
cultural_stage_1: true,
74+
cultural_stage_2: true,
75+
cultural_stage_3: true,
76+
regional_culture_1: true,
77+
regional_culture_2: true,
78+
regional_culture_3: true,
79+
now_syn_loc: {
80+
select: { synonym: true },
81+
},
82+
},
83+
})
84+
85+
const localities: Locality[] = localitiesResult.map(locality => {
86+
const synonyms = locality.now_syn_loc
87+
.map(({ synonym }) => synonym)
88+
.filter((syn): syn is string => typeof syn === 'string' && syn.trim().length > 0)
89+
90+
const { now_syn_loc, altitude, appr_num_spm, country, ...rest } = locality
91+
92+
return {
93+
...rest,
94+
country: country ?? '',
95+
altitude: altitude ?? 0,
96+
appr_num_spm: appr_num_spm ?? 0,
97+
synonyms,
98+
has_synonym: synonyms.length > 0,
99+
}
100+
})
29101

30102
const combined = {
31103
...museum,

cypress/e2e/locality.cy.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,22 @@ describe('Editing a locality', () => {
432432
})
433433
})
434434

435+
describe('Locality table filtering', () => {
436+
beforeEach('Login as admin', () => {
437+
cy.login('testSu')
438+
})
439+
440+
it('supports filtering by synonym names', () => {
441+
cy.visit('/locality')
442+
cy.contains('Name').should('be.visible')
443+
444+
cy.get('[aria-label="Filter by Name"]').clear()
445+
cy.get('[aria-label="Filter by Name"]').type('Bahe')
446+
447+
cy.contains('Lantian-Shuijiazui').should('be.visible')
448+
})
449+
})
450+
435451
// This test needs GEONAMES_USERNAME to be set in .anon.env!
436452

437453
describe("Locality's coordinate selection map works", () => {
@@ -447,7 +463,8 @@ describe("Locality's coordinate selection map works", () => {
447463
cy.contains('Get Coordinates').click()
448464
cy.contains('OpenStreetMap')
449465
cy.contains('Leaflet')
450-
cy.get('[alt="Marker"]').should('have.attr', 'src').should('include', 'marker-icon.png')
466+
cy.get('[alt="Marker"]').should('have.attr', 'src')
467+
cy.get('[alt="Marker"]').invoke('attr', 'src').should('include', 'marker-icon.png')
451468
cy.get('[id=geonames-search-textfield]').type('kum')
452469
cy.get('[id=geonames-search-button]').should('not.be.disabled')
453470
cy.get('[id=geonames-search-textfield]').type('{backspace}{backspace}{backspace}')

documentation/functionality/species_taxonomy_checks.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
### Species taxonomy checks
22

3+
NOTE: This document might be outdated, especially the order/content of the lists.
4+
35
If you are unsure how the taxonomic conventions work or what the different taxonomic fields mean, you should read [this document](https://github.com/nowcommunity/NOW-Django/wiki/Data-Conventions%3A-Taxonomic-Fields) first. (NOTE: some information might be incorrect, outdated or not implemented in the new version of the app. Once an updated document is made, it should be linked here.)
46

5-
When the user is creating a new species or editing an existing one, clicking the write button calls the functions inside [taxonomyFunctions.tsx](../../frontend/src/components/Species/taxonomyFunctions.tsx) in the following order:
7+
In edit views which have the need for a taxonomy check, the `taxonomy` prop should be given to the [WriteButton](../../frontend/src/components/DetailView/components.tsx) component. The WriteButton then runs the taxonomy checks once pressed. For example, if the user is creating a new species or editing an existing one, clicking the write button calls the functions inside [taxonomyUtilities.tsx](../../frontend/src/util/taxonomyUtilities.tsx) in the following order:
68

79
1. The `convertTaxonomyFields` function formats the taxonomy values given by the user. Everything except `species_name` and `unique_identifier` begins with a capital letter, with all other letters lowercased. The exceptions to this are special values: "incertae sedis", "indet.", "fam.", "gen." and "sp.", which are left unchanged. `species_name` is fully lowercased, and `unique_identifier` is also left unchanged.
810
2. The `checkTaxonomy` function ensures that the species does not already exist in the database, and that the taxonomy tree is correct. The function loops through every species in the database and for each one:
@@ -13,7 +15,7 @@ When the user is creating a new species or editing an existing one, clicking the
1315

1416
If any of these checks fail, the creating/updating fails and the user is notified about it.
1517

16-
The [taxonomyFunctions file](../../frontend/src/components/Species/taxonomyFunctions.tsx) has a bunch of functions named `checkXY` which check the validity of taxonomic data between fields X and Y (step 4 in the list above). For example, X could be `family` and Y could be `subfamily`, and the function `checkFamilySubfamily` returns `true` if:
18+
The [taxonomyUtilities file](../../frontend/src/util/taxonomyUtilities.tsx) has a bunch of functions named `checkXY` which check the validity of taxonomic data between fields X and Y (step 4 in the list above). For example, X could be `family` and Y could be `subfamily`, and the function `checkFamilySubfamily` returns `true` if:
1719

1820
1. The new species contains both `family` and `subfamily` values
1921
2. These values are not "incertae sedis" or "indet."

frontend/src/components/Locality/LocalityTable.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,20 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
5656
accessorFn: row => row.loc_name || '',
5757
header: 'Name',
5858
enableHiding: false,
59-
filterFn: 'contains',
59+
filterFn: (row, columnId, filterValue) => {
60+
const search =
61+
typeof filterValue === 'string'
62+
? filterValue.trim().toLowerCase()
63+
: Array.isArray(filterValue)
64+
? filterValue.join(' ').trim().toLowerCase()
65+
: ''
66+
if (!search) return true
67+
68+
const nameValue = `${row.getValue<string>(columnId) ?? ''}`.toLowerCase()
69+
if (nameValue.includes(search)) return true
70+
71+
return (row.original.synonyms ?? []).some(synonym => synonym.toLowerCase().includes(search))
72+
},
6073
},
6174
{
6275
accessorKey: 'dms_lat',

frontend/src/components/Locality/Tabs/AgeTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const AgeTab = () => {
106106
targetField="bfa_min"
107107
fraction={editData.frac_min}
108108
timeUnit={bfaMinData}
109-
selectorTable={<TimeUnitTable />}
109+
selectorTable={<TimeUnitTable clickableRows={false} />}
110110
disabled={minAgeTimeUnitDisabled}
111111
/>,
112112
dropdown('frac_min', fracOptions, 'Minimum fraction', minAgeTimeUnitDisabled),

frontend/src/components/Locality/Tabs/LocalityTab.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Editable, LocalityDetailsType, LocalitySynonym, Locality } from '@/shared/types'
1+
import { Editable, LocalityDetailsType, LocalitySynonym } from '@/shared/types'
22
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
33
import { Grouped, ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
44
import { Box, TextField } from '@mui/material'
@@ -18,7 +18,7 @@ export const LocalityTab = () => {
1818
useDetailContext<LocalityDetailsType>()
1919
const { editData, setEditData } = useDetailContext<LocalityDetailsType>()
2020

21-
const locality: Locality[] = [editData as Locality]
21+
const hasCoordinates = editData.dec_lat != null && editData.dec_long != null
2222

2323
const generalLocalityOptions = [emptyOption, { display: 'No', value: 'n' }, { display: 'Yes', value: 'y' }]
2424

@@ -192,7 +192,7 @@ export const LocalityTab = () => {
192192
>
193193
<ArrayFrame array={latlong} title="Latitude & Longitude" />
194194
<Box sx={{ width: '50%' }}>
195-
{locality && <SingleLocalityMap decLat={editData.dec_lat} decLong={editData.dec_long} />}
195+
{hasCoordinates && <SingleLocalityMap decLat={editData.dec_lat} decLong={editData.dec_long} />}
196196
</Box>
197197
</Box>
198198
{!mode.read && coordinateButton}

frontend/src/components/Sequence/SequenceTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const SequenceTable = ({ selectorFn }: { selectorFn?: (id: Sequence) => v
3434
title="Sequences"
3535
isFetching={isFetching}
3636
visibleColumns={visibleColumns}
37+
clickableRows={false}
3738
data={sequenceQueryData}
3839
url="sequence"
3940
/>

frontend/src/components/TimeUnit/TimeUnitTable.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { useGetAllTimeUnitsQuery } from '../../redux/timeUnitReducer'
44
import { TimeUnit } from '@/shared/types'
55
import { TableView } from '../TableView/TableView'
66

7-
export const TimeUnitTable = ({ selectorFn }: { selectorFn?: (newTimeUnit: TimeUnit) => void }) => {
7+
export const TimeUnitTable = ({
8+
selectorFn,
9+
clickableRows = true,
10+
}: {
11+
selectorFn?: (newTimeUnit: TimeUnit) => void
12+
clickableRows?: boolean
13+
}) => {
814
const { data: timeUnitQueryData, isFetching } = useGetAllTimeUnitsQuery()
915
const columns = useMemo<MRT_ColumnDef<TimeUnit>[]>(
1016
() => [
@@ -60,6 +66,7 @@ export const TimeUnitTable = ({ selectorFn }: { selectorFn?: (newTimeUnit: TimeU
6066
columns={columns}
6167
isFetching={isFetching}
6268
visibleColumns={visibleColumns}
69+
clickableRows={clickableRows}
6370
data={timeUnitQueryData}
6471
url="time-unit"
6572
enableColumnFilterModes={true}

0 commit comments

Comments
 (0)