diff --git a/superset/assets/javascripts/explore/components/ExploreChartHeader.jsx b/superset/assets/javascripts/explore/components/ExploreChartHeader.jsx
index b8b52e36fdb3f..30b47994eb3bf 100644
--- a/superset/assets/javascripts/explore/components/ExploreChartHeader.jsx
+++ b/superset/assets/javascripts/explore/components/ExploreChartHeader.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { chartPropType } from '../../chart/chartReducer';
import ExploreActionButtons from './ExploreActionButtons';
+import RowCountLabel from './RowCountLabel';
import EditableTitle from '../../components/EditableTitle';
import AlteredSliceTag from '../../components/AlteredSliceTag';
import FaveStar from '../../components/FaveStar';
@@ -66,11 +67,12 @@ class ExploreChartHeader extends React.PureComponent {
}
render() {
+ const formData = this.props.form_data;
const queryResponse = this.props.chart.queryResponse;
const data = {
- csv_endpoint: getExploreUrl(this.props.form_data, 'csv'),
- json_endpoint: getExploreUrl(this.props.form_data, 'json'),
- standalone_endpoint: getExploreUrl(this.props.form_data, 'standalone'),
+ csv_endpoint: getExploreUrl(formData, 'csv'),
+ json_endpoint: getExploreUrl(formData, 'json'),
+ standalone_endpoint: getExploreUrl(formData, 'standalone'),
};
return (
@@ -109,13 +111,20 @@ class ExploreChartHeader extends React.PureComponent {
{this.props.chart.sliceFormData &&
}
+ {this.props.chart.chartStatus === 'success' && queryResponse &&
+
+ }
{this.props.chart.chartStatus === 'success' &&
queryResponse &&
queryResponse.is_cached &&
+
diff --git a/superset/assets/javascripts/explore/components/RowCountLabel.jsx b/superset/assets/javascripts/explore/components/RowCountLabel.jsx
new file mode 100644
index 0000000000000..1b29a0309ec37
--- /dev/null
+++ b/superset/assets/javascripts/explore/components/RowCountLabel.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Label } from 'react-bootstrap';
+
+import { t } from '../../locales';
+import { defaultNumberFormatter } from '../../modules/utils';
+import TooltipWrapper from '../../components/TooltipWrapper';
+
+
+const propTypes = {
+ rowcount: PropTypes.number,
+ limit: PropTypes.number,
+};
+
+const defaultProps = {
+};
+
+export default function RowCountLabel({ rowcount, limit }) {
+ const limitReached = rowcount === limit;
+ const bsStyle = (limitReached || rowcount === 0) ? 'warning' : 'default';
+ const formattedRowCount = defaultNumberFormatter(rowcount);
+ const tooltip = (
+
+ {limitReached &&
+ {t('Limit reached')}
}
+ {rowcount}
+
+ );
+ return (
+
+
+
+ );
+}
+
+RowCountLabel.propTypes = propTypes;
+RowCountLabel.defaultProps = defaultProps;
diff --git a/superset/assets/javascripts/modules/utils.js b/superset/assets/javascripts/modules/utils.js
index e7757d4b06d8a..b5590f0e79cee 100644
--- a/superset/assets/javascripts/modules/utils.js
+++ b/superset/assets/javascripts/modules/utils.js
@@ -4,6 +4,17 @@ import $ from 'jquery';
import { formatDate, UTC } from './dates';
+const siFormatter = d3.format('.3s');
+
+export function defaultNumberFormatter(n) {
+ let si = siFormatter(n);
+ // Removing trailing `.00` if any
+ if (si.slice(-1) < 'A') {
+ si = parseFloat(si).toString();
+ }
+ return si;
+}
+
export function d3FormatPreset(format) {
// like d3.format, but with support for presets like 'smart_date'
if (format === 'smart_date') {
@@ -12,7 +23,7 @@ export function d3FormatPreset(format) {
if (format) {
return d3.format(format);
}
- return d3.format('.3s');
+ return defaultNumberFormatter;
}
export const d3TimeFormatPreset = function (format) {
const effFormat = format || 'smart_date';
diff --git a/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx
new file mode 100644
index 0000000000000..1642fd7df680f
--- /dev/null
+++ b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { expect } from 'chai';
+import { describe, it } from 'mocha';
+import { shallow } from 'enzyme';
+import { Label } from 'react-bootstrap';
+
+import TooltipWrapper from './../../../../javascripts/components/TooltipWrapper';
+
+import RowCountLabel from '../../../../javascripts/explore/components/RowCountLabel';
+
+describe('RowCountLabel', () => {
+ const defaultProps = {
+ rowcount: 51,
+ limit: 100,
+ };
+
+ it('is valid', () => {
+ expect(React.isValidElement()).to.equal(true);
+ });
+ it('renders a Label and a TooltipWrapper', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(Label)).to.have.lengthOf(1);
+ expect(wrapper.find(TooltipWrapper)).to.have.lengthOf(1);
+ });
+ it('renders a warning when limit is reached', () => {
+ const props = {
+ rowcount: 100,
+ limit: 100,
+ };
+ const wrapper = shallow();
+ expect(wrapper.find(Label).first().props().bsStyle).to.equal('warning');
+ });
+});
diff --git a/superset/assets/spec/javascripts/modules/utils_spec.jsx b/superset/assets/spec/javascripts/modules/utils_spec.jsx
index 1e3f2d4007454..174e0e1e61db8 100644
--- a/superset/assets/spec/javascripts/modules/utils_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/utils_spec.jsx
@@ -2,7 +2,7 @@ import { it, describe } from 'mocha';
import { expect } from 'chai';
import {
tryNumify, slugify, formatSelectOptionsForRange, d3format,
- d3FormatPreset, d3TimeFormatPreset,
+ d3FormatPreset, d3TimeFormatPreset, defaultNumberFormatter,
} from '../../../javascripts/modules/utils';
describe('utils', () => {
@@ -52,4 +52,21 @@ describe('utils', () => {
expect(d3FormatPreset('smart_date')(0)).to.equal('1970');
});
});
+ describe('d3TimeFormatPreset', () => {
+ expect(defaultNumberFormatter(10)).to.equal('10');
+ expect(defaultNumberFormatter(1)).to.equal('1');
+ expect(defaultNumberFormatter(1.0)).to.equal('1');
+ expect(defaultNumberFormatter(10.0)).to.equal('10');
+ expect(defaultNumberFormatter(10001)).to.equal('10.0k');
+ expect(defaultNumberFormatter(111000000)).to.equal('111M');
+ expect(defaultNumberFormatter(0.23)).to.equal('230m');
+
+ expect(defaultNumberFormatter(-10)).to.equal('-10');
+ expect(defaultNumberFormatter(-1)).to.equal('-1');
+ expect(defaultNumberFormatter(-1.0)).to.equal('-1');
+ expect(defaultNumberFormatter(-10.0)).to.equal('-10');
+ expect(defaultNumberFormatter(-10001)).to.equal('-10.0k');
+ expect(defaultNumberFormatter(-111000000)).to.equal('-111M');
+ expect(defaultNumberFormatter(-0.23)).to.equal('-230m');
+ });
});
diff --git a/superset/viz.py b/superset/viz.py
index 2ea85d26a8b25..6551577de15c4 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -273,11 +273,13 @@ def get_payload(self, force=False):
cache_timeout = self.cache_timeout
stacktrace = None
annotations = []
+ rowcount = None
try:
df = self.get_df()
if not self.error_message:
data = self.get_data(df)
annotations = self.get_annotations()
+ rowcount = len(df.index)
except Exception as e:
logging.exception(e)
if not self.error_message:
@@ -295,6 +297,7 @@ def get_payload(self, force=False):
'status': self.status,
'stacktrace': stacktrace,
'annotations': annotations,
+ 'rowcount': rowcount,
}
payload['cached_dttm'] = datetime.utcnow().isoformat().split('.')[0]
logging.info('Caching for the next {} seconds'.format(