Skip to content

Commit

Permalink
Merge pull request #331 from RolnickLab/web-ui-entities
Browse files Browse the repository at this point in the history
Introduce related entities
  • Loading branch information
mihow authored Jan 17, 2024
2 parents 1f26335 + d184095 commit 8f02271
Show file tree
Hide file tree
Showing 30 changed files with 965 additions and 42 deletions.
3 changes: 3 additions & 0 deletions ui/src/data-services/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const API_ROUTES = {
CAPTURES: 'captures',
COLLECTIONS: 'captures/collections',
DEPLOYMENTS: 'deployments',
DEVICES: 'deployments/devices',
IDENTIFICATIONS: 'identifications',
JOBS: 'jobs',
LOGIN: 'auth/token/login',
Expand All @@ -14,7 +15,9 @@ export const API_ROUTES = {
PIPELINES: 'ml/pipelines',
PROJECTS: 'projects',
SESSIONS: 'events',
SITES: 'deployments/sites',
SPECIES: 'taxa',
STORAGE: 'storage',
SUMMARY: 'status/summary',
USERS: 'users',
}
Expand Down
5 changes: 3 additions & 2 deletions ui/src/data-services/hooks/deployments/useCreateDeployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'

const convertToServerFieldValues = (fieldValues: DeploymentFieldValues) => ({
data_source: fieldValues.path,
description: fieldValues.description,
data_source_id: fieldValues.dataSourceId,
device_id: fieldValues.deviceId,
events: [],
name: fieldValues.name,
latitude: fieldValues.latitude,
longitude: fieldValues.longitude,
occurrences: [],
project_id: fieldValues.projectId,
research_site_id: fieldValues.siteId,
})

export const useCreateDeployment = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'

const convertToServerFieldValues = (fieldValues: DeploymentFieldValues) => ({
data_source: fieldValues.path,
data_source_id: fieldValues.dataSourceId,
description: fieldValues.description,
device_id: fieldValues.deviceId,
name: fieldValues.name,
latitude: fieldValues.latitude,
longitude: fieldValues.longitude,
research_site_id: fieldValues.siteId,
})

export const useUpdateDeployment = (id: string) => {
Expand Down
6 changes: 6 additions & 0 deletions ui/src/data-services/hooks/entities/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface EntityFieldValues {
description: string
name: string
projectId: string
customFields?: { [key: string]: string }
}
32 changes: 32 additions & 0 deletions ui/src/data-services/hooks/entities/useCreateEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
import { API_URL } from 'data-services/constants'
import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'
import { EntityFieldValues } from './types'
import { convertToServerFieldValues } from './utils'

const SUCCESS_TIMEOUT = 1000 // Reset success after 1 second

export const useCreateEntity = (collection: string, onSuccess?: () => void) => {
const { user } = useUser()
const queryClient = useQueryClient()

const { mutateAsync, isLoading, isSuccess, reset, error } = useMutation({
mutationFn: (fieldValues: EntityFieldValues) =>
axios.post(
`${API_URL}/${collection}/`,
convertToServerFieldValues(fieldValues),
{
headers: getAuthHeader(user),
}
),
onSuccess: () => {
queryClient.invalidateQueries([collection])
onSuccess?.()
setTimeout(reset, SUCCESS_TIMEOUT)
},
})

return { createEntity: mutateAsync, isLoading, isSuccess, error }
}
23 changes: 23 additions & 0 deletions ui/src/data-services/hooks/entities/useDeleteEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
import { API_URL } from 'data-services/constants'
import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'

export const useDeleteEntity = (collection: string, onSuccess?: () => void) => {
const { user } = useUser()
const queryClient = useQueryClient()

const { mutateAsync, isLoading, isSuccess, error } = useMutation({
mutationFn: (id: string) =>
axios.delete(`${API_URL}/${collection}/${id}/`, {
headers: getAuthHeader(user),
}),
onSuccess: () => {
queryClient.invalidateQueries([collection])
onSuccess?.()
},
})

return { deleteEntity: mutateAsync, isLoading, isSuccess, error }
}
49 changes: 49 additions & 0 deletions ui/src/data-services/hooks/entities/useEntities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Entity, ServerEntity } from 'data-services/models/entity'
import { Storage } from 'data-services/models/storage'
import { FetchParams } from 'data-services/types'
import { getFetchUrl } from 'data-services/utils'
import { useMemo } from 'react'
import { useAuthorizedQuery } from '../auth/useAuthorizedQuery'

const convertServerRecord = (collection: string, record: ServerEntity) => {
if (collection === 'storage') {
return new Storage(record)
}

return new Entity(record)
}

export const useEntities = (
collection: string,
params?: FetchParams
): {
entities?: Entity[]
total: number
isLoading: boolean
isFetching: boolean
error?: unknown
} => {
const fetchUrl = getFetchUrl({ collection, params })

const { data, isLoading, isFetching, error } = useAuthorizedQuery<{
results: ServerEntity[]
count: number
}>({
queryKey: [collection, params],
url: fetchUrl,
})

const entities = useMemo(
() =>
data?.results.map((record) => convertServerRecord(collection, record)),
[data]
)

return {
entities,
total: data?.count ?? 0,
isLoading,
isFetching,
error,
}
}
36 changes: 36 additions & 0 deletions ui/src/data-services/hooks/entities/useUpdateEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
import { API_URL } from 'data-services/constants'
import { getAuthHeader } from 'data-services/utils'
import { useUser } from 'utils/user/userContext'
import { EntityFieldValues } from './types'
import { convertToServerFieldValues } from './utils'

const SUCCESS_TIMEOUT = 1000 // Reset success after 1 second

export const useUpdateEntity = (
id: string,
collection: string,
onSuccess?: () => void
) => {
const { user } = useUser()
const queryClient = useQueryClient()

const { mutateAsync, isLoading, isSuccess, reset, error } = useMutation({
mutationFn: (fieldValues: EntityFieldValues) =>
axios.patch(
`${API_URL}/${collection}/${id}/`,
convertToServerFieldValues(fieldValues),
{
headers: getAuthHeader(user),
}
),
onSuccess: () => {
queryClient.invalidateQueries([collection])
onSuccess?.()
setTimeout(reset, SUCCESS_TIMEOUT)
},
})

return { updateEntity: mutateAsync, isLoading, error, isSuccess }
}
10 changes: 10 additions & 0 deletions ui/src/data-services/hooks/entities/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EntityFieldValues } from './types'

export const convertToServerFieldValues = (fieldValues: EntityFieldValues) => {
return {
description: fieldValues.description,
name: fieldValues.name,
project: fieldValues.projectId,
...(fieldValues.customFields ?? {}),
}
}
24 changes: 21 additions & 3 deletions ui/src/data-services/models/deployment-details.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Deployment, ServerDeployment } from './deployment'
import { Entity } from './entity'
import { Storage } from './storage'

export type ServerDeploymentDetails = ServerDeployment & any // TODO: Update this type

export interface DeploymentFieldValues {
dataSourceId?: string
description: string
name: string
siteId?: string
deviceId?: string
latitude: number
longitude: number
path: string
projectId?: string
}

Expand All @@ -27,6 +31,12 @@ export class DeploymentDetails extends Deployment {
}
}

get device(): Entity | undefined {
if (this._deployment.device) {
return new Entity(this._deployment.device)
}
}

get description(): string {
return this._deployment.description
}
Expand All @@ -35,7 +45,15 @@ export class DeploymentDetails extends Deployment {
return this._exampleCaptures
}

get path(): string {
return this._deployment.data_source
get dataSource(): Storage | undefined {
if (this._deployment.data_source) {
return new Storage(this._deployment.data_source)
}
}

get site(): Entity | undefined {
if (this._deployment.research_site) {
return new Entity(this._deployment.research_site)
}
}
}
44 changes: 44 additions & 0 deletions ui/src/data-services/models/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getFormatedDateTimeString } from 'utils/date/getFormatedDateTimeString/getFormatedDateTimeString'
import { UserPermission } from 'utils/user/types'

export type ServerEntity = any // TODO: Update this type

export class Entity {
protected readonly _data: ServerEntity

public constructor(entity: ServerEntity) {
this._data = entity
}

get canDelete(): boolean {
return this._data.user_permissions.includes(UserPermission.Delete)
}

get createdAt(): string {
return getFormatedDateTimeString({
date: new Date(this._data.created_at),
})
}

get description(): string {
return this._data.description
}

get id(): string {
return `${this._data.id}`
}

get name(): string {
return this._data.name
}

get updatedAt(): string | undefined {
if (!this._data.updated_at) {
return undefined
}

return getFormatedDateTimeString({
date: new Date(this._data.updated_at),
})
}
}
21 changes: 21 additions & 0 deletions ui/src/data-services/models/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Entity } from './entity'

export type ServerStorage = any // TODO: Update this type

export class Storage extends Entity {
public constructor(entity: Storage) {
super(entity)
}

get bucket(): string {
return this._data.bucket
}

get endpointUrl(): string {
return this._data.endpoint_url
}

get publicBaseUrl(): string {
return this._data.public_base_url
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ export const config: FormConfig = {
description: {
label: translate(STRING.FIELD_LABEL_DESCRIPTION),
},
siteId: {
label: translate(STRING.FIELD_LABEL_SITE),
},
deviceId: {
label: translate(STRING.FIELD_LABEL_DEVICE),
},
dataSourceId: {
label: 'Data source',
},
latitude: {
label: translate(STRING.FIELD_LABEL_LATITUDE),
rules: { min: -90, max: 90 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const DeploymentDetailsForm = ({
values: {
name: deployment.name,
description: deployment.description,
deviceId: deployment.device?.id,
siteId: deployment.site?.id,
},
isValid: startValid,
},
Expand All @@ -52,7 +54,7 @@ export const DeploymentDetailsForm = ({
},
[Section.SourceImages]: {
values: {
path: deployment.path,
dataSourceId: deployment.dataSource?.id,
},
isValid: startValid,
},
Expand Down
Loading

0 comments on commit 8f02271

Please sign in to comment.