Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Add task retry info in job detail page #4966

Merged
merged 45 commits into from
Oct 22, 2020
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
dfb02e6
Create first version without sorting
debuggy Sep 25, 2020
e59fdc8
fix
debuggy Sep 25, 2020
68fed23
refine 1247
debuggy Sep 28, 2020
c2b85f3
attempt info 1300
debuggy Sep 28, 2020
4d12433
106
debuggy Sep 28, 2020
45ea596
0247 demo
debuggy Sep 28, 2020
89a1599
0405
debuggy Sep 29, 2020
a90e1c0
new version
debuggy Oct 10, 2020
2ac3926
new design
debuggy Oct 12, 2020
a03e3e6
fix lint
debuggy Oct 12, 2020
bbba96c
work with rest server
debuggy Oct 14, 2020
30cdd61
new page
debuggy Oct 14, 2020
634c968
remove uid
debuggy Oct 14, 2020
cb3e6a8
fix bug when job is running
debuggy Oct 16, 2020
17da236
Add job name and attempt index in task retry page
debuggy Oct 16, 2020
307290d
fix action button and rename
debuggy Oct 16, 2020
dfceb1f
Add time duration
debuggy Oct 16, 2020
eadc3f6
align the attempt id to attempt index
debuggy Oct 16, 2020
123650a
datetime format to MED_WITH_SECONDS
debuggy Oct 16, 2020
50d191d
datetime format with seconds
debuggy Oct 16, 2020
b4abb10
change createdTime to appLaunchedTime
debuggy Oct 16, 2020
9b3f5a1
descending order of task attempts
debuggy Oct 16, 2020
c1f005e
Add loading when change attempt index
debuggy Oct 16, 2020
5783345
add exit phrase
debuggy Oct 16, 2020
2fe76bd
add a line, revert sort
debuggy Oct 16, 2020
9151c63
fix attempt index
debuggy Oct 16, 2020
0d771f0
fix bug
debuggy Oct 16, 2020
434840b
1. original attempt index
debuggy Oct 19, 2020
66beed6
move exitType col before exitCode col
debuggy Oct 19, 2020
a028730
Change complete time to interval
debuggy Oct 19, 2020
ed28b22
fix lint
debuggy Oct 19, 2020
cca0716
disable dropdown when attempt is loading
debuggy Oct 19, 2020
6b015d0
remove gpu
debuggy Oct 19, 2020
4e0a463
rename
debuggy Oct 19, 2020
c743644
rename
debuggy Oct 20, 2020
e45f395
Add task role count
debuggy Oct 20, 2020
390e77c
Add attempt running time
debuggy Oct 20, 2020
572ed45
change time
debuggy Oct 20, 2020
74773c3
Merge remote-tracking branch 'origin/master' into mintao/task_retry
debuggy Oct 20, 2020
a2fd1ae
remove job retry page
debuggy Oct 20, 2020
11158be
rename col
debuggy Oct 21, 2020
a9a6ce4
order
debuggy Oct 21, 2020
12e4938
Change text
debuggy Oct 21, 2020
75669b4
Change time
debuggy Oct 21, 2020
2d2a7ff
comment
debuggy Oct 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/rest-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


module.exports = require('./src/server');
6 changes: 3 additions & 3 deletions src/webportal/config/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const config = (env, argv) => ({
submit_v1: './src/app/job/job-submit-v1/job-submit.component.js',
jobList: './src/app/job/job-view/fabric/job-list.jsx',
jobDetail: './src/app/job/job-view/fabric/job-detail.jsx',
jobRetry: './src/app/job/job-view/fabric/job-retry.jsx',
taskAttempt: './src/app/job/job-view/fabric/task-attempt.jsx',
jobEvent: './src/app/job/job-view/fabric/job-event.jsx',
virtualClusters: './src/app/vc/vc.component.js',
services: './src/app/cluster-view/services/services.component.js',
Expand Down Expand Up @@ -336,8 +336,8 @@ const config = (env, argv) => ({
chunks: ['layout', 'jobDetail'],
}),
generateHtml({
filename: 'job-retry.html',
chunks: ['layout', 'jobRetry'],
filename: 'task-attempt.html',
chunks: ['layout', 'taskAttempt'],
}),
generateHtml({
filename: 'job-event.html',
Expand Down
14 changes: 14 additions & 0 deletions src/webportal/src/app/components/horizontal-line.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import styled from 'styled-components';

const HorizontalLine = styled.hr`
display: block;
height: 1px;
border: 0;
border-top: 1px solid #f3f2f1;
margin: 1px 0;
padding: 0;
`;

export default HorizontalLine;
1 change: 0 additions & 1 deletion src/webportal/src/app/env.js.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ window.ENV = {
alertManagerUri: '${ALERT_MANAGER_URI}/alert-manager',
launcherType: '${LAUNCHER_TYPE}',
launcherScheduler: '${LAUNCHER_SCHEDULER}',
jobHistory: '${JOB_HISTORY}',
};

window.PAI_PLUGINS = [${WEBPORTAL_PLUGINS}][0] || [];
215 changes: 168 additions & 47 deletions src/webportal/src/app/job/job-view/fabric/job-detail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import classNames from 'classnames';
import { get, isEmpty, isNil } from 'lodash';
import { capitalize, isEmpty, isNil } from 'lodash';
import { DateTime, Interval } from 'luxon';
import {
FontClassNames,
MessageBar,
MessageBarType,
Stack,
Dropdown,
Text,
Toggle,
} from 'office-ui-fabric-react';
import React from 'react';
import ReactDOM from 'react-dom';

import t from '../../../components/tachyons.scss';

import { getDurationString } from '../../../components/util/job';
import Context from './job-detail/components/context';
import Top from './job-detail/components/top';
import Summary from './job-detail/components/summary';
import { SpinnerLoading } from '../../../components/loading';
import TaskRole from './job-detail/components/task-role';
import {
fetchJobConfig,
fetchJobInfo,
Expand All @@ -40,7 +43,11 @@ import {
NotFoundError,
fetchRawJobConfig,
} from './job-detail/conn';
import { getHumanizedJobStateString } from '../../../components/util/job';
import Card from './job-detail/components/card';
import HorizontalLine from '../../../components/horizontal-line';
import StatusBadge from '../../../components/status-badge';
import TaskRoleContainerList from './job-detail/components/task-role-container-list';
import TaskRoleCount from './job-detail/components/task-role-count';

class JobDetail extends React.Component {
constructor(props) {
debuggy marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -55,9 +62,16 @@ class JobDetail extends React.Component {
rawJobConfig: null,
jobConfig: null,
sshInfo: null,
showMoreDiagnostics: false,
selectedAttemptIndex: null,
loadingAttempt: false,
};
this.stop = this.stop.bind(this);
this.reload = this.reload.bind(this);
this.onChangeJobAttempt = this.onChangeJobAttempt.bind(this);
this.onChangeShowMoreDiagnostics = this.onChangeShowMoreDiagnostics.bind(
this,
);
}

componentDidMount() {
Expand All @@ -76,7 +90,7 @@ class JobDetail extends React.Component {
};
const loadJobInfo = async () => {
try {
nextState.jobInfo = await fetchJobInfo();
nextState.jobInfo = await fetchJobInfo(this.state.selectedAttemptIndex);
} catch (err) {
nextState.error = `fetch job status failed: ${err.message}`;
}
Expand Down Expand Up @@ -132,6 +146,9 @@ class JobDetail extends React.Component {
if (alertFlag === true && !isNil(nextState.error)) {
alert(nextState.error);
}
if (isNil(this.state.selectedAttemptIndex)) {
nextState.selectedAttemptIndex = nextState.jobInfo.jobStatus.retries;
}
this.setState(nextState);
}

Expand All @@ -140,45 +157,32 @@ class JobDetail extends React.Component {
await this.reload();
}

renderTaskRoles() {
const { jobConfig, jobInfo } = this.state;
if (!isEmpty(jobInfo.taskRoles)) {
const failedTaskRole =
getHumanizedJobStateString(jobInfo.jobStatus) === 'Failed' &&
get(jobInfo, 'jobStatus.appExitTriggerTaskRoleName');
return Object.keys(jobInfo.taskRoles).map(name => (
<TaskRole
key={name}
className={t.mt3}
name={name}
taskInfo={jobInfo.taskRoles[name]}
isFailed={failedTaskRole && name === failedTaskRole}
/>
));
} else if (jobConfig && jobConfig.taskRoles) {
return Object.entries(jobConfig.taskRoles).map(([name, taskConfig]) => {
// dummy tasks
let dummyTaskInfo = null;
if (taskConfig) {
const instances = isNil(taskConfig.instances)
? 1
: taskConfig.instances;
dummyTaskInfo = {
taskStatuses: Array.from({ length: instances }, (v, idx) => ({
taskState: 'WAITING',
})),
};
}

return (
<TaskRole
key={name}
name={name}
className={t.mt3}
taskInfo={dummyTaskInfo}
/>
);
onChangeJobAttempt(event, item) {
this.setState({ loadingAttempt: true, selectedAttemptIndex: item.key });
fetchJobInfo(item.key).then(data => {
this.setState({
jobInfo: data,
loadingAttempt: false,
});
});
}

onChangeShowMoreDiagnostics(event, checked) {
this.setState({
showMoreDiagnostics: checked,
});
}

getTimeDuration(startMs, endMs) {
const start = startMs && DateTime.fromMillis(startMs);
const end = endMs && DateTime.fromMillis(endMs);
if (start) {
return Interval.fromDateTimes(start, end || DateTime.utc()).toDuration([
'days',
'hours',
'minutes',
'seconds',
]);
} else {
return null;
}
Expand All @@ -193,13 +197,26 @@ class JobDetail extends React.Component {
jobConfig,
rawJobConfig,
sshInfo,
selectedAttemptIndex,
loadingAttempt,
} = this.state;

const attemptIndexOptions = [];
if (!isNil(jobInfo)) {
for (let index = jobInfo.jobStatus.retries; index >= 0; index -= 1) {
if (index === jobInfo.jobStatus.retries) {
attemptIndexOptions.push({ key: index, text: `${index} (latest)` });
} else {
attemptIndexOptions.push({ key: index, text: index });
}
}
}
if (loading) {
return <SpinnerLoading />;
} else {
return (
<Context.Provider value={{ sshInfo, rawJobConfig, jobConfig }}>
<div className={classNames(t.w100, t.pa4, FontClassNames.medium)}>
<Stack styles={{ root: { margin: '30px' } }} gap='l1'>
<Top />
{!isEmpty(error) && (
<div className={t.bgWhite}>
Expand All @@ -215,8 +232,112 @@ class JobDetail extends React.Component {
onStopJob={this.stop}
onReload={this.reload}
/>
{this.renderTaskRoles()}
</div>
<Card>
<Stack gap='m' padding='l2'>
<Stack horizontal gap='m' verticalAlign='center'>
<Text>Job Attempt Index</Text>
<Dropdown
styles={{ root: { width: '150px' } }}
placeholder='Select Attempt Index'
options={attemptIndexOptions}
selectedKey={selectedAttemptIndex}
onChange={this.onChangeJobAttempt}
disabled={loadingAttempt}
/>
</Stack>
<HorizontalLine />
{loadingAttempt ? (
<SpinnerLoading />
) : (
<Stack gap='l2'>
<Stack
horizontal
horizontalAlign='space-between'
verticalAlign='end'
gap='m'
>
<Stack horizontal gap='l1'>
<Stack gap='m'>
<Text>Attempt State</Text>
<StatusBadge
status={capitalize(jobInfo.jobStatus.attemptState)}
/>
</Stack>
<Stack gap='m'>
<Text>Attempt Created Time</Text>
<Text>
{isNil(jobInfo.jobStatus.appCreatedTime)
? 'N/A'
: DateTime.fromMillis(
jobInfo.jobStatus.appCreatedTime,
).toLocaleString(
DateTime.DATETIME_MED_WITH_SECONDS,
)}
</Text>
</Stack>
<Stack gap='m'>
<Text>Attempt Duration</Text>
<Text>
{getDurationString(
this.getTimeDuration(
jobInfo.jobStatus.appCreatedTime,
jobInfo.jobStatus.appCompletedTime,
),
)}
</Text>
</Stack>
<Stack gap='m'>
<Text>Attempt Running Start Time</Text>
<Text>
{isNil(jobInfo.jobStatus.appLaunchedTime)
? 'N/A'
: DateTime.fromMillis(
jobInfo.jobStatus.appLaunchedTime,
).toLocaleString(
DateTime.DATETIME_MED_WITH_SECONDS,
)}
</Text>
</Stack>
<Stack gap='m'>
<Text>Attempt Running Duration</Text>
<Text>
{getDurationString(
this.getTimeDuration(
jobInfo.jobStatus.appLaunchedTime,
jobInfo.jobStatus.appCompletedTime,
),
)}
</Text>
</Stack>
</Stack>
<Toggle
onText='More Diagnostics'
offText='More Diagnostics'
onChange={this.onChangeShowMoreDiagnostics}
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when switch job attempt, table content and this button is unmatched

</Stack>
{!isEmpty(jobInfo.taskRoles) &&
Object.keys(jobInfo.taskRoles).map(name => (
<Stack key={name} gap='m'>
<HorizontalLine />
<Stack horizontal gap='l1'>
<Text>{`Task Role: ${name}`}</Text>
<TaskRoleCount taskInfo={jobInfo.taskRoles[name]} />
</Stack>

<TaskRoleContainerList
taskRoleName={name}
tasks={jobInfo.taskRoles[name].taskStatuses}
showMoreDiagnostics={this.state.showMoreDiagnostics}
jobAttemptIndex={this.state.selectedAttemptIndex}
/>
</Stack>
))}
</Stack>
)}
</Stack>
</Card>
</Stack>
</Context.Provider>
);
}
Expand Down
Loading