diff --git a/Dockerfile b/Dockerfile index 5944b825..e8702a0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ FROM openjdk:8-jre-alpine WORKDIR /app -COPY --from=builder /app/src/target/poli-0.12.1.jar /app/poli-0.12.1.jar +COPY --from=builder /app/src/target/poli-0.12.2.jar /app/poli-0.12.2.jar COPY --from=builder /app/src/db/poli.db /app/db/poli.db COPY --from=builder /app/src/build_release/start.sh /app/start.sh COPY --from=builder /app/src/config/poli.docker.properties /app/config/poli.properties diff --git a/README.md b/README.md index 7e5741c3..ab8dd838 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # **Poli(魄力)** -[![Version](https://img.shields.io/badge/Version-0.12.1-0065FF.svg)](#) +[![Version](https://img.shields.io/badge/Version-0.12.2-0065FF.svg)](#) [![license: MIT](https://img.shields.io/badge/license-MIT-FF5630.svg)](https://opensource.org/licenses/MIT) [![Download](https://img.shields.io/github/downloads/shzlw/poli/total.svg?color=6554C0)](https://github.com/shzlw/poli/releases) [![Docker Pulls](https://img.shields.io/docker/pulls/zhonglu/poli.svg)](https://cloud.docker.com/u/zhonglu/repository/docker/zhonglu/poli) @@ -56,13 +56,13 @@ Auto refresh, drill through, fullscreen, embeds, color themes + more features in Windows/Linux ```sh -java -jar poli-0.12.1.jar +java -jar poli-0.12.2.jar ``` Docker ```sh -docker run -d -p 6688:6688 --name poli zhonglu/poli:0.12.1 +docker run -d -p 6688:6688 --name poli zhonglu/poli:0.12.2 ``` Check [installation guide](https://shzlw.github.io/poli/#/installation) for more details. diff --git a/build_release/start.bat b/build_release/start.bat index 0eb816e5..7f5e0d45 100644 --- a/build_release/start.bat +++ b/build_release/start.bat @@ -1 +1 @@ -java -jar poli-0.12.1.jar --spring.config.name=application,poli \ No newline at end of file +java -jar poli-0.12.2.jar --spring.config.name=application,poli \ No newline at end of file diff --git a/build_release/start.sh b/build_release/start.sh index ca5be500..d2e6c529 100644 --- a/build_release/start.sh +++ b/build_release/start.sh @@ -1,4 +1,4 @@ #!/bin/sh set -e -java -jar poli-0.12.1.jar --spring.config.name=application,poli +java -jar poli-0.12.2.jar --spring.config.name=application,poli diff --git a/docs/change-logs.md b/docs/change-logs.md index 05f53e8d..f11f82d5 100644 --- a/docs/change-logs.md +++ b/docs/change-logs.md @@ -1,5 +1,12 @@ # Change Logs +## v0.12.2 + +### Bug Fixes +- Add default value option for multi series charts (bar, line and area). +- Fix the issue that table column is not sorted correctly if the data type is number. +- Fix the query editor input lagging. + ## v0.12.1 ### Bug Fixes diff --git a/docs/installation.md b/docs/installation.md index d65685ae..257b16bf 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -96,7 +96,7 @@ 1. Pull and run the Poli image. ```bash - docker run -d -p 6688:6688 --name poli zhonglu/poli:0.12.1 + docker run -d -p 6688:6688 --name poli zhonglu/poli:0.12.2 ``` 2. Add JDBC drivers. diff --git a/pom.xml b/pom.xml index c8e8cde5..59a12780 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.shzlw.poli poli jar - 0.12.1 + 0.12.2 Poli The SQL BI tool diff --git a/src/main/java/com/shzlw/poli/service/JdbcQueryService.java b/src/main/java/com/shzlw/poli/service/JdbcQueryService.java index 4d173d78..b624cdb4 100644 --- a/src/main/java/com/shzlw/poli/service/JdbcQueryService.java +++ b/src/main/java/com/shzlw/poli/service/JdbcQueryService.java @@ -139,7 +139,7 @@ public QueryResult extractData(ResultSet rs) { if (Constants.CONTENT_TYPE_CSV.equals(contentType)) { data = resultSetToCsvString(rs, columnNames, maxQueryResult); } else { - data = resultSetToJsonString(rs, columnNames, maxQueryResult); + data = resultSetToJsonString(rs, metadata, maxQueryResult); } return QueryResult.ofData(data, columns); } catch (Exception e) { @@ -167,7 +167,7 @@ public QueryResult extractData(ResultSet rs) { ResultSetMetaData metadata = rs.getMetaData(); String[] columnNames = getColumnNames(metadata); List columns = getColumnList(metadata); - String data = resultSetToJsonString(rs, columnNames, maxQueryResult); + String data = resultSetToJsonString(rs, metadata, maxQueryResult); return QueryResult.ofData(data, columns); } catch (Exception e) { String error = CommonUtils.getSimpleError(e); @@ -247,7 +247,7 @@ private List getColumnList(ResultSetMetaData metadata) throws SQLExcepti int columnCount = metadata.getColumnCount(); List columns = new ArrayList<>(); for (int i = 1; i <= columnCount; i++) { - int columnType = metadata.getColumnType(i); + int columnType = metadata.getColumnType(i);; String dbType = metadata.getColumnTypeName(i); int length = metadata.getColumnDisplaySize(i); // Use column label to fetch the column alias instead of using column name. @@ -258,15 +258,52 @@ private List getColumnList(ResultSetMetaData metadata) throws SQLExcepti return columns; } - private String resultSetToJsonString(ResultSet rs, String[] columnNames, int maxQueryResult) throws SQLException { - int columnCount = columnNames.length - 1; + private String resultSetToJsonString(ResultSet rs, ResultSetMetaData metadata, int maxQueryResult) throws SQLException { + int columnCount = metadata.getColumnCount(); ObjectMapper mapper = new ObjectMapper(); ArrayNode array = mapper.createArrayNode(); int rowCount = 0; while (rs.next()) { ObjectNode node = mapper.createObjectNode(); for (int i = 1; i <= columnCount; i++) { - node.put(columnNames[i], rs.getString(i)); + String columnLabel = metadata.getColumnLabel(i); + int columnType = metadata.getColumnType(i); + switch (columnType) { + case java.sql.Types.VARCHAR: + case java.sql.Types.CHAR: + case java.sql.Types.LONGVARCHAR: + node.put(columnLabel, rs.getString(i)); + break; + case java.sql.Types.TINYINT: + case java.sql.Types.SMALLINT: + case java.sql.Types.INTEGER: + node.put(columnLabel, rs.getInt(i)); + break; + case java.sql.Types.NUMERIC: + case java.sql.Types.DECIMAL: + node.put(columnLabel, rs.getBigDecimal(i)); + break; + case java.sql.Types.DOUBLE: + case java.sql.Types.FLOAT: + case java.sql.Types.REAL: + node.put(columnLabel, rs.getDouble(i)); + break; + case java.sql.Types.BOOLEAN: + case java.sql.Types.BIT: + node.put(columnLabel, rs.getBoolean(i)); + break; + case java.sql.Types.BIGINT: + node.put(columnLabel, rs.getLong(i)); + break; + case java.sql.Types.NVARCHAR: + case java.sql.Types.NCHAR: + node.put(columnLabel, rs.getNString(i)); + break; + default: + // Unhandled types + node.put(columnLabel, rs.getString(i)); + break; + } } array.add(node); rowCount++; @@ -277,6 +314,7 @@ private String resultSetToJsonString(ResultSet rs, String[] columnNames, int max return array.toString(); } + private String resultSetToCsvString(ResultSet rs, String[] columnNames, int maxQueryResult) throws SQLException { int columnCount = columnNames.length - 1; StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/shzlw/poli/util/Constants.java b/src/main/java/com/shzlw/poli/util/Constants.java index 2b05e36f..d481a586 100644 --- a/src/main/java/com/shzlw/poli/util/Constants.java +++ b/src/main/java/com/shzlw/poli/util/Constants.java @@ -4,7 +4,7 @@ public final class Constants { private Constants() {} - public static final String CURRENT_VERSION = "0.12.1"; + public static final String CURRENT_VERSION = "0.12.2"; public static final String SUCCESS = "success"; public static final String GOOD = ""; diff --git a/version b/version index 317d6af1..c1851924 100755 --- a/version +++ b/version @@ -1,4 +1,4 @@ #!/bin/sh -VERSION=0.12.1 +VERSION=0.12.2 echo $VERSION diff --git a/web-app/package-lock.json b/web-app/package-lock.json index c8dc69fe..cc86d777 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -1,6 +1,6 @@ { "name": "poli-web-app", - "version": "0.12.1", + "version": "0.12.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13703,7 +13703,8 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -13712,7 +13713,8 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -13815,7 +13817,8 @@ }, "inherits": { "version": "2.0.4", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -13825,6 +13828,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -13850,6 +13854,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -13866,6 +13871,7 @@ "mkdirp": { "version": "0.5.3", "bundled": true, + "optional": true, "requires": { "minimist": "^1.2.5" } @@ -13958,6 +13964,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -14056,6 +14063,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/web-app/package.json b/web-app/package.json index e08cd95a..b64810a6 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -1,6 +1,6 @@ { "name": "poli-web-app", - "version": "0.12.1", + "version": "0.12.2", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.28", diff --git a/web-app/src/api/EchartsApi.js b/web-app/src/api/EchartsApi.js index 32ab7f1a..6e340614 100644 --- a/web-app/src/api/EchartsApi.js +++ b/web-app/src/api/EchartsApi.js @@ -175,9 +175,7 @@ const getBarOptionTemplate = (colorPlatte = 'default', legendData, axisData, ser } } - const legend = legendData !== null ? { - data: legendData - }: {}; + const legend = parseLegendData(legendData); return { color: getColorPlatte(colorPlatte), @@ -204,7 +202,8 @@ const getBarOption = (data, config, title) => { yAxis, hasMultiSeries = false, isStacked = true, - colorPlatte = 'default' + colorPlatte = 'default', + multiSeriesDefaultValue = 0 } = config; const type = 'bar'; @@ -215,7 +214,7 @@ const getBarOption = (data, config, title) => { legendList, xAxisList, grid - } = dataListToGrid(data, xAxis, yAxis, legend); + } = dataListToGrid(data, xAxis, yAxis, legend, multiSeriesDefaultValue); // From grid to series list. for (let i = 0; i < legendList.length; i++) { @@ -267,9 +266,7 @@ const getLineOptionTemplate = (colorPlatte = 'default', legendData, xAxisData, s } : {}; - const legend = legendData !== null ? { - data: legendData - }: {}; + const legend = parseLegendData(legendData); return { color: getColorPlatte(colorPlatte), @@ -302,7 +299,8 @@ const getLineOption = (data, config) => { yAxis, hasMultiSeries = false, isSmooth = false, - colorPlatte = 'default' + colorPlatte = 'default', + multiSeriesDefaultValue = 0 } = config; const seriesData = []; @@ -313,7 +311,7 @@ const getLineOption = (data, config) => { legendList, xAxisList, grid - } = dataListToGrid(data, xAxis, yAxis, legend); + } = dataListToGrid(data, xAxis, yAxis, legend, multiSeriesDefaultValue); // From grid to series list. for (let i = 0; i < legendList.length; i++) { @@ -363,9 +361,7 @@ const getAreaOptionTemplate = (colorPlatte = 'default', legendData, xAxisData, s interval: 0 } : {}; - const legend = legendData !== null ? { - data: legendData - }: {}; + const legend = parseLegendData(legendData); return { color: getColorPlatte(colorPlatte), tooltip: { @@ -398,7 +394,8 @@ const getAreaOption = (data, config) => { yAxis, hasMultiSeries = false, isSmooth = false, - colorPlatte = 'default' + colorPlatte = 'default', + multiSeriesDefaultValue = 0 } = config; const seriesData = []; @@ -409,7 +406,7 @@ const getAreaOption = (data, config) => { legendList, xAxisList, grid - } = dataListToGrid(data, xAxis, yAxis, legend); + } = dataListToGrid(data, xAxis, yAxis, legend, multiSeriesDefaultValue); // From grid to series list. for (let i = 0; i < legendList.length; i++) { @@ -614,13 +611,13 @@ const getHeatmapOption = (data, config) => { const yAxisVal = row[yAxis]; const seriesVal = Number(row[series]); - let xIndex = xAxisData.findIndex(a => a == xAxisVal); + let xIndex = xAxisData.findIndex(a => a === xAxisVal); if (xIndex === -1) { xAxisData.push(xAxisVal); xIndex = xAxisData.length - 1; } - let yIndex = yAxisData.findIndex(a => a == yAxisVal); + let yIndex = yAxisData.findIndex(a => a === yAxisVal); if (yIndex === -1) { yAxisData.push(yAxisVal); yIndex = yAxisData.length - 1; @@ -744,7 +741,7 @@ const getTimeLineOptionTemplate = (seriesData) => { }; -const dataListToGrid = (dataList = [], xAxis, yAxis, legend) => { +const dataListToGrid = (dataList = [], xAxis, yAxis, legend, defaultValue = 0) => { const legendData = new Set(); const xAxisData = new Set(); @@ -763,6 +760,7 @@ const dataListToGrid = (dataList = [], xAxis, yAxis, legend) => { const grid = new Array(legendList.length); for (let i = 0; i < grid.length; i++) { grid[i] = new Array(xAxisList.length); + grid[i].fill(defaultValue); } // Empty element in the grid is undefined. @@ -778,4 +776,16 @@ const dataListToGrid = (dataList = [], xAxis, yAxis, legend) => { xAxisList, grid }; +} + +const parseLegendData = (legendData) => { + if (legendData !== null) { + const list = legendData || []; + const dataList = list.map(val => String(val)); + return { + data: dataList + } + } else { + return {}; + } } \ No newline at end of file diff --git a/web-app/src/components/GridItem.js b/web-app/src/components/GridItem.js index 89eed0c5..72596c5a 100644 --- a/web-app/src/components/GridItem.js +++ b/web-app/src/components/GridItem.js @@ -18,7 +18,7 @@ import DatePicker from './filters/DatePicker'; import Card from './widgets/Card'; import Kanban from './Kanban/Kanban'; -class GridItem extends React.Component { +class GridItem extends React.PureComponent { constructor(props) { super(props); diff --git a/web-app/src/components/Select.js b/web-app/src/components/Select.js index 95b65dbf..7d1f5cf1 100644 --- a/web-app/src/components/Select.js +++ b/web-app/src/components/Select.js @@ -54,7 +54,7 @@ class Select extends React.Component { } optionList.push( - + ) }); diff --git a/web-app/src/components/Tabs/Tabs.js b/web-app/src/components/Tabs/Tabs.js index d38ea3fb..ec5ecfd1 100644 --- a/web-app/src/components/Tabs/Tabs.js +++ b/web-app/src/components/Tabs/Tabs.js @@ -34,7 +34,7 @@ class Tabs extends React.Component { } tabHeaders.push( -
  • this.handleTabClick(title)}> +
  • this.handleTabClick(title)}> { iconOnly ? ( ) : ( diff --git a/web-app/src/components/filters/Slicer.js b/web-app/src/components/filters/Slicer.js index 56a606df..1fe8406b 100644 --- a/web-app/src/components/filters/Slicer.js +++ b/web-app/src/components/filters/Slicer.js @@ -62,16 +62,18 @@ class Slicer extends React.Component { const checkBoxItems = []; for (let i = 0; i < checkBoxes.length; i++) { - const checkBox = checkBoxes[i]; - const value = checkBox.value; + const { + value, + isChecked + } = checkBoxes[i]; if (!searchValue || (searchValue && value.includes(searchValue))) { checkBoxItems.push( ( diff --git a/web-app/src/components/table/Table.js b/web-app/src/components/table/Table.js index e4f20d6e..74c21f0a 100644 --- a/web-app/src/components/table/Table.js +++ b/web-app/src/components/table/Table.js @@ -4,7 +4,7 @@ import ReactTable from 'react-table'; import 'react-table/react-table.css'; import './Table.css'; -class Table extends React.Component { +class Table extends React.PureComponent { static propTypes = { data: PropTypes.array.isRequired, diff --git a/web-app/src/views/DataSource.js b/web-app/src/views/DataSource.js index 3c6d37c9..75cef3a5 100644 --- a/web-app/src/views/DataSource.js +++ b/web-app/src/views/DataSource.js @@ -211,11 +211,14 @@ class DataSource extends Component { const jdbcDataSourceItems = []; for (let i = 0; i < jdbcDataSources.length; i++) { const ds = jdbcDataSources[i]; - const name = ds.name; + const { + id, + name + } = ds; if (!searchValue || (searchValue && name.includes(searchValue))) { jdbcDataSourceItems.push( ( -
    +
    {name}
    diff --git a/web-app/src/views/Event/SharedReportView.js b/web-app/src/views/Event/SharedReportView.js index 5a270069..050e15f0 100644 --- a/web-app/src/views/Event/SharedReportView.js +++ b/web-app/src/views/Event/SharedReportView.js @@ -76,7 +76,7 @@ class SharedReportView extends React.Component { ); const sharedReportRowItems = sharedReportRows.map((sharedReport, index) => - + {sharedReport.reportName} {sharedReport.reportType} {sharedReport.createdBy} diff --git a/web-app/src/views/Report/ComponentEditPanel.js b/web-app/src/views/Report/ComponentEditPanel.js index 6d1fedc8..92d553b2 100644 --- a/web-app/src/views/Report/ComponentEditPanel.js +++ b/web-app/src/views/Report/ComponentEditPanel.js @@ -39,7 +39,10 @@ class ComponentEditPanel extends React.Component { title: '', sqlQuery: '', jdbcDataSourceId: '', - queryResult: {}, + // Query result + queryResultData: [], + queryResultColumns: [], + queryResultError: null, type: Constants.STATIC, subType: Constants.TEXT, style: this.initialStyle, @@ -280,8 +283,16 @@ class ComponentEditPanel extends React.Component { axios.post('/ws/jdbcquery/query', queryRequest) .then(res => { const result = res.data; + + const queryResultData = Util.jsonToArray(result.data); + const { + columns = [], + error + } = result; this.setState({ - queryResult: result + queryResultData, + queryResultColumns: columns, + queryResultError: error }); }); } @@ -351,10 +362,9 @@ class ComponentEditPanel extends React.Component { const { t } = this.props; const { subType, - queryResult = {}, + queryResultColumns: columns = [], data = {} } = this.state; - const columns = queryResult.columns || []; const { colorPlatte = 'default' @@ -376,8 +386,9 @@ class ComponentEditPanel extends React.Component { legend, yAxis, hasMultiSeries = false, + multiSeriesDefaultValue = 0 } = data; - const seriesChartPanel = ( + const multiSeriesChartPanel = (
    this.handleComponentDataChange('multiSeriesDefaultValue', event.target.value)} + />
    )}
    @@ -549,7 +568,7 @@ class ComponentEditPanel extends React.Component { chartConfigPanel = (
    - {seriesChartPanel} + {multiSeriesChartPanel}
    @@ -575,7 +594,7 @@ class ComponentEditPanel extends React.Component { chartConfigPanel = (
    - {seriesChartPanel} + {multiSeriesChartPanel} {hasMultiSeries && (
    @@ -874,7 +893,6 @@ class ComponentEditPanel extends React.Component { const { type, subType, - queryResult, jdbcDataSources = [], drillThrough = [], drillReports = [], @@ -883,12 +901,6 @@ class ComponentEditPanel extends React.Component { searchSchemaName } = this.state; - const data = Util.jsonToArray(queryResult.data); - const { - columns = [], - error - } = queryResult; - // Render the drill through list. const drillItems = []; for (let i = 0; i < drillThrough.length; i++) { @@ -914,7 +926,7 @@ class ComponentEditPanel extends React.Component { } // Render the column list. - const columnItems = columns.map(column => + const columnItems = this.state.queryResultColumns.map(column =>
    {column.name}
    {column.dbType}({column.length})
    @@ -1088,15 +1100,15 @@ class ComponentEditPanel extends React.Component { /> - { error ? ( + { this.state.queryResultError ? (
    - {error} + {this.state.queryResultError}
    ) : ( )} @@ -1173,7 +1185,7 @@ class ComponentEditPanel extends React.Component {
    diff --git a/web-app/src/views/UserManagement/Group.js b/web-app/src/views/UserManagement/Group.js index 193c9cf9..23f5045c 100644 --- a/web-app/src/views/UserManagement/Group.js +++ b/web-app/src/views/UserManagement/Group.js @@ -226,11 +226,14 @@ class Group extends React.Component { const groupItems = []; for (let i = 0; i < groups.length; i++) { const group = groups[i]; - const name = group.name; + const { + id, + name + } = group; if (!searchValue || (searchValue && name.includes(searchValue))) { groupItems.push( ( -
    +
    {name}
    diff --git a/web-app/src/views/UserManagement/User.js b/web-app/src/views/UserManagement/User.js index 69b59aaf..ff059977 100644 --- a/web-app/src/views/UserManagement/User.js +++ b/web-app/src/views/UserManagement/User.js @@ -324,13 +324,16 @@ class User extends React.Component { const userItems = []; for (let i = 0; i < users.length; i++) { - const user = users[i]; - const name = user.name; + const user = users[i] + const { + id, + name + }= user; const username = user.username; if (!searchValue || (searchValue && (username.includes(searchValue) || name.includes(searchValue)))) { userItems.push( ( -
    +
    {user.username}
    diff --git a/web-app/src/views/Workspace.js b/web-app/src/views/Workspace.js index 825f24da..0686f204 100644 --- a/web-app/src/views/Workspace.js +++ b/web-app/src/views/Workspace.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Route, Link, Switch, withRouter } from "react-router-dom"; +import { Route, Switch, withRouter } from "react-router-dom"; import axios from 'axios'; import { withTranslation } from 'react-i18next';