Skip to content
Open
17 changes: 14 additions & 3 deletions ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import { Deployments } from 'pages/deployments/deployments'
import { Jobs } from 'pages/jobs/jobs'
import { Occurrences } from 'pages/occurrences/occurrences'
import { Algorithms } from 'pages/project/algorithms/algorithms'
import { CollectionDetails } from 'pages/project/collections/collection-details'
import { Collections } from 'pages/project/collections/collections'
import { CaptureSets } from 'pages/project/capture-sets/capture-sets'
import { DefaultFilters } from 'pages/project/default-filters/default-filters'
import { Devices } from 'pages/project/entities/devices'
import { Sites } from 'pages/project/entities/sites'
Expand Down Expand Up @@ -110,8 +109,8 @@ export const App = () => (
element={<Navigate to={{ pathname: 'summary' }} replace={true} />}
/>
<Route path="summary" element={<Summary />} />
<Route path="capture-sets" element={<CaptureSets />} />
<Route path="collections" element={<Collections />} />
<Route path="collections/:id" element={<CollectionDetails />} />
<Route path="exports/:id?" element={<Exports />} />
<Route
path="processing-services/:id?"
Expand Down Expand Up @@ -284,3 +283,15 @@ const NotFound = () => (
</main>
</>
)

/* We have changed the wording from "Collections" to "Capture sets". This will redirect users to the new route. */
const Collections = () => {
const { projectId } = useParams()

return (
<Navigate
replace
to={APP_ROUTES.CAPTURE_SETS({ projectId: projectId as string })}
/>
)
}
52 changes: 43 additions & 9 deletions ui/src/components/filtering/filter-control.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { XIcon } from 'lucide-react'
import { Button } from 'nova-ui-kit'
import classNames from 'classnames'
import { ChevronRightIcon, InfoIcon, XIcon } from 'lucide-react'
import { Button, buttonVariants, Tooltip } from 'nova-ui-kit'
import { Link } from 'react-router-dom'
import { useFilters } from 'utils/useFilters'
import { AlgorithmFilter, NotAlgorithmFilter } from './filters/algorithm-filter'
import { BooleanFilter } from './filters/boolean-filter'
import { CollectionFilter } from './filters/collection-filter'
import { CaptureSetFilter } from './filters/capture-set-filter'
import { DateFilter } from './filters/date-filter'
import { ImageFilter } from './filters/image-filter'
import { PipelineFilter } from './filters/pipeline-filter'
Expand All @@ -22,8 +24,8 @@ const ComponentMap: {
[key: string]: (props: FilterProps) => JSX.Element
} = {
algorithm: AlgorithmFilter,
collection: CollectionFilter,
collections: CollectionFilter,
collection: CaptureSetFilter,
collections: CaptureSetFilter,
date_end: DateFilter,
date_start: DateFilter,
deployment: StationFilter,
Expand All @@ -35,7 +37,7 @@ const ComponentMap: {
not_tag_id: TagFilter,
not_taxa_list_id: TaxaListFilter,
pipeline: PipelineFilter,
source_image_collection: CollectionFilter,
source_image_collection: CaptureSetFilter,
source_image_single: ImageFilter,
status: StatusFilter,
tag_id: TagFilter,
Expand Down Expand Up @@ -72,9 +74,14 @@ export const FilterControl = ({

return (
<div>
<label className="flex pl-2 pb-3 text-muted-foreground body-overline-small font-bold">
{filter.label}
</label>
<div className="min-h-8 flex items-center gap-1">
<span className="text-muted-foreground body-overline-small font-bold pt-0.5">
{filter.label}
</span>
{filter.info ? (
<FilterInfo text={filter.info.text} to={filter.info.to} />
) : null}
</div>
<div className="flex items-center justify-between gap-2">
<FilterComponent
data={data}
Expand Down Expand Up @@ -102,3 +109,30 @@ export const FilterControl = ({
</div>
)
}

export const FilterInfo = ({ text, to }: { text: string; to?: string }) => (
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button className="text-muted-foreground" size="icon" variant="ghost">
<InfoIcon className="w-4 h-4" />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side="bottom" className="p-4 space-y-4 max-w-xs">
<p className="whitespace-normal">{text}</p>
{to ? (
<Link
className={classNames(
buttonVariants({ size: 'small', variant: 'outline' }),
'!w-auto'
)}
to={to}
>
<span>Configure</span>
<ChevronRightIcon className="w-4 h-4" />
</Link>
) : null}
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
)
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { useCollections } from 'data-services/hooks/collections/useCollections'
import { useCaptureSets } from 'data-services/hooks/capture-sets/useCaptureSets'
import { Select } from 'nova-ui-kit'
import { useParams } from 'react-router-dom'
import { FilterProps } from './types'

export const CollectionFilter = ({ value, onAdd }: FilterProps) => {
export const CaptureSetFilter = ({ value, onAdd }: FilterProps) => {
const { projectId } = useParams()
const { collections = [], isLoading } = useCollections({
const { captureSets = [], isLoading } = useCaptureSets({
projectId: projectId as string,
})

return (
<Select.Root
disabled={collections.length === 0}
disabled={captureSets.length === 0}
value={value ?? ''}
onValueChange={onAdd}
>
<Select.Trigger loading={isLoading}>
<Select.Value placeholder="All collections" />
<Select.Value placeholder="All capture sets" />
</Select.Trigger>
<Select.Content className="max-h-72">
{collections.map((c) => (
{captureSets.map((c) => (
<Select.Item key={c.id} value={c.id}>
{c.name}
</Select.Item>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/data-services/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ export const API_URL = '/api/v2'

export const API_ROUTES = {
ALGORITHM: 'ml/algorithms',
CAPTURE_SETS: 'captures/collections',
CAPTURES: 'captures',
CLASSIFICATIONS: 'classifications',
COLLECTIONS: 'captures/collections',
DEPLOYMENTS: 'deployments',
DEVICES: 'deployments/devices',
EXPORTS: 'exports',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import { API_ROUTES, REFETCH_INTERVAL } from 'data-services/constants'
import { Collection, ServerCollection } from 'data-services/models/collection'
import { CaptureSet, ServerCaptureSet } from 'data-services/models/capture-set'
import { FetchParams } from 'data-services/types'
import { getFetchUrl } from 'data-services/utils'
import { useMemo } from 'react'
import { UserPermission } from 'utils/user/types'
import { useAuthorizedQuery } from '../auth/useAuthorizedQuery'

const convertServerRecord = (record: ServerCollection) => new Collection(record)
const convertServerRecord = (record: ServerCaptureSet) => new CaptureSet(record)

export const useCollections = (
export const useCaptureSets = (
params: FetchParams | undefined,
poll?: boolean
): {
collections?: Collection[]
captureSets?: CaptureSet[]
total: number
userPermissions?: UserPermission[]
isLoading: boolean
isFetching: boolean
error?: unknown
} => {
const fetchUrl = getFetchUrl({ collection: API_ROUTES.COLLECTIONS, params })
const fetchUrl = getFetchUrl({ collection: API_ROUTES.CAPTURE_SETS, params })

const { data, isLoading, isFetching, error } = useAuthorizedQuery<{
results: ServerCollection[]
results: ServerCaptureSet[]
user_permissions?: UserPermission[]
count: number
}>({
queryKey: [API_ROUTES.COLLECTIONS, params],
queryKey: [API_ROUTES.CAPTURE_SETS, params],
url: fetchUrl,
refetchInterval: poll ? REFETCH_INTERVAL : undefined,
})

const collections = useMemo(
const captureSets = useMemo(
() => data?.results.map(convertServerRecord),
[data]
)

return {
collections,
captureSets,
total: data?.count ?? 0,
userPermissions: data?.user_permissions,
isLoading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { API_ROUTES, API_URL } from 'data-services/constants'
import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'

export const usePopulateCollection = () => {
export const usePopulateCaptureSet = () => {
const { user } = useUser()
const queryClient = useQueryClient()

const { mutateAsync, isLoading, isSuccess, error } = useMutation({
mutationFn: (id: string) =>
axios.post<{ id: number }>(
`${API_URL}/${API_ROUTES.COLLECTIONS}/${id}/populate/`,
`${API_URL}/${API_ROUTES.CAPTURE_SETS}/${id}/populate/`,
undefined,
{
headers: getAuthHeader(user),
}
),
onSuccess: () => {
queryClient.invalidateQueries([API_ROUTES.COLLECTIONS])
queryClient.invalidateQueries([API_ROUTES.CAPTURE_SETS])
},
})

return { populateCollection: mutateAsync, isLoading, isSuccess, error }
return { populateCaptureSet: mutateAsync, isLoading, isSuccess, error }
}
35 changes: 0 additions & 35 deletions ui/src/data-services/hooks/collections/useCollectionDetails.ts

This file was deleted.

6 changes: 3 additions & 3 deletions ui/src/data-services/hooks/entities/useEntities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Collection } from 'data-services/models/collection'
import { CaptureSet } from 'data-services/models/capture-set'
import { Entity, ServerEntity } from 'data-services/models/entity'
import { StorageSource } from 'data-services/models/storage'
import { FetchParams } from 'data-services/types'
Expand All @@ -12,8 +12,8 @@ const convertServerRecord = (collection: string, record: ServerEntity) => {
// look at the customFormMap in constants.ts
if (collection === 'storage') {
return new StorageSource(record)
} else if (collection === 'collection') {
return new Collection(record)
} else if (collection === 'capture-set') {
return new CaptureSet(record)
}

return new Entity(record)
Expand Down
34 changes: 0 additions & 34 deletions ui/src/data-services/hooks/storage-sources/useStorageDetails.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { UserPermission } from 'utils/user/types'
import { Entity } from './entity'
import { Job } from './job'

export type ServerCollection = any // TODO: Update this type
export type ServerCaptureSet = any // TODO: Update this type

export class Collection extends Entity {
export class CaptureSet extends Entity {
private readonly _jobs: Job[] = []

public constructor(entity: ServerCollection) {
public constructor(entity: ServerCaptureSet) {
super(entity)

if (this._data.jobs) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useCollections } from 'data-services/hooks/collections/useCollections'
import { useCaptureSets } from 'data-services/hooks/capture-sets/useCaptureSets'
import { Select } from 'design-system/components/select/select'
import { useParams } from 'react-router-dom'

export const CollectionsPicker = ({
export const CaptureSetPicker = ({
onValueChange,
showClear,
value,
Expand All @@ -12,15 +12,15 @@ export const CollectionsPicker = ({
value?: string
}) => {
const { projectId } = useParams()
const { collections = [], isLoading } = useCollections({
const { captureSets = [], isLoading } = useCaptureSets({
projectId: projectId as string,
})

return (
<Select
loading={isLoading}
onValueChange={onValueChange}
options={collections.map((c) => ({
options={captureSets.map((c) => ({
value: c.id,
label: c.name,
}))}
Expand Down
1 change: 1 addition & 0 deletions ui/src/pages/captures/captures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const Captures = () => {
</div>
<div className="w-full overflow-hidden">
<PageHeader
tooltip={translate(STRING.TOOLTIP_CAPTURE)}
docsLink={DOCS_LINKS.UPLOADING_DATA}
isFetching={isFetching}
isLoading={isLoading}
Expand Down
Loading