add missing event placeholders and recompute heights on load

This commit is contained in:
Jake McDermott 2019-07-23 13:12:12 -04:00 committed by Marliana Lara
parent 0a3633113e
commit 033308de69
No known key found for this signature in database
GPG Key ID: 38C73B40DFA809EE
4 changed files with 187 additions and 46 deletions

View File

@ -98,11 +98,11 @@ function getTimestamp({ created }) {
const dateMinutes = date.getMinutes();
const dateSeconds = date.getSeconds();
const stampHour = dateHours < 10 ? `0${dateHours}` : dateHours;
const stampMinute = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
const stampSecond = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours;
const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
return `${stampHour}:${stampMinute}:${stampSecond}`;
return `${stampHours}:${stampMinutes}:${stampSeconds}`;
}
function getLineTextHtml({ created, event, start_line, stdout }) {
@ -127,13 +127,16 @@ function getLineTextHtml({ created, event, start_line, stdout }) {
});
}
function JobEvent({ created, event, stdout, start_line, ...rest }) {
function JobEvent({ counter, created, event, stdout, start_line, ...rest }) {
return !stdout ? null : (
<JobEventWrapper {...rest}>
{getLineTextHtml({ created, event, start_line, stdout }).map(
({ lineNumber, html }) =>
lineNumber > 0 && (
<JobEventLine key={lineNumber} isFirst={lineNumber === 1}>
lineNumber !== 0 && (
<JobEventLine
key={`${counter}-${lineNumber}`}
isFirst={lineNumber === 1}
>
<JobEventLineToggle />
<JobEventLineNumber>{lineNumber}</JobEventLineNumber>
<JobEventLineText

View File

@ -0,0 +1,85 @@
import styled from 'styled-components';
import React from 'react';
const JobEventSkeletonWrapper = styled.div``;
const JobEventSkeletonLine = styled.div`
display: flex;
&:hover {
background-color: white;
}
&:hover div {
background-color: white;
}
&--hidden {
display: none;
}
`;
const JobEventSkeletonLineToggle = styled.div`
background-color: #ebebeb;
color: #646972;
display: flex;
flex: 0 0 30px;
font-size: 18px;
justify-content: center;
line-height: 12px;
& > i {
cursor: pointer;
}
user-select: none;
`;
const JobEventSkeletonLineNumber = styled.div`
color: #161b1f;
background-color: #ebebeb;
flex: 0 0 45px;
text-align: right;
vertical-align: top;
padding-right: 5px;
border-right: 1px solid #b7b7b7;
user-select: none;
`;
const JobEventSkeletonContentWrapper = styled.div`
padding: 0 15px;
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
.content {
background: var(--pf-global--disabled-color--200);
background: linear-gradient(
to right,
#f5f5f5 10%,
#e8e8e8 18%,
#f5f5f5 33%
);
border-radius: 5px;
}
`;
function JobEventSkeletonContent({ contentLength }) {
return (
<JobEventSkeletonContentWrapper>
<span className="content">{' '.repeat(contentLength)}</span>
</JobEventSkeletonContentWrapper>
);
}
function JobEventSkeleton({ counter, contentLength, ...rest }) {
return (
counter > 1 && (
<JobEventSkeletonWrapper {...rest}>
<JobEventSkeletonLine key={counter}>
<JobEventSkeletonLineToggle />
<JobEventSkeletonLineNumber />
<JobEventSkeletonContent contentLength={contentLength} />
</JobEventSkeletonLine>
</JobEventSkeletonWrapper>
)
);
}
export default JobEventSkeleton;

View File

@ -0,0 +1,22 @@
import React from 'react';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import JobEventSkeleton from './JobEventSkeleton';
const contentSelector = 'JobEventSkeletonContent';
describe('<JobEvenSkeletont />', () => {
test('initially renders successfully', () => {
const wrapper = mountWithContexts(
<JobEventSkeleton contentLength={80} counter={100} />
);
expect(wrapper.find(contentSelector).length).toEqual(1);
});
test('always skips first counter', () => {
const wrapper = mountWithContexts(
<JobEventSkeleton contentLength={80} counter={1} />
);
expect(wrapper.find(contentSelector).length).toEqual(0);
});
});

View File

@ -14,6 +14,7 @@ import { JobsAPI } from '@api';
import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import JobEvent from './JobEvent';
import JobEventSkeleton from './JobEventSkeleton';
import MenuControls from './shared/MenuControls';
const OutputToolbar = styled.div`
@ -37,9 +38,15 @@ const OutputFooter = styled.div`
flex: 1;
`;
class JobOutput extends Component {
listRef = React.createRef();
function range(low, high) {
const numbers = [];
for (let n = low; n <= high; n++) {
numbers.push(n);
}
return numbers;
}
class JobOutput extends Component {
constructor(props) {
super(props);
@ -47,10 +54,7 @@ class JobOutput extends Component {
contentError: null,
hasContentLoading: true,
results: {},
scrollToIndex: -1,
loadedRowCount: 0,
loadedRowsMap: {},
loadingRowCount: 0,
currentlyLoading: [],
remoteRowCount: 0,
};
@ -74,10 +78,32 @@ class JobOutput extends Component {
this.loadJobEvents();
}
componentDidUpdate(prevProps, prevState) {
// recompute row heights for any job events that have transitioned
// from loading to loaded
const { currentlyLoading } = this.state;
let shouldRecomputeRowHeights = false;
prevState.currentlyLoading
.filter(n => !currentlyLoading.includes(n))
.forEach(n => {
shouldRecomputeRowHeights = true;
this.cache.clear(n);
});
if (shouldRecomputeRowHeights) {
this.listRef.recomputeRowHeights();
}
}
listRef = React.createRef();
async loadJobEvents() {
const { job } = this.props;
this.setState({ hasContentLoading: true });
const loadRange = range(1, 50);
this.setState(({ currentlyLoading }) => ({
hasContentLoading: true,
currentlyLoading: currentlyLoading.concat(loadRange),
}));
try {
const {
data: { results: newResults = [], count },
@ -85,7 +111,6 @@ class JobOutput extends Component {
page_size: 50,
order_by: 'start_line',
});
this.setState(({ results }) => {
newResults.forEach(jobEvent => {
results[jobEvent.counter] = jobEvent;
@ -95,21 +120,23 @@ class JobOutput extends Component {
} catch (err) {
this.setState({ contentError: err });
} finally {
this.setState({ hasContentLoading: false });
this.setState(({ currentlyLoading }) => ({
hasContentLoading: false,
currentlyLoading: currentlyLoading.filter(n => !loadRange.includes(n)),
}));
}
}
isRowLoaded({ index }) {
const { results } = this.state;
return !!results[index];
const { results, currentlyLoading } = this.state;
if (results[index]) {
return true;
}
return currentlyLoading.includes(index);
}
rowRenderer({ index, parent, key, style }) {
const { results } = this.state;
if (!results[index]) {
return;
}
const { created, event, stdout, start_line } = results[index];
return (
<CellMeasurer
key={key}
@ -118,33 +145,43 @@ class JobOutput extends Component {
rowIndex={index}
columnIndex={0}
>
<JobEvent
className="row"
style={style}
created={created}
event={event}
start_line={start_line}
stdout={stdout}
/>
{results[index] ? (
<JobEvent className="row" style={style} {...results[index]} />
) : (
<JobEventSkeleton
className="row"
style={style}
counter={index}
contentLength={80}
/>
)}
</CellMeasurer>
);
}
async loadMoreRows({ startIndex, stopIndex }) {
loadMoreRows({ startIndex, stopIndex }) {
const { job } = this.props;
let params = {
const loadRange = range(startIndex, stopIndex);
this.setState(({ currentlyLoading }) => ({
currentlyLoading: currentlyLoading.concat(loadRange),
}));
const params = {
counter__gte: startIndex,
counter__lte: stopIndex,
order_by: 'start_line',
};
return await JobsAPI.readEvents(job.id, job.type, params).then(response => {
this.setState(({ results }) => {
return JobsAPI.readEvents(job.id, job.type, params).then(response => {
this.setState(({ results, currentlyLoading }) => {
response.data.results.forEach(jobEvent => {
results[jobEvent.counter] = jobEvent;
});
return { results };
return {
results,
currentlyLoading: currentlyLoading.filter(
n => !loadRange.includes(n)
),
};
});
});
}
@ -152,8 +189,8 @@ class JobOutput extends Component {
handleScrollPrevious() {
const startIndex = this.listRef.Grid._renderedRowStartIndex;
const stopIndex = this.listRef.Grid._renderedRowStopIndex;
const range = stopIndex - startIndex + 1;
this.listRef.scrollToRow(Math.max(0, startIndex - range));
const scrollRange = stopIndex - startIndex + 1;
this.listRef.scrollToRow(Math.max(0, startIndex - scrollRange));
}
handleScrollNext() {
@ -168,7 +205,6 @@ class JobOutput extends Component {
handleScrollBottom() {
const { remoteRowCount } = this.state;
this.listRef.scrollToRow(remoteRowCount - 1);
this.setState({ scrollToIndex: remoteRowCount - 1 });
}
handleResize({ width }) {
@ -181,12 +217,7 @@ class JobOutput extends Component {
render() {
const { job } = this.props;
const {
hasContentLoading,
contentError,
scrollToIndex,
remoteRowCount,
} = this.state;
const { hasContentLoading, contentError, remoteRowCount } = this.state;
if (hasContentLoading) {
return <ContentLoading />;
@ -229,8 +260,8 @@ class JobOutput extends Component {
rowHeight={this.cache.rowHeight}
rowRenderer={this.rowRenderer}
scrollToAlignment="start"
scrollToIndex={scrollToIndex}
width={width}
overscanRowCount={20}
/>
);
}}