grafana/ui: New table component (#20991)

* first working example

* Support sorting, adding types while waiting for official ones

* using react-window for windowing

* styles via emotion

* sizing

* set an offset for the table

* change table export

* fixing table cell widths

* Explore: Use new table component in explore (#21031)

* Explore: Use new table component in explore

* enable oncellclick

* only let filterable columns be clickable, refactor renderrow

* remove explore table

* Keep using old merge tables logic

* prettier

* remove unused typings file

* fixing tests

* Fixed explore table issue

* NewTable: Updated styles

* Fixed unit test

* Updated TableModel

* Minor update to explore table height

* typing
This commit is contained in:
Peter Holmberg 2019-12-18 08:38:50 +01:00 committed by GitHub
parent 841cffbe9a
commit e9079c3faa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 909 additions and 222 deletions

View File

@ -46,7 +46,6 @@
"@types/react-grid-layout": "0.16.7",
"@types/react-redux": "7.1.2",
"@types/react-select": "2.0.15",
"@types/react-table": "6.8.5",
"@types/react-test-renderer": "16.9.0",
"@types/react-transition-group": "2.0.16",
"@types/react-virtualized": "9.18.12",
@ -246,7 +245,6 @@
"react-popper": "1.3.3",
"react-redux": "7.1.1",
"react-sizeme": "2.5.2",
"react-table": "6.9.2",
"react-transition-group": "2.6.1",
"react-use": "12.8.0",
"react-virtualized": "9.21.0",

View File

@ -50,6 +50,7 @@
"react-highlight-words": "0.11.0",
"react-popper": "1.3.3",
"react-storybook-addon-props-combinations": "1.1.0",
"react-table": "7.0.0-rc.4",
"react-transition-group": "2.6.1",
"react-virtualized": "9.21.0",
"slate": "0.47.8",

View File

@ -0,0 +1,696 @@
import React from 'react';
import { NewTable } from './NewTable';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import mdx from './NewTable.mdx';
import { DataFrame, toDataFrame } from '@grafana/data';
export default {
title: 'UI/Table/NewTable',
component: NewTable,
decorators: [withCenteredStory],
parameters: {
docs: {
page: mdx,
},
},
};
const mockDataFrame: DataFrame = toDataFrame({
refId: 'A',
fields: [
{
name: 'Time',
config: {},
values: [
1575951385249,
1575951415249,
1575951445249,
1575951475249,
1575951505249,
1575951535249,
1575951565249,
1575951595249,
1575951625249,
1575951655249,
1575951685249,
1575951715249,
1575951745249,
1575951775249,
1575951805249,
1575951835249,
1575951865249,
1575951895249,
1575951925249,
1575951955249,
1575951985249,
1575952015249,
1575952045249,
1575952075249,
1575952105249,
1575952135249,
1575952165249,
1575952195249,
1575952225249,
1575952255249,
1575952285249,
1575952315249,
1575952345249,
1575952375249,
1575952405249,
1575952435249,
1575952465249,
1575952495249,
1575952525249,
1575952555249,
1575952585249,
1575952615249,
1575952645249,
1575952675249,
1575952705249,
1575952735249,
1575952765249,
1575952795249,
1575952825249,
1575952855249,
1575952885249,
1575952915249,
1575952945249,
1575952975249,
1575953005249,
1575953035249,
1575953065249,
1575953095249,
1575953125249,
1575953155249,
1575953185249,
1575953215249,
1575953245249,
1575953275249,
1575953305249,
1575953335249,
1575953365249,
1575953395249,
1575953425249,
1575953455249,
1575953485249,
1575953515249,
1575953545249,
1575953575249,
1575953605249,
1575953635249,
1575953665249,
1575953695249,
1575953725249,
1575953755249,
1575953785249,
1575953815249,
1575953845249,
1575953875249,
1575953905249,
1575953935249,
1575953965249,
1575953995249,
1575954025249,
1575954055249,
1575954085249,
1575954115249,
1575954145249,
1575954175249,
1575954205249,
1575954235249,
1575954265249,
1575954295249,
1575954325249,
1575954355249,
1575954385249,
1575954415249,
1575954445249,
1575954475249,
1575954505249,
1575954535249,
1575954565249,
1575954595249,
1575954625249,
1575954655249,
1575954685249,
1575954715249,
1575954745249,
1575954775249,
1575954805249,
1575954835249,
1575954865249,
1575954895249,
1575954925249,
1575954955249,
1575954985249,
1575955015249,
1575955045249,
1575955075249,
1575955105249,
1575955135249,
],
type: 'time',
calcs: null,
},
{
name: 'Value',
config: {},
values: [
1.5000254936150939,
1.0764011931793371,
0.909466911538386,
1.3833044968776655,
1.6330889934233457,
1.6709668856700475,
1.2645776897559702,
1.4943939986749317,
1.2437720606210307,
0.7883544129607633,
0.5531910797833525,
1.0457642543811316,
1.382667661114637,
1.1568426549305126,
1.1402599321207862,
0.8483451908731177,
1.0317957901350685,
0.920556237728358,
0.5119097189376975,
0.3251335563131686,
0.7108123250650906,
0.4363759782763406,
0.8734924745632022,
0.7937634746603249,
0.68612096153115,
0.18988522007983766,
0.6576558170343919,
0.25301964245089764,
0.5277493691957931,
0.46587339439817715,
0.36227094078701305,
0.778938585446131,
1.2377378919033881,
0.9610752640855127,
1.1282013840938148,
0.9283745710857219,
0.674699599869639,
0.8974372977635153,
0.4075745811903667,
0.26659108532003495,
0.38371243779290887,
0.7832793846413076,
0.6259127747122668,
0.37112598577068756,
0.0613275616200267,
0.3576963952766255,
0.1665079834081022,
0.38990450556730366,
0.2214623170358675,
0.20133177397788687,
0.3346036375598696,
-0.0192909674691417,
-0.3396463212802728,
-0.4335565435802561,
-0.7225467771514991,
-0.9774803860859353,
-0.5596691380280212,
-0.8782204226162458,
-1.0379530805725203,
-0.6536190918926427,
-0.6041277845604902,
-0.5788054694749062,
-0.2328568877382495,
0.20668025552369196,
0.28117302534150723,
0.05146978829879428,
0.10096018560031056,
0.4377253335922943,
0.15035296580222518,
0.4462013793721102,
0.24812034480027956,
0.2889496029069344,
0.6625835159165457,
0.8233581741905972,
0.8643260393590533,
0.540435640675776,
0.22824187203884172,
0.10870831703051109,
0.554460912136038,
0.6954620954703324,
0.865285151446149,
0.4590424126447909,
0.9320188770520323,
1.2566497496880387,
1.092195469867563,
1.1523766803990934,
1.1257555973909357,
1.0506441818153138,
1.5101269591969075,
1.4541940510170406,
1.0403320961662672,
0.6435721407405451,
0.8163247288505439,
0.4178187803641794,
0.09659517321642153,
0.45565501812146547,
0.7800744768194877,
1.1266848495467974,
1.2708917952148475,
1.3699637357813657,
1.119789948351233,
1.117797940539129,
1.00138292407247,
-0.5357586740093938,
0.9678918429118637,
1.1063917602204467,
0.6309338947655425,
0.5056026636112202,
0.18233746755391178,
0.48969623185962496,
0.0383434172496539,
0.4599318303590843,
0.5291405395663644,
0.05265539291739823,
0.08586187516518445,
0.3449932185560655,
-0.100853852934114,
0.3440262305668972,
0.5284856664940929,
0.6022770024221724,
0.9832806104256895,
0.8094695843624107,
1.242977252512111,
0.9569361971250467,
0.7403221157223554,
0.5468202659043195,
],
type: 'number',
calcs: null,
},
{
name: 'Min',
config: {},
values: [
0.5129631778211692,
0.7232700407273998,
0.16692418924289476,
1.024024764754911,
0.42165950459107204,
-0.3468147988918235,
-0.3167406429414228,
0.6210986915363178,
0.561637573529631,
-1.5683226420805032,
-0.6132946301719228,
-1.2930551884568044,
0.5969846324236683,
-0.6778519171751289,
1.0732733813642974,
-0.0485285093265273,
-1.4112601180935862,
0.4756584208435448,
-1.012064501202603,
-0.5689593016201331,
-1.3031450412933574,
-1.7399086775804553,
-0.4439715146977987,
-0.9065877953376303,
0.6573706451163406,
-0.444001654734782,
-0.8360433807048399,
-1.52827092245891,
-0.7065563339902561,
-0.865577257288909,
0.3329923695343399,
1.5753706037331285,
-1.0567027770721693,
-0.2765803248460174,
-0.5510056746352008,
-0.1362911756340101,
0.34259473615447383,
-0.862039744634204,
-0.2241446290872408,
0.0566330081088942,
-1.786862521362104,
-1.2217196242864676,
-0.5525489161472246,
-0.446196522037665,
-1.08755269321232,
-0.991794378457034,
-1.3196225564796082,
0.3304532639159836,
-0.6722727699185566,
-0.280099872766854,
-0.6484766249626335,
-2.2303404241475,
-1.28856885905242,
-2.07345573739152,
-2.581726910011512,
-2.692554424690295,
-1.899011280327467,
-2.276770149534113,
-2.906877502022452,
-2.07669049858435,
-1.862840289223948,
-1.086158472280301,
-1.57239417318637,
-1.39281505610553,
-1.750680246703866,
-1.528573710879417,
-1.281799433260939,
-0.2599408767486406,
-1.586049427178506,
-1.6670976842241279,
-0.684309922512285,
-0.8545089289126312,
0.5201061712499606,
-1.6652198364354112,
-0.9509609321749597,
0.4192582642664334,
-1.196222288493242,
-0.494764687123139,
1.3552818811266552,
-1.0234990807153594,
0.46793077094438473,
-1.7338037743327357,
-1.5493688554047804,
0.255316081309259,
0.7604396194452134,
-0.9407405613725452,
-0.943211396012134,
0.8183880116376108,
1.2329439293285518,
-0.7894319837927357,
-0.1457874726259571,
-1.5441109867114653,
-1.6234083951372025,
0.06122646291653344,
-0.672772416353517,
-1.855602297194385,
-0.8581361043718111,
-1.2492249157608226,
0.7605483882403175,
0.6895236955186106,
0.5298057924915338,
0.6476408123812207,
0.8803790600375676,
-1.6612590895779469,
0.39253794740960923,
0.09690185316807232,
0.6185716145887097,
-1.009104994225043,
-0.231484679530671,
-1.471267622823986,
-0.0833848908404255,
-0.8881970225399474,
-1.8962127836845482,
0.009913417830268,
-0.134180309099165,
-0.1502931584064166,
-0.91729270269899,
-1.3759721816599786,
0.16530985337628945,
-1.134691317842945,
-0.2562806810548419,
-0.1965185306373604,
0.6468148901350499,
-0.5662636534063714,
-1.32379253681237,
-1.702413625201898,
],
type: 'number',
calcs: null,
},
{
name: 'Max',
config: {},
values: [
2.8039573837115217,
2.6926679712037664,
1.372618545664734,
0.2330280293445357,
3.257945982361415,
3.760438343013144,
3.059552434000727,
1.77318515129639,
0.8064077504286082,
1.944602327610411,
1.4599691331772737,
1.7681328471222444,
0.577781199251837,
3.3997499156309985,
2.9528330295399954,
2.430318614428602,
2.3611702277354194,
2.630203980321264,
2.6988109280000505,
2.3242683979742083,
2.917442297706927,
2.2740903408929314,
3.2823972469296656,
1.5795427555845518,
0.7227749081990877,
1.07447590570726,
1.6951780861683337,
0.368757195632994,
2.3404684729518603,
2.130240287562029,
1.3899466672909542,
0.8059006834071859,
1.3584656113532596,
3.133274081047666,
3.002448585766932,
1.787231129006741,
1.4640413244581476,
3.276124198626958,
2.087269772786033,
2.30377367911983,
1.4608814767279665,
1.8833281423383506,
1.3067082695296433,
1.253273064443,
1.969057793049115,
1.3339218780045436,
2.062355825798883,
1.1698837256390395,
2.6118487933225496,
2.03861316329396,
0.8473854714851563,
2.2316983803163,
0.540932137109768,
-0.04956492174973,
-0.450813944553707,
0.375695756046867,
0.785759004771657,
0.0954526298052838,
0.59597948146765,
0.6572571584655279,
0.605149574038378,
1.870031884630303,
1.37713543903307,
0.914062948450487,
2.649030343824894,
0.579644863230952,
1.944602570596079,
0.7252492696045203,
1.257270614288119,
1.9385686341149715,
1.797919709901303,
2.5158662971442625,
1.4514368283700079,
0.866789052718946,
1.3049445896679253,
2.198601835481704,
1.8277158002442289,
1.24254816068483,
0.990008891408783,
3.181979463237438,
1.206035697890917,
1.1511435801749499,
2.495205603723621,
0.574817574306939,
0.383599298845083,
3.6200850261933706,
3.5922041855779407,
1.5478860273718356,
2.8506619018807706,
3.4281960853527425,
3.289980262325269,
1.103020879390874,
1.942842426855671,
0.857081451423311,
2.112393659806772,
1.601319369146539,
2.230806028974528,
1.5729019554294,
2.4371495138987163,
2.2635324007929634,
0.5089790871644464,
1.3764330789309522,
0.535805463302483,
2.6704309768995014,
1.605171233903551,
1.3849464601885664,
1.4699084469214156,
1.2065460833969008,
2.665190338566064,
2.65455092203953,
1.33562376437657,
1.6303855496985555,
2.2931655808635956,
0.53289540133395,
2.42344985717817,
1.034880799185153,
2.02710661062796,
0.589535726373407,
1.1198114199523561,
2.3500012113011195,
2.911904933444892,
3.271532648889891,
2.181016258408353,
3.1900649798681133,
0.154494474449462,
2.7911175973201736,
],
type: 'number',
calcs: null,
},
{
name: 'Info',
config: {},
values: [
'up',
'down fast',
'down',
'up fast',
'up',
'up',
'down fast',
'up',
'down',
'down fast',
'down',
'up fast',
'up',
'down',
'down',
'down',
'up',
'down',
'down fast',
'down',
'up',
'down',
'up fast',
'down',
'down',
'down fast',
'up fast',
'down fast',
'up',
'down',
'down',
'up fast',
'up fast',
'down',
'up',
'down',
'down',
'up',
'down fast',
'down',
'up',
'up',
'down',
'down',
'down fast',
'up fast',
'down',
'up',
'down',
'down',
'up',
'down',
'down',
'down',
'down',
'down',
'up fast',
'down',
'down',
'up',
'up',
'up',
'up',
'up fast',
'up',
'down',
'up',
'up',
'down',
'up',
'down',
'up',
'up',
'up',
'up',
'down',
'down',
'down',
'up fast',
'up',
'up',
'down fast',
'up fast',
'up',
'down',
'up',
'down',
'down',
'up fast',
'down',
'down fast',
'down',
'up',
'down',
'down',
'up',
'up',
'up',
'up',
'up',
'down',
'down',
'down',
'down fast',
'up fast',
'up',
'down fast',
'down',
'down',
'up',
'down fast',
'up fast',
'up',
'down fast',
'up',
'up',
'down fast',
'up fast',
'up',
'up',
'up',
'down',
'up fast',
'down',
'down',
'down',
],
type: 'string',
calcs: null,
},
],
});
export const simple = () => {
return <NewTable data={mockDataFrame} height={500} width={500} />;
};

View File

@ -0,0 +1,140 @@
import React, { useMemo } from 'react';
import { DataFrame, GrafanaTheme } from '@grafana/data';
// @ts-ignore
import { useBlockLayout, useSortBy, useTable } from 'react-table';
import { FixedSizeList } from 'react-window';
import { css } from 'emotion';
import { stylesFactory, useTheme, selectThemeVariant as stv } from '../../themes';
export interface Props {
data: DataFrame;
width: number;
height: number;
onCellClick?: (key: string, value: string) => void;
}
const getTableData = (data: DataFrame) => {
const tableData = [];
for (let i = 0; i < data.length; i++) {
const row: { [key: string]: string | number } = {};
for (let j = 0; j < data.fields.length; j++) {
const prop = data.fields[j].name;
row[prop] = data.fields[j].values.get(i);
}
tableData.push(row);
}
return tableData;
};
const getColumns = (data: DataFrame) => {
return data.fields.map(field => {
return {
Header: field.name,
accessor: field.name,
field: field,
};
});
};
const getTableStyles = stylesFactory((theme: GrafanaTheme, columnWidth: number) => {
const colors = theme.colors;
const headerBg = stv({ light: colors.gray6, dark: colors.dark7 }, theme.type);
const padding = 5;
return {
cellHeight: padding * 2 + 14 * 1.5 + 2,
tableHeader: css`
padding: ${padding}px 10px;
background: ${headerBg};
cursor: pointer;
white-space: nowrap;
color: ${colors.blue};
border-bottom: 2px solid ${colors.bodyBg};
`,
tableCell: css`
display: 'table-cell';
padding: ${padding}px 10px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: ${columnWidth}px;
border-right: 2px solid ${colors.bodyBg};
border-bottom: 2px solid ${colors.bodyBg};
`,
};
});
const renderCell = (cell: any, columnWidth: number, cellStyles: string, onCellClick?: any) => {
const filterable = cell.column.field.config.filterable;
const style = {
cursor: `${filterable && onCellClick ? 'pointer' : 'default'}`,
};
return (
<div
className={cellStyles}
{...cell.getCellProps()}
onClick={filterable ? () => onCellClick(cell.column.Header, cell.value) : undefined}
style={style}
>
{cell.render('Cell')}
</div>
);
};
export const NewTable = ({ data, height, onCellClick, width }: Props) => {
const theme = useTheme();
const columnWidth = Math.floor(width / data.fields.length);
const tableStyles = getTableStyles(theme, columnWidth);
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
{
columns: useMemo(() => getColumns(data), [data]),
data: useMemo(() => getTableData(data), [data]),
},
useSortBy,
useBlockLayout
);
const RenderRow = React.useCallback(
({ index, style }) => {
const row = rows[index];
prepareRow(row);
return (
<div {...row.getRowProps({ style })}>
{row.cells.map((cell: any) => renderCell(cell, columnWidth, tableStyles.tableCell, onCellClick))}
</div>
);
},
[prepareRow, rows]
);
return (
<div {...getTableProps()}>
<div>
{headerGroups.map((headerGroup: any) => (
<div {...headerGroup.getHeaderGroupProps()} style={{ display: 'table-row' }}>
{headerGroup.headers.map((column: any) => (
<div
className={tableStyles.tableHeader}
{...column.getHeaderProps(column.getSortByToggleProps())}
style={{ display: 'table-cell', width: `${columnWidth}px` }}
>
{column.render('Header')}
<span>{column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}</span>
</div>
))}
</div>
))}
</div>
<FixedSizeList height={height} itemCount={rows.length} itemSize={tableStyles.cellHeight} width={width}>
{RenderRow}
</FixedSizeList>
</div>
);
};

View File

@ -1,81 +0,0 @@
// .ReactVirtualized__Table {
// }
// .ReactVirtualized__Table__Grid {
// }
.ReactVirtualized__Table__headerRow {
font-weight: 700;
display: flex;
flex-direction: row;
align-items: left;
}
.ReactVirtualized__Table__row {
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 2px solid $body-bg;
}
.ReactVirtualized__Table__headerTruncatedText {
display: inline-block;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.ReactVirtualized__Table__headerColumn,
.ReactVirtualized__Table__rowColumn {
margin-right: 10px;
min-width: 0px;
}
.ReactVirtualized__Table__headerColumn:first-of-type,
.ReactVirtualized__Table__rowColumn:first-of-type {
margin-left: 10px;
}
.ReactVirtualized__Table__sortableHeaderColumn {
cursor: pointer;
}
.ReactVirtualized__Table__sortableHeaderIconContainer {
align-items: center;
}
.ReactVirtualized__Table__sortableHeaderIcon {
flex: 0 0 24px;
height: 1em;
width: 1em;
fill: currentColor;
}
.gf-table-header {
padding: 3px 10px;
background: $list-item-bg;
border-top: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
cursor: pointer;
white-space: nowrap;
color: $blue;
}
.gf-table-cell {
padding: 3px 10px;
background: $page-gradient;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
border-right: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
}
.gf-table-fixed-column {
border-right: 1px solid #ccc;
}

View File

@ -9,7 +9,6 @@
@import 'PanelOptionsGroup/PanelOptionsGroup';
@import 'RefreshPicker/RefreshPicker';
@import 'Select/Select';
@import 'Table/Table';
@import 'Table/TableInputCSV';
@import 'ThresholdsEditor/ThresholdsEditor';
@import 'TimePicker/TimeOfDayPicker';

View File

@ -46,7 +46,7 @@ export { QueryField } from './QueryField/QueryField';
// Renderless
export { SetInterval } from './SetInterval/SetInterval';
export { Table } from './Table/Table';
export { NewTable as Table } from './Table/NewTable';
export { TableInputCSV } from './Table/TableInputCSV';
// Visualizations

View File

@ -315,7 +315,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
/>
)}
{mode === ExploreMode.Metrics && (
<TableContainer exploreId={exploreId} onClickCell={this.onClickFilterLabel} />
<TableContainer width={width} exploreId={exploreId} onClickCell={this.onClickFilterLabel} />
)}
{mode === ExploreMode.Logs && (
<LogsContainer

View File

@ -1,68 +0,0 @@
import _ from 'lodash';
import React, { PureComponent } from 'react';
import ReactTable, { RowInfo } from 'react-table';
import TableModel from 'app/core/table_model';
const EMPTY_TABLE = new TableModel();
// Identify columns that contain values
const VALUE_REGEX = /^[Vv]alue #\d+/;
interface TableProps {
data: TableModel;
loading: boolean;
onClickCell?: (columnKey: string, rowValue: string) => void;
}
function prepareRows(rows: any[], columnNames: string[]) {
return rows.map(cells => _.zipObject(columnNames, cells));
}
export default class Table extends PureComponent<TableProps> {
getCellProps = (state: any, rowInfo: RowInfo, column: any) => {
return {
onClick: (e: React.SyntheticEvent) => {
// Only handle click on link, not the cell
if (e.target) {
const link = e.target as HTMLElement;
if (link.className === 'link') {
const columnKey = column.Header().props.title;
const rowValue = rowInfo.row[columnKey];
this.props.onClickCell?.(columnKey, rowValue);
}
}
},
};
};
render() {
const { data, loading } = this.props;
const tableModel = data || EMPTY_TABLE;
const columnNames = tableModel.columns.map(({ text }) => text);
const columns = tableModel.columns.map(({ filterable, text }) => ({
Header: () => <span title={text}>{text}</span>,
accessor: text,
className: VALUE_REGEX.test(text) ? 'text-right' : '',
show: text !== 'Time',
Cell: (row: any) => (
<span className={filterable ? 'link' : ''} title={text + ': ' + row.value}>
{typeof row.value === 'string' ? row.value : JSON.stringify(row.value)}
</span>
),
}));
const noDataText = data ? 'The queries returned no data for a table.' : '';
return (
<ReactTable
columns={columns}
data={tableModel.rows}
getTdProps={this.getCellProps}
loading={loading}
minRows={0}
noDataText={noDataText}
resolveData={data => prepareRows(data, columnNames)}
showPagination={Boolean(data)}
/>
);
}
}

View File

@ -1,21 +1,19 @@
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import { Collapse } from '@grafana/ui';
import { DataFrame } from '@grafana/data';
import { Table, Collapse } from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types';
import { toggleTable } from './state/actions';
import Table from './Table';
import TableModel from 'app/core/table_model';
interface TableContainerProps {
exploreId: ExploreId;
loading: boolean;
width: number;
onClickCell: (key: string, value: string) => void;
showingTable: boolean;
tableResult?: TableModel;
tableResult?: DataFrame;
toggleTable: typeof toggleTable;
}
@ -24,12 +22,27 @@ export class TableContainer extends PureComponent<TableContainerProps> {
this.props.toggleTable(this.props.exploreId, this.props.showingTable);
};
getTableHeight() {
const { tableResult } = this.props;
if (!tableResult || tableResult.length === 0) {
return 200;
}
// tries to estimate table height
return Math.max(Math.min(600, tableResult.length * 35) + 35);
}
render() {
const { loading, onClickCell, showingTable, tableResult } = this.props;
const { loading, onClickCell, showingTable, tableResult, width } = this.props;
const height = this.getTableHeight();
const paddingWidth = 16;
const tableWidth = width - paddingWidth;
return (
<Collapse label="Table" loading={loading} collapsible isOpen={showingTable} onToggle={this.onClickTableButton}>
{tableResult && <Table data={tableResult} loading={loading} onClickCell={onClickCell} />}
{tableResult && <Table data={tableResult} width={tableWidth} height={height} onCellClick={onClickCell} />}
</Collapse>
);
}
@ -40,7 +53,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
// @ts-ignore
const item: ExploreItemState = explore[exploreId];
const { loading: loadingInState, showingTable, tableResult } = item;
const loading = tableResult && tableResult.rows.length > 0 ? false : loadingInState;
const loading = tableResult && tableResult.length > 0 ? false : loadingInState;
return { loading, showingTable, tableResult };
}

View File

@ -24,8 +24,7 @@ import { Reducer } from 'redux';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { updateLocation } from 'app/core/actions/location';
import { serializeStateToUrlParam } from 'app/core/utils/explore';
import TableModel from 'app/core/table_model';
import { DataSourceApi, DataQuery, LogsDedupStrategy, dateTime, LoadingState } from '@grafana/data';
import { DataSourceApi, DataQuery, LogsDedupStrategy, dateTime, LoadingState, toDataFrame } from '@grafana/data';
describe('Explore item reducer', () => {
describe('scanning', () => {
@ -174,12 +173,23 @@ describe('Explore item reducer', () => {
describe('when toggleTableAction is dispatched', () => {
it('then it should set correct state', () => {
const table = toDataFrame({
name: 'logs',
fields: [
{
name: 'time',
type: 'number',
values: [1, 2],
},
],
});
reducerTester()
.givenReducer(itemReducer, { tableResult: {} })
.givenReducer(itemReducer, { tableResult: table })
.whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual({ showingTable: true, tableResult: {} })
.thenStateShouldEqual({ showingTable: true, tableResult: table })
.whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual({ showingTable: false, tableResult: new TableModel() });
.thenStateShouldEqual({ showingTable: false, tableResult: null });
});
});
});

View File

@ -62,7 +62,6 @@ import {
import { reducerFactory, ActionOf } from 'app/core/redux';
import { updateLocation } from 'app/core/actions/location';
import { LocationUpdate } from '@grafana/runtime';
import TableModel from 'app/core/table_model';
import { ResultProcessor } from '../utils/ResultProcessor';
export const DEFAULT_RANGE = {
@ -448,7 +447,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
return { ...state, showingTable };
}
return { ...state, showingTable, tableResult: new TableModel() };
return { ...state, showingTable, tableResult: null };
},
})
.addMapper({

View File

@ -131,21 +131,23 @@ describe('ResultProcessor', () => {
it('then it should return correct table result', () => {
const { resultProcessor } = testContext();
const theResult = resultProcessor.getTableResult();
const resultDataFrame = toDataFrame(
new TableModel({
columns: [
{ text: 'value', type: 'number' },
{ text: 'time', type: 'time' },
{ text: 'message', type: 'string' },
],
rows: [
[4, 100, 'this is a message'],
[5, 200, 'second message'],
[6, 300, 'third'],
],
type: 'table',
})
);
expect(theResult).toEqual({
columnMap: {},
columns: [
{ text: 'value', type: 'number', filterable: undefined },
{ text: 'time', type: 'time', filterable: undefined },
{ text: 'message', type: 'string', filterable: undefined },
],
rows: [
[4, 100, 'this is a message'],
[5, 200, 'second message'],
[6, 300, 'third'],
],
type: 'table',
});
expect(theResult).toEqual(resultDataFrame);
});
});

View File

@ -1,5 +1,4 @@
import { LogsModel, GraphSeriesXY, DataFrame, FieldType, TimeZone } from '@grafana/data';
import { LogsModel, GraphSeriesXY, DataFrame, FieldType, TimeZone, toDataFrame } from '@grafana/data';
import { ExploreItemState, ExploreMode } from 'app/types/explore';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
import { sortLogsResult, refreshIntervalToSortOrder } from 'app/core/utils/explore';
@ -34,7 +33,7 @@ export class ResultProcessor {
);
}
getTableResult(): TableModel | null {
getTableResult(): DataFrame | null {
if (this.state.mode !== ExploreMode.Metrics) {
return null;
}
@ -75,7 +74,8 @@ export class ResultProcessor {
});
});
return mergeTablesIntoModel(new TableModel(), ...tables);
const mergedTable = mergeTablesIntoModel(new TableModel(), ...tables);
return toDataFrame(mergedTable);
}
getLogsResult(): LogsModel | null {

View File

@ -2,29 +2,27 @@
import React, { Component } from 'react';
// Types
import { ThemeContext } from '@grafana/ui';
import { Table } from '@grafana/ui';
import { PanelProps } from '@grafana/data';
import { Options } from './types';
import Table from '@grafana/ui/src/components/Table/Table';
interface Props extends PanelProps<Options> {}
// So that the table does not go all the way to the edge of the panel chrome
const paddingBottom = 35;
export class TablePanel extends Component<Props> {
constructor(props: Props) {
super(props);
}
render() {
const { data, options } = this.props;
const { data, height, width } = this.props;
if (data.series.length < 1) {
return <div>No Table Data...</div>;
}
return (
<ThemeContext.Consumer>
{theme => <Table {...this.props} {...options} theme={theme} data={data.series[0]} />}
</ThemeContext.Consumer>
);
return <Table height={height - paddingBottom} width={width} data={data.series[0]} />;
}
}

View File

@ -13,10 +13,10 @@ import {
LogsDedupStrategy,
AbsoluteTimeRange,
GraphSeriesXY,
DataFrame,
} from '@grafana/data';
import { Emitter } from 'app/core/core';
import TableModel from 'app/core/table_model';
export enum ExploreMode {
Metrics = 'Metrics',
@ -130,7 +130,7 @@ export interface ExploreItemState {
/**
* Table model that combines all query table results into a single table.
*/
tableResult?: TableModel;
tableResult?: DataFrame;
/**
* React keys for rendering of QueryRows

View File

@ -1,6 +1,3 @@
// DEPENDENCIES
@import '../../node_modules/react-table/react-table.css';
// MIXINS
@import 'mixins/mixins';
@import 'mixins/animations';

View File

@ -4286,13 +4286,6 @@
dependencies:
"@types/react" "*"
"@types/react-table@6.8.5":
version "6.8.5"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-6.8.5.tgz#deb2bf2fcedcfb81e9020edbb7df0d8459ca348b"
integrity sha512-ueCsAadG1IwuuAZM+MWf2SoxbccSWweyQa9YG6xGN5cOVK3SayPOJW4MsUHGpY0V/Q+iZWgohpasliiao29O6g==
dependencies:
"@types/react" "*"
"@types/react-test-renderer@*":
version "16.9.1"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4"
@ -15446,7 +15439,6 @@ npm@6.13.4:
cmd-shim "^3.0.3"
columnify "~1.5.4"
config-chain "^1.1.12"
debuglog "*"
detect-indent "~5.0.0"
detect-newline "^2.1.0"
dezalgo "~1.0.3"
@ -15461,7 +15453,6 @@ npm@6.13.4:
has-unicode "~2.0.1"
hosted-git-info "^2.8.5"
iferr "^1.0.2"
imurmurhash "*"
infer-owner "^1.0.4"
inflight "~1.0.6"
inherits "^2.0.4"
@ -15480,14 +15471,8 @@ npm@6.13.4:
libnpx "^10.2.0"
lock-verify "^2.1.0"
lockfile "^1.0.4"
lodash._baseindexof "*"
lodash._baseuniq "~4.6.0"
lodash._bindcallback "*"
lodash._cacheindexof "*"
lodash._createcache "*"
lodash._getnative "*"
lodash.clonedeep "~4.5.0"
lodash.restparam "*"
lodash.union "~4.6.0"
lodash.uniq "~4.5.0"
lodash.without "~4.4.0"
@ -18200,12 +18185,10 @@ react-syntax-highlighter@^8.0.1:
prismjs "^1.8.4"
refractor "^2.4.1"
react-table@6.9.2:
version "6.9.2"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-6.9.2.tgz#6a59adfeb8d5deced288241ed1c7847035b5ec5f"
integrity sha512-sTbNHU8Um0xRtmCd1js873HXnXaMWeBwZoiljuj0l1d44eaqjKyYPK/3HCBbJg1yeE2O5pQJ3Km0tlm9niNL9w==
dependencies:
classnames "^2.2.5"
react-table@7.0.0-rc.4:
version "7.0.0-rc.4"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-rc.4.tgz#88bc61747821f3c3bbbfc7e1a4a088cbe94ed9ee"
integrity sha512-NOYmNmAIvQ9sSZd5xMNSthqiZ/o5h8h28MhFQFSxCu5u3v9J8PNh7x9wYMnk737MTjoKCZWIZT/dMFCPItXzEg==
react-test-renderer@16.9.0:
version "16.9.0"