From 19d1ede7063a35c7aa907f1b3c5bc12b988eeeb1 Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Thu, 29 Jan 2026 23:56:23 +0300 Subject: [PATCH 1/4] [Feature]: Show probe statuses in the UI #3204 --- frontend/src/libs/run.ts | 11 ++++++++ frontend/src/locale/en.json | 1 + .../pages/Runs/Details/Jobs/List/helpers.ts | 25 +++++++++++++++++++ .../pages/Runs/Details/Jobs/List/hooks.tsx | 6 +++++ .../pages/Runs/Details/RunDetails/index.tsx | 9 ++++++- frontend/src/types/run.d.ts | 13 ++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) diff --git a/frontend/src/libs/run.ts b/frontend/src/libs/run.ts index b1a626bf82..8dd8be3af6 100644 --- a/frontend/src/libs/run.ts +++ b/frontend/src/libs/run.ts @@ -4,6 +4,7 @@ import { StatusIndicatorProps } from '@cloudscape-design/components'; import { capitalize } from 'libs'; import { finishedRunStatuses } from '../pages/Runs/constants'; +import { getJobProbesStatuses } from '../pages/Runs/Details/Jobs/List/helpers'; import { IModelExtended } from '../pages/Models/List/types'; @@ -75,6 +76,16 @@ export const getRunError = (run: IRun): string | null => { return error ? capitalize(error) : null; }; +export const getRunProbe = (run: IRun): string | null => { + const job = run.jobs[0]; + + if (!job) { + return '-'; + } + + return getJobProbesStatuses(run.jobs[0]); +}; + export const getRunPriority = (run: IRun): number | null => { return run.run_spec.configuration?.priority ?? null; }; diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index 342ce0cdd7..7cf3187722 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -402,6 +402,7 @@ "priority": "Priority", "provider_name": "Provider", "status": "Status", + "probe": "Probes", "submitted_at": "Submitted", "finished_at": "Finished", "metrics": { diff --git a/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts b/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts index 71247dce6a..6855c45804 100644 --- a/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts +++ b/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts @@ -48,6 +48,31 @@ export const getJobStatus = (job: IJob) => { return job.job_submissions?.[job.job_submissions.length - 1].status; }; +export const getJobSubmissionProbes = (job: IJob) => { + return job.job_submissions?.[job.job_submissions.length - 1].probes; +}; + +export const getJobProbesStatuses = (job: IJob) => { + const status = getJobStatus(job); + const probes = getJobSubmissionProbes(job); + + if (!probes?.length || status !== 'running') { + return null; + } + + return probes + ?.map((probe, index) => { + if (job.job_spec?.probes?.[index] && probe.success_streak >= job.job_spec.probes[index].ready_after) { + return '✓'; + } else if (probe.success_streak > 0) { + return '~'; + } else { + return '×'; + } + }) + .join(''); +}; + export const getJobTerminationReason = (job: IJob) => { return job.job_submissions?.[job.job_submissions.length - 1].termination_reason ?? '-'; }; diff --git a/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx b/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx index a5fabd5a2f..b7ebbdb74f 100644 --- a/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx +++ b/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx @@ -15,6 +15,7 @@ import { getJobListItemRegion, getJobListItemResources, getJobListItemSpot, + getJobProbesStatuses, getJobStatus, getJobStatusMessage, getJobSubmittedAt, @@ -67,6 +68,11 @@ export const useColumnsDefinitions = ({ ); }, }, + { + id: 'probe', + header: t('projects.run.probe'), + cell: (item: IJob) => getJobProbesStatuses(item), + }, { id: 'priority', header: t('projects.run.priority'), diff --git a/frontend/src/pages/Runs/Details/RunDetails/index.tsx b/frontend/src/pages/Runs/Details/RunDetails/index.tsx index 24e5c2718d..5de6bb9a02 100644 --- a/frontend/src/pages/Runs/Details/RunDetails/index.tsx +++ b/frontend/src/pages/Runs/Details/RunDetails/index.tsx @@ -7,7 +7,7 @@ import { format } from 'date-fns'; import { Box, ColumnLayout, Container, Header, Loader, NavigateLink, StatusIndicator } from 'components'; import { DATE_TIME_FORMAT } from 'consts'; -import { getRunError, getRunPriority, getRunStatusMessage, getStatusIconColor, getStatusIconType } from 'libs/run'; +import { getRunError, getRunPriority, getRunProbe, getRunStatusMessage, getStatusIconColor, getStatusIconType } from 'libs/run'; import { ROUTES } from 'routes'; import { useGetRunQuery } from 'services/run'; @@ -122,6 +122,13 @@ export const RunDetails = () => { + {runData.jobs.length <= 1 && ( +
+ {t('projects.run.probe')} +
{getRunProbe(runData)}
+
+ )} +
{t('projects.run.error')}
{getRunError(runData) ?? '-'}
diff --git a/frontend/src/types/run.d.ts b/frontend/src/types/run.d.ts index 452d3a9f41..2e17206a05 100644 --- a/frontend/src/types/run.d.ts +++ b/frontend/src/types/run.d.ts @@ -171,6 +171,17 @@ declare interface IAppSpec { url_query_params?: { [key: string]: string }; } +declare interface IJobProbe { + type: 'http'; + url: string; + method?: 'head' | 'post' | 'put' | 'patch' | 'delete' | 'get'; + headers?: Array<{ name: string; value: string }>; + body?: string; + timeout: number; + interval: number; + ready_after: number; +} + declare interface IJobSpec { app_specs?: IAppSpec; commands: string[]; @@ -181,6 +192,7 @@ declare interface IJobSpec { job_num: number; max_duration?: number; working_dir: string; + probes?: IJobProbe[]; } declare interface IGpu { @@ -234,6 +246,7 @@ declare interface IJobSubmission { exit_status?: number | null; status_message?: string | null; error?: string | null; + probes?: Array<{ success_streak: number }>; } declare interface IJob { From 76eec42ce11c90a495224cc325c852a3d2757060 Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Fri, 30 Jan 2026 20:41:22 +0300 Subject: [PATCH 2/4] [Feature]: Show probe statuses in the UI #3204 --- frontend/package-lock.json | 12 +++++----- frontend/package.json | 2 +- .../src/components/NavigateLink/index.tsx | 9 ++++++- .../components/NavigateLink/style.module.scss | 5 ++++ .../pages/Runs/Details/Jobs/List/helpers.ts | 24 +++++++++---------- .../pages/Runs/Details/Jobs/List/hooks.tsx | 5 +++- 6 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/NavigateLink/style.module.scss diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 445c61daa9..65e33fd915 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,7 @@ "@cloudscape-design/chat-components": "^1.0.62", "@cloudscape-design/collection-hooks": "^1.0.74", "@cloudscape-design/component-toolkit": "^1.0.0-beta.120", - "@cloudscape-design/components": "^3.0.1091", + "@cloudscape-design/components": "^3.0.1188", "@cloudscape-design/design-tokens": "^3.0.60", "@cloudscape-design/global-styles": "^1.0.45", "@hookform/resolvers": "^2.9.10", @@ -2137,9 +2137,9 @@ } }, "node_modules/@cloudscape-design/components": { - "version": "3.0.1091", - "resolved": "https://registry.npmjs.org/@cloudscape-design/components/-/components-3.0.1091.tgz", - "integrity": "sha512-ESV83m/laX9OkuITjeucYRBi4WQSu9w8yniRZjRapiTH+zTlBxQv8Gcnvr9UYPo3cbYyig2HIdbAlOagDplgfA==", + "version": "3.0.1188", + "resolved": "https://registry.npmjs.org/@cloudscape-design/components/-/components-3.0.1188.tgz", + "integrity": "sha512-Ajk7wFr2boPO7v4pgBLjdLVcA7myBiCk5hzpzURGcg+oogX9lYtXHv80k60dqyn1kzx+J0xnZwEjLa0oRmflmA==", "license": "Apache-2.0", "dependencies": { "@cloudscape-design/collection-hooks": "^1.0.0", @@ -2150,7 +2150,6 @@ "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "ace-builds": "^1.34.0", - "balanced-match": "^1.0.2", "clsx": "^1.1.0", "d3-shape": "^1.3.7", "date-fns": "^2.25.0", @@ -6979,7 +6978,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/batch": { "version": "0.6.1", diff --git a/frontend/package.json b/frontend/package.json index f2ef0d9ca1..ae701fc95f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -101,7 +101,7 @@ "@cloudscape-design/chat-components": "^1.0.62", "@cloudscape-design/collection-hooks": "^1.0.74", "@cloudscape-design/component-toolkit": "^1.0.0-beta.120", - "@cloudscape-design/components": "^3.0.1091", + "@cloudscape-design/components": "^3.0.1188", "@cloudscape-design/design-tokens": "^3.0.60", "@cloudscape-design/global-styles": "^1.0.45", "@hookform/resolvers": "^2.9.10", diff --git a/frontend/src/components/NavigateLink/index.tsx b/frontend/src/components/NavigateLink/index.tsx index eabf4002b5..7bac639685 100644 --- a/frontend/src/components/NavigateLink/index.tsx +++ b/frontend/src/components/NavigateLink/index.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; import Link, { LinkProps } from '@cloudscape-design/components/link'; + +import styles from './style.module.scss'; + export const NavigateLink: React.FC = ({ onFollow, ...props }) => { const navigate = useNavigate(); const onFollowHandler: LinkProps['onFollow'] = (event) => { @@ -10,5 +13,9 @@ export const NavigateLink: React.FC = ({ onFollow, ...props }) => { if (event.detail.href) navigate(event.detail.href); }; - return ; + return ( + + + + ); }; diff --git a/frontend/src/components/NavigateLink/style.module.scss b/frontend/src/components/NavigateLink/style.module.scss new file mode 100644 index 0000000000..27eca2e170 --- /dev/null +++ b/frontend/src/components/NavigateLink/style.module.scss @@ -0,0 +1,5 @@ +.link { + & > a { + text-decoration: underline !important; + } +} diff --git a/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts b/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts index 6855c45804..cb5ef5a468 100644 --- a/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts +++ b/frontend/src/pages/Runs/Details/Jobs/List/helpers.ts @@ -1,4 +1,5 @@ import { format } from 'date-fns'; +import type { StatusIndicatorProps } from '@cloudscape-design/components/status-indicator'; import { DATE_TIME_FORMAT } from 'consts'; import { capitalize } from 'libs'; @@ -52,25 +53,22 @@ export const getJobSubmissionProbes = (job: IJob) => { return job.job_submissions?.[job.job_submissions.length - 1].probes; }; -export const getJobProbesStatuses = (job: IJob) => { +export const getJobProbesStatuses = (job: IJob): StatusIndicatorProps.Type[] => { const status = getJobStatus(job); const probes = getJobSubmissionProbes(job); if (!probes?.length || status !== 'running') { - return null; + return []; } - return probes - ?.map((probe, index) => { - if (job.job_spec?.probes?.[index] && probe.success_streak >= job.job_spec.probes[index].ready_after) { - return '✓'; - } else if (probe.success_streak > 0) { - return '~'; - } else { - return '×'; - } - }) - .join(''); + return probes.map((probe, index) => { + if (job.job_spec?.probes?.[index] && probe.success_streak >= job.job_spec.probes[index].ready_after) { + return 'success'; + } else if (probe.success_streak > 0) { + return 'in-progress'; + } + return 'not-started'; + }); }; export const getJobTerminationReason = (job: IJob) => { diff --git a/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx b/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx index b7ebbdb74f..2d89cdcaef 100644 --- a/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx +++ b/frontend/src/pages/Runs/Details/Jobs/List/hooks.tsx @@ -71,7 +71,10 @@ export const useColumnsDefinitions = ({ { id: 'probe', header: t('projects.run.probe'), - cell: (item: IJob) => getJobProbesStatuses(item), + cell: (item: IJob) => { + const statuses = getJobProbesStatuses(item); + return statuses.map((statusType, index) => ); + }, }, { id: 'priority', From e6c031692b5194a9ae4a1701638a724c6c00a2bb Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Fri, 30 Jan 2026 20:46:51 +0300 Subject: [PATCH 3/4] Small fixes --- frontend/src/locale/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index 7cf3187722..e8388f1602 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -478,6 +478,7 @@ "offer": "Offer", "offer_description": "Select an offer for the dev environment.", "name": "Name", + "name_description": "The name of the run, e.g. 'my-dev-env'", "name_constraint": "Example: 'my-fleet' or 'default'. If not specified, generated automatically.", "name_placeholder": "Optional", "ide": "IDE", From 8cdb0ec57196129044e412819ad9535ff97b9dbe Mon Sep 17 00:00:00 2001 From: Oleg Vavilov Date: Sat, 31 Jan 2026 10:29:30 +0300 Subject: [PATCH 4/4] Small fixes --- frontend/src/libs/run.ts | 4 ++-- .../pages/Runs/Details/RunDetails/index.tsx | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/libs/run.ts b/frontend/src/libs/run.ts index 8dd8be3af6..1c528b8b5f 100644 --- a/frontend/src/libs/run.ts +++ b/frontend/src/libs/run.ts @@ -76,11 +76,11 @@ export const getRunError = (run: IRun): string | null => { return error ? capitalize(error) : null; }; -export const getRunProbe = (run: IRun): string | null => { +export const getRunProbeStatuses = (run: IRun): StatusIndicatorProps.Type[] => { const job = run.jobs[0]; if (!job) { - return '-'; + return []; } return getJobProbesStatuses(run.jobs[0]); diff --git a/frontend/src/pages/Runs/Details/RunDetails/index.tsx b/frontend/src/pages/Runs/Details/RunDetails/index.tsx index 5de6bb9a02..f878f367ff 100644 --- a/frontend/src/pages/Runs/Details/RunDetails/index.tsx +++ b/frontend/src/pages/Runs/Details/RunDetails/index.tsx @@ -7,7 +7,14 @@ import { format } from 'date-fns'; import { Box, ColumnLayout, Container, Header, Loader, NavigateLink, StatusIndicator } from 'components'; import { DATE_TIME_FORMAT } from 'consts'; -import { getRunError, getRunPriority, getRunProbe, getRunStatusMessage, getStatusIconColor, getStatusIconType } from 'libs/run'; +import { + getRunError, + getRunPriority, + getRunProbeStatuses, + getRunStatusMessage, + getStatusIconColor, + getStatusIconType, +} from 'libs/run'; import { ROUTES } from 'routes'; import { useGetRunQuery } from 'services/run'; @@ -65,6 +72,14 @@ export const RunDetails = () => { const statusMessage = getRunStatusMessage(runData); + const renderRobeStatuses = () => { + const statuses = getRunProbeStatuses(runData); + + if (!statuses.length) return '-'; + + return statuses.map((statusType, index) => ); + }; + return ( <> {t('common.general')}}> @@ -112,6 +127,7 @@ export const RunDetails = () => {
{t('projects.run.status')} +
{ {runData.jobs.length <= 1 && (
{t('projects.run.probe')} -
{getRunProbe(runData)}
+
{renderRobeStatuses()}
)}