-
Notifications
You must be signed in to change notification settings - Fork 272
feat(plugin-chart-table): Implement showing totals #1034
feat(plugin-chart-table): Implement showing totals #1034
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/superset/superset-ui/5AKiPUPLrY8UE6tFJpjyGgbCY7sC |
Codecov Report
@@ Coverage Diff @@
## master #1034 +/- ##
==========================================
- Coverage 27.74% 27.65% -0.09%
==========================================
Files 428 428
Lines 8758 8797 +39
Branches 1314 1335 +21
==========================================
+ Hits 2430 2433 +3
- Misses 6155 6183 +28
- Partials 173 181 +8
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting stuff! Looks good, just a few small comments
@@ -17,7 +17,7 @@ | |||
* under the License. | |||
*/ | |||
import { t, QueryMode } from '@superset-ui/core'; | |||
import { DTTM_ALIAS } from '@superset-ui/core/src/query/buildQueryObject'; | |||
import { DTTM_ALIAS } from '@superset-ui/core/lib/query/buildQueryObject'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I think we're slowly moving away from a dedicated temporal column, we might want to consider exporting this on superset-ui/core
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a hotfix that doesn't really belong in this PR, so I went for "low effort" solution. But you're right, I'll do it properly 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this. I agree it should be exported in @superset-ui/core
properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me fix this in a separate PR as this is preventing apache/superset#13758 to go through.
<td colSpan={totalsHeaderSpan}>Totals</td> | ||
{columnsMeta.slice(totalsHeaderSpan).map(column => ( | ||
<td className={column.dataType === GenericDataType.NUMERIC ? 'dt-metric' : ''}> | ||
{(totals as Record<string, any>)[column.key]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{totals && ( | ||
<tfoot> | ||
<tr key="totals" className="dt-totals"> | ||
<td colSpan={totalsHeaderSpan}>Totals</td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing translation
<td colSpan={totalsHeaderSpan}>Totals</td> | |
<td colSpan={totalsHeaderSpan}>{t('Totals')}</td> |
@@ -193,6 +199,8 @@ export default function DataTable<D extends object>({ | |||
return (wrapStickyTable ? wrapStickyTable(getNoResults) : getNoResults()) as JSX.Element; | |||
} | |||
|
|||
const totalsHeaderSpan = totals && columnsMeta.length - Object.keys(totals).length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we need to remove percentage metrics, otherwise regular metrics won't show up:
const totalsHeaderSpan = totals && columnsMeta.length - Object.keys(totals).length; | |
const totalsHeaderSpan = | |
totals && columnsMeta.filter(col => !col.isPercentMetric).length - Object.keys(totals).length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting about this feature, too! Thanks for the quick turn around.
I'd assume there will be an accompanying Superset PR that updates the query action to run SQL with totals? Could you link it here when it's ready?
|
||
export interface DataTableProps<D extends object> extends TableOptions<D> { | ||
tableClassName?: string; | ||
columnsMeta: DataColumnMeta[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add @superset-ui
specific stuff to DataTable
. We should strive for handling all these in the TableChart
. The goal was to have DataTable
be a standalone package that can be used outside of Superset, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
@@ -134,7 +142,9 @@ function StickyWrap({ | |||
}, [thead]); | |||
|
|||
const theadRef = useRef<HTMLTableSectionElement>(null); // original thead for layout computation | |||
const tfootRef = useRef<HTMLTableSectionElement>(null); // original thead for layout computation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const tfootRef = useRef<HTMLTableSectionElement>(null); // original thead for layout computation | |
const tfootRef = useRef<HTMLTableSectionElement>(null); // original tfoot for layout computation |
innerWidth: widths.reduce(sum), | ||
scrollBarSize, | ||
}); | ||
// real container height, include table header and space for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// real container height, include table header and space for | |
// real container height, include table header, footer and space for |
@@ -242,10 +256,26 @@ function StickyWrap({ | |||
</div> | |||
); | |||
|
|||
footerTable = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
footerTable = ( | |
footerTable = tfoot && ( |
?
row_offset: 0, | ||
post_processing: [], | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should only run extra queries when backend pagination is enabled. The totals should also respect row_limit
because otherwise you will get a table with incorrect sums and no way to browse remaining rows.
It's also possible that users who used row limit may only want to see the totals of the top n rows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked into different ways of restricting the total to the row_limit
, but it gets pretty ugly pretty quickly. Based on my quick experimentation one would either have to wrap the query in a subquery, but then the aggregate would need to be reaggregated (SELECT AVG("AVG(col2)") from (SELECT col1, AVG(col2) as "AVG(col2)" GROUP BY col1 LIMIT 10)
). Alternatively the raw value would be left unaggregated in the subquery and that would work just fine for simple metrics (SELECT AVG("col2") from (SELECT col1, col2 LIMIT 10)
), but then that wouldn't work for custom SQL metrics without parsing. So I think for now we should just add a good disclaimer in the description that the total applies to all rows despite the row limit, and then let the users limit the query with filters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, for tables without backend pagination, can we maybe just not send the "total" query and compute the sums in the frontend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we can assume all aggregations are additive - if there is an average, min, max, percentile or any other non-additive aggregation then displaying a sum will be confusing. For this reason I think the only really comprehensive solution is to perform the aggregation in the database where the cells are calculated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we decide to always show totals as sums (which might not be what the user expects), we'd need to send all records in a response to correctly calculate totals like average or standard deviation on frontend. In that case, I think sending an additional query and performing the calculations on backend is a "lesser evil"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Just checked and it seems Tableau also has the totals on disaggregated data as the default, but provides an option to calculate a so-called "two-pass total". I can see if we want to implement this, the ColumnConfigControl would come in handy.
@@ -17,7 +17,7 @@ | |||
* under the License. | |||
*/ | |||
import { t, QueryMode } from '@superset-ui/core'; | |||
import { DTTM_ALIAS } from '@superset-ui/core/src/query/buildQueryObject'; | |||
import { DTTM_ALIAS } from '@superset-ui/core/lib/query/buildQueryObject'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me fix this in a separate PR as this is preventing apache/superset#13758 to go through.
moving conversation to open. @villebro please educate me, what do you mean by aggregated column is comparable to the cells value? @kgabryje thank you for the great improvement, do you mind adding more details of how total works in different aggregation in the descriptions? we may need to add tooltips here and there, in separate PR. |
a2de804
to
5d0a79d
Compare
@junlincc The total aggregation is an aggregation based on all cell values. So a total of an average is not a sum of average values, but an average of all values. Same goes for other agg functions. |
840c438
to
023793e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
const rowCount = queriesData?.[1]?.data?.[0]?.rowcount as number; | ||
const rowCount = serverPagination | ||
? (queriesData?.[1]?.data?.[0]?.rowcount as number) | ||
: queriesData?.[0]?.rowcount; | ||
const totals = | ||
showTotals && | ||
queryMode === QueryMode.aggregate && | ||
queriesData?.[queriesData.length - 1]?.data[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make finding the correct query easier, I think we should add an optional id
field to QueryObject
that gets passed to the result. I can open a PR for this.
Yes for now. @mihir174 and I can take it(tooltip) from here. Thank you so much! |
: queriesData?.[0]?.rowcount; | ||
const totals = | ||
showTotals && queryMode === QueryMode.aggregate | ||
? queriesData?.[queriesData.length - 1]?.data[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we maybe do more explicit array expansion to avoid all these queriesData?.[xxx]?
const [baseQuery, countQuery, totalQuery] = queriesData || [];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
89e7469
to
a3aafc1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
||
export const dnd_timeseries_limit_metric: SharedControlConfig<'DndMetricSelect'> = { | ||
type: 'DndMetricSelect', | ||
label: t('Sort By'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
label: t('Sort By'), | |
label: t('Sort by'), |
Sentence case
) * feat(plugin-chart-table): implement totals row * Fix typo * Fix totals with percentage metrics * Code review fixes * Use dnd with percentage metrics and sortby controls * Make totals checkbox tooltip more descriptive * Remove console.log * Change totals tooltip * Fix typing error * Use array destructuring * Fix typo
Implement sending an additional query for total aggregations of metrics. Can be toggled with a checkbox in "Data" tab. Totals are displayed as a sticky row at the bottom of the table. Totals row is not affected by paging or sorting.
Additionaly, this PR contains a fix of an import in
superset-ui-core
.Screen.Recording.2021-03-30.at.10.05.27.mov
CC: @villebro @ktmud @junlincc