Add pagination for query result list

This commit is contained in:
maxiaoqi2020 2021-02-21 14:41:16 +08:00
parent 93c6de4f28
commit c994438d4b
13 changed files with 694 additions and 110 deletions

View File

@ -22,12 +22,18 @@ public class PreviewResponse
{
private final List<Map<String, String>> columns;
private final List<List<String>> data;
private final Integer total;
private final String fileName;
public PreviewResponse(List<Map<String, String>> columns,
List<List<String>> data)
List<List<String>> data,
Integer total,
String fileName)
{
this.columns = columns;
this.data = data;
this.total = total;
this.fileName = fileName;
}
@JsonProperty
@ -41,4 +47,16 @@ public class PreviewResponse
{
return data;
}
@JsonProperty
public int getTotal()
{
return total;
}
@JsonProperty
public String getFileName()
{
return fileName;
}
}

View File

@ -19,7 +19,6 @@ import io.prestosql.queryeditorui.store.files.ExpiringFileStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@ -53,10 +52,12 @@ public class ResultsPreviewResource
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public Response getFile(@QueryParam("fileURI") URI fileURI,
@DefaultValue("100") @QueryParam("lines") int numLines)
public Response getFile(
@QueryParam("pageNum") Integer pageNum,
@QueryParam("pageSize") Integer pageSize,
@QueryParam("fileURI") URI fileURI)
{
return getFilePreview(fileURI, numLines);
return getFilePreview(fileURI, pageNum, pageSize);
}
private String getFilename(URI fileURI)
@ -64,33 +65,42 @@ public class ResultsPreviewResource
return fileURI.getPath().substring(fileURI.getPath().lastIndexOf('/') + 1);
}
private Response getPreviewFromCSV(CSVReader reader, final int numLines)
private Response getPreviewFromCSV(CSVReader reader, Integer pageNum, Integer pageSize, String fileName)
{
List<Map<String, String>> columns = new ArrayList<>();
List<List<String>> rows = new ArrayList<>();
List<List<String>> data = new ArrayList<>();
try {
for (final String columnName : reader.readNext()) {
HashMap<String, String> columnsMap = new HashMap<String, String>();
HashMap<String, String> columnsMap = new HashMap<>();
columnsMap.put("name", columnName);
columns.add(columnsMap);
}
int counter = 0;
for (String[] line : reader) {
counter++;
rows.add(Arrays.asList(line));
if (counter >= numLines) {
break;
}
data.add(Arrays.asList(line));
}
int total = data.toArray().length;
if (pageNum == null || pageSize == null || total == 0) {
return Response.ok(new PreviewResponse(columns, data, total, fileName)).build();
}
if (pageNum <= 0 || pageSize <= 0) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
else if (total - pageSize * (pageNum - 1) <= 0) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
int start = (pageNum - 1) * pageSize;
int end = Math.min(pageNum * pageSize, total);
List<List<String>> subData = data.subList(start, end);
return Response.ok(new PreviewResponse(columns, subData, total, fileName)).build();
}
catch (IOException e) {
LOG.error(e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
return Response.ok(new PreviewResponse(columns, rows)).build();
}
private Response getFilePreview(URI fileURI, int numLines)
private Response getFilePreview(URI fileURI, Integer pageNum, Integer pageSize)
{
String fileName = getFilename(fileURI);
final File file = fileStore.get(fileName);
@ -98,11 +108,13 @@ public class ResultsPreviewResource
if (file == null) {
throw new FileNotFoundException(fileName + " could not be found");
}
try (final CSVReader reader = new CSVReader(new FileReader(file))) {
return getPreviewFromCSV(reader, numLines);
}
catch (IOException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
else {
try (final CSVReader reader = new CSVReader(new FileReader(file))) {
return getPreviewFromCSV(reader, pageNum, pageSize, fileName);
}
catch (IOException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
}
catch (FileNotFoundException e) {

View File

@ -0,0 +1,279 @@
.rc-pagination {
margin: 0;
padding: 0;
font-size: 14px;
}
.rc-pagination ul,
.rc-pagination ol {
margin: 0;
padding: 0;
list-style: none;
}
.rc-pagination::after {
display: block;
clear: both;
height: 0;
overflow: hidden;
visibility: hidden;
content: ' ';
}
.rc-pagination-total-text {
display: inline-block;
height: 28px;
margin-right: 8px;
line-height: 26px;
vertical-align: middle;
}
.rc-pagination-item {
display: inline-block;
min-width: 28px;
height: 28px;
margin-right: 8px;
font-family: Arial;
line-height: 26px;
text-align: center;
vertical-align: middle;
list-style: none;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 2px;
outline: 0;
cursor: pointer;
user-select: none;
}
.rc-pagination-item a {
display: block;
padding: 0 6px;
color: rgba(0, 0, 0, 0.85);
transition: none;
}
.rc-pagination-item a:hover {
text-decoration: none;
}
.rc-pagination-item:focus,
.rc-pagination-item:hover {
border-color: #1890ff;
transition: all 0.3s;
}
.rc-pagination-item:focus a,
.rc-pagination-item:hover a {
color: #1890ff;
}
.rc-pagination-item-active {
font-weight: 500;
background: #fff;
border-color: #1890ff;
}
.rc-pagination-item-active a {
color: #1890ff;
}
.rc-pagination-item-active:focus,
.rc-pagination-item-active:hover {
border-color: #40a9ff;
}
.rc-pagination-item-active:focus a,
.rc-pagination-item-active:hover a {
color: #40a9ff;
}
.rc-pagination-jump-prev,
.rc-pagination-jump-next {
outline: 0;
}
.rc-pagination-jump-prev button,
.rc-pagination-jump-next button {
background: transparent;
border: none;
cursor: pointer;
color: #666;
}
.rc-pagination-jump-prev button:after,
.rc-pagination-jump-next button:after {
display: block;
content: '•••';
}
.rc-pagination-prev,
.rc-pagination-jump-prev,
.rc-pagination-jump-next {
margin-right: 8px;
}
.rc-pagination-prev,
.rc-pagination-next,
.rc-pagination-jump-prev,
.rc-pagination-jump-next {
display: inline-block;
min-width: 28px;
height: 28px;
color: rgba(0, 0, 0, 0.85);
font-family: Arial;
line-height: 28px;
text-align: center;
vertical-align: middle;
list-style: none;
border-radius: 2px;
cursor: pointer;
transition: all 0.3s;
}
.rc-pagination-prev,
.rc-pagination-next {
outline: 0;
}
.rc-pagination-prev button,
.rc-pagination-next button {
color: rgba(0, 0, 0, 0.85);
cursor: pointer;
user-select: none;
}
.rc-pagination-prev:hover button,
.rc-pagination-next:hover button {
border-color: #40a9ff;
}
.rc-pagination-prev .rc-pagination-item-link,
.rc-pagination-next .rc-pagination-item-link {
display: block;
width: 100%;
height: 100%;
font-size: 12px;
text-align: center;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 2px;
outline: none;
transition: all 0.3s;
}
.rc-pagination-prev:focus .rc-pagination-item-link,
.rc-pagination-next:focus .rc-pagination-item-link,
.rc-pagination-prev:hover .rc-pagination-item-link,
.rc-pagination-next:hover .rc-pagination-item-link {
color: #1890ff;
border-color: #1890ff;
}
.rc-pagination-prev button:after {
content: '';
display: block;
}
.rc-pagination-next button:after {
content: '';
display: block;
}
.rc-pagination-disabled,
.rc-pagination-disabled:hover,
.rc-pagination-disabled:focus {
cursor: not-allowed;
}
.rc-pagination-disabled .rc-pagination-item-link,
.rc-pagination-disabled:hover .rc-pagination-item-link,
.rc-pagination-disabled:focus .rc-pagination-item-link {
color: rgba(0, 0, 0, 0.25);
border-color: #d9d9d9;
cursor: not-allowed;
}
.rc-pagination-slash {
margin: 0 10px 0 5px;
}
.rc-pagination-options {
display: inline-block;
margin-left: 16px;
vertical-align: middle;
}
@media all and (-ms-high-contrast: none) {
.rc-pagination-options *::-ms-backdrop,
.rc-pagination-options {
vertical-align: top;
}
}
.rc-pagination-options-size-changer.rc-select {
display: inline-block;
width: auto;
margin-right: 8px;
}
.rc-pagination-options-quick-jumper {
display: inline-block;
height: 28px;
line-height: 28px;
vertical-align: top;
}
.rc-pagination-options-quick-jumper input {
width: 50px;
margin: 0 8px;
}
.rc-pagination-simple .rc-pagination-prev,
.rc-pagination-simple .rc-pagination-next {
height: 24px;
line-height: 24px;
vertical-align: top;
}
.rc-pagination-simple .rc-pagination-prev .rc-pagination-item-link,
.rc-pagination-simple .rc-pagination-next .rc-pagination-item-link {
height: 24px;
background-color: transparent;
border: 0;
}
.rc-pagination-simple .rc-pagination-prev .rc-pagination-item-link::after,
.rc-pagination-simple .rc-pagination-next .rc-pagination-item-link::after {
height: 24px;
line-height: 24px;
}
.rc-pagination-simple .rc-pagination-simple-pager {
display: inline-block;
height: 24px;
margin-right: 8px;
}
.rc-pagination-simple .rc-pagination-simple-pager input {
box-sizing: border-box;
height: 100%;
margin-right: 8px;
padding: 0 6px;
text-align: center;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 2px;
outline: none;
transition: border-color 0.3s;
}
.rc-pagination-simple .rc-pagination-simple-pager input:hover {
border-color: #1890ff;
}
.rc-pagination.rc-pagination-disabled {
cursor: not-allowed;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item {
background: #f5f5f5;
border-color: #d9d9d9;
cursor: not-allowed;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item a {
color: rgba(0, 0, 0, 0.25);
background: transparent;
border: none;
cursor: not-allowed;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item-active {
background: #dbdbdb;
border-color: transparent;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item-active a {
color: #fff;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item-link {
color: rgba(0, 0, 0, 0.25);
background: #f5f5f5;
border-color: #d9d9d9;
cursor: not-allowed;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item-link-icon {
opacity: 0;
}
.rc-pagination.rc-pagination-disabled .rc-pagination-item-ellipsis {
opacity: 1;
}
@media only screen and (max-width: 992px) {
.rc-pagination-item-after-jump-prev,
.rc-pagination-item-before-jump-next {
display: none;
}
}
@media only screen and (max-width: 576px) {
.rc-pagination-options {
display: none;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -44,6 +44,7 @@
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="assets/stylesheets/hetuui.css">
<link rel="stylesheet" href="assets/stylesheets/headerfooter.css"/>
<link rel="stylesheet" href="assets/stylesheets/plugins/rc-pagination.css"/>
</head>
<body>

View File

@ -39,7 +39,8 @@
"@bosket/core": "latest",
"@bosket/tools": "latest",
"@bosket/react": "latest",
"react-transition-group": "^1.2.1"
"react-transition-group": "^1.2.1",
"rc-pagination": "^3.1.3"
},
"babel": {
"presets": [

View File

@ -24,8 +24,8 @@ class ResultsPreviewActions {
);
}
loadResultsPreview(file) {
ResultsPreviewApiUtils.loadResultsPreview(file).then((results) => {
loadResultsPreview(file, pageNum) {
ResultsPreviewApiUtils.loadResultsPreview(file, pageNum).then((results) => {
this.actions.receivedResultsPreview(results);
}).catch(logError);
}

View File

@ -17,6 +17,7 @@ import _ from 'lodash';
import ResultsPreviewStore from '../stores/ResultsPreviewStore';
import ResultsPreviewActions from '../actions/ResultsPreviewActions';
import QueryActions from '../actions/QueryActions';
import Pagination from 'rc-pagination';
let isColumnResizing = false;
@ -24,7 +25,7 @@ let isColumnResizing = false;
function getStateFromStore() {
return {
query: ResultsPreviewStore.getPreviewQuery(),
table: ResultsPreviewStore.getResultsPreview()
table: ResultsPreviewStore.getResultsPreview(),
};
}
@ -54,12 +55,19 @@ class ResultsTable
extends React.Component {
constructor(props) {
super(props);
this.state = getStateFromStore();
this.state = {
currentPage: 1,
total: 0,
query: "",
table: [],
fileName: ""
};
this._onChange = this._onChange.bind(this);
this._enhancedData = this._enhancedData.bind(this);
this.rowGetter = this.rowGetter.bind(this);
this._enhancedColumns = this._enhancedColumns.bind(this);
this._renderColumns = this._renderColumns.bind(this);
this.onPageChange = this.onPageChange.bind(this);
}
componentDidMount() {
@ -87,30 +95,49 @@ class ResultsTable
}
_renderColumns() {
let table = this.state.table;
let currentPage = this.state.currentPage;
let total = this.state.table.total;
if (table === null || total === 0) {
this._renderEmptyMessage();
}
return (
<div className='flex flex-column hetu-table'>
<div className='editor-menu'>
<div
style={{width: this.props.tableWidth - 20}}
className="text-overflow-ellipsis">
<a href="#" onClick={selectQuery.bind(null, this.state.query)}>
{this.state.query.query}
</a>
<div>
<div className='flex flex-column hetu-table'>
<div className='editor-menu'>
<div
style={{width: this.props.tableWidth - 20}}
className="text-overflow-ellipsis">
<a href="#" onClick={selectQuery.bind(null, this.state.query)}>
{this.state.query.query}
</a>
</div>
</div>
<Table
headerHeight={25}
rowHeight={40}
rowsCount={this.state.table.data.length}
width={this.props.tableWidth}
height={this.props.tableHeight - 39}
maxHeight={this.props.tableHeight - 39}
isResizable={isColumnResizing}
onColumnResizeEndCallback={this._onColumnResizeEndCallback}
{... this.props}>
{getColumns(this.state.table.columns, this.state.table.columnWidths, this.rowGetter)}
</Table>
</div>
<Table
headerHeight={25}
rowHeight={40}
rowsCount={this.state.table.data.length}
width={this.props.tableWidth}
height={this.props.tableHeight - 39}
maxHeight={this.props.tableHeight - 39}
isResizable={isColumnResizing}
onColumnResizeEndCallback={this._onColumnResizeEndCallback}
{... this.props}>
{getColumns(this.state.table.columns, this.state.table.columnWidths, this.rowGetter)}
</Table>
</div>);
<div>
<Pagination
defaultCurrent={1}
current={currentPage}
total={total}
onChange={this.onPageChange}
showTotal={total => `Total ${total} items`}
style={{ marginTop: 10 }}
/>
</div>
</div>
);
}
rowGetter(rowIndex) {
@ -137,6 +164,20 @@ class ResultsTable
this.setState(getStateFromStore());
}
onPageChange(current) {
let fileName = "../api/files/" + ResultsPreviewStore.getResultsPreview().fileName;
ResultsPreviewActions.loadResultsPreview(fileName, current);
let page = ResultsPreviewStore.getResultsPreview();
let query = ResultsPreviewStore.getPreviewQuery();
this.setState({
currentPage: current,
query: query,
table: page,
total: page.total,
fileName: page.fileName,
});
}
_onColumnResizeEndCallback(newColumnWidth, dataKey) {
isColumnResizing = false;
ResultsPreviewActions.setTableColumnWidth(dataKey, newColumnWidth);

View File

@ -247,9 +247,10 @@ function selectTable(table, e) {
}
function previewQueryResult(file, query, e) {
let pageNum = 1;
e.preventDefault();
ResultsPreviewActions.selectPreviewQuery(query);
ResultsPreviewActions.loadResultsPreview(file);
ResultsPreviewActions.loadResultsPreview(file, pageNum);
TabActions.selectTab(TabConstants.RESULTS_PREVIEW);
}

View File

@ -14,8 +14,11 @@
import xhr from './xhr';
const ResultsPreviewApiUtils = {
loadResultsPreview(file) {
return xhr(`../api/preview?fileURI=${encodeURIComponent(file)}`, {
loadResultsPreview(file, pageNum) {
if (!Number.isInteger(pageNum)) {
pageNum = 1;
}
return xhr(`../api/preview?fileURI=${encodeURIComponent(file)}&pageSize=10&pageNum=${pageNum}`, {
method: 'get'
});
}