Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add GFWOnly vesselId field to advance search and link to profile without related ids #2940

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions apps/fishing-map/features/dataviews/dataviews.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ export function dataviewHasVesselGroupId(dataview: UrlDataviewInstance, vesselGr

export const getVesselInfoDataviewInstanceDatasetConfig = (
vesselId: string,
{ info }: VesselInstanceDatasets
{ info }: VesselInstanceDatasets,
includeRelatedIdentities = true
) => {
return {
datasetId: info,
params: [{ id: 'vesselId', value: vesselId }],
query: [
{ id: 'dataset', value: info },
{
id: 'includes',
value: [INCLUDES_RELATED_SELF_REPORTED_INFO_ID],
},
...(includeRelatedIdentities
? [{ id: 'includes', value: [INCLUDES_RELATED_SELF_REPORTED_INFO_ID] }]
: []),
],
endpoint: EndpointId.Vessel,
} as DataviewDatasetConfig
Expand Down
18 changes: 18 additions & 0 deletions apps/fishing-map/features/search/advanced/SearchAdvanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import SearchPlaceholder, {
SearchEmptyState,
} from 'features/search/SearchPlaceholders'
import { isAdvancedSearchAllowed } from 'features/search/search.selectors'
import { selectIsGFWUser } from 'features/user/selectors/user.selectors'
import SearchError from '../basic/SearchError'

const SearchAdvancedResults = dynamic(
Expand All @@ -44,12 +45,14 @@ function SearchAdvanced({
const dispatch = useAppDispatch()
const { searchPagination, searchSuggestion, searchSuggestionClicked } = useSearchConnect()
const advancedSearchAllowed = useSelector(isAdvancedSearchAllowed)
const { searchFilters, setSearchFilters } = useSearchFiltersConnect()
const searchStatus = useSelector(selectSearchStatus)
const searchQuery = useSelector(selectSearchQuery)
const searchStatusCode = useSelector(selectSearchStatusCode)
const { dispatchQueryParams } = useLocationConnect()
const { hasFilters } = useSearchFiltersConnect()
const searchFilterErrors = useSearchFiltersErrors()
const isGFWUser = useSelector(selectIsGFWUser)
const ref = useEventKeyListener(['Enter'], fetchResults)

const resetSearchState = useCallback(() => {
Expand All @@ -73,12 +76,27 @@ function SearchAdvanced({
dispatchQueryParams({ query: e.target.value })
}

const handleSearchIdChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchFilters({ id: e.target.value })
}

const hasSearchFilterErrors = Object.keys(searchFilterErrors).length > 0

return (
<div className={styles.advancedLayout}>
<div className={styles.form}>
<div className={styles.formFields} ref={ref}>
{isGFWUser && (
<div>
<label>Vessel ID (ONLY FOR GFW USERS)</label>
<InputText
onChange={handleSearchIdChange}
id="id"
value={searchFilters.id || ''}
className={styles.input}
/>
</div>
)}
<InputText
onChange={handleSearchQueryChange}
id="name"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useSelector } from 'react-redux'
import type { MRT_ColumnDef } from 'material-react-table';
import type { MRT_ColumnDef } from 'material-react-table'
import { MaterialReactTable } from 'material-react-table'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { uniq } from 'es-toolkit'
import { IconButton, Tooltip, TransmissionsTimeline } from '@globalfishingwatch/ui-components'
import type { Dataset} from '@globalfishingwatch/api-types';
import type { Dataset } from '@globalfishingwatch/api-types'
import { VesselIdentitySourceEnum } from '@globalfishingwatch/api-types'
import type {
VesselLastIdentity} from 'features/search/search.slice';
import type { VesselLastIdentity } from 'features/search/search.slice'
import {
cleanVesselSearchResults,
selectSearchResults,
Expand All @@ -28,11 +27,10 @@ import { AsyncReducerStatus } from 'utils/async-slice'
import type { SearchComponentProps } from 'features/search/basic/SearchBasic'
import { useAppDispatch } from 'features/app/app.hooks'
import { FIRST_YEAR_OF_DATA } from 'data/config'
import type { Locale } from 'types'
import type { Locale, QueryParam } from 'types'
import type { VesselSearchState } from 'features/search/search.types'
import I18nDate from 'features/i18n/i18nDate'
import type {
VesselIdentityProperty} from 'features/vessel/vessel.utils';
import type { VesselIdentityProperty } from 'features/vessel/vessel.utils'
import {
getBestMatchCriteriaIdentity,
getOtherVesselNames,
Expand Down Expand Up @@ -201,7 +199,11 @@ function SearchAdvancedResults({ fetchResults, fetchMoreResults }: SearchCompone
const { transmissionDateFrom, transmissionDateTo } = vesselData
const name = shipname ? formatInfoField(shipname, 'shipname') : EMPTY_FIELD_PLACEHOLDER
const label = `${name} ${otherNamesLabel || ''}`
const vesselQuery = { start: transmissionDateFrom, end: transmissionDateTo }
const vesselQuery = {
start: transmissionDateFrom,
end: transmissionDateTo,
includeRelatedIdentities: searchFilters.id ? false : true,
} as Record<QueryParam, any>

return (
<VesselLink
Expand Down Expand Up @@ -333,7 +335,15 @@ function SearchAdvancedResults({ fetchResults, fetchMoreResults }: SearchCompone
header: t('vessel.transmission_other', 'Transmissions'),
},
]
}, [fetchResults, i18n.language, isSearchLocation, onVesselClick, searchFilters?.infoSource, t])
}, [
fetchResults,
i18n.language,
isSearchLocation,
onVesselClick,
searchFilters.id,
searchFilters?.infoSource,
t,
])

const fetchMoreOnBottomReached = useCallback(() => {
if (tableContainerRef.current) {
Expand Down
4 changes: 4 additions & 0 deletions apps/fishing-map/features/search/search.config.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function selectVesselSearchStateProperty<P extends VesselSearchStateProperty>(pr
})
}

export const selectSearchId = selectVesselSearchStateProperty('id')
export const selectSearchQuery = selectVesselSearchStateProperty('query')
export const selectSearchOption = selectVesselSearchStateProperty('searchOption')
export const selectSearchInfoSource = selectVesselSearchStateProperty('infoSource')
Expand All @@ -34,6 +35,7 @@ export const selectSearchOrigin = selectVesselSearchStateProperty('origin')

export const selectSearchFilters = createSelector(
[
selectSearchId,
selectSearchFlag,
selectSearchSources,
selectSearchTransmissionDateFrom,
Expand All @@ -52,6 +54,7 @@ export const selectSearchFilters = createSelector(
selectSearchInfoSource,
],
(
id,
flag,
sources,
transmissionDateFrom,
Expand All @@ -70,6 +73,7 @@ export const selectSearchFilters = createSelector(
infoSource
): VesselSearchState => {
return {
id,
flag,
sources,
transmissionDateFrom,
Expand Down
1 change: 1 addition & 0 deletions apps/fishing-map/features/search/search.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const RESULTS_PER_PAGE = 20
export const SSVID_LENGTH = 9
export const IMO_LENGTH = 7
export const EMPTY_FILTERS = {
id: undefined,
query: undefined,
flag: undefined,
infoSource: undefined,
Expand Down
1 change: 1 addition & 0 deletions apps/fishing-map/features/search/search.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { GearType, VesselIdentitySourceEnum, VesselType } from '@globalfish
import type { SearchType } from './search.config'

export type VesselSearchState = {
id?: string
query?: string
shipname?: string
sources?: string[]
Expand Down
9 changes: 9 additions & 0 deletions apps/fishing-map/features/vessel/Vessel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@
position: relative;
z-index: 1;
}

.fullProfileMessage {
color: var(--color-secondary-blue);
padding: var(--space-S) var(--space-S) var(--space-S) var(--space-M);
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: var(--border);
}
37 changes: 30 additions & 7 deletions apps/fishing-map/features/vessel/Vessel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useSelector } from 'react-redux'
import { Fragment, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import type { Tab} from '@globalfishingwatch/ui-components';
import { Spinner, Tabs } from '@globalfishingwatch/ui-components'
import type { Tab } from '@globalfishingwatch/ui-components'
import { Button, Spinner, Tabs } from '@globalfishingwatch/ui-components'
import { isAuthError } from '@globalfishingwatch/api-client'
import type { Dataview} from '@globalfishingwatch/api-types';
import type { Dataview } from '@globalfishingwatch/api-types'
import { VesselIdentitySourceEnum } from '@globalfishingwatch/api-types'
import {
selectIsWorkspaceVesselLocation,
Expand All @@ -18,6 +18,7 @@ import { AsyncReducerStatus } from 'utils/async-slice'
import { useFetchDataviewResources } from 'features/resources/resources.hooks'
import { ErrorPlaceHolder, WorkspaceLoginError } from 'features/workspace/WorkspaceError'
import {
selectIncludeRelatedIdentities,
selectVesselAreaSubsection,
selectVesselDatasetId,
selectVesselSection,
Expand All @@ -39,7 +40,7 @@ import { BASEMAP_DATAVIEW_SLUG } from 'data/workspaces'
import { useVesselFitBounds } from 'features/vessel/vessel-bounds.hooks'
import { getVesselIdentities } from 'features/vessel/vessel.utils'
import { TrackCategory, trackEvent } from 'features/app/analytics.hooks'
import { selectIsGuestUser } from 'features/user/selectors/user.selectors'
import { selectIsGFWUser, selectIsGuestUser } from 'features/user/selectors/user.selectors'
import {
selectVesselInfoStatus,
selectVesselInfoError,
Expand All @@ -57,6 +58,8 @@ const Vessel = () => {
const { dispatchQueryParams } = useLocationConnect()
const { removeDataviewInstance, upsertDataviewInstance } = useDataviewInstancesConnect()
const vesselId = useSelector(selectVesselId)
const isGFWUser = useSelector(selectIsGFWUser)
const includeRelatedIdentities = useSelector(selectIncludeRelatedIdentities)
const vesselSection = useSelector(selectVesselSection)
const vesselArea = useSelector(selectVesselAreaSubsection)
const datasetId = useSelector(selectVesselDatasetId)
Expand Down Expand Up @@ -152,15 +155,13 @@ const Vessel = () => {
infoStatus === AsyncReducerStatus.Idle ||
(infoStatus === AsyncReducerStatus.Error && infoError?.status === 401)
) {
dispatch(fetchVesselInfoThunk({ vesselId, datasetId }))
dispatch(fetchVesselInfoThunk({ vesselId, datasetId, includeRelatedIdentities }))
}

}, [datasetId, dispatch, vesselId, urlWorkspaceId])

useEffect(() => {
dispatchClickedEvent(null)
cancelPendingInteractionRequests()

}, [])

const changeTab = useCallback(
Expand All @@ -175,6 +176,16 @@ const Vessel = () => {
[dispatchQueryParams, updateAreaLayersVisibility, vesselArea]
)

const handleFullProfileClick = useCallback(() => {
dispatchQueryParams({
includeRelatedIdentities: true,
start: undefined,
end: undefined,
vesselSelfReportedId: undefined,
})
window.location.reload()
}, [dispatchQueryParams])

if (infoStatus === AsyncReducerStatus.Loading) {
return <Spinner />
}
Expand All @@ -199,6 +210,18 @@ const Vessel = () => {
<Fragment>
{infoStatus === AsyncReducerStatus.Finished && (
<Fragment>
{isGFWUser && !includeRelatedIdentities && (
<div className={styles.fullProfileMessage}>
<div>
Identity and activity of a single vessel id (only for GFW users):
<br />
{vesselId}
</div>
<Button type="secondary" size="small" className="" onClick={handleFullProfileClick}>
See full profile
</Button>
</div>
)}
<div className={styles.headerContainer}>
<VesselHeader />
</div>
Expand Down
3 changes: 3 additions & 0 deletions apps/fishing-map/features/vessel/vessel.config.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const selectVesselRelatedSubsection = selectVesselProfileStateProperty('v
export const selectViewOnlyVessel = selectVesselProfileStateProperty('viewOnlyVessel')
export const selectVesselRegistryId = selectVesselProfileStateProperty('vesselRegistryId')
export const selectVesselSelfReportedId = selectVesselProfileStateProperty('vesselSelfReportedId')
export const selectIncludeRelatedIdentities = selectVesselProfileStateProperty(
'includeRelatedIdentities'
)

export const selectVesselIdentityId = createSelector(
[selectVesselIdentitySource, selectVesselRegistryId, selectVesselSelfReportedId],
Expand Down
1 change: 1 addition & 0 deletions apps/fishing-map/features/vessel/vessel.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const DEFAULT_VESSEL_STATE: VesselProfileState = {
vesselArea: 'eez',
vesselRelated: 'encounters',
viewOnlyVessel: true,
includeRelatedIdentities: true,
}

export type VesselRenderField<Key = string> = {
Expand Down
22 changes: 17 additions & 5 deletions apps/fishing-map/features/vessel/vessel.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,19 @@ const initialState: VesselState = {

type VesselSliceState = { vessel: VesselState }

type FetchVesselThunkParams = { vesselId: string; datasetId: string }
type FetchVesselThunkParams = {
vesselId: string
datasetId: string
includeRelatedIdentities: boolean
}
export const fetchVesselInfoThunk = createAsyncThunk(
'vessel/fetchInfo',
async (
{ vesselId, datasetId }: FetchVesselThunkParams = {} as FetchVesselThunkParams,
{
vesselId,
datasetId,
includeRelatedIdentities,
}: FetchVesselThunkParams = {} as FetchVesselThunkParams,
{ dispatch, rejectWithValue, getState }
) => {
try {
Expand All @@ -123,9 +131,13 @@ export const fetchVesselInfoThunk = createAsyncThunk(
})
dispatch(fetchDatasetsByIdsThunk({ ids: datasetsToFetch }))

const datasetConfig = getVesselInfoDataviewInstanceDatasetConfig(vesselId, {
info: dataset.id,
})
const datasetConfig = getVesselInfoDataviewInstanceDatasetConfig(
vesselId,
{
info: dataset.id,
},
includeRelatedIdentities
)
if (guestUser) {
// This changes the order of the query params to avoid the cache
datasetConfig.query?.push(CACHE_FALSE_PARAM)
Expand Down
1 change: 1 addition & 0 deletions apps/fishing-map/features/vessel/vessel.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type VesselProfileState = {
vesselIdentitySource: VesselIdentitySourceEnum
vesselActivityMode: VesselProfileActivityMode
viewOnlyVessel: boolean
includeRelatedIdentities?: boolean
}

export type VesselProfileStateProperty = keyof VesselProfileState
3 changes: 3 additions & 0 deletions libs/api-client/src/utils/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export const getAdvancedSearchQuery = (

const getFieldValue = (value: string) => {
const operator = field?.operator || params?.operator || '='
if (field.key === 'id') {
return `selfReportedInfo.id ${operator} '${value}'`
}
if (field.key === 'owner') {
return `registryOwners.name ${operator} ${value}`
}
Expand Down