Skip to content

Commit d4e0c75

Browse files
authored
Show Schedule and Next run on Run page (#3203)
* Add Run.next_triggered_at * Return next_triggered_at=None for finished runs * Show Schedule and Next run on Run page
1 parent e0fb521 commit d4e0c75

File tree

7 files changed

+52
-12
lines changed

7 files changed

+52
-12
lines changed

frontend/src/locale/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@
417417
"backend": "Backend",
418418
"region": "Region",
419419
"instance_id": "Instance ID",
420+
"schedule": "Schedule",
421+
"next_run": "Next run",
420422
"resources": "Resources",
421423
"spot": "Spot",
422424
"termination_reason": "Termination reason",

frontend/src/pages/Runs/Details/RunDetails/index.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
getRunListItemPrice,
2121
getRunListItemRegion,
2222
getRunListItemResources,
23+
getRunListItemSchedule,
2324
getRunListItemServiceUrl,
2425
getRunListItemSpot,
2526
} from '../../List/helpers';
@@ -38,6 +39,8 @@ export const RunDetails = () => {
3839
});
3940

4041
const serviceUrl = runData ? getRunListItemServiceUrl(runData) : null;
42+
const schedule = runData ? getRunListItemSchedule(runData) : null;
43+
const nextTriggeredAt = runData ? runData.next_triggered_at : null;
4144

4245
if (isLoadingRun)
4346
return (
@@ -115,7 +118,7 @@ export const RunDetails = () => {
115118

116119
<div>
117120
<Box variant="awsui-key-label">{t('projects.run.error')}</Box>
118-
<div>{getRunError(runData)}</div>
121+
<div>{getRunError(runData) ?? '-'}</div>
119122
</div>
120123

121124
<div>
@@ -138,6 +141,11 @@ export const RunDetails = () => {
138141
<div>{getRunListItemResources(runData)}</div>
139142
</div>
140143

144+
<div>
145+
<Box variant="awsui-key-label">{t('projects.run.backend')}</Box>
146+
<div>{getRunListItemBackend(runData)}</div>
147+
</div>
148+
141149
<div>
142150
<Box variant="awsui-key-label">{t('projects.run.region')}</Box>
143151
<div>{getRunListItemRegion(runData)}</div>
@@ -152,11 +160,6 @@ export const RunDetails = () => {
152160
<Box variant="awsui-key-label">{t('projects.run.spot')}</Box>
153161
<div>{getRunListItemSpot(runData)}</div>
154162
</div>
155-
156-
<div>
157-
<Box variant="awsui-key-label">{t('projects.run.backend')}</Box>
158-
<div>{getRunListItemBackend(runData)}</div>
159-
</div>
160163
</ColumnLayout>
161164

162165
{serviceUrl && (
@@ -169,6 +172,19 @@ export const RunDetails = () => {
169172
</div>
170173
</ColumnLayout>
171174
)}
175+
176+
{schedule && (
177+
<ColumnLayout columns={4} variant="text-grid">
178+
<div>
179+
<Box variant="awsui-key-label">{t('projects.run.schedule')}</Box>
180+
<div>{schedule}</div>
181+
</div>
182+
<div>
183+
<Box variant="awsui-key-label">{t('projects.run.next_run')}</Box>
184+
<div>{nextTriggeredAt ? format(new Date(nextTriggeredAt), DATE_TIME_FORMAT) : '-'}</div>
185+
</div>
186+
</ColumnLayout>
187+
)}
172188
</Container>
173189

174190
{runData.run_spec.configuration.type === 'dev-environment' && !runIsStopped(runData.status) && (

frontend/src/pages/Runs/List/helpers.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const getRunListItemResources = (run: IRun) => {
1414
return '-';
1515
}
1616

17-
return run.latest_job_submission?.job_provisioning_data?.instance_type?.resources?.description;
17+
return run.latest_job_submission?.job_provisioning_data?.instance_type?.resources?.description ?? '-';
1818
};
1919

2020
export const getRunListItemSpotLabelKey = (run: IRun) => {
@@ -31,7 +31,7 @@ export const getRunListItemSpotLabelKey = (run: IRun) => {
3131

3232
export const getRunListItemSpot = (run: IRun) => {
3333
if (run.jobs.length > 1) {
34-
return '';
34+
return '-';
3535
}
3636

3737
return run.latest_job_submission?.job_provisioning_data?.instance_type?.resources?.spot?.toString() ?? '-';
@@ -57,31 +57,31 @@ export const getRunListItemPrice = (run: IRun) => {
5757

5858
export const getRunListItemInstance = (run: IRun) => {
5959
if (run.jobs.length > 1) {
60-
return '';
60+
return '-';
6161
}
6262

6363
return run.latest_job_submission?.job_provisioning_data?.instance_type?.name;
6464
};
6565

6666
export const getRunListItemInstanceId = (run: IRun) => {
6767
if (run.jobs.length > 1) {
68-
return '';
68+
return '-';
6969
}
7070

7171
return run.latest_job_submission?.job_provisioning_data?.instance_id ?? '-';
7272
};
7373

7474
export const getRunListItemRegion = (run: IRun) => {
7575
if (run.jobs.length > 1) {
76-
return '';
76+
return '-';
7777
}
7878

7979
return run.latest_job_submission?.job_provisioning_data?.region ?? '-';
8080
};
8181

8282
export const getRunListItemBackend = (run: IRun) => {
8383
if (run.jobs.length > 1) {
84-
return '';
84+
return '-';
8585
}
8686

8787
return run.latest_job_submission?.job_provisioning_data?.backend ?? '-';
@@ -92,3 +92,9 @@ export const getRunListItemServiceUrl = (run: IRun) => {
9292
if (!url) return null;
9393
return url.startsWith('/') ? `${getBaseUrl()}${url}` : url;
9494
};
95+
96+
export const getRunListItemSchedule = (run: IRun) => {
97+
if (run.run_spec.configuration.type != 'task' || !run.run_spec.configuration.schedule) return null;
98+
99+
return run.run_spec.configuration.schedule.cron.join(', ');
100+
};

frontend/src/types/run.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,22 @@ declare interface IJob {
240240
job_submissions: IJobSubmission[];
241241
}
242242

243+
declare interface ISchedule {
244+
cron: string[];
245+
}
246+
243247
declare interface ITaskConfiguration {
244248
type: 'task';
245249
priority?: number | null;
250+
schedule?: ISchedule | null;
246251
}
247252

248253
declare interface IServiceConfiguration {
249254
type: 'service';
250255
gateway: string | null;
251256
priority?: number | null;
252257
}
258+
253259
declare interface IRunSpec {
254260
configuration: TDevEnvironmentConfiguration | ITaskConfiguration | IServiceConfiguration;
255261
configuration_path: string;
@@ -286,6 +292,7 @@ declare interface IRun {
286292
cost: number;
287293
service: IRunService | null;
288294
status_message?: string | null;
295+
next_triggered_at?: string | null;
289296
}
290297

291298
declare interface IMetricsItem {

src/dstack/_internal/core/models/runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ class Run(CoreModel):
553553
deployment_num: int = 0 # default for compatibility with pre-0.19.14 servers
554554
error: Optional[str] = None
555555
deleted: Optional[bool] = None
556+
next_triggered_at: Optional[datetime] = None
556557

557558
def is_deployment_in_progress(self) -> bool:
558559
return any(

src/dstack/_internal/server/services/runs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ def run_model_to_run(
715715
status_message = _get_run_status_message(run_model)
716716
error = _get_run_error(run_model)
717717
fleet = _get_run_fleet(run_model)
718+
next_triggered_at = None
719+
if not run_model.status.is_finished():
720+
next_triggered_at = _get_next_triggered_at(run_spec)
718721
run = Run(
719722
id=run_model.id,
720723
project_name=run_model.project.name,
@@ -734,6 +737,7 @@ def run_model_to_run(
734737
deployment_num=run_model.deployment_num,
735738
error=error,
736739
deleted=run_model.deleted,
740+
next_triggered_at=next_triggered_at,
737741
)
738742
run.cost = _get_run_cost(run)
739743
return run

src/tests/_internal/server/routers/test_runs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ def get_dev_env_run_dict(
517517
"termination_reason": None,
518518
"error": None,
519519
"deleted": deleted,
520+
"next_triggered_at": None,
520521
}
521522

522523

@@ -665,6 +666,7 @@ async def test_lists_runs(self, test_db, session: AsyncSession, client: AsyncCli
665666
"termination_reason": None,
666667
"error": None,
667668
"deleted": False,
669+
"next_triggered_at": None,
668670
},
669671
{
670672
"id": str(run2.id),
@@ -687,6 +689,7 @@ async def test_lists_runs(self, test_db, session: AsyncSession, client: AsyncCli
687689
"termination_reason": None,
688690
"error": None,
689691
"deleted": False,
692+
"next_triggered_at": None,
690693
},
691694
]
692695

@@ -853,6 +856,7 @@ async def test_limits_job_submissions(
853856
"termination_reason": None,
854857
"error": None,
855858
"deleted": False,
859+
"next_triggered_at": None,
856860
},
857861
]
858862

0 commit comments

Comments
 (0)