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

Commit

Permalink
[Webportal] Add filter, search box and export csv file in task detail…
Browse files Browse the repository at this point in the history
… list (#5175)

* update

* fix filter and keyword search

* Add csv exporter

* fix case sensitive in keyword search
  • Loading branch information
yiyione committed Dec 16, 2020
1 parent 27ddaca commit 93396f4
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 33 deletions.
18 changes: 2 additions & 16 deletions src/webportal/src/app/job/job-view/fabric/job-detail.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// 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.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { capitalize, isEmpty, isNil, get, cloneDeep } from 'lodash';
import { DateTime, Interval } from 'luxon';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const Context = React.createContext({
rawJobConfig: null,
sshInfo: null,
isViewingSelf: null,
filter: null,
setFilter: null,
});

export default Context;
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// 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.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { ThemeProvider } from '@uifabric/foundation';
import {
Expand Down Expand Up @@ -52,6 +38,8 @@ import t from '../../../../../components/tachyons.scss';

import Context from './context';
import Timer from './timer';
import TaskRoleFilter from './task-role-filter';
import TaskRoleContainerTop from './task-role-container-top';
import { getContainerLog, getContainerLogList } from '../conn';
import config from '../../../../../config/webportal.config';
import MonacoPanel from '../../../../../components/monaco-panel';
Expand Down Expand Up @@ -169,6 +157,8 @@ export default class TaskRoleContainerList extends React.Component {
items: props.tasks,
ordering: { field: null, descending: false },
hideAllLogsDialog: true,
filter: new TaskRoleFilter(),
taskRoleName: props.taskRoleName,
};

this.showSshInfo = this.showSshInfo.bind(this);
Expand Down Expand Up @@ -492,16 +482,24 @@ export default class TaskRoleContainerList extends React.Component {
tailLogUrls,
hideAllLogsDialog,
items,
filter,
taskRoleName,
} = this.state;
const { showMoreDiagnostics } = this.props;
return (
<div>
<ThemeProvider theme={theme}>
<TaskRoleContainerTop
taskStatuses={items}
taskRoleName={taskRoleName}
filter={filter}
setFilter={newFilter => this.setState({ filter: newFilter })}
/>
<DetailsList
styles={{ root: { overflow: 'auto' } }}
columns={this.getColumns(showMoreDiagnostics)}
disableSelectionZone
items={items}
items={filter.apply(items)}
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.none}
onRenderRow={this.onRenderRow}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React from 'react';
import { getTheme } from '@uifabric/styling';
import {
ColorClassNames,
CommandBarButton,
SearchBox,
Stack,
} from 'office-ui-fabric-react';
import PropTypes from 'prop-types';
import TaskRoleFilter from './task-role-filter';
import FilterButton from '../../JobList/FilterButton';
import TaskRoleCsvExporter from './task-role-csv-exporter';

function KeywordSearchBox({ filter, setFilter }) {
function onKeywordChange(keyword) {
const { statuses, exitType, exitCode, nodeName } = filter;
const newFilter = new TaskRoleFilter(
keyword,
statuses,
exitType,
exitCode,
nodeName,
);
setFilter(newFilter);
}

/** @type {import('office-ui-fabric-react').IStyle} */
const rootStyles = {
backgroundColor: 'transparent',
alignSelf: 'center',
width: 220,
};
return (
<SearchBox
underlined
placeholder='Filter by keyword'
styles={{ root: rootStyles }}
value={filter.keyword}
onChange={onKeywordChange}
/>
);
}

KeywordSearchBox.propTypes = {
filter: PropTypes.object.isRequired,
setFilter: PropTypes.func.isRequired,
};

export default function TaskRoleContainerTop({
taskStatuses,
filter,
setFilter,
taskRoleName,
}) {
const exitTypes = new Set();
const exitCodes = new Set();
const nodeNames = new Set();

for (const item of taskStatuses) {
if (item.containerExitSpec && item.containerExitSpec.type) {
exitTypes.add(item.containerExitSpec.type);
}
if (item.containerExitCode) {
exitCodes.add(item.containerExitCode.toString());
}
if (item.containerNodeName) {
nodeNames.add(item.containerNodeName);
}
}

const statuses = {
Waiting: true,
Succeeded: true,
Running: true,
Stopped: true,
Failed: true,
};

const { spacing } = getTheme();
const csvExporter = new TaskRoleCsvExporter();
const expCsv = () => csvExporter.apply(taskRoleName + '.csv', taskStatuses);

return (
<React.Fragment>
<Stack
horizontal
verticalAlign='stretch'
horizontalAlign='space-between'
styles={{
root: [
ColorClassNames.neutralLightBackground,
{
marginTop: spacing.s2,
padding: spacing.m,
},
],
}}
>
<Stack horizontal>
<KeywordSearchBox filter={filter} setFilter={setFilter} />
<CommandBarButton
iconProps={{ iconName: 'ExcelDocument' }}
text='Export CSV'
onClick={expCsv}
/>
</Stack>
<Stack horizontal>
<FilterButton
styles={{ root: { backgroundColor: 'transparent' } }}
text='Status'
iconProps={{ iconName: 'Clock' }}
items={Object.keys(statuses)}
selectedItems={Array.from(filter.statuses)}
onSelect={statuses => {
const { keyword, exitType, exitCode, nodeName } = filter;
setFilter(
new TaskRoleFilter(
keyword,
new Set(statuses),
exitType,
exitCode,
nodeName,
),
);
}}
clearButton
/>
<FilterButton
styles={{ root: { backgroundColor: 'transparent' } }}
text='Exit Type'
iconProps={{ iconName: 'Tablet' }}
items={Array.from(exitTypes)}
selectedItems={Array.from(filter.exitType)}
onSelect={exitTypes => {
const { keyword, statuses, exitCode, nodeName } = filter;
setFilter(
new TaskRoleFilter(
keyword,
statuses,
new Set(exitTypes),
exitCode,
nodeName,
),
);
}}
searchBox
clearButton
/>
<FilterButton
styles={{ root: { backgroundColor: 'transparent' } }}
text='Exit Code'
iconProps={{ iconName: 'NumberSymbol' }}
items={Array.from(exitCodes)}
selectedItems={Array.from(filter.exitCode)}
onSelect={exitCodes => {
const { keyword, statuses, exitType, nodeName } = filter;
setFilter(
new TaskRoleFilter(
keyword,
statuses,
exitType,
new Set(exitCodes),
nodeName,
),
);
}}
searchBox
clearButton
/>
<FilterButton
styles={{ root: { backgroundColor: 'transparent' } }}
text='Node Name'
iconProps={{ iconName: 'TVMonitor' }}
items={Array.from(nodeNames)}
selectedItems={Array.from(filter.nodeName)}
onSelect={nodeNames => {
const { keyword, statuses, exitType, exitCode } = filter;
setFilter(
new TaskRoleFilter(
keyword,
statuses,
exitType,
exitCode,
new Set(nodeNames),
),
);
}}
searchBox
clearButton
/>
<CommandBarButton
styles={{
root: { backgroundColor: 'transparent', height: '100%' },
}}
iconProps={{ iconName: 'Cancel' }}
onClick={() => setFilter(new TaskRoleFilter())}
/>
</Stack>
</Stack>
</React.Fragment>
);
}

TaskRoleContainerTop.propTypes = {
taskStatuses: PropTypes.array.isRequired,
filter: PropTypes.object.isRequired,
setFilter: PropTypes.func.isRequired,
taskRoleName: PropTypes.string.isRequired,
};
Loading

0 comments on commit 93396f4

Please sign in to comment.