diff --git a/ui/src/components/filtering/filter-control.tsx b/ui/src/components/filtering/filter-control.tsx
index 2b739a695..dbf4ba9b0 100644
--- a/ui/src/components/filtering/filter-control.tsx
+++ b/ui/src/components/filtering/filter-control.tsx
@@ -5,10 +5,13 @@ import { AlgorithmFilter, NotAlgorithmFilter } from './filters/algorithm-filter'
import { CollectionFilter } from './filters/collection-filter'
import { DateFilter } from './filters/date-filter'
import { ImageFilter } from './filters/image-filter'
+import { PipelineFilter } from './filters/pipeline-filter'
import { ScoreFilter } from './filters/score-filter'
import { SessionFilter } from './filters/session-filter'
import { StationFilter } from './filters/station-filter'
+import { StatusFilter } from './filters/status-filter'
import { TaxonFilter } from './filters/taxon-filter'
+import { TypeFilter } from './filters/type-filter'
import { FilterProps } from './filters/types'
import { VerificationStatusFilter } from './filters/verification-status-filter'
@@ -23,9 +26,14 @@ const ComponentMap: {
deployment: StationFilter,
detections__source_image: ImageFilter,
event: SessionFilter,
- verified: VerificationStatusFilter,
+ pipeline: PipelineFilter,
not_algorithm: NotAlgorithmFilter,
+ source_image_collection: CollectionFilter,
+ source_image_single: ImageFilter,
+ status: StatusFilter,
taxon: TaxonFilter,
+ type: TypeFilter,
+ verified: VerificationStatusFilter,
}
interface FilterControlProps {
diff --git a/ui/src/components/filtering/filtering.tsx b/ui/src/components/filtering/filtering.tsx
index a9381c154..458fd7764 100644
--- a/ui/src/components/filtering/filtering.tsx
+++ b/ui/src/components/filtering/filtering.tsx
@@ -1,22 +1,13 @@
import { BREAKPOINTS } from 'components/constants'
import { ChevronsUpDown } from 'lucide-react'
import { Box, Button, Collapsible } from 'nova-ui-kit'
-import { FilterControl } from './filter-control'
+import { ReactNode } from 'react'
interface FilteringProps {
- config: {
- capture?: boolean
- endDate?: boolean
- scoreThreshold?: boolean
- session?: boolean
- startDate?: boolean
- station?: boolean
- taxon?: boolean
- verified?: boolean
- }
+ children?: ReactNode
}
-export const Filtering = ({ config }: FilteringProps) => (
+export const Filtering = ({ children }: FilteringProps) => (
(
- {config.capture && (
-
- )}
- {config.session && }
- {config.startDate && }
- {config.endDate && }
- {config.taxon && }
- {config.scoreThreshold && (
-
- )}
- {config.verified && }
- {config.station && }
+ {children}
diff --git a/ui/src/components/filtering/filters/image-filter.tsx b/ui/src/components/filtering/filters/image-filter.tsx
index 25b8b399c..081ab9dce 100644
--- a/ui/src/components/filtering/filters/image-filter.tsx
+++ b/ui/src/components/filtering/filters/image-filter.tsx
@@ -6,7 +6,7 @@ export const ImageFilter = ({ value }: FilterProps) => {
const label = (() => {
if (capture) {
- return capture?.dateTimeLabel
+ return `#${capture.id}`
}
if (value && isLoading) {
return 'Loading...'
@@ -15,7 +15,7 @@ export const ImageFilter = ({ value }: FilterProps) => {
})()
return (
-
+
{label}
)
diff --git a/ui/src/components/filtering/filters/pipeline-filter.tsx b/ui/src/components/filtering/filters/pipeline-filter.tsx
new file mode 100644
index 000000000..55df36728
--- /dev/null
+++ b/ui/src/components/filtering/filters/pipeline-filter.tsx
@@ -0,0 +1,30 @@
+import { usePipelines } from 'data-services/hooks/pipelines/usePipelines'
+import { Select } from 'nova-ui-kit'
+import { useParams } from 'react-router-dom'
+import { FilterProps } from './types'
+
+export const PipelineFilter = ({ value, onAdd }: FilterProps) => {
+ const { projectId } = useParams()
+ const { pipelines = [], isLoading } = usePipelines({
+ projectId: projectId as string,
+ })
+
+ return (
+
+
+
+
+
+ {pipelines.map((p) => (
+
+ {p.name}
+
+ ))}
+
+
+ )
+}
diff --git a/ui/src/components/filtering/filters/status-filter.tsx b/ui/src/components/filtering/filters/status-filter.tsx
new file mode 100644
index 000000000..b22495b61
--- /dev/null
+++ b/ui/src/components/filtering/filters/status-filter.tsx
@@ -0,0 +1,32 @@
+import { Job, SERVER_JOB_STATUS_CODES } from 'data-services/models/job'
+import { Select } from 'nova-ui-kit'
+import { FilterProps } from './types'
+
+const OPTIONS = SERVER_JOB_STATUS_CODES.map((code) => {
+ const statusInfo = Job.getStatusInfo(code)
+
+ return {
+ ...statusInfo,
+ }
+}).sort((o1, o2) => o1.type - o2.type)
+
+export const StatusFilter = ({ value, onAdd }: FilterProps) => (
+
+
+
+
+
+ {OPTIONS.map((option) => (
+
+
+
+ {option.label}
+
+
+ ))}
+
+
+)
diff --git a/ui/src/components/filtering/filters/type-filter.tsx b/ui/src/components/filtering/filters/type-filter.tsx
new file mode 100644
index 000000000..1a50eb967
--- /dev/null
+++ b/ui/src/components/filtering/filters/type-filter.tsx
@@ -0,0 +1,26 @@
+import { Job, SERVER_JOB_TYPES } from 'data-services/models/job'
+import { Select } from 'nova-ui-kit'
+import { FilterProps } from './types'
+
+const OPTIONS = SERVER_JOB_TYPES.map((key) => {
+ const typrInfo = Job.getJobTypeInfo(key)
+
+ return {
+ ...typrInfo,
+ }
+})
+
+export const TypeFilter = ({ value, onAdd }: FilterProps) => (
+
+
+
+
+
+ {OPTIONS.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+)
diff --git a/ui/src/data-services/models/capture-details.ts b/ui/src/data-services/models/capture-details.ts
index 4a5d0c105..6c91b214b 100644
--- a/ui/src/data-services/models/capture-details.ts
+++ b/ui/src/data-services/models/capture-details.ts
@@ -1,5 +1,5 @@
import { Capture, ServerCapture } from './capture'
-import { Job, JobStatus } from './job'
+import { Job } from './job'
export type ServerCaptureDetails = ServerCapture & any // TODO: Update this type
@@ -21,9 +21,9 @@ export class CaptureDetails extends Capture {
get hasJobInProgress(): boolean {
return this._jobs.some(
(job) =>
- job.status === JobStatus.Created ||
- job.status === JobStatus.Pending ||
- job.status === JobStatus.Started
+ job.status.code === 'CREATED' ||
+ job.status.code === 'PENDING' ||
+ job.status.code === 'STARTED'
)
}
diff --git a/ui/src/data-services/models/job-details.ts b/ui/src/data-services/models/job-details.ts
index 756aa27f5..33180d082 100644
--- a/ui/src/data-services/models/job-details.ts
+++ b/ui/src/data-services/models/job-details.ts
@@ -1,5 +1,4 @@
-import { Job, JobStatus, ServerJob } from './job'
-import { Pipeline } from './pipeline'
+import { Job, JobStatusType, ServerJob, ServerJobStatusCode } from './job'
export type ServerJobDetails = ServerJob & any // TODO: Update this type
@@ -20,25 +19,23 @@ export class JobDetails extends Job {
return this._job.progress.errors ?? []
}
- get pipeline(): Pipeline | undefined {
- return this._job.pipeline ? new Pipeline(this._job.pipeline) : undefined
- }
-
get logs(): string[] {
return this._job.progress.logs ?? []
}
get stages(): {
+ details: string
fields: { key: string; label: string; value?: string | number }[]
- name: string
key: string
- status: JobStatus
- statusLabel: string
- statusDetails: string
+ name: string
+ status: {
+ code: ServerJobStatusCode
+ label: string
+ type: JobStatusType
+ color: string
+ }
}[] {
return this._job.progress.stages.map((stage: any) => {
- const status = this.getStatus(stage.status)
-
const fields: { key: string; label: string; value?: string | number }[] =
stage.params.map((param: any) => ({
key: param.key,
@@ -47,33 +44,12 @@ export class JobDetails extends Job {
}))
return {
+ details: stage.status_label,
fields,
key: stage.key,
name: stage.name,
- status,
- statusLabel: this.getStatusLabel(status),
- statusDetails: stage.status_label,
+ status: Job.getStatusInfo(stage.status),
}
})
}
-
- get sourceImages(): { id: string; name: string } | undefined {
- const collection = this._job.source_image_collection
-
- return collection
- ? { id: `${collection.id}`, name: collection.name }
- : undefined
- }
-
- get sourceImage() {
- const capture = this._job.source_image_single
-
- return capture
- ? {
- id: `${capture.id}`,
- label: `${capture.id}`,
- sessionId: capture.event_id ? `${capture.event_id}` : undefined,
- }
- : undefined
- }
}
diff --git a/ui/src/data-services/models/job.ts b/ui/src/data-services/models/job.ts
index 145e9210e..2ff240706 100644
--- a/ui/src/data-services/models/job.ts
+++ b/ui/src/data-services/models/job.ts
@@ -1,24 +1,38 @@
import { getFormatedDateTimeString } from 'utils/date/getFormatedDateTimeString/getFormatedDateTimeString'
-import { STRING, translate } from 'utils/language'
import { UserPermission } from 'utils/user/types'
+import { Pipeline } from './pipeline'
+
+export const SERVER_JOB_STATUS_CODES = [
+ 'CANCELING',
+ 'CREATED',
+ 'FAILURE',
+ 'PENDING',
+ 'RECEIVED',
+ 'RETRY',
+ 'REVOKED',
+ 'STARTED',
+ 'SUCCESS',
+ 'UNKNOWN',
+] as const
+
+export const SERVER_JOB_TYPES = [
+ 'ml',
+ 'data_storage_sync',
+ 'populate_captures_collection',
+ 'unknown',
+] as const
export type ServerJob = any // TODO: Update this type
-export enum JobStatus {
- Created = 'created',
- Pending = 'pending',
- Started = 'started',
- Success = 'success',
- Canceling = 'canceling',
- Revoked = 'revoked',
- Failed = 'failed',
- Retrying = 'retrying',
- Unknown = 'unknown',
-}
+export type ServerJobStatusCode = (typeof SERVER_JOB_STATUS_CODES)[number]
+
+export type ServerJobType = (typeof SERVER_JOB_TYPES)[number]
-export type JobType = {
- name: string
- key: string
+export enum JobStatusType {
+ Success,
+ Warning,
+ Error,
+ Neutral,
}
export class Job {
@@ -31,7 +45,7 @@ export class Job {
get canCancel(): boolean {
return (
this._job.user_permissions.includes(UserPermission.Update) &&
- (this.status === JobStatus.Started || this.status === JobStatus.Pending)
+ (this.status.code === 'STARTED' || this.status.code === 'PENDING')
)
}
@@ -42,16 +56,16 @@ export class Job {
get canQueue(): boolean {
return (
this._job.user_permissions.includes(UserPermission.Update) &&
- this.status === JobStatus.Created
+ this.status.code === 'CREATED'
)
}
get canRetry(): boolean {
return (
this._job.user_permissions.includes(UserPermission.Update) &&
- this.status !== JobStatus.Created &&
- this.status !== JobStatus.Started &&
- this.status !== JobStatus.Pending
+ this.status.code !== 'CREATED' &&
+ this.status.code !== 'STARTED' &&
+ this.status.code !== 'PENDING'
)
}
@@ -63,14 +77,6 @@ export class Job {
return getFormatedDateTimeString({ date: new Date(this._job.created_at) })
}
- get updatedAt(): string | undefined {
- if (!this._job.updated_at) {
- return
- }
-
- return getFormatedDateTimeString({ date: new Date(this._job.updated_at) })
- }
-
get finishedAt(): string | undefined {
if (!this._job.finished_at) {
return
@@ -95,73 +101,118 @@ export class Job {
return this._job.name
}
+ get pipeline(): Pipeline | undefined {
+ return this._job.pipeline ? new Pipeline(this._job.pipeline) : undefined
+ }
+
+ get progress(): {
+ label: string | undefined
+ value: number
+ } {
+ return {
+ label: this._job.progress?.summary.status_label,
+ value: this._job.progress?.summary.progress ?? 0,
+ }
+ }
get project(): string {
return this._job.project.name
}
- get jobType(): JobType {
- return this._job.job_type
+ get type(): {
+ key: ServerJobType
+ label: string
+ } {
+ return Job.getJobTypeInfo(this._job.job_type.key)
}
- get status(): JobStatus {
- return this.getStatus(this._job.status)
+ get deployment(): { id: string; name: string } | undefined {
+ const deployment = this._job.deployment
+
+ return deployment
+ ? { id: `${deployment.id}`, name: deployment.name }
+ : undefined
}
- get statusDetails(): string {
- return this._job.progress?.summary.status_label
+ get sourceImage() {
+ const capture = this._job.source_image_single
+
+ return capture
+ ? {
+ id: `${capture.id}`,
+ label: `#${capture.id}`,
+ sessionId: capture.event_id ? `${capture.event_id}` : undefined,
+ }
+ : undefined
}
- get statusValue(): number {
- return this._job.progress?.summary.progress ?? this._job.status
+ get sourceImages(): { id: string; name: string } | undefined {
+ const collection = this._job.source_image_collection
+
+ return collection
+ ? { id: `${collection.id}`, name: collection.name }
+ : undefined
}
- get statusLabel(): string {
- return this.getStatusLabel(this.status)
+ get status(): {
+ code: ServerJobStatusCode
+ label: string
+ type: JobStatusType
+ color: string
+ } {
+ return Job.getStatusInfo(this._job.status)
}
- protected getStatus(status: string): JobStatus {
- switch (status) {
- case 'CREATED':
- return JobStatus.Created
- case 'PENDING':
- return JobStatus.Pending
- case 'STARTED':
- return JobStatus.Started
- case 'RUNNING':
- return JobStatus.Started
- case 'SUCCESS':
- return JobStatus.Success
- case 'CANCELING':
- return JobStatus.Canceling
- case 'REVOKED':
- return JobStatus.Revoked
- case 'RETRY':
- return JobStatus.Retrying
- case 'FAILURE':
- return JobStatus.Failed
- default:
- return JobStatus.Unknown
+ get updatedAt(): string | undefined {
+ if (!this._job.updated_at) {
+ return
+ }
+
+ return getFormatedDateTimeString({ date: new Date(this._job.updated_at) })
+ }
+
+ static getJobTypeInfo(key: ServerJobType) {
+ const label = {
+ ml: 'ML pipeline',
+ data_storage_sync: 'Data storage sync',
+ populate_captures_collection: 'Populate captures collection',
+ unknown: 'Unknown',
+ }[key]
+
+ return {
+ key,
+ label,
}
}
- protected getStatusLabel(status: JobStatus): string {
- switch (status) {
- case JobStatus.Created:
- return translate(STRING.CREATED)
- case JobStatus.Pending:
- return translate(STRING.PENDING)
- case JobStatus.Started:
- return translate(STRING.RUNNING)
- case JobStatus.Success:
- return translate(STRING.DONE)
- case JobStatus.Canceling:
- return translate(STRING.CANCELING)
- case JobStatus.Revoked:
- return translate(STRING.REVOKED)
- case JobStatus.Failed:
- return translate(STRING.FAILED)
- default:
- return translate(STRING.UNKNOWN)
+ static getStatusInfo(code: ServerJobStatusCode) {
+ const label =
+ String(code).charAt(0).toUpperCase() + String(code).toLowerCase().slice(1)
+
+ const type = {
+ CANCELING: JobStatusType.Warning,
+ CREATED: JobStatusType.Neutral,
+ FAILURE: JobStatusType.Error,
+ PENDING: JobStatusType.Warning,
+ RECEIVED: JobStatusType.Neutral,
+ RETRY: JobStatusType.Warning,
+ REVOKED: JobStatusType.Error,
+ STARTED: JobStatusType.Warning,
+ SUCCESS: JobStatusType.Success,
+ UNKNOWN: JobStatusType.Neutral,
+ }[code]
+
+ const color = {
+ [JobStatusType.Error]: '#ef4444', // color-destructive-500,
+ [JobStatusType.Neutral]: '#78777f', // color-neutral-300
+ [JobStatusType.Success]: '#09af8a', // color-success-500
+ [JobStatusType.Warning]: '#f59e0b', // color-warning-500
+ }[type]
+
+ return {
+ code,
+ label,
+ type,
+ color,
}
}
}
diff --git a/ui/src/design-system/components/status/status-bar/status-bar.module.scss b/ui/src/design-system/components/status/status-bar/status-bar.module.scss
index 7e629e4df..50b4eec3e 100644
--- a/ui/src/design-system/components/status/status-bar/status-bar.module.scss
+++ b/ui/src/design-system/components/status/status-bar/status-bar.module.scss
@@ -16,22 +16,6 @@
top: 0;
left: 0;
transition: width 400ms ease, background-color 200ms ease;
-
- &.success {
- background-color: $color-success-500;
- }
-
- &.warning {
- background-color: $color-warning-500;
- }
-
- &.error {
- background-color: $color-destructive-500;
- }
-
- &.neutral {
- background-color: $color-neutral-300;
- }
}
.description {
diff --git a/ui/src/design-system/components/status/status-bar/status-bar.tsx b/ui/src/design-system/components/status/status-bar/status-bar.tsx
index b71013593..1fdb55624 100644
--- a/ui/src/design-system/components/status/status-bar/status-bar.tsx
+++ b/ui/src/design-system/components/status/status-bar/status-bar.tsx
@@ -1,22 +1,13 @@
-import classNames from 'classnames'
-import { Status } from '../types'
import styles from './status-bar.module.scss'
-const statusClasses: { [key in Status]: string } = {
- [Status.Success]: styles.success,
- [Status.Warning]: styles.warning,
- [Status.Error]: styles.error,
- [Status.Neutral]: styles.neutral,
-}
-
export const StatusBar = ({
+ color,
description,
progress,
- status,
}: {
+ color: string
description?: string
progress: number // Value in range [0,1]
- status: Status
}) => {
if (progress < 0 || progress > 1) {
throw Error(
@@ -28,8 +19,8 @@ export const StatusBar = ({
{description?.length ? (
diff --git a/ui/src/design-system/components/status/status-marker/status-marker.module.scss b/ui/src/design-system/components/status/status-marker/status-marker.module.scss
index 150a9b5e6..d2b465608 100644
--- a/ui/src/design-system/components/status/status-marker/status-marker.module.scss
+++ b/ui/src/design-system/components/status/status-marker/status-marker.module.scss
@@ -5,20 +5,4 @@
height: 12px;
border-radius: 50%;
background-color: $color-neutral-100;
-
- &.success {
- background-color: $color-success-500;
- }
-
- &.warning {
- background-color: $color-warning-500;
- }
-
- &.error {
- background-color: $color-destructive-500;
- }
-
- &.neutral {
- background-color: $color-neutral-300;
- }
}
diff --git a/ui/src/design-system/components/status/status-marker/status-marker.tsx b/ui/src/design-system/components/status/status-marker/status-marker.tsx
index 2f5a5faeb..773a25d4f 100644
--- a/ui/src/design-system/components/status/status-marker/status-marker.tsx
+++ b/ui/src/design-system/components/status/status-marker/status-marker.tsx
@@ -1,14 +1,9 @@
import classNames from 'classnames'
-import { Status } from '../types'
import styles from './status-marker.module.scss'
-const statusClasses: { [key in Status]: string } = {
- [Status.Success]: styles.success,
- [Status.Warning]: styles.warning,
- [Status.Error]: styles.error,
- [Status.Neutral]: styles.neutral,
-}
-
-export const StatusMarker = ({ status }: { status: Status }) => (
-
+export const StatusMarker = ({ color }: { color: string }) => (
+
)
diff --git a/ui/src/design-system/components/table/column-settings/column-settings.tsx b/ui/src/design-system/components/table/column-settings/column-settings.tsx
index 9fd1c4f1c..20b88d21a 100644
--- a/ui/src/design-system/components/table/column-settings/column-settings.tsx
+++ b/ui/src/design-system/components/table/column-settings/column-settings.tsx
@@ -32,20 +32,22 @@ export const ColumnSettings = ({
{translate(STRING.COLUMNS)}
- {columns.map((column) => (
- {
- onColumnSettingsChange({
- ...columnSettings,
- [column.id]: checked,
- })
- }}
- />
- ))}
+ {columns.map((column) =>
+ column.name.length ? (
+ {
+ onColumnSettingsChange({
+ ...columnSettings,
+ [column.id]: checked,
+ })
+ }}
+ />
+ ) : null
+ )}
diff --git a/ui/src/design-system/components/table/status-table-cell/status-table-cell.tsx b/ui/src/design-system/components/table/status-table-cell/status-table-cell.tsx
index e74172cea..765422c7c 100644
--- a/ui/src/design-system/components/table/status-table-cell/status-table-cell.tsx
+++ b/ui/src/design-system/components/table/status-table-cell/status-table-cell.tsx
@@ -1,28 +1,27 @@
import { StatusMarker } from 'design-system/components/status/status-marker/status-marker'
-import { Status } from 'design-system/components/status/types'
import { Tooltip } from 'design-system/components/tooltip/tooltip'
import styles from './status-table-cell.module.scss'
interface StatusTableCellProps {
+ color: string
details?: string
label: string
- status: Status
}
export const StatusTableCell = ({
+ color,
details,
label,
- status,
}: StatusTableCellProps) => (
{details?.length ? (
-
+
) : (
-
+
)}
{label}
diff --git a/ui/src/pages/job-details/job-details.tsx b/ui/src/pages/job-details/job-details.tsx
index 0266579ba..98326fd76 100644
--- a/ui/src/pages/job-details/job-details.tsx
+++ b/ui/src/pages/job-details/job-details.tsx
@@ -1,6 +1,6 @@
import { FetchInfo } from 'components/fetch-info/fetch-info'
import { FormRow, FormSection } from 'components/form/layout/layout'
-import { JobStatus } from 'data-services/models/job'
+import { JobStatusType } from 'data-services/models/job'
import { JobDetails as Job } from 'data-services/models/job-details'
import {
CodeBlock,
@@ -10,7 +10,6 @@ import * as Dialog from 'design-system/components/dialog/dialog'
import { IconType } from 'design-system/components/icon/icon'
import { InputContent, InputValue } from 'design-system/components/input/input'
import { StatusBar } from 'design-system/components/status/status-bar/status-bar'
-import { Status } from 'design-system/components/status/types'
import {
StatusBullet,
StatusBulletTheme,
@@ -67,38 +66,15 @@ export const JobDetails = ({
const JobSummary = ({ job }: { job: Job }) => {
const { projectId } = useParams()
- const status = (() => {
- switch (job.status) {
- case JobStatus.Created:
- return Status.Neutral
- case JobStatus.Pending:
- return Status.Warning
- case JobStatus.Started:
- return Status.Warning
- case JobStatus.Success:
- return Status.Success
- case JobStatus.Retrying:
- return Status.Warning
- case JobStatus.Canceling:
- return Status.Warning
- case JobStatus.Revoked:
- return Status.Error
- case JobStatus.Failed:
- return Status.Error
- default:
- return Status.Error
- }
- })()
-
return (
<>
@@ -107,9 +83,31 @@ const JobSummary = ({ job }: { job: Job }) => {
value={job.name}
/>
+ {job.delay ? (
+
+ ) : null}
+ {job.deployment ? (
+
+ ) : null}
+ {job.pipeline ? (
+
+ ) : null}
{job.sourceImage ? (
{
) : job.sourceImages ? (
) : null}
- {job.pipeline ? (
-
- ) : null}
{
{job.stages.map((stage, index) => {
const isOpen = activeStage === stage.key
- const status = (() => {
- switch (stage.status) {
- case JobStatus.Created:
- return Status.Neutral
- case JobStatus.Pending:
- return Status.Neutral
- case JobStatus.Started:
- return Status.Warning
- case JobStatus.Success:
- return Status.Success
- default:
- return Status.Error
- }
- })()
-
return (
- {status === Status.Success ? (
+ {stage.status.type === JobStatusType.Success ? (
(
{label}
- {statusDetails?.length ? (
-
+ {details?.length ? (
+
-
+
) : (
-
+
)}
)
diff --git a/ui/src/pages/jobs/jobs-columns.tsx b/ui/src/pages/jobs/jobs-columns.tsx
index 057c7acad..38a9b32de 100644
--- a/ui/src/pages/jobs/jobs-columns.tsx
+++ b/ui/src/pages/jobs/jobs-columns.tsx
@@ -1,5 +1,4 @@
-import { Job, JobStatus } from 'data-services/models/job'
-import { Status } from 'design-system/components/status/types'
+import { Job } from 'data-services/models/job'
import { BasicTableCell } from 'design-system/components/table/basic-table-cell/basic-table-cell'
import { StatusTableCell } from 'design-system/components/table/status-table-cell/status-table-cell'
import { CellTheme, TableColumn } from 'design-system/components/table/types'
@@ -35,29 +34,91 @@ export const columns: (projectId: string) => TableColumn[] = (
name: translate(STRING.FIELD_LABEL_STATUS),
tooltip: translate(STRING.TOOLTIP_STATUS),
sortField: 'status',
- renderCell: (item: Job) => {
- const status = (() => {
- switch (item.status) {
- case JobStatus.Created:
- return Status.Neutral
- case JobStatus.Pending:
- case JobStatus.Started:
- return Status.Warning
- case JobStatus.Success:
- return Status.Success
- default:
- return Status.Error
- }
- })()
-
- return (
-
- )
- },
+ renderCell: (item: Job) => (
+
+ ),
+ },
+ {
+ id: 'job-type',
+ name: 'Type',
+ renderCell: (item: Job) => ,
+ },
+ {
+ id: 'deployment',
+ sortField: 'deployment',
+ name: translate(STRING.FIELD_LABEL_DEPLOYMENT),
+ renderCell: (item: Job) =>
+ item.deployment ? (
+
+
+
+ ) : (
+ <>>
+ ),
+ },
+ {
+ id: 'pipeline',
+ sortField: 'pipeline',
+ name: translate(STRING.FIELD_LABEL_PIPELINE),
+ renderCell: (item: Job) => ,
+ },
+ {
+ id: 'source-image',
+ name: translate(STRING.FIELD_LABEL_SOURCE_IMAGE),
+ renderCell: (item: Job) =>
+ item.sourceImage?.sessionId ? (
+
+
+
+ ) : (
+ <>>
+ ),
+ },
+ {
+ id: 'source-image-collection',
+ sortField: 'source_image_collection',
+ name: translate(STRING.FIELD_LABEL_SOURCE_IMAGES),
+ renderCell: (item: Job) =>
+ item.sourceImages ? (
+
+
+
+ ) : (
+ <>>
+ ),
},
{
id: 'created-at',
@@ -71,17 +132,18 @@ export const columns: (projectId: string) => TableColumn[] = (
sortField: 'updated_at',
renderCell: (item: Job) => ,
},
+ {
+ id: 'started-at',
+ name: translate(STRING.FIELD_LABEL_STARTED_AT),
+ sortField: 'started_at',
+ renderCell: (item: Job) => ,
+ },
{
id: 'finished-at',
name: translate(STRING.FIELD_LABEL_FINISHED_AT),
sortField: 'finished_at',
renderCell: (item: Job) => ,
},
- {
- id: 'job-type',
- name: 'Type',
- renderCell: (item: Job) => ,
- },
{
id: 'actions',
name: '',
diff --git a/ui/src/pages/jobs/jobs.tsx b/ui/src/pages/jobs/jobs.tsx
index 57b2d991c..b9862fb94 100644
--- a/ui/src/pages/jobs/jobs.tsx
+++ b/ui/src/pages/jobs/jobs.tsx
@@ -1,9 +1,12 @@
+import { FilterControl } from 'components/filtering/filter-control'
+import { Filtering } from 'components/filtering/filtering'
import { useJobDetails } from 'data-services/hooks/jobs/useJobDetails'
import { useJobs } from 'data-services/hooks/jobs/useJobs'
import * as Dialog from 'design-system/components/dialog/dialog'
import { PageFooter } from 'design-system/components/page-footer/page-footer'
import { PageHeader } from 'design-system/components/page-header/page-header'
import { PaginationBar } from 'design-system/components/pagination-bar/pagination-bar'
+import { ColumnSettings } from 'design-system/components/table/column-settings/column-settings'
import { Table } from 'design-system/components/table/table/table'
import _ from 'lodash'
import { Error } from 'pages/error/error'
@@ -15,6 +18,8 @@ import { BreadcrumbContext } from 'utils/breadcrumbContext'
import { APP_ROUTES } from 'utils/constants'
import { getAppRoute } from 'utils/getAppRoute'
import { STRING, translate } from 'utils/language'
+import { useColumnSettings } from 'utils/useColumnSettings'
+import { useFilters } from 'utils/useFilters'
import { usePagination } from 'utils/usePagination'
import { useSort } from 'utils/useSort'
import { UserPermission } from 'utils/user/types'
@@ -23,12 +28,21 @@ import { columns } from './jobs-columns'
export const Jobs = () => {
const { projectId, id } = useParams()
const { pagination, setPage } = usePagination()
+ const { filters } = useFilters()
const { sort, setSort } = useSort({ field: 'created_at', order: 'desc' })
+ const { columnSettings, setColumnSettings } = useColumnSettings('jobs', {
+ name: true,
+ status: true,
+ 'job-type': true,
+ pipeline: true,
+ 'created-at': true,
+ })
const { jobs, userPermissions, total, isLoading, isFetching, error } =
useJobs({
projectId,
- pagination,
sort,
+ pagination,
+ filters,
})
const canCreate = userPermissions?.includes(UserPermission.Create)
@@ -37,26 +51,47 @@ export const Jobs = () => {
}
return (
- <>
-
- {canCreate ? : null}
-
-
+
+
+
+
+ {/* TODO: Uncomment when supported by backend */}
+ {/* */}
+
+
+ {/* TODO: Uncomment when supported by backend */}
+ {/* */}
+
+
+
+
+
+
+ {canCreate ? : null}
+
+
!!columnSettings[column.id]
+ )}
+ sortable
+ sortSettings={sort}
+ onSortSettingsChange={setSort}
+ />
+
{jobs?.length ? (
{
) : null}
{id ? : null}
- >
+
)
}
diff --git a/ui/src/pages/occurrences/occurrences.tsx b/ui/src/pages/occurrences/occurrences.tsx
index 10d636065..e46bc6204 100644
--- a/ui/src/pages/occurrences/occurrences.tsx
+++ b/ui/src/pages/occurrences/occurrences.tsx
@@ -1,4 +1,5 @@
import { AdvancedFiltering } from 'components/filtering/advanced-filtering'
+import { FilterControl } from 'components/filtering/filter-control'
import { Filtering } from 'components/filtering/filtering'
import { useOccurrenceDetails } from 'data-services/hooks/occurrences/useOccurrenceDetails'
import { useOccurrences } from 'data-services/hooks/occurrences/useOccurrences'
@@ -77,17 +78,15 @@ export const Occurrences = () => {
<>
-
+
+
+
+
+
+
+
+
+
{
const { projectId } = useParams()
@@ -52,11 +53,9 @@ export const Sessions = () => {
return (
<>
-
+
+
+
{
return (
<>
-
+
+
+
+
+
Pending > Running > Done. A Failed status means the job stopped before it had finished.',
+ 'A status is the processing stage of a job once submitted: Created > Pending > Started > Success. A Failed status means the job stopped before it had finished.',
[STRING.TOOLTIP_STORAGE]:
'A storage is a place where source images are kept, for example a S3 bucket. One or many stations can be connected to a storage.',
diff --git a/ui/src/utils/useFilters.ts b/ui/src/utils/useFilters.ts
index 90c89773c..7a8b5e4ea 100644
--- a/ui/src/utils/useFilters.ts
+++ b/ui/src/utils/useFilters.ts
@@ -11,7 +11,7 @@ export const AVAILABLE_FILTERS = [
},
{
label: 'Collection',
- field: 'collection',
+ field: 'source_image_collection', // TODO: Can we update this key to "collection" to streamline?
},
{
label: 'Station',
@@ -27,15 +27,15 @@ export const AVAILABLE_FILTERS = [
},
{
label: 'Image',
- field: 'detections__source_image',
+ field: 'detections__source_image', // TODO: Can we update this key to "source_image" to streamline?
},
{
label: 'Session',
field: 'event',
},
{
- label: 'Verification status',
- field: 'verified',
+ label: 'Pipeline',
+ field: 'pipeline',
},
{
label: 'Exclude algorithm',
@@ -45,6 +45,26 @@ export const AVAILABLE_FILTERS = [
label: 'Taxon',
field: 'taxon',
},
+ {
+ label: 'Source image',
+ field: 'source_image_single', // TODO: Can we update this key to "source_image" to streamline?
+ },
+ {
+ label: 'Source image collection',
+ field: 'source_image_collection',
+ },
+ {
+ label: 'Status',
+ field: 'status',
+ },
+ {
+ label: 'Type',
+ field: 'type',
+ },
+ {
+ label: 'Verification status',
+ field: 'verified',
+ },
]
export const useFilters = (defaultFilters?: { [field: string]: string }) => {