mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
add missing event placeholders and recompute heights on load
This commit is contained in:
parent
0a3633113e
commit
033308de69
@ -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
|
||||
|
||||
85
awx/ui_next/src/screens/Job/JobOutput/JobEventSkeleton.jsx
Normal file
85
awx/ui_next/src/screens/Job/JobOutput/JobEventSkeleton.jsx
Normal 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;
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user