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 }) => {