Skip to content

Commit 2015c39

Browse files
authored
Add paging on jobs panel (#2852)
* Initial commit of run pagination for jobs Signed-off-by: sharpd <davidsharp7@gmail.com> * added linted code Signed-off-by: sharpd <davidsharp7@gmail.com> * add pageInit to prevent multiple initial fetches Signed-off-by: sharpd <davidsharp7@gmail.com> * fix react error and add additional formatting for runs. Signed-off-by: sharpd <davidsharp7@gmail.com> * move paging to JobDetailPage to simplify Signed-off-by: sharpd <davidsharp7@gmail.com> * fix runs messaging Signed-off-by: sharpd <davidsharp7@gmail.com> * add new fetchLatestRuns and state Signed-off-by: sharpd <davidsharp7@gmail.com> * aligning action types Signed-off-by: sharpd <davidsharp7@gmail.com> --------- Signed-off-by: sharpd <davidsharp7@gmail.com>
1 parent f0b2195 commit 2015c39

11 files changed

Lines changed: 204 additions & 32 deletions

File tree

api/src/main/java/marquez/api/JobResource.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ public Response listRuns(
238238

239239
final List<Run> runs =
240240
runService.findAll(namespaceName.getValue(), jobName.getValue(), limit, offset);
241-
return Response.ok(new Runs(runs)).build();
241+
final int totalCount = jobService.countJobRuns(namespaceName.getValue(), jobName.getValue());
242+
return Response.ok(new Runs(runs, totalCount)).build();
242243
}
243244

244245
@Path("/jobs/runs/{id}")
@@ -329,5 +330,8 @@ public static class Runs {
329330
@NonNull
330331
@JsonProperty("runs")
331332
List<Run> value;
333+
334+
@JsonProperty("totalCount")
335+
int totalCount;
332336
}
333337
}

api/src/main/java/marquez/db/JobDao.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,20 @@ job_tags as (
252252
@SqlQuery("SELECT count(*) FROM jobs_view AS j WHERE symlink_target_uuid IS NULL")
253253
int count();
254254

255+
@SqlQuery(
256+
"""
257+
select
258+
count(*)
259+
from
260+
runs
261+
where
262+
namespace_name = :namespaceName
263+
and
264+
job_name = :job
265+
;
266+
""")
267+
int countJobRuns(String namespaceName, String job);
268+
255269
@SqlQuery(
256270
"SELECT count(*) FROM jobs_view AS j WHERE j.namespace_name = :namespaceName\n"
257271
+ "AND symlink_target_uuid IS NULL")

api/src/test/java/marquez/db/JobDaoTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public void testCountFor() {
136136
assertThat(jobDao.count()).isEqualTo(4);
137137

138138
assertThat(jobDao.countFor(namespace.getName())).isEqualTo(3);
139+
assertThat(jobDao.countJobRuns(namespace.getName(), "targetJob")).isEqualTo(0);
139140
}
140141

141142
@Test

web/src/components/jobs/JobDetailPage.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
deleteJob,
2121
dialogToggle,
2222
fetchJobTags,
23-
fetchRuns,
23+
fetchLatestRuns,
2424
resetJobs,
2525
resetRuns,
2626
setTabIndex,
@@ -44,7 +44,7 @@ import Runs from './Runs'
4444
import SpeedRounded from '@mui/icons-material/SpeedRounded'
4545

4646
interface DispatchProps {
47-
fetchRuns: typeof fetchRuns
47+
fetchLatestRuns: typeof fetchLatestRuns
4848
resetRuns: typeof resetRuns
4949
resetJobs: typeof resetJobs
5050
deleteJob: typeof deleteJob
@@ -56,41 +56,42 @@ interface DispatchProps {
5656
type IProps = {
5757
job: LineageJob
5858
jobs: IState['jobs']
59-
runs: Run[]
60-
runsLoading: boolean
6159
display: IState['display']
6260
tabIndex: IState['lineage']['tabIndex']
6361
jobTags: string[]
62+
latestRuns: Run[]
63+
isLatestRunsLoading: boolean
6464
} & DispatchProps
6565

6666
const JobDetailPage: FunctionComponent<IProps> = (props) => {
6767
const theme = createTheme(useTheme())
6868
const {
6969
job,
7070
jobs,
71-
fetchRuns,
71+
fetchLatestRuns,
7272
resetRuns,
7373
deleteJob,
7474
dialogToggle,
75-
runs,
75+
latestRuns,
7676
display,
77-
runsLoading,
7877
tabIndex,
7978
setTabIndex,
8079
jobTags,
8180
fetchJobTags,
81+
isLatestRunsLoading,
8282
} = props
8383
const navigate = useNavigate()
8484
const [_, setSearchParams] = useSearchParams()
8585

8686
const handleChange = (event: ChangeEvent, newValue: number) => {
8787
setTabIndex(newValue)
8888
}
89+
8990
const i18next = require('i18next')
9091

9192
useEffect(() => {
92-
fetchRuns(job.name, job.namespace)
9393
fetchJobTags(job.namespace, job.name)
94+
fetchLatestRuns(job.name, job.namespace)
9495
}, [job.name])
9596

9697
useEffect(() => {
@@ -102,12 +103,13 @@ const JobDetailPage: FunctionComponent<IProps> = (props) => {
102103
// unmounting
103104
useEffect(() => {
104105
return () => {
105-
resetRuns()
106106
resetJobs()
107+
resetRuns()
108+
setTabIndex(0)
107109
}
108110
}, [])
109111

110-
if (runsLoading || jobs.isLoading) {
112+
if (jobs.isLoading || isLatestRunsLoading) {
111113
return (
112114
<Box display={'flex'} justifyContent={'center'} mt={2}>
113115
<CircularProgress color='primary' />
@@ -244,7 +246,7 @@ const JobDetailPage: FunctionComponent<IProps> = (props) => {
244246
<MqInfo
245247
icon={<DirectionsRun color={'disabled'} />}
246248
label={'Running Status'.toUpperCase()}
247-
value={<MqStatus label={job.latestRun?.state} color={jobRunsStatus(runs)} />}
249+
value={<MqStatus label={job.latestRun?.state} color={jobRunsStatus(latestRuns)} />}
248250
/>
249251
</Grid>
250252
</Grid>
@@ -271,14 +273,14 @@ const JobDetailPage: FunctionComponent<IProps> = (props) => {
271273
)
272274
)
273275
) : null}
274-
{tabIndex === 1 && <Runs runs={runs} />}
276+
{tabIndex === 1 && <Runs jobName={job.name} jobNamespace={job.namespace} />}
275277
</Box>
276278
)
277279
}
278280

279281
const mapStateToProps = (state: IState) => ({
280-
runs: state.runs.result,
281-
runsLoading: state.runs.isLoading,
282+
latestRuns: state.runs.latestRuns,
283+
isLatestRunsLoading: state.runs.isLatestRunsLoading,
282284
display: state.display,
283285
jobs: state.jobs,
284286
tabIndex: state.lineage.tabIndex,
@@ -288,7 +290,7 @@ const mapStateToProps = (state: IState) => ({
288290
const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
289291
bindActionCreators(
290292
{
291-
fetchRuns: fetchRuns,
293+
fetchLatestRuns: fetchLatestRuns,
292294
resetRuns: resetRuns,
293295
resetJobs: resetJobs,
294296
deleteJob: deleteJob,

web/src/components/jobs/Runs.tsx

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,94 @@
11
// Copyright 2018-2023 contributors to the Marquez project
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import * as Redux from 'redux'
45
import { ArrowBackIosRounded } from '@mui/icons-material'
56
import {
67
Box,
78
Chip,
9+
CircularProgress,
810
IconButton,
911
Table,
1012
TableBody,
1113
TableCell,
1214
TableHead,
1315
TableRow,
1416
} from '@mui/material'
17+
import { IState } from '../../store/reducers'
1518
import { Run } from '../../types/api'
1619
import { alpha, createTheme } from '@mui/material/styles'
20+
import { bindActionCreators } from 'redux'
21+
import { connect } from 'react-redux'
22+
import { fetchRuns } from '../../store/actionCreators'
1723
import { formatUpdatedAt } from '../../helpers'
1824
import { runStateColor } from '../../helpers/nodes'
1925
import { stopWatchDuration } from '../../helpers/time'
2026
import { useTheme } from '@emotion/react'
2127
import MqCode from '../core/code/MqCode'
2228
import MqCopy from '../core/copy/MqCopy'
2329
import MqEmpty from '../core/empty/MqEmpty'
30+
import MqPaging from '../paging/MqPaging'
2431
import MqStatus from '../core/status/MqStatus'
2532
import MqText from '../core/text/MqText'
2633
import React, { FunctionComponent, SetStateAction } from 'react'
2734
import RunInfo from './RunInfo'
2835

36+
interface DispatchProps {
37+
fetchRuns: typeof fetchRuns
38+
}
39+
2940
interface RunsProps {
3041
runs: Run[]
3142
facets?: object
43+
totalCount: number
44+
runsLoading: boolean
45+
jobName: string
46+
jobNamespace: string
47+
}
48+
49+
interface RunsState {
50+
page: number
3251
}
3352

34-
const Runs: FunctionComponent<RunsProps> = (props) => {
35-
const { runs, facets } = props
53+
const PAGE_SIZE = 10
54+
55+
const Runs: FunctionComponent<RunsProps & DispatchProps> = (props) => {
56+
const { runs, facets, totalCount, runsLoading, fetchRuns, jobName, jobNamespace } = props
3657
const i18next = require('i18next')
37-
if (runs.length === 0) {
38-
return <MqEmpty title={i18next.t('jobs.empty_title')} body={i18next.t('jobs.empty_body')} />
39-
}
58+
59+
const [state, setState] = React.useState<RunsState>({
60+
page: 0,
61+
})
4062

4163
const [infoView, setInfoView] = React.useState<Run | null>(null)
4264
const handleClick = (newValue: SetStateAction<Run | null>) => {
4365
setInfoView(newValue)
4466
}
4567

68+
const handleClickPage = (direction: 'prev' | 'next') => {
69+
const directionPage = direction === 'next' ? state.page + 1 : state.page - 1
70+
window.scrollTo(0, 0)
71+
setState({ ...state, page: directionPage })
72+
}
73+
74+
React.useEffect(() => {
75+
fetchRuns(jobName, jobNamespace, PAGE_SIZE, state.page * PAGE_SIZE)
76+
}, [state.page])
77+
4678
const theme = createTheme(useTheme())
4779

80+
if (runs.length === 0) {
81+
return <MqEmpty title={i18next.t('jobs.empty_title')} body={i18next.t('jobs.empty_body')} />
82+
}
83+
84+
if (runsLoading) {
85+
return (
86+
<Box display={'flex'} justifyContent={'center'} mt={2}>
87+
<CircularProgress color='primary' />
88+
</Box>
89+
)
90+
}
91+
4892
if (infoView) {
4993
return (
5094
<>
@@ -148,12 +192,23 @@ const Runs: FunctionComponent<RunsProps> = (props) => {
148192
<TableCell align='left'>{formatUpdatedAt(run.createdAt)}</TableCell>
149193
<TableCell align='left'>{formatUpdatedAt(run.startedAt)}</TableCell>
150194
<TableCell align='left'>N/A</TableCell>
151-
<TableCell align='left'>{stopWatchDuration(run.durationMs)}</TableCell>
195+
<TableCell align='left'>
196+
{run.state === 'RUNNING' || run.state === 'NEW'
197+
? 'N/A'
198+
: stopWatchDuration(run.durationMs)}{' '}
199+
</TableCell>
152200
</TableRow>
153201
)
154202
})}
155203
</TableBody>
156204
</Table>
205+
<MqPaging
206+
pageSize={PAGE_SIZE}
207+
currentPage={state.page}
208+
totalCount={totalCount}
209+
incrementPage={() => handleClickPage('next')}
210+
decrementPage={() => handleClickPage('prev')}
211+
/>
157212
{facets && (
158213
<Box mt={2}>
159214
<Box mb={1}>
@@ -166,4 +221,18 @@ const Runs: FunctionComponent<RunsProps> = (props) => {
166221
)
167222
}
168223

169-
export default Runs
224+
const mapStateToProps = (state: IState) => ({
225+
runs: state.runs.result,
226+
totalCount: state.runs.totalCount,
227+
runsLoading: state.runs.isLoading,
228+
})
229+
230+
const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
231+
bindActionCreators(
232+
{
233+
fetchRuns: fetchRuns,
234+
},
235+
dispatch
236+
)
237+
238+
export default connect(mapStateToProps, mapDispatchToProps)(Runs)

web/src/store/actionCreators/actionTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const DIALOG_TOGGLE = 'DIALOG_TOGGLE'
88
export const FETCH_RUNS = 'FETCH_RUNS'
99
export const FETCH_RUNS_SUCCESS = 'FETCH_RUNS_SUCCESS'
1010
export const RESET_RUNS = 'RESET_RUNS'
11+
export const FETCH_LATEST_RUNS = 'FETCH_LATEST_RUNS'
12+
export const FETCH_LATEST_RUNS_SUCCESS = 'FETCH_LATEST_RUNS_SUCCESS'
1113

1214
export const FETCH_NAMESPACES_SUCCESS = 'FETCH_NAMESPACES_SUCCESS'
1315
export const SELECT_NAMESPACE = 'SELECT_NAMESPACE'

web/src/store/actionCreators/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,19 +296,37 @@ export const deleteJobSuccess = (jobName: string) => ({
296296
},
297297
})
298298

299-
export const fetchRuns = (jobName: string, namespace: string) => ({
299+
export const fetchRuns = (jobName: string, namespace: string, limit: number, offset: number) => ({
300300
type: actionTypes.FETCH_RUNS,
301301
payload: {
302302
jobName,
303303
namespace,
304+
limit,
305+
offset,
304306
},
305307
})
306308

307-
export const fetchRunsSuccess = (jobName: string, jobRuns: Run[]) => ({
309+
export const fetchRunsSuccess = (jobName: string, jobRuns: Run[], totalCount: number) => ({
308310
type: actionTypes.FETCH_RUNS_SUCCESS,
309311
payload: {
310312
jobName,
311313
runs: jobRuns,
314+
totalCount,
315+
},
316+
})
317+
318+
export const fetchLatestRuns = (jobName: string, namespace: string) => ({
319+
type: actionTypes.FETCH_LATEST_RUNS,
320+
payload: {
321+
jobName,
322+
namespace,
323+
},
324+
})
325+
326+
export const fetchLatestRunsSuccess = (jobRuns: Run[]) => ({
327+
type: actionTypes.FETCH_LATEST_RUNS_SUCCESS,
328+
payload: {
329+
runs: jobRuns,
312330
},
313331
})
314332

0 commit comments

Comments
 (0)