mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 09:27:36 -02:30
add missing event placeholders and recompute heights on load
This commit is contained in:
committed by
Marliana Lara
parent
0a3633113e
commit
033308de69
@@ -98,11 +98,11 @@ function getTimestamp({ created }) {
|
|||||||
const dateMinutes = date.getMinutes();
|
const dateMinutes = date.getMinutes();
|
||||||
const dateSeconds = date.getSeconds();
|
const dateSeconds = date.getSeconds();
|
||||||
|
|
||||||
const stampHour = dateHours < 10 ? `0${dateHours}` : dateHours;
|
const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours;
|
||||||
const stampMinute = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
|
const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
|
||||||
const stampSecond = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
|
const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
|
||||||
|
|
||||||
return `${stampHour}:${stampMinute}:${stampSecond}`;
|
return `${stampHours}:${stampMinutes}:${stampSeconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLineTextHtml({ created, event, start_line, stdout }) {
|
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 : (
|
return !stdout ? null : (
|
||||||
<JobEventWrapper {...rest}>
|
<JobEventWrapper {...rest}>
|
||||||
{getLineTextHtml({ created, event, start_line, stdout }).map(
|
{getLineTextHtml({ created, event, start_line, stdout }).map(
|
||||||
({ lineNumber, html }) =>
|
({ lineNumber, html }) =>
|
||||||
lineNumber > 0 && (
|
lineNumber !== 0 && (
|
||||||
<JobEventLine key={lineNumber} isFirst={lineNumber === 1}>
|
<JobEventLine
|
||||||
|
key={`${counter}-${lineNumber}`}
|
||||||
|
isFirst={lineNumber === 1}
|
||||||
|
>
|
||||||
<JobEventLineToggle />
|
<JobEventLineToggle />
|
||||||
<JobEventLineNumber>{lineNumber}</JobEventLineNumber>
|
<JobEventLineNumber>{lineNumber}</JobEventLineNumber>
|
||||||
<JobEventLineText
|
<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 ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import JobEvent from './JobEvent';
|
import JobEvent from './JobEvent';
|
||||||
|
import JobEventSkeleton from './JobEventSkeleton';
|
||||||
import MenuControls from './shared/MenuControls';
|
import MenuControls from './shared/MenuControls';
|
||||||
|
|
||||||
const OutputToolbar = styled.div`
|
const OutputToolbar = styled.div`
|
||||||
@@ -37,9 +38,15 @@ const OutputFooter = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class JobOutput extends Component {
|
function range(low, high) {
|
||||||
listRef = React.createRef();
|
const numbers = [];
|
||||||
|
for (let n = low; n <= high; n++) {
|
||||||
|
numbers.push(n);
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JobOutput extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -47,10 +54,7 @@ class JobOutput extends Component {
|
|||||||
contentError: null,
|
contentError: null,
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
results: {},
|
results: {},
|
||||||
scrollToIndex: -1,
|
currentlyLoading: [],
|
||||||
loadedRowCount: 0,
|
|
||||||
loadedRowsMap: {},
|
|
||||||
loadingRowCount: 0,
|
|
||||||
remoteRowCount: 0,
|
remoteRowCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,10 +78,32 @@ class JobOutput extends Component {
|
|||||||
this.loadJobEvents();
|
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() {
|
async loadJobEvents() {
|
||||||
const { job } = this.props;
|
const { job } = this.props;
|
||||||
|
|
||||||
this.setState({ hasContentLoading: true });
|
const loadRange = range(1, 50);
|
||||||
|
this.setState(({ currentlyLoading }) => ({
|
||||||
|
hasContentLoading: true,
|
||||||
|
currentlyLoading: currentlyLoading.concat(loadRange),
|
||||||
|
}));
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { results: newResults = [], count },
|
data: { results: newResults = [], count },
|
||||||
@@ -85,7 +111,6 @@ class JobOutput extends Component {
|
|||||||
page_size: 50,
|
page_size: 50,
|
||||||
order_by: 'start_line',
|
order_by: 'start_line',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState(({ results }) => {
|
this.setState(({ results }) => {
|
||||||
newResults.forEach(jobEvent => {
|
newResults.forEach(jobEvent => {
|
||||||
results[jobEvent.counter] = jobEvent;
|
results[jobEvent.counter] = jobEvent;
|
||||||
@@ -95,21 +120,23 @@ class JobOutput extends Component {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ contentError: err });
|
this.setState({ contentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ hasContentLoading: false });
|
this.setState(({ currentlyLoading }) => ({
|
||||||
|
hasContentLoading: false,
|
||||||
|
currentlyLoading: currentlyLoading.filter(n => !loadRange.includes(n)),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRowLoaded({ index }) {
|
isRowLoaded({ index }) {
|
||||||
const { results } = this.state;
|
const { results, currentlyLoading } = this.state;
|
||||||
return !!results[index];
|
if (results[index]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return currentlyLoading.includes(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
rowRenderer({ index, parent, key, style }) {
|
rowRenderer({ index, parent, key, style }) {
|
||||||
const { results } = this.state;
|
const { results } = this.state;
|
||||||
if (!results[index]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { created, event, stdout, start_line } = results[index];
|
|
||||||
return (
|
return (
|
||||||
<CellMeasurer
|
<CellMeasurer
|
||||||
key={key}
|
key={key}
|
||||||
@@ -118,33 +145,43 @@ class JobOutput extends Component {
|
|||||||
rowIndex={index}
|
rowIndex={index}
|
||||||
columnIndex={0}
|
columnIndex={0}
|
||||||
>
|
>
|
||||||
<JobEvent
|
{results[index] ? (
|
||||||
className="row"
|
<JobEvent className="row" style={style} {...results[index]} />
|
||||||
style={style}
|
) : (
|
||||||
created={created}
|
<JobEventSkeleton
|
||||||
event={event}
|
className="row"
|
||||||
start_line={start_line}
|
style={style}
|
||||||
stdout={stdout}
|
counter={index}
|
||||||
/>
|
contentLength={80}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CellMeasurer>
|
</CellMeasurer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMoreRows({ startIndex, stopIndex }) {
|
loadMoreRows({ startIndex, stopIndex }) {
|
||||||
const { job } = this.props;
|
const { job } = this.props;
|
||||||
|
|
||||||
let params = {
|
const loadRange = range(startIndex, stopIndex);
|
||||||
|
this.setState(({ currentlyLoading }) => ({
|
||||||
|
currentlyLoading: currentlyLoading.concat(loadRange),
|
||||||
|
}));
|
||||||
|
const params = {
|
||||||
counter__gte: startIndex,
|
counter__gte: startIndex,
|
||||||
counter__lte: stopIndex,
|
counter__lte: stopIndex,
|
||||||
order_by: 'start_line',
|
order_by: 'start_line',
|
||||||
};
|
};
|
||||||
|
return JobsAPI.readEvents(job.id, job.type, params).then(response => {
|
||||||
return await JobsAPI.readEvents(job.id, job.type, params).then(response => {
|
this.setState(({ results, currentlyLoading }) => {
|
||||||
this.setState(({ results }) => {
|
|
||||||
response.data.results.forEach(jobEvent => {
|
response.data.results.forEach(jobEvent => {
|
||||||
results[jobEvent.counter] = 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() {
|
handleScrollPrevious() {
|
||||||
const startIndex = this.listRef.Grid._renderedRowStartIndex;
|
const startIndex = this.listRef.Grid._renderedRowStartIndex;
|
||||||
const stopIndex = this.listRef.Grid._renderedRowStopIndex;
|
const stopIndex = this.listRef.Grid._renderedRowStopIndex;
|
||||||
const range = stopIndex - startIndex + 1;
|
const scrollRange = stopIndex - startIndex + 1;
|
||||||
this.listRef.scrollToRow(Math.max(0, startIndex - range));
|
this.listRef.scrollToRow(Math.max(0, startIndex - scrollRange));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScrollNext() {
|
handleScrollNext() {
|
||||||
@@ -168,7 +205,6 @@ class JobOutput extends Component {
|
|||||||
handleScrollBottom() {
|
handleScrollBottom() {
|
||||||
const { remoteRowCount } = this.state;
|
const { remoteRowCount } = this.state;
|
||||||
this.listRef.scrollToRow(remoteRowCount - 1);
|
this.listRef.scrollToRow(remoteRowCount - 1);
|
||||||
this.setState({ scrollToIndex: remoteRowCount - 1 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize({ width }) {
|
handleResize({ width }) {
|
||||||
@@ -181,12 +217,7 @@ class JobOutput extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { job } = this.props;
|
const { job } = this.props;
|
||||||
const {
|
const { hasContentLoading, contentError, remoteRowCount } = this.state;
|
||||||
hasContentLoading,
|
|
||||||
contentError,
|
|
||||||
scrollToIndex,
|
|
||||||
remoteRowCount,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (hasContentLoading) {
|
if (hasContentLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
@@ -229,8 +260,8 @@ class JobOutput extends Component {
|
|||||||
rowHeight={this.cache.rowHeight}
|
rowHeight={this.cache.rowHeight}
|
||||||
rowRenderer={this.rowRenderer}
|
rowRenderer={this.rowRenderer}
|
||||||
scrollToAlignment="start"
|
scrollToAlignment="start"
|
||||||
scrollToIndex={scrollToIndex}
|
|
||||||
width={width}
|
width={width}
|
||||||
|
overscanRowCount={20}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user