WIP - react virtualizer

This commit is contained in:
Marliana Lara
2019-07-12 13:57:21 -04:00
parent 859c364fbe
commit da92889323
7 changed files with 220 additions and 30 deletions

View File

@@ -4576,6 +4576,11 @@
} }
} }
}, },
"clsx": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz",
"integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -5429,6 +5434,14 @@
"esutils": "^2.0.2" "esutils": "^2.0.2"
} }
}, },
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "^7.1.2"
}
},
"dom-serializer": { "dom-serializer": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
@@ -7174,7 +7187,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -7195,12 +7209,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -7215,17 +7231,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -7342,7 +7361,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -7354,6 +7374,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -7368,6 +7389,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -7375,12 +7397,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -7399,6 +7423,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -7479,7 +7504,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -7491,6 +7517,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -7576,7 +7603,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -7612,6 +7640,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -7631,6 +7660,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -7674,12 +7704,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@@ -10790,6 +10822,11 @@
"type-check": "~0.3.2" "type-check": "~0.3.2"
} }
}, },
"linear-layout-vector": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz",
"integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA="
},
"load-json-file": { "load-json-file": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -13130,8 +13167,7 @@
"react-lifecycles-compat": { "react-lifecycles-compat": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
"dev": true
}, },
"react-router": { "react-router": {
"version": "4.3.1", "version": "4.3.1",
@@ -13190,6 +13226,20 @@
} }
} }
}, },
"react-virtualized": {
"version": "9.21.1",
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz",
"integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==",
"requires": {
"babel-runtime": "^6.26.0",
"clsx": "^1.0.1",
"dom-helpers": "^2.4.0 || ^3.0.0",
"linear-layout-vector": "0.0.1",
"loose-envify": "^1.3.0",
"prop-types": "^15.6.0",
"react-lifecycles-compat": "^3.0.4"
}
},
"read-pkg": { "read-pkg": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",

View File

@@ -70,6 +70,7 @@
"react-codemirror2": "^6.0.0", "react-codemirror2": "^6.0.0",
"react-dom": "^16.4.1", "react-dom": "^16.4.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-virtualized": "^9.21.1",
"styled-components": "^4.2.0" "styled-components": "^4.2.0"
} }
} }

View File

@@ -18,6 +18,12 @@ class Jobs extends Base {
readDetail(id, type) { readDetail(id, type) {
return this.http.get(`/api/v2${BASE_URLS[type]}${id}/`); return this.http.get(`/api/v2${BASE_URLS[type]}${id}/`);
} }
readJobEvents(id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/job_events/`, {
params,
});
}
} }
export default Jobs; export default Jobs;

View File

@@ -1,31 +1,154 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { CardBody } from '@patternfly/react-core'; import { CardBody } from '@patternfly/react-core';
import MenuControls from './shared/MenuControls';
import styled from 'styled-components'; import styled from 'styled-components';
import { JobsAPI } from '@api';
import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import MenuControls from './shared/MenuControls';
import { List, AutoSizer } from 'react-virtualized';
const OutputToolbar = styled.div` const OutputToolbar = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: flex-end;
`;
const OutputWrapper = styled.div`
height: calc(100vh - 325px);
background-color: #fafafa;
margin-top: 24px;
`;
const OutputRow = styled.div`
display: flex;
`; `;
class JobOutput extends Component { class JobOutput extends Component {
listRef = React.createRef();
constructor(props) {
super(props);
this.state = {
contentError: null,
hasContentLoading: true,
results: [],
scrollToIndex: -1,
startIndex: 0,
stopIndex: 0,
};
this.loadJobEvents = this.loadJobEvents.bind(this);
this.renderRow = this.renderRow.bind(this);
this.handleScrollTop = this.handleScrollTop.bind(this);
this.handleScrollBottom = this.handleScrollBottom.bind(this);
this.handleScrollNext = this.handleScrollNext.bind(this);
this.handleScrollPrevious = this.handleScrollPrevious.bind(this);
}
componentDidMount() {
this.loadJobEvents();
}
async loadJobEvents() {
const { job } = this.props;
try {
const {
data: { results = [] },
} = await JobsAPI.readJobEvents(job.id);
this.setState({ results, hasContentLoading: true });
} catch (err) {
this.setState({ contentError: err });
} finally {
this.setState({ hasContentLoading: false });
}
}
renderRow({ index, key, style }) {
const { results } = this.state;
return (
<OutputRow key={key} style={style} className="row">
<div className="id">{results[index].id}</div>
<div className="content">{results[index].stdout}</div>
</OutputRow>
);
}
onRowsRendered = ({ startIndex, stopIndex }) => {
this.setState({ startIndex, stopIndex });
};
handleScrollPrevious() {
const { startIndex, stopIndex } = this.state;
const index = startIndex - (stopIndex - startIndex);
this.setState({ scrollToIndex: Math.max(0, index) });
}
handleScrollNext() {
const { stopIndex } = this.state;
this.setState({ scrollToIndex: stopIndex + 1});
}
handleScrollTop() {
this.setState({ scrollToIndex: 0 });
}
handleScrollBottom() {
const { results } = this.state;
this.setState({ scrollToIndex: results.length - 1 });
}
render() { render() {
const { job } = this.props; const { job } = this.props;
const {
results,
hasContentLoading,
contentError,
scrollToIndex,
startIndex,
stopIndex
} = this.state;
if (hasContentLoading) {
return <ContentLoading />;
}
if (contentError) {
return <ContentError error={contentError} />;
}
return ( return (
<CardBody> <CardBody>
<b>{job.name}</b> <b>{job.name}</b>
{/*Heading and Job Stats */}
{/*Host Status Bar */}
<OutputToolbar> <OutputToolbar>
{/* Filter and Pagination */} <MenuControls
<b>Filter placeholder</b> onScrollTop={this.handleScrollTop}
<MenuControls /> onScrollBottom={this.handleScrollBottom}
onScrollNext={this.handleScrollNext}
onScrollPrevious={this.handleScrollPrevious}
/>
</OutputToolbar> </OutputToolbar>
<ul> <OutputWrapper>
<li> <AutoSizer>
</li> {({ width, height }) => {
</ul> console.log('scroll to index', scrollToIndex);
console.log('start index', startIndex);
console.log('stop index', stopIndex);
return (
<List
ref={this.listRef}
width={width}
height={height}
rowHeight={50}
rowRenderer={this.renderRow}
rowCount={results.length}
overscanRowCount={5}
scrollToIndex={scrollToIndex}
onRowsRendered={this.onRowsRendered}
scrollToAlignment="start"
/>
);
}}
</AutoSizer>
</OutputWrapper>
</CardBody> </CardBody>
); );
} }

View File

@@ -23,22 +23,32 @@ const Button = styled(PFButton)`
`; `;
class MenuControls extends Component { class MenuControls extends Component {
constructor(props) {
super(props);
}
render() { render() {
const {
onScrollTop,
onScrollBottom,
onScrollNext,
onScrollPrevious,
} = this.props;
return ( return (
<Wrapper> <Wrapper>
<Button variant="plain"> <Button variant="plain">
<PlusIcon /> <PlusIcon />
</Button> </Button>
<Button variant="plain"> <Button onClick={onScrollPrevious} variant="plain">
<AngleUpIcon /> <AngleUpIcon />
</Button> </Button>
<Button variant="plain"> <Button onClick={onScrollNext} variant="plain">
<AngleDownIcon /> <AngleDownIcon />
</Button> </Button>
<Button variant="plain"> <Button onClick={onScrollTop} variant="plain">
<AngleDoubleUpIcon /> <AngleDoubleUpIcon />
</Button> </Button>
<Button variant="plain"> <Button onClick={onScrollBottom} variant="plain">
<AngleDoubleDownIcon /> <AngleDoubleDownIcon />
</Button> </Button>
</Wrapper> </Wrapper>

View File

@@ -15,7 +15,7 @@ const findChildren = () => {
AngleDoubleDownIcon = wrapper.find('AngleDoubleDownIcon'); AngleDoubleDownIcon = wrapper.find('AngleDoubleDownIcon');
AngleUpIcon = wrapper.find('AngleUpIcon'); AngleUpIcon = wrapper.find('AngleUpIcon');
AngleDownIcon = wrapper.find('AngleDownIcon'); AngleDownIcon = wrapper.find('AngleDownIcon');
} };
describe('MenuControls', () => { describe('MenuControls', () => {
test('should render successfully', () => { test('should render successfully', () => {

View File

@@ -1 +1 @@
export { default as MenuControls } from './MenuControls'; export { default } from './MenuControls';