From da9288932336c02f8341fe8934ad4e6b9a2eb1a8 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Fri, 12 Jul 2019 13:57:21 -0400 Subject: [PATCH] WIP - react virtualizer --- awx/ui_next/package-lock.json | 76 +++++++-- awx/ui_next/package.json | 1 + awx/ui_next/src/api/models/Jobs.js | 6 + .../src/screens/Job/JobOutput/JobOutput.jsx | 145 ++++++++++++++++-- .../Job/JobOutput/shared/MenuControls.jsx | 18 ++- .../JobOutput/shared/MenuControls.test.jsx | 2 +- .../src/screens/Job/JobOutput/shared/index.js | 2 +- 7 files changed, 220 insertions(+), 30 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index a1783d92e1..7c82f572c3 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -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": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5429,6 +5434,14 @@ "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": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", @@ -7174,7 +7187,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -7195,12 +7209,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7215,17 +7231,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7342,7 +7361,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7354,6 +7374,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7368,6 +7389,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7375,12 +7397,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7399,6 +7423,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -7479,7 +7504,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7491,6 +7517,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7576,7 +7603,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -7612,6 +7640,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7631,6 +7660,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7674,12 +7704,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -10790,6 +10822,11 @@ "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": { "version": "1.1.0", "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -13130,8 +13167,7 @@ "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-router": { "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index aab41746ae..48d0855cda 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -70,6 +70,7 @@ "react-codemirror2": "^6.0.0", "react-dom": "^16.4.1", "react-router-dom": "^4.3.1", + "react-virtualized": "^9.21.1", "styled-components": "^4.2.0" } } diff --git a/awx/ui_next/src/api/models/Jobs.js b/awx/ui_next/src/api/models/Jobs.js index bd103c072a..a47b939a41 100644 --- a/awx/ui_next/src/api/models/Jobs.js +++ b/awx/ui_next/src/api/models/Jobs.js @@ -18,6 +18,12 @@ class Jobs extends Base { readDetail(id, type) { 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; diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx index 6e97e03269..6f70d01c78 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx @@ -1,31 +1,154 @@ import React, { Component } from 'react'; import { CardBody } from '@patternfly/react-core'; -import MenuControls from './shared/MenuControls'; 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` 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 { + 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 ( + +
{results[index].id}
+
{results[index].stdout}
+
+ ); + } + + 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() { const { job } = this.props; + const { + results, + hasContentLoading, + contentError, + scrollToIndex, + startIndex, + stopIndex + } = this.state; + + if (hasContentLoading) { + return ; + } + + if (contentError) { + return ; + } return ( {job.name} - {/*Heading and Job Stats */} - {/*Host Status Bar */} - {/* Filter and Pagination */} - Filter placeholder - + -
    -
  • -
  • -
+ + + {({ width, height }) => { + console.log('scroll to index', scrollToIndex); + console.log('start index', startIndex); + console.log('stop index', stopIndex); + return ( + + ); + }} + +
); } diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx index 8d135110dd..9391ccadc6 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.jsx @@ -23,22 +23,32 @@ const Button = styled(PFButton)` `; class MenuControls extends Component { + constructor(props) { + super(props); + } + render() { + const { + onScrollTop, + onScrollBottom, + onScrollNext, + onScrollPrevious, + } = this.props; return ( - - - - diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx index ab63883778..8ac5fe4c46 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/shared/MenuControls.test.jsx @@ -15,7 +15,7 @@ const findChildren = () => { AngleDoubleDownIcon = wrapper.find('AngleDoubleDownIcon'); AngleUpIcon = wrapper.find('AngleUpIcon'); AngleDownIcon = wrapper.find('AngleDownIcon'); -} +}; describe('MenuControls', () => { test('should render successfully', () => { diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/index.js b/awx/ui_next/src/screens/Job/JobOutput/shared/index.js index 307985d03c..3a2f81055d 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/shared/index.js +++ b/awx/ui_next/src/screens/Job/JobOutput/shared/index.js @@ -1 +1 @@ -export { default as MenuControls } from './MenuControls'; +export { default } from './MenuControls';