Web console: Misc table fixes (#12489)

* Misc table fixes

* extract default className

* table spacing updates

* fix e2e action selector

* try more times

* make the web console exist again
This commit is contained in:
Vadim Ogievetsky 2022-05-03 12:08:08 -07:00 committed by GitHub
parent de14f511d6
commit fb08bac01a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 3086 additions and 2428 deletions

View File

@ -48,6 +48,13 @@
<artifactId>druid-gcp-common</artifactId>
<version>${project.parent.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<!-- This is needed to bundle the web console -->
<groupId>org.apache.druid</groupId>
<artifactId>druid-console</artifactId>
<version>${project.parent.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.druid</groupId>

View File

@ -128,7 +128,7 @@ As part of this directory:
- `assets/` - The images (and other assets) used within the console
- `e2e-tests/` - End-to-end tests for the console
- `lib/` - A place where some overrides to the react-table stylus files live, this is outside of the normal SCSS build system.
- `lib/` - A place where keywords and generated docs live.
- `public/` - The compiled destination for the files powering this console
- `script/` - Some helper bash scripts for running this console
- `src/` - This directory (together with `lib`) constitutes all the source code for this console

View File

@ -124,7 +124,7 @@ export class DatasourcesOverview {
throw new Error(`Could not find datasource: ${datasourceName}`);
}
const editActions = await this.page.$$('span[icon=wrench]');
const editActions = await this.page.$$('.action-cell span[icon=more]');
await editActions[index].click();
await this.waitForPopupMenu();
}

View File

@ -1,340 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This is a heavily modified version of the style file originally distributed here:
// https://github.com/react-tools/react-table/blob/master/src/index.styl
// Released originally under the MIT License https://github.com/react-tools/react-table/blob/master/LICENSE
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
$expandSize = 7px
$border-color = alpha(black, 0.15)
input-select-style()
border: 1px solid rgba(0,0,0,0.1)
background: white
padding: 5px 7px
font-size: inherit
border-radius: 3px
font-weight: normal
outline:none
.ReactTable
position:relative
display: flex
flex-direction: column
*
box-sizing: border-box
.rt-table
flex: auto 1
display: flex
flex-direction: column
align-items: stretch
width: 100%
border-collapse: collapse
overflow: auto
.rt-thead
flex: 1 0 auto
display: flex
flex-direction: column
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&.-headerGroups
background: alpha(black, .03)
border-bottom: 1px solid $border-color
&.-filters
border-bottom: 1px solid $border-color
//input
//select
// input-select-style();
.rt-th
border-right: 1px solid $border-color
&.-header
box-shadow: 0 1px 0 0 $border-color
.rt-tr
text-align: left
.rt-th
.rt-td
padding: 5px 5px
line-height: normal
position: relative
border-right: 1px solid $border-color
transition box-shadow .3s $easeOutBack
box-shadow:inset 0 0 0 0 transparent
&.-sort-asc
box-shadow:inset 0 3px 0 0 #8489A1
&.-sort-desc
box-shadow:inset 0 -3px 0 0 #8489A1
&.-cursor-pointer
cursor: pointer
&:last-child
border-right: 0
.rt-resizable-header
overflow: visible
&:last-child
overflow: hidden
.rt-resizable-header-content
overflow: hidden
text-overflow: ellipsis
.rt-header-pivot
border-right-color: $border-color
.rt-header-pivot:after, .rt-header-pivot:before
left: 100%
top: 50%
border: solid transparent
content: " "
height: 0
width: 0
position: absolute
pointer-events: none
.rt-header-pivot:after
border-color: rgba(255, 255, 255, 0)
border-left-color: #FFF
border-width: 8px
margin-top: -8px
.rt-header-pivot:before
border-color: rgba(102, 102, 102, 0)
border-left-color: #f7f7f7
border-width: 10px
margin-top: -10px
.rt-tbody
flex: 99999 1 auto
display: flex
flex-direction: column
overflow: auto
// z-index:0
.rt-tr-group
border-bottom: 1px solid $border-color
&:last-child
border-bottom: 0
.rt-td
border-right:1px solid $border-color
&:last-child
border-right:0
.rt-expandable
cursor: pointer
text-overflow: clip
.rt-tr-group
flex: 1 0 auto
display: flex
flex-direction: column
align-items: stretch
.rt-tr
flex: 1 0 auto
display: inline-flex
.rt-th
.rt-td
flex: 1 0 0px
white-space: nowrap
text-overflow: ellipsis
padding: 7px 5px
overflow: hidden
transition: .3s ease
transition-property: width, min-width, padding, opacity
&.-hidden
width: 0 !important
min-width: 0 !important
padding: 0 !important
border:0 !important
opacity: 0 !important
.rt-expander
display: inline-block
position:relative
margin: 0
color: transparent
margin: 0 10px
&:after
content: ''
position: absolute
width: 0
height: 0
top:50%
left:50%
transform: translate(-50%, -50%) rotate(-90deg)
border-left: ($expandSize * .72) solid transparent
border-right: ($expandSize * .72) solid transparent
border-top: $expandSize solid alpha(white, .8)
transition: all .3s $easeOutBack
cursor: pointer
&.-open:after
transform: translate(-50%, -50%) rotate(0deg)
.rt-resizer
display: inline-block
position: absolute
width: 36px
top: 0
bottom: 0
right: -18px
cursor: col-resize
z-index: 10
.rt-tfoot
flex: 1 0 auto
display: flex
flex-direction: column
box-shadow: 0 0px 15px 0px alpha(black, 0)
.rt-td
border-right:1px solid $border-color
&:last-child
border-right:0
&.-striped
.rt-tr.-odd
background: alpha(white, .025)
&.-highlight
.rt-tbody
.rt-tr:not(.-padRow):hover
background: alpha(black, .05)
.-pagination
z-index: 1
display:flex
justify-content: space-between
align-items: stretch
flex-wrap: wrap
padding: 3px
border-top: 1px solid $border-color
input
select
input-select-style()
.-btn
appearance:none
display:block
width:100%
height:100%
border: 0
border-radius: 3px
padding: 6px
font-size: 1em
color: alpha(black, .6)
background: alpha(black, .1)
transition: all .1s ease
cursor: pointer
outline:none
&[disabled]
opacity: .5
cursor: default
&:not([disabled]):hover
background: alpha(black, .3)
color: white
.-previous
.-next
flex: 1
text-align: center
.-center
flex: 1.5
text-align:center
margin-bottom:0
display: flex
flex-direction: row
flex-wrap: wrap
align-items: center
justify-content: space-around
.-pageInfo
display: inline-block
margin: 3px 10px
white-space: nowrap
.-pageJump
display:inline-block
input
width: 70px
text-align:center
.-pageSizeOptions
margin: 3px 10px
.rt-noData
display:block
position:absolute
left:50%
top:40%
transform: translate(-50%, -50%)
background: alpha(black, .4)
transition: all .3s ease
z-index: 1
padding: 20px
color: alpha(white, .5)
border-radius: 5px
.-loading
display:block
position:absolute
left:0
right:0
top:0
bottom:0
background: alpha(#4c4c4c, .8)
transition: all .3s ease
z-index: -1
opacity: 0
pointer-events: none
> div
position:absolute
display: block
text-align:center
width:100%
top:50%
left: 0
font-size: 15px
color: alpha(black, .6)
transform: translateY(-52%)
transition: all .3s $easeOutQuad
&.-active
opacity: 1
z-index: 2
pointer-events: all
> div
transform: translateY(50%)
.rt-resizing
.rt-th
.rt-td
transition: none!important
cursor: col-resize
user-select none

View File

@ -7762,26 +7762,6 @@
"which": "^1.2.9"
}
},
"css": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
"integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"source-map": "^0.6.1",
"source-map-resolve": "^0.5.2",
"urix": "^0.1.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"css-blank-pseudo": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz",
@ -7980,15 +7960,6 @@
}
}
},
"css-parse": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
"integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=",
"dev": true,
"requires": {
"css": "^2.0.0"
}
},
"css-prefers-color-scheme": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz",
@ -19734,12 +19705,6 @@
"neo-async": "^2.6.2"
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@ -21512,36 +21477,6 @@
"postcss-value-parser": "^4.1.0"
}
},
"stylus": {
"version": "0.54.7",
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.7.tgz",
"integrity": "sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug==",
"dev": true,
"requires": {
"css-parse": "~2.0.0",
"debug": "~3.1.0",
"glob": "^7.1.3",
"mkdirp": "~0.5.x",
"safer-buffer": "^2.1.2",
"sax": "~1.2.4",
"semver": "^6.0.0",
"source-map": "^0.7.3"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"sugarss": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz",

View File

@ -163,7 +163,6 @@
"snarkdown": "^2.0.0",
"style-loader": "^2.0.0",
"stylelint": "^13.12.0",
"stylus": "^0.54.7",
"ts-jest": "^27.1.3",
"ts-loader": "^8.1.0",
"ts-node": "^8.4.1",

View File

@ -21,9 +21,6 @@ set -e
echo "Adding SQL docs..."
./script/create-sql-docs.js
echo "Transpiling ReactTable CSS..."
./node_modules/.bin/stylus lib/react-table.styl -o lib/react-table.css
# add BUNDLE_ANALYZER_PLUGIN='TRUE' here to enable webpack-bundle-analyzer as a plugin
echo "Webpacking everything..."
NODE_ENV=production ./node_modules/.bin/webpack -c webpack.config.js --mode=production

View File

@ -110,7 +110,7 @@ function start() {
echo "$pid" > "$DRUID_PID_FILE"
_log "Druid started with pid ${pid}"
_wait_for_200_response "http://localhost:8888/unified-console.html" 3 20
_wait_for_200_response "http://localhost:8888/unified-console.html" 3 30
}
function stop() {

View File

@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, Classes, HTMLSelect } from '@blueprintjs/core';
import React from 'react';
import './react-table-custom-pagination.scss';
interface ReactTableCustomPaginationProps {
pages: number;
page: number;
showPageSizeOptions: boolean;
pageSizeOptions: number[];
pageSize: number;
showPageJump: boolean;
canPrevious: boolean;
canNext: boolean;
onPageSizeChange: any;
previousText: string;
nextText: string;
onPageChange: any;
ofText: string;
pageText: string;
rowsText: string;
style: Record<string, any>;
}
interface ReactTableCustomPaginationState {
tempPage?: string;
}
export class ReactTableCustomPagination extends React.PureComponent<
ReactTableCustomPaginationProps,
ReactTableCustomPaginationState
> {
constructor(props: ReactTableCustomPaginationProps) {
super(props);
this.state = {};
}
private readonly changePage = (page: number) => {
page = Math.min(Math.max(page, 0), this.props.pages - 1);
if (this.props.page !== page) {
this.props.onPageChange(page);
}
};
private readonly applyTempPage = (e: any) => {
if (e) {
e.preventDefault();
}
const { tempPage } = this.state;
if (!tempPage) return;
this.setState({ tempPage: undefined });
this.changePage(parseInt(tempPage, 10) - 1);
};
render(): JSX.Element {
const {
pages,
page,
showPageSizeOptions,
pageSizeOptions,
pageSize,
showPageJump,
canPrevious,
canNext,
onPageSizeChange,
style,
previousText,
nextText,
ofText,
pageText,
rowsText,
} = this.props;
const { tempPage } = this.state;
return (
<div className="-pagination" style={style}>
<div className="-previous">
<Button
fill
onClick={() => {
if (!canPrevious) return;
this.changePage(page - 1);
}}
disabled={!canPrevious}
>
{previousText}
</Button>
</div>
<div className="-center">
<span className="-pageInfo">
{pageText}{' '}
{showPageJump ? (
<div className="-pageJump">
<input
className={Classes.INPUT}
type="number"
min="1"
onChange={e => {
const val: string = e.target.value;
this.setState({
tempPage: val,
});
}}
value={tempPage || String(page + 1)}
onBlur={this.applyTempPage}
onKeyPress={e => {
if (e.key === 'Enter') {
this.applyTempPage(e);
}
}}
/>
</div>
) : (
<span className="-currentPage">{page + 1}</span>
)}{' '}
{ofText} <span className="-totalPages">{pages || 1}</span>
</span>
{showPageSizeOptions && (
<span className="select-wrap -pageSizeOptions">
<HTMLSelect onChange={e => onPageSizeChange(Number(e.target.value))} value={pageSize}>
{pageSizeOptions.map((option: any, i: number) => (
<option key={i} value={option}>
{`${option} ${rowsText}`}
</option>
))}
</HTMLSelect>
</span>
)}
</div>
<div className="-next">
<Button
fill
onClick={() => {
if (!canNext) return;
this.changePage(page + 1);
}}
disabled={!canNext}
>
{nextText}
</Button>
</div>
</div>
);
}
}

View File

@ -20,9 +20,13 @@ import React from 'react';
import { Filter, ReactTableDefaults } from 'react-table';
import { Loader } from '../components';
import { booleanCustomTableFilter, countBy, makeTextFilter } from '../utils';
import { ReactTableCustomPagination } from './react-table-custom-pagination';
import {
booleanCustomTableFilter,
DEFAULT_TABLE_CLASS_NAME,
GenericFilterInput,
ReactTablePagination,
} from '../react-table';
import { countBy } from '../utils';
const NoData = React.memo(function NoData(props) {
const { children } = props;
@ -32,7 +36,7 @@ const NoData = React.memo(function NoData(props) {
export function bootstrapReactTable() {
Object.assign(ReactTableDefaults, {
className: '-striped -highlight',
className: DEFAULT_TABLE_CLASS_NAME,
defaultFilterMethod: (filter: Filter, row: any) => {
const id = filter.pivotId || filter.id;
return booleanCustomTableFilter(filter, row[id]);
@ -40,8 +44,8 @@ export function bootstrapReactTable() {
LoadingComponent: Loader,
loadingText: '',
NoDataComponent: NoData,
FilterComponent: makeTextFilter(),
PaginationComponent: ReactTableCustomPagination,
FilterComponent: GenericFilterInput,
PaginationComponent: ReactTablePagination,
AggregatedComponent: function Aggregated(opt: any) {
const { subRows, column } = opt;
const previewValues = subRows

View File

@ -16,7 +16,11 @@
* limitations under the License.
*/
@import '../../variables';
.action-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
& > * {
margin-right: 10px;

View File

@ -44,7 +44,7 @@ export const ActionCell = React.memo(function ActionCell(props: ActionCellProps)
{onDetail && <ActionIcon icon={IconNames.SEARCH_TEMPLATE} onClick={onDetail} />}
{actionsMenu && (
<Popover2 content={actionsMenu} position={Position.BOTTOM_RIGHT}>
<ActionIcon icon={IconNames.WRENCH} />
<ActionIcon icon={IconNames.MORE} />
</Popover2>
)}
</div>

View File

@ -16,6 +16,7 @@
* limitations under the License.
*/
import classNames from 'classnames';
import { max } from 'd3-array';
import React, { Fragment } from 'react';
@ -24,6 +25,7 @@ import './braced-text.scss';
const THOUSANDS_SEPARATOR = ','; // Maybe one day make this locale aware
export interface BracedTextProps {
className?: string;
text: string;
braces: string[];
padFractionalPart?: boolean;
@ -80,7 +82,7 @@ function hideThousandsSeparator(text: string) {
}
export const BracedText = React.memo(function BracedText(props: BracedTextProps) {
const { text, braces, padFractionalPart, unselectableThousandsSeparator } = props;
const { className, text, braces, padFractionalPart, unselectableThousandsSeparator } = props;
let effectiveBraces = braces.concat(text);
@ -110,7 +112,7 @@ export const BracedText = React.memo(function BracedText(props: BracedTextProps)
}
return (
<span className="braced-text">
<span className={classNames('braced-text', className)}>
<span className="brace-text">{findMostNumbers(effectiveBraces)}</span>
<span className="real-text">
{unselectableThousandsSeparator ? hideThousandsSeparator(text) : text}

View File

@ -67,10 +67,14 @@ exports[`DatasourceColumnsTable matches snapshot on error 1`] = `
Object {
"Header": "Column name",
"accessor": "COLUMN_NAME",
"className": "padded",
"width": 300,
},
Object {
"Header": "Data type",
"accessor": "DATA_TYPE",
"className": "padded",
"width": 200,
},
]
}
@ -224,10 +228,14 @@ exports[`DatasourceColumnsTable matches snapshot on init 1`] = `
Object {
"Header": "Column name",
"accessor": "COLUMN_NAME",
"className": "padded",
"width": 300,
},
Object {
"Header": "Data type",
"accessor": "DATA_TYPE",
"className": "padded",
"width": 200,
},
]
}
@ -393,10 +401,14 @@ exports[`DatasourceColumnsTable matches snapshot on no data 1`] = `
Object {
"Header": "Column name",
"accessor": "COLUMN_NAME",
"className": "padded",
"width": 300,
},
Object {
"Header": "Data type",
"accessor": "DATA_TYPE",
"className": "padded",
"width": 200,
},
]
}
@ -550,10 +562,14 @@ exports[`DatasourceColumnsTable matches snapshot on some data 1`] = `
Object {
"Header": "Column name",
"accessor": "COLUMN_NAME",
"className": "padded",
"width": 300,
},
Object {
"Header": "Data type",
"accessor": "DATA_TYPE",
"className": "padded",
"width": 200,
},
]
}

View File

@ -21,12 +21,8 @@ import React from 'react';
import ReactTable from 'react-table';
import { useQueryManager } from '../../hooks';
import {
ColumnMetadata,
queryDruidSql,
SMALL_TABLE_PAGE_SIZE,
SMALL_TABLE_PAGE_SIZE_OPTIONS,
} from '../../utils';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { ColumnMetadata, queryDruidSql } from '../../utils';
import { Loader } from '../loader/loader';
import './datasource-columns-table.scss';
@ -67,10 +63,14 @@ export const DatasourceColumnsTable = React.memo(function DatasourceColumnsTable
{
Header: 'Column name',
accessor: 'COLUMN_NAME',
width: 300,
className: 'padded',
},
{
Header: 'Data type',
accessor: 'DATA_TYPE',
width: 200,
className: 'padded',
},
]}
noDataText={columnsState.getErrorMessage() || 'No column data found'}

View File

@ -42,6 +42,8 @@ export * from './show-json/show-json';
export * from './show-log/show-log';
export * from './suggestion-menu/suggestion-menu';
export * from './table-cell/table-cell';
export * from './table-clickable-cell/table-clickable-cell';
export * from './table-column-selector/table-column-selector';
export * from './table-filterable-cell/table-filterable-cell';
export * from './timed-button/timed-button';
export * from './view-control-bar/view-control-bar';

View File

@ -21,7 +21,8 @@ import React from 'react';
import ReactTable from 'react-table';
import { useQueryManager } from '../../hooks';
import { queryDruidSql, SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../utils';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { queryDruidSql } from '../../utils';
import { Loader } from '../loader/loader';
import './lookup-values-table.scss';
@ -57,20 +58,23 @@ export const LookupValuesTable = React.memo(function LookupValuesTable(
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={entries.length > SMALL_TABLE_PAGE_SIZE}
filterable
columns={[
{
Header: 'Key',
accessor: 'k',
},
{
Header: 'Value',
accessor: 'v',
},
]}
noDataText={
entriesState.getErrorMessage() ||
'Lookup data not found. If this is a new lookup it might not have propagated yet.'
}
columns={[
{
Header: 'Key',
accessor: 'k',
className: 'padded',
width: 300,
},
{
Header: 'Value',
accessor: 'v',
className: 'padded',
},
]}
/>
);
}

View File

@ -81,13 +81,17 @@ exports[`SupervisorStatisticsTable matches snapshot on error 1`] = `
Object {
"Header": "Task ID",
"accessor": [Function],
"className": "padded",
"id": "task_id",
"width": 400,
},
Object {
"Cell": [Function],
"Header": "Totals",
"accessor": [Function],
"className": "padded",
"id": "total",
"width": 200,
},
]
}
@ -255,13 +259,17 @@ exports[`SupervisorStatisticsTable matches snapshot on init 1`] = `
Object {
"Header": "Task ID",
"accessor": [Function],
"className": "padded",
"id": "task_id",
"width": 400,
},
Object {
"Cell": [Function],
"Header": "Totals",
"accessor": [Function],
"className": "padded",
"id": "total",
"width": 200,
},
]
}
@ -455,13 +463,17 @@ exports[`SupervisorStatisticsTable matches snapshot on no data 1`] = `
Object {
"Header": "Task ID",
"accessor": [Function],
"className": "padded",
"id": "task_id",
"width": 400,
},
Object {
"Cell": [Function],
"Header": "Totals",
"accessor": [Function],
"className": "padded",
"id": "total",
"width": 200,
},
]
}
@ -629,31 +641,41 @@ exports[`SupervisorStatisticsTable matches snapshot on some data 1`] = `
Object {
"Header": "Task ID",
"accessor": [Function],
"className": "padded",
"id": "task_id",
"width": 400,
},
Object {
"Cell": [Function],
"Header": "Totals",
"accessor": [Function],
"className": "padded",
"id": "total",
"width": 200,
},
Object {
"Cell": [Function],
"Header": "1m",
"accessor": [Function],
"className": "padded",
"id": "1m",
"width": 200,
},
Object {
"Cell": [Function],
"Header": "5m",
"accessor": [Function],
"className": "padded",
"id": "5m",
"width": 200,
},
Object {
"Cell": [Function],
"Header": "15m",
"accessor": [Function],
"className": "padded",
"id": "15m",
"width": 200,
},
]
}

View File

@ -21,8 +21,9 @@ import React from 'react';
import ReactTable, { CellInfo, Column } from 'react-table';
import { useQueryManager } from '../../hooks';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { Api, UrlBaser } from '../../singletons';
import { deepGet, SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../utils';
import { deepGet } from '../../utils';
import { Loader } from '../loader/loader';
import './supervisor-statistics-table.scss';
@ -86,11 +87,15 @@ export const SupervisorStatisticsTable = React.memo(function SupervisorStatistic
{
Header: 'Task ID',
id: 'task_id',
className: 'padded',
accessor: d => d.taskId,
width: 400,
},
{
Header: 'Totals',
id: 'total',
className: 'padded',
width: 200,
accessor: d => {
return deepGet(d, 'summary.totals.buildSegments') as StatsEntry;
},
@ -110,6 +115,8 @@ export const SupervisorStatisticsTable = React.memo(function SupervisorStatistic
return {
Header: interval,
id: interval,
className: 'padded',
width: 200,
accessor: d => {
return deepGet(d, `summary.movingAverages.buildSegments.${interval}`);
},

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,11 @@
* limitations under the License.
*/
@import '../../variables';
.table-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
&.null {
font-style: italic;
}
@ -41,8 +45,8 @@
.action-icon {
position: absolute;
top: 0;
right: 0;
top: $table-cell-v-padding;
right: $table-cell-h-padding;
color: #f5f8fa;
}
}

View File

@ -64,28 +64,28 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) {
function renderTruncated(str: string): JSX.Element {
if (str.length <= MAX_CHARS_TO_SHOW) {
return <span className="table-cell plain">{str}</span>;
return <div className="table-cell plain">{str}</div>;
}
if (unlimited) {
return (
<span className="table-cell plain">
<div className="table-cell plain">
{str.length < ABSOLUTE_MAX_CHARS_TO_SHOW
? str
: `${str.substr(0, ABSOLUTE_MAX_CHARS_TO_SHOW)}...`}
</span>
</div>
);
}
const { prefix, omitted, suffix } = shortenString(str);
return (
<span className="table-cell truncated">
<div className="table-cell truncated">
{prefix}
<span className="omitted">{omitted}</span>
{suffix}
<ActionIcon icon={IconNames.MORE} onClick={() => setShowValue(str)} />
{renderShowValueDialog()}
</span>
</div>
);
}
@ -93,9 +93,9 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) {
if (value instanceof Date) {
const dateValue = value.valueOf();
return (
<span className="table-cell timestamp" title={String(value.valueOf())}>
<div className="table-cell timestamp" title={String(value.valueOf())}>
{isNaN(dateValue) ? 'Unusable date' : value.toISOString()}
</span>
</div>
);
} else if (Array.isArray(value)) {
return renderTruncated(`[${value.join(', ')}]`);
@ -105,6 +105,6 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) {
return renderTruncated(String(value));
}
} else {
return <span className="table-cell null">null</span>;
return <div className="table-cell null">null</div>;
}
});

View File

@ -16,31 +16,23 @@
* limitations under the License.
*/
@import '../variables';
@import '../../variables';
.ReactTable {
.-pageJump {
.#{$bp-ns}-input {
position: relative;
top: -2px;
border: none;
.table-clickable-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
.#{$bp-ns}-dark & {
background-color: $dark-gray4;
}
}
.hover-icon {
position: absolute;
top: $table-cell-v-padding;
right: $table-cell-h-padding;
color: #f5f8fa;
display: none;
}
.-pageSizeOptions {
.#{$bp-ns}-html-select select {
width: 90px;
position: relative;
top: -2px;
border: none;
.#{$bp-ns}-dark & {
background-color: $dark-gray4;
}
}
&:hover .hover-icon {
display: block;
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Icon, IconName } from '@blueprintjs/core';
import React, { MouseEventHandler, ReactNode } from 'react';
import './table-clickable-cell.scss';
export interface TableClickableCellProps {
onClick: MouseEventHandler<any>;
hoverIcon?: IconName;
children?: ReactNode;
}
export const TableClickableCell = React.memo(function TableClickableCell(
props: TableClickableCellProps,
) {
const { onClick, hoverIcon, children } = props;
return (
<div className="table-clickable-cell" onClick={onClick}>
{children}
{hoverIcon && <Icon className="hover-icon" icon={hoverIcon} />}
</div>
);
});

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../variables';
.table-filterable-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
&.bp4-popover2-target {
display: block; // extra nesting for stronger CSS selectors
}
}

View File

@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2';
import React, { ReactNode } from 'react';
import { Filter } from 'react-table';
import { addFilter, FilterMode, filterModeToIcon } from '../../react-table';
import { Deferred } from '../deferred/deferred';
import './table-filterable-cell.scss';
const FILTER_MODES: FilterMode[] = ['=', '!=', '<=', '>='];
const FILTER_MODES_NO_COMPARISONS: FilterMode[] = ['=', '!='];
export interface TableFilterableCellProps {
field: string;
value: string;
filters: Filter[];
onFiltersChange(filters: Filter[]): void;
disableComparisons?: boolean;
children?: ReactNode;
}
export const TableFilterableCell = React.memo(function TableFilterableCell(
props: TableFilterableCellProps,
) {
const { field, value, children, filters, disableComparisons, onFiltersChange } = props;
return (
<Popover2
className="table-filterable-cell"
content={
<Deferred
content={() => (
<Menu>
<MenuDivider title="Filter" />
{(disableComparisons ? FILTER_MODES_NO_COMPARISONS : FILTER_MODES).map((mode, i) => (
<MenuItem
key={i}
icon={filterModeToIcon(mode)}
text={value}
onClick={() => onFiltersChange(addFilter(filters, field, mode, value))}
/>
))}
</Menu>
)}
/>
}
>
{children}
</Popover2>
);
});

View File

@ -22,8 +22,8 @@ import ReactTable, { Filter } from 'react-table';
import { Loader } from '../../components';
import { useQueryManager } from '../../hooks';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { Api, UrlBaser } from '../../singletons';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../utils';
import './status-dialog.scss';
@ -86,15 +86,19 @@ export const StatusDialog = React.memo(function StatusDialog(props: StatusDialog
Header: 'Extension name',
accessor: 'artifact',
width: 200,
},
{
Header: 'Fully qualified name',
accessor: 'name',
className: 'padded',
},
{
Header: 'Version',
accessor: 'version',
width: 200,
className: 'padded',
},
{
Header: 'Fully qualified name',
accessor: 'name',
width: 500,
className: 'padded',
},
],
},

View File

@ -16,9 +16,7 @@
* limitations under the License.
*/
function nonEmptyArray(a: any) {
return Array.isArray(a) && Boolean(a.length);
}
import { nonEmptyArray } from '../utils';
export interface InputSource {
type: string;

View File

@ -23,7 +23,8 @@
@import '~@blueprintjs/datetime/src/blueprint-datetime';
@import '~@blueprintjs/popover2/src/blueprint-popover2';
@import '~react-splitter-layout/lib/index';
@import '../lib/react-table';
@import './react-table/react-table-base-styles';
@import './react-table/react-table-extra';
html,
body {

View File

@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './react-table-inputs';
export * from './react-table-pagination/react-table-pagination';
export * from './react-table-utils';

View File

@ -0,0 +1,303 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This is a heavily modified version of the style file originally distributed here:
// https://github.com/react-tools/react-table/blob/master/src/index.styl
// Released originally under the MIT License https://github.com/react-tools/react-table/blob/master/LICENSE
@import '../variables';
$easeOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
$easeOutBack: cubic-bezier(0.175, 0.885, 0.32, 1.275);
$expandSize: 7px;
$border-color: rgba(0, 0, 0, 0.15);
.ReactTable {
position: relative;
display: flex;
flex-direction: column;
* {
box-sizing: border-box;
}
.rt-table {
flex: auto 1;
display: flex;
flex-direction: column;
align-items: stretch;
width: 100%;
border-collapse: collapse;
overflow: auto;
}
.rt-thead {
flex: 1 0 auto;
display: flex;
flex-direction: column;
user-select: none;
&.-headerGroups {
background: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid $border-color;
}
&.-filters {
border-bottom: 1px solid $border-color;
.rt-th {
border-right: 1px solid $border-color;
}
}
&.-header {
box-shadow: 0 1px 0 0 $border-color;
}
.rt-tr {
text-align: left;
}
.rt-th,
.rt-td {
//padding: 5px 5px;
line-height: normal;
position: relative;
border-right: 1px solid $border-color;
transition: box-shadow 0.3s $easeOutBack;
box-shadow: inset 0 0 0 0 transparent;
&.-sort-asc {
box-shadow: inset 0 3px 0 0 #8489a1;
}
&.-sort-desc {
box-shadow: inset 0 -3px 0 0 #8489a1;
}
&.-cursor-pointer {
cursor: pointer;
}
&:last-child {
border-right: 0;
}
}
.rt-resizable-header {
overflow: visible;
&:last-child {
overflow: hidden;
}
}
.rt-resizable-header-content {
overflow: hidden;
text-overflow: ellipsis;
}
.rt-header-pivot {
border-right-color: $border-color;
}
.rt-header-pivot:after,
.rt-header-pivot:before {
left: 100%;
top: 50%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.rt-header-pivot:after {
border-color: rgba(255, 255, 255, 0);
border-left-color: #fff;
border-width: 8px;
margin-top: -8px;
}
.rt-header-pivot:before {
border-color: rgba(102, 102, 102, 0);
border-left-color: #f7f7f7;
border-width: 10px;
margin-top: -10px;
}
}
.rt-tbody {
flex: 99999 1 auto;
display: flex;
flex-direction: column;
overflow: auto;
overflow-x: hidden; // Prevents strange double horizontal scroll bar
// z-index:0
.rt-tr-group {
border-bottom: 1px solid $border-color;
&:last-child {
border-bottom: 0;
}
}
.rt-td {
border-right: 1px solid $border-color;
&:last-child {
border-right: 0;
}
}
.rt-expandable {
cursor: pointer;
text-overflow: clip;
}
}
.rt-tr-group {
flex: 1 0 auto;
display: flex;
flex-direction: column;
align-items: stretch;
}
.rt-tr {
flex: 1 0 auto;
display: inline-flex;
}
.rt-th,
.rt-td {
flex: 1 0 0px;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
overflow: hidden;
}
&.-hidden {
width: 0 !important;
min-width: 0 !important;
padding: 0 !important;
border: 0 !important;
opacity: 0 !important;
}
.rt-expander {
display: inline-block;
position: relative;
color: transparent;
margin: 0 10px;
&:after {
content: '';
position: absolute;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-90deg);
border-left: ($expandSize * 0.72) solid transparent;
border-right: ($expandSize * 0.72) solid transparent;
border-top: $expandSize solid rgba(255, 255, 255, 0.8);
transition: all 0.3s $easeOutBack;
cursor: pointer;
}
&.-open:after {
transform: translate(-50%, -50%) rotate(0deg);
}
}
.rt-resizer {
display: inline-block;
position: absolute;
width: 36px;
top: 0;
bottom: 0;
right: -18px;
cursor: col-resize;
z-index: 10;
}
.rt-tfoot {
flex: 1 0 auto;
display: flex;
flex-direction: column;
box-shadow: 0 0px 15px 0px black;
.rt-td {
border-right: 1px solid $border-color;
&:last-child {
border-right: 0;
}
}
}
&.-striped {
.rt-tr.-odd {
background: rgba(255, 255, 255, 0.025);
}
}
&.-highlight {
.rt-tbody {
.rt-tr:not(.-padRow):hover {
background: rgba(0, 0, 0, 0.05);
}
}
}
.rt-noData {
display: block;
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.4);
transition: all 0.3s ease;
z-index: 1;
padding: 20px;
color: rgba(255, 255, 255, 0.5);
border-radius: 5px;
}
.-loading {
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(76, 76, 76, 0.8);
transition: all 0.3s ease;
z-index: -1;
opacity: 0;
pointer-events: none;
* > div {
position: absolute;
display: block;
text-align: center;
width: 100%;
top: 50%;
left: 0;
font-size: 15px;
color: rgba(0, 0, 0, 0.6);
transform: translateY(-52%);
transition: all 0.3s $easeOutQuad;
}
&.-active {
opacity: 1;
z-index: 2;
pointer-events: all;
* > div {
transform: translateY(50%);
}
}
}
.rt-resizing {
.rt-th,
.rt-td {
cursor: col-resize;
user-select: none;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import 'src/variables';
.ReactTable {
.rt-tr {
min-height: 38px;
.rt-th.padded,
.rt-td.padded,
.rt-expandable {
padding: $table-cell-v-padding $table-cell-h-padding;
}
}
&.padded-header .rt-thead .rt-th {
padding: $table-cell-v-padding $table-cell-h-padding;
}
.braced-text.table-padding {
display: inline-block;
margin: $table-cell-v-padding $table-cell-h-padding;
}
.generic-filter-input {
&.hide-icon:not(:hover) {
.filter-mode-button {
visibility: hidden;
}
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, HTMLSelect, Icon, InputGroup, Menu, MenuItem } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Popover2 } from '@blueprintjs/popover2';
import classNames from 'classnames';
import React, { useState } from 'react';
import { Column, ReactTableFunction } from 'react-table';
import {
combineModeAndNeedle,
FILTER_MODES,
FILTER_MODES_NO_COMPARISON,
filterModeToIcon,
filterModeToTitle,
parseFilterModeAndNeedle,
} from './react-table-utils';
interface FilterRendererProps {
column: Column;
filter: any;
onChange: ReactTableFunction;
key?: string;
}
export function GenericFilterInput({ column, filter, onChange, key }: FilterRendererProps) {
const [menuOpen, setMenuOpen] = useState(false);
const [focused, setFocused] = useState(false);
const disableComparisons = String(column.headerClassName).includes('disable-comparisons');
const { mode, needle } = (filter ? parseFilterModeAndNeedle(filter, true) : undefined) || {
mode: '~',
needle: '',
};
return (
<InputGroup
className={classNames('generic-filter-input', {
'hide-icon': !filter && !(menuOpen || focused),
})}
key={key}
leftElement={
<Popover2
placement="bottom-start"
minimal
isOpen={menuOpen}
onInteraction={setMenuOpen}
content={
<Menu>
{(disableComparisons ? FILTER_MODES_NO_COMPARISON : FILTER_MODES).map((m, i) => (
<MenuItem
key={i}
icon={filterModeToIcon(m)}
text={filterModeToTitle(m)}
onClick={() => onChange(combineModeAndNeedle(m, needle))}
labelElement={m === mode ? <Icon icon={IconNames.TICK} /> : undefined}
/>
))}
</Menu>
}
>
<Button className="filter-mode-button" icon={filterModeToIcon(mode)} minimal />
</Popover2>
}
value={needle}
onChange={e => onChange(combineModeAndNeedle(mode, e.target.value))}
rightElement={
filter ? <Button icon={IconNames.CROSS} minimal onClick={() => onChange('')} /> : undefined
}
onFocus={() => setFocused(true)}
onBlur={e => {
setFocused(false);
if (filter && !e.target.value) onChange('');
}}
/>
);
}
export function BooleanFilterInput({ filter, onChange, key }: FilterRendererProps) {
const filterValue = filter ? filter.value : '';
return (
<HTMLSelect
className="boolean-filter-input"
key={key}
style={{ width: '100%' }}
onChange={(event: any) => onChange(event.target.value)}
value={filterValue || 'all'}
fill
>
<option value="all">Show all</option>
<option value="true">true</option>
<option value="false">false</option>
</HTMLSelect>
);
}

View File

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, Classes, Dialog, FormGroup, Intent, NumericInput } from '@blueprintjs/core';
import React, { useState } from 'react';
interface PageJumpDialogProps {
initPage: number;
maxPage: number;
onJump(page: number): void;
onClose(): void;
}
export const PageJumpDialog = React.memo(function PageJumpDialog(props: PageJumpDialogProps) {
const { initPage, maxPage, onJump, onClose } = props;
const [page, setPage] = useState<string>(String(initPage + 1));
const pageAsNumber = parseInt(page, 10);
const validPage = pageAsNumber >= 1;
return (
<Dialog
className="page-jump-dialog"
onClose={onClose}
isOpen
title="Jump to page"
canOutsideClickClose={false}
>
<div className={Classes.DIALOG_BODY}>
<FormGroup>
<NumericInput
value={page}
onValueChange={(_vn, vs) => setPage(vs)}
autoFocus
selectAllOnFocus
min={1}
max={maxPage}
minorStepSize={1}
fill
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button text="Close" onClick={onClose} />
<Button
text="Go"
intent={Intent.PRIMARY}
disabled={!validPage}
onClick={() => {
onJump(pageAsNumber - 1);
onClose();
}}
/>
</div>
</div>
</Dialog>
);
});

View File

@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.react-table-pagination {
position: relative;
padding-top: 5px;
}

View File

@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, ButtonGroup, Menu, MenuItem } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Popover2 } from '@blueprintjs/popover2';
import React, { useState } from 'react';
import { formatInteger, nonEmptyArray } from '../../utils';
import { PageJumpDialog } from './page-jump-dialog/page-jump-dialog';
import './react-table-pagination.scss';
interface ReactTablePaginationProps {
pages: number;
page: number;
showPageSizeOptions: boolean;
pageSizeOptions: number[];
pageSize: number;
showPageJump: boolean;
canPrevious: boolean;
canNext: boolean;
onPageSizeChange: any;
previousText: string;
nextText: string;
onPageChange: any;
ofText: string;
pageText: string;
rowsText: string;
sortedData?: any[];
style: Record<string, any>;
}
export const ReactTablePagination = React.memo(function ReactTablePagination(
props: ReactTablePaginationProps,
) {
const {
page,
pages,
onPageChange,
pageSize,
onPageSizeChange,
pageSizeOptions,
showPageJump,
showPageSizeOptions,
canPrevious,
canNext,
style,
ofText,
sortedData,
} = props;
const [showPageJumpDialog, setShowPageJumpDialog] = useState(false);
function changePage(newPage: number) {
newPage = Math.min(Math.max(newPage, 0), pages - 1);
if (page !== newPage) {
onPageChange(newPage);
}
}
function renderPageJumpMenuItem() {
if (!showPageJump) return;
return <MenuItem text="Jump to page..." onClick={() => setShowPageJumpDialog(true)} />;
}
function renderPageSizeChangeMenuItem() {
if (!showPageSizeOptions) return;
return (
<MenuItem text={`Page size: ${pageSize}`}>
{pageSizeOptions.map((option, i) => (
<MenuItem
key={i}
icon={option === pageSize ? IconNames.TICK : IconNames.BLANK}
text={String(option)}
onClick={() => {
if (option === pageSize) return;
onPageSizeChange(option);
}}
/>
))}
</MenuItem>
);
}
const start = page * pageSize + 1;
const end =
page * pageSize +
(nonEmptyArray(sortedData) ? Math.min(sortedData.length, pageSize) : pageSize);
let pageInfo = 'Showing';
if (end) {
pageInfo += ` ${formatInteger(start)}-${formatInteger(end)}`;
} else {
pageInfo += '...';
}
if (ofText && Array.isArray(sortedData)) {
pageInfo += ` ${ofText} ${formatInteger(sortedData.length)}`;
}
const pageJumpMenuItem = renderPageJumpMenuItem();
const pageSizeChangeMenuItem = renderPageSizeChangeMenuItem();
return (
<div className="react-table-pagination" style={style}>
<ButtonGroup>
<Button
icon={IconNames.CHEVRON_LEFT}
minimal
disabled={!canPrevious}
onClick={() => changePage(page - 1)}
/>
<Button
icon={IconNames.CHEVRON_RIGHT}
minimal
disabled={!canNext}
onClick={() => changePage(page + 1)}
/>
<Popover2
position="top-left"
disabled={!pageJumpMenuItem && !pageSizeChangeMenuItem}
content={
<Menu>
{pageJumpMenuItem}
{pageSizeChangeMenuItem}
</Menu>
}
>
<Button minimal text={pageInfo} />
</Popover2>
</ButtonGroup>
{showPageJumpDialog && (
<PageJumpDialog
initPage={page}
maxPage={pages}
onJump={changePage}
onClose={() => setShowPageJumpDialog(false)}
/>
)}
</div>
);
});

View File

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { sqlQueryCustomTableFilter } from './react-table-utils';
describe('react-table-utils', () => {
describe('sqlQueryCustomTableFilter', () => {
it('works with contains', () => {
expect(
String(
sqlQueryCustomTableFilter({
id: 'datasource',
value: `Hello`,
}),
),
).toEqual(`LOWER("datasource") LIKE '%hello%'`);
});
it('works with exact', () => {
expect(
String(
sqlQueryCustomTableFilter({
id: 'datasource',
value: `=Hello`,
}),
),
).toEqual(`"datasource" = 'Hello'`);
});
it('works with less than or equal', () => {
expect(
String(
sqlQueryCustomTableFilter({
id: 'datasource',
value: `<=Hello`,
}),
),
).toEqual(`"datasource" <= 'Hello'`);
});
});
});

View File

@ -0,0 +1,165 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IconName } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { SqlExpression, SqlFunction, SqlLiteral, SqlRef } from 'druid-query-toolkit';
import { Filter } from 'react-table';
import { addOrUpdate, caseInsensitiveContains } from '../utils';
export const DEFAULT_TABLE_CLASS_NAME = '-striped -highlight padded-header';
export const STANDARD_TABLE_PAGE_SIZE = 50;
export const STANDARD_TABLE_PAGE_SIZE_OPTIONS = [50, 100, 200];
export const SMALL_TABLE_PAGE_SIZE = 25;
export const SMALL_TABLE_PAGE_SIZE_OPTIONS = [25, 50, 100];
export type FilterMode = '~' | '=' | '!=' | '<=' | '>=';
export const FILTER_MODES: FilterMode[] = ['~', '=', '!=', '<=', '>='];
export const FILTER_MODES_NO_COMPARISON: FilterMode[] = ['~', '=', '!='];
export function filterModeToIcon(mode: FilterMode): IconName {
switch (mode) {
case '~':
return IconNames.SEARCH;
case '=':
return IconNames.EQUALS;
case '!=':
return IconNames.NOT_EQUAL_TO;
case '<=':
return IconNames.LESS_THAN_OR_EQUAL_TO;
case '>=':
return IconNames.GREATER_THAN_OR_EQUAL_TO;
default:
return IconNames.BLANK;
}
}
export function filterModeToTitle(mode: FilterMode): string {
switch (mode) {
case '~':
return 'Search';
case '=':
return 'Equals';
case '!=':
return 'Not equals';
case '<=':
return 'Less than or equal';
case '>=':
return 'Greater than or equal';
default:
return '?';
}
}
interface FilterModeAndNeedle {
mode: FilterMode;
needle: string;
}
export function addFilter(
filters: readonly Filter[],
id: string,
mode: FilterMode,
needle: string,
): Filter[] {
return addOrUpdateFilter(filters, { id, value: combineModeAndNeedle(mode, needle) });
}
export function parseFilterModeAndNeedle(
filter: Filter,
loose = false,
): FilterModeAndNeedle | undefined {
const m = String(filter.value).match(/^(~|=|!=|<=|>=)?(.*)$/);
if (!m) return;
if (!loose && !m[2]) return;
const mode = (m[1] as FilterMode) || '~';
return {
mode,
needle: m[2] || '',
};
}
export function combineModeAndNeedle(mode: FilterMode, needle: string): string {
return `${mode}${needle}`;
}
export function addOrUpdateFilter(filters: readonly Filter[], filter: Filter): Filter[] {
return addOrUpdate(filters, filter, f => f.id);
}
export function syncFilterClauseById(
target: readonly Filter[],
source: readonly Filter[],
id: string,
): Filter[] {
const clause = source.find(filter => filter.id === id);
return clause ? addOrUpdateFilter(target, clause) : target.filter(filter => filter.id !== id);
}
export function booleanCustomTableFilter(filter: Filter, value: any): boolean {
if (value == null) return false;
const modeAndNeedle = parseFilterModeAndNeedle(filter);
if (!modeAndNeedle) return true;
const { mode, needle } = modeAndNeedle;
switch (mode) {
case '=':
return String(value) === needle;
case '!=':
return String(value) !== needle;
case '<=':
return String(value) <= needle;
case '>=':
return String(value) >= needle;
default:
return caseInsensitiveContains(String(value), needle);
}
}
export function sqlQueryCustomTableFilter(filter: Filter): SqlExpression | undefined {
const modeAndNeedle = parseFilterModeAndNeedle(filter);
if (!modeAndNeedle) return;
const { mode, needle } = modeAndNeedle;
const column = SqlRef.columnWithQuotes(filter.id);
const needleLiteral = SqlLiteral.create(needle);
switch (mode) {
case '=':
return column.equal(needleLiteral);
case '!=':
return column.unequal(needleLiteral);
case '<=':
return column.lessThanOrEqual(needleLiteral);
case '>=':
return column.greaterThanOrEqual(needleLiteral);
default:
return SqlFunction.simple('LOWER', [column]).like(
SqlLiteral.create(`%${needle.toLowerCase()}%`),
);
}
}

View File

@ -31,6 +31,8 @@ export function dataTypeToIcon(dataType: string): IconName {
return IconNames.FONT;
case 'BIGINT':
case 'DECIMAL':
case 'REAL':
case 'LONG':
case 'FLOAT':
case 'DOUBLE':
@ -53,3 +55,40 @@ export function dataTypeToIcon(dataType: string): IconName {
return IconNames.HELP;
}
}
export function dataTypeToColumnWidth(dataType: string | undefined): number {
const typeUpper = String(dataType).toUpperCase();
switch (typeUpper) {
case 'TIMESTAMP':
return 180;
case 'VARCHAR':
case 'STRING':
return 150;
case 'BIGINT':
case 'DECIMAL':
case 'REAL':
case 'LONG':
case 'FLOAT':
case 'DOUBLE':
return 120;
case 'ARRAY<STRING>':
return 200;
case 'ARRAY<LONG>':
case 'ARRAY<FLOAT>':
case 'ARRAY<DOUBLE>':
return 180;
case 'COMPLEX<JSON>':
return 300;
default:
if (typeUpper.startsWith('ARRAY')) return 200;
if (typeUpper.startsWith('COMPLEX')) return 150;
return 180;
}
}

View File

@ -29,7 +29,6 @@ import {
moveToIndex,
objectHash,
parseCsvLine,
sqlQueryCustomTableFilter,
swapElements,
} from './general';
@ -56,30 +55,6 @@ describe('general', () => {
});
});
describe('sqlQueryCustomTableFilter', () => {
it('works with contains', () => {
expect(
String(
sqlQueryCustomTableFilter({
id: 'datasource',
value: `Hello`,
}),
),
).toEqual(`LOWER("datasource") LIKE '%hello%'`);
});
it('works with exact', () => {
expect(
String(
sqlQueryCustomTableFilter({
id: 'datasource',
value: `"Hello"`,
}),
),
).toEqual(`"datasource" = 'Hello'`);
});
});
describe('swapElements', () => {
const array = ['a', 'b', 'c', 'd', 'e'];

View File

@ -16,25 +16,16 @@
* limitations under the License.
*/
import { Button, HTMLSelect, InputGroup, Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { Intent } from '@blueprintjs/core';
import copy from 'copy-to-clipboard';
import { SqlExpression, SqlFunction, SqlLiteral, SqlRef } from 'druid-query-toolkit';
import FileSaver from 'file-saver';
import hasOwnProp from 'has-own-prop';
import * as JSONBig from 'json-bigint-native';
import numeral from 'numeral';
import React from 'react';
import { Filter, FilterRender } from 'react-table';
import { AppToaster } from '../singletons';
export const STANDARD_TABLE_PAGE_SIZE = 50;
export const STANDARD_TABLE_PAGE_SIZE_OPTIONS = [50, 100, 200];
export const SMALL_TABLE_PAGE_SIZE = 25;
export const SMALL_TABLE_PAGE_SIZE_OPTIONS = [25, 50, 100];
// These constants are used to make sure that they are not constantly recreated thrashing the pure components
export const EMPTY_OBJECT: any = {};
export const EMPTY_ARRAY: any[] = [];
@ -45,108 +36,31 @@ export function isNumberLikeNaN(x: NumberLike): boolean {
return isNaN(Number(x));
}
export function nonEmptyArray(a: any): a is unknown[] {
return Array.isArray(a) && Boolean(a.length);
}
export function wait(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
export function addFilter(filters: Filter[], id: string, value: string): Filter[] {
return addFilterRaw(filters, id, `"${value}"`);
}
export function addFilterRaw(filters: Filter[], id: string, value: string): Filter[] {
const currentFilter = filters.find(f => f.id === id);
if (currentFilter) {
filters = filters.filter(f => f.id !== id);
if (currentFilter.value !== value) {
filters = filters.concat({ id, value });
export function addOrUpdate<T>(xs: readonly T[], x: T, keyFn: (x: T) => string | number): T[] {
const keyX = keyFn(x);
let added = false;
const newXs = xs.map(currentX => {
if (keyFn(currentX) === keyX) {
added = true;
return x;
} else {
return currentX;
}
} else {
filters = filters.concat({ id, value });
}
return filters;
}
export function makeTextFilter(placeholder = ''): FilterRender {
return function TextFilter({ filter, onChange, key }) {
const filterValue = filter ? filter.value : '';
return (
<InputGroup
key={key}
onChange={(e: any) => onChange(e.target.value)}
value={filterValue}
rightElement={
filterValue && <Button icon={IconNames.CROSS} minimal onClick={() => onChange('')} />
}
placeholder={placeholder}
/>
);
};
}
export function makeBooleanFilter(): FilterRender {
return function BooleanFilter({ filter, onChange, key }) {
const filterValue = filter ? filter.value : '';
return (
<HTMLSelect
key={key}
style={{ width: '100%' }}
onChange={(event: any) => onChange(event.target.value)}
value={filterValue || 'all'}
fill
>
<option value="all">Show all</option>
<option value="true">true</option>
<option value="false">false</option>
</HTMLSelect>
);
};
}
// ----------------------------
interface NeedleAndMode {
needle: string;
mode: 'exact' | 'includes';
}
export function getNeedleAndMode(filter: Filter): NeedleAndMode {
const input = filter.value;
if (input.startsWith(`"`) && input.endsWith(`"`)) {
return {
needle: input.slice(1, -1),
mode: 'exact',
};
} else {
return {
needle: input.replace(/^"/, '').toLowerCase(),
mode: 'includes',
};
}
}
export function booleanCustomTableFilter(filter: Filter, value: any): boolean {
if (value == null) return false;
const needleAndMode: NeedleAndMode = getNeedleAndMode(filter);
const needle = needleAndMode.needle;
if (needleAndMode.mode === 'exact') {
return needle === String(value);
} else {
return String(value).toLowerCase().includes(needle);
}
}
export function sqlQueryCustomTableFilter(filter: Filter): SqlExpression {
const needleAndMode: NeedleAndMode = getNeedleAndMode(filter);
const needle = needleAndMode.needle;
if (needleAndMode.mode === 'exact') {
return SqlRef.columnWithQuotes(filter.id).equal(SqlLiteral.create(needle));
} else {
return SqlFunction.simple('LOWER', [SqlRef.columnWithQuotes(filter.id)]).like(
SqlLiteral.create(`%${needle}%`),
);
});
if (!added) {
newXs.push(x);
}
return newXs;
}
// ----------------------------

View File

@ -18,6 +18,7 @@
export * from './capabilities';
export * from './column-metadata';
export * from './data-type-utils';
export * from './date';
export * from './druid-lookup';
export * from './druid-query';

View File

@ -185,18 +185,17 @@ export class QueryManager<Q, R, I = never, E extends Error = Error> {
}
private trigger() {
const currentlyLoading = Boolean(this.currentRunCancelFn);
this.setState(
new QueryState<R, E>({
loading: true,
lastData: this.state.getSomeData(),
}),
);
if (currentlyLoading) {
if (this.currentRunCancelFn) {
// Currently loading
this.runWhenLoading();
} else {
this.setState(
new QueryState<R, E>({
loading: true,
lastData: this.state.getSomeData(),
}),
);
this.runWhenIdle();
}
}

View File

@ -31,6 +31,11 @@ $druid-brand: #2ceefb;
$druid-brand2: #00b6bf;
$druid-brand-background: #1c1c26;
// ReactTable related
$table-cell-v-padding: 10px;
$table-cell-h-padding: 5px;
@mixin card-background {
background: $white;
border-radius: $pt-border-radius;

View File

@ -2,7 +2,7 @@
exports[`DatasourcesView matches snapshot 1`] = `
<div
className="datasource-view app-view"
className="datasources-view app-view"
>
<Memo(ViewControlBar)
label="Datasources"
@ -164,10 +164,11 @@ exports[`DatasourcesView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Availability",
"accessor": "num_segments",
"className": "padded",
"filterable": false,
"minWidth": 200,
"show": true,
"sortMethod": [Function],
"width": 220,
},
Object {
"Cell": [Function],
@ -177,9 +178,10 @@ exports[`DatasourcesView matches snapshot 1`] = `
detail
</React.Fragment>,
"accessor": "num_segments_to_load",
"className": "padded",
"filterable": false,
"minWidth": 100,
"show": true,
"width": 180,
},
Object {
"Cell": [Function],
@ -189,6 +191,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
data size
</React.Fragment>,
"accessor": "total_data_size",
"className": "padded",
"filterable": false,
"show": true,
"width": 100,
@ -201,6 +204,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
minimum / average / maximum
</React.Fragment>,
"accessor": "avg_segment_rows",
"className": "padded",
"filterable": false,
"show": true,
"width": 220,
@ -213,6 +217,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
minimum / average / maximum
</React.Fragment>,
"accessor": "avg_segment_size",
"className": "padded",
"filterable": false,
"show": false,
"width": 270,
@ -225,6 +230,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
granularity
</React.Fragment>,
"accessor": [Function],
"className": "padded",
"filterable": false,
"id": "segment_granularity",
"show": false,
@ -238,6 +244,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
rows
</React.Fragment>,
"accessor": "total_rows",
"className": "padded",
"filterable": false,
"show": true,
"width": 100,
@ -250,6 +257,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
(bytes)
</React.Fragment>,
"accessor": "avg_row_size",
"className": "padded",
"filterable": false,
"show": true,
"width": 100,
@ -262,6 +270,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
size
</React.Fragment>,
"accessor": "replicated_size",
"className": "padded",
"filterable": false,
"show": true,
"width": 100,
@ -283,6 +292,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
bytes / segments / intervals
</React.Fragment>,
"accessor": [Function],
"className": "padded",
"filterable": false,
"id": "percentCompacted",
"show": true,
@ -296,6 +306,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
compacted
</React.Fragment>,
"accessor": [Function],
"className": "padded",
"filterable": false,
"id": "leftToBeCompacted",
"show": true,
@ -307,8 +318,8 @@ exports[`DatasourcesView matches snapshot 1`] = `
"accessor": [Function],
"filterable": false,
"id": "retention",
"minWidth": 100,
"show": true,
"width": 200,
},
Object {
"Cell": [Function],

View File

@ -18,7 +18,7 @@
@import '../../variables';
.datasource-view {
.datasources-view {
height: 100%;
width: 100%;
overflow: auto;

View File

@ -21,7 +21,7 @@ import React from 'react';
import { Capabilities } from '../../utils';
import { DatasourcesView } from './datasource-view';
import { DatasourcesView } from './datasources-view';
describe('DatasourcesView', () => {
it('matches snapshot', () => {

View File

@ -28,11 +28,11 @@ import {
ACTION_COLUMN_LABEL,
ACTION_COLUMN_WIDTH,
ActionCell,
ActionIcon,
BracedText,
MoreButton,
RefreshButton,
SegmentTimeline,
TableClickableCell,
TableColumnSelector,
ViewControlBar,
} from '../../components';
@ -44,9 +44,9 @@ import {
formatCompactionConfigAndStatus,
zeroCompactionStatus,
} from '../../druid-models';
import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { Api, AppToaster } from '../../singletons';
import {
addFilter,
Capabilities,
CapabilitiesMode,
compact,
@ -67,14 +67,12 @@ import {
queryDruidSql,
QueryManager,
QueryState,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
twoLines,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
import { Rule, RuleUtil } from '../../utils/load-rule';
import './datasource-view.scss';
import './datasources-view.scss';
const tableColumns: Record<CapabilitiesMode, string[]> = {
'full': [
@ -931,7 +929,7 @@ ORDER BY 1`;
}
}
renderRetentionDialog(): JSX.Element | undefined {
private renderRetentionDialog(): JSX.Element | undefined {
const { retentionDialogOpenOn, tiersState, datasourcesAndDefaultRulesState } = this.state;
const { defaultRules } = datasourcesAndDefaultRulesState.data || {
datasources: [],
@ -952,7 +950,7 @@ ORDER BY 1`;
);
}
renderCompactionDialog() {
private renderCompactionDialog() {
const { datasourcesAndDefaultRulesState, compactionDialogOpenOn } = this.state;
if (!compactionDialogOpenOn || !datasourcesAndDefaultRulesState.data) return;
@ -967,7 +965,16 @@ ORDER BY 1`;
);
}
renderDatasourceTable() {
private onDetail(datasource: Datasource): void {
const { unused, rules, compactionConfig } = datasource;
this.setState({
datasourceTableActionDialogId: datasource.datasource,
actions: this.getDatasourceActions(datasource.datasource, unused, rules, compactionConfig),
});
}
private renderDatasourcesTable() {
const { goToSegments, capabilities } = this.props;
const { datasourcesAndDefaultRulesState, datasourceFilter, showUnused, visibleColumns } =
this.state;
@ -1005,452 +1012,443 @@ ORDER BY 1`;
);
return (
<>
<ReactTable
data={datasources}
loading={datasourcesAndDefaultRulesState.loading}
noDataText={
datasourcesAndDefaultRulesState.getErrorMessage() ||
(!datasourcesAndDefaultRulesState.loading && datasources && !datasources.length
? 'No datasources'
: '')
}
filterable
filtered={datasourceFilter}
onFilteredChange={filtered => {
this.setState({ datasourceFilter: filtered });
}}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={datasources.length > STANDARD_TABLE_PAGE_SIZE}
columns={[
{
Header: twoLines('Datasource', 'name'),
show: visibleColumns.shown('Datasource name'),
accessor: 'datasource',
width: 150,
Cell: ({ value }) => {
<ReactTable
data={datasources}
loading={datasourcesAndDefaultRulesState.loading}
noDataText={
datasourcesAndDefaultRulesState.getErrorMessage() ||
(!datasourcesAndDefaultRulesState.loading && datasources && !datasources.length
? 'No datasources'
: '')
}
filterable
filtered={datasourceFilter}
onFilteredChange={filtered => {
this.setState({ datasourceFilter: filtered });
}}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={datasources.length > STANDARD_TABLE_PAGE_SIZE}
columns={[
{
Header: twoLines('Datasource', 'name'),
show: visibleColumns.shown('Datasource name'),
accessor: 'datasource',
width: 150,
Cell: row => (
<TableClickableCell
onClick={() => this.onDetail(row.original)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{row.value}
</TableClickableCell>
),
},
{
Header: 'Availability',
show: visibleColumns.shown('Availability'),
filterable: false,
width: 220,
accessor: 'num_segments',
className: 'padded',
Cell: ({ value: num_segments, original }) => {
const { datasource, unused, num_segments_to_load } = original as Datasource;
if (unused) {
return (
<a
onClick={() => {
this.setState({
datasourceFilter: addFilter(datasourceFilter, 'datasource', value),
});
}}
>
{value}
</a>
);
},
},
{
Header: 'Availability',
show: visibleColumns.shown('Availability'),
filterable: false,
minWidth: 200,
accessor: 'num_segments',
Cell: ({ value: num_segments, original }) => {
const { datasource, unused, num_segments_to_load } = original as Datasource;
if (unused) {
return (
<span>
<span style={{ color: DatasourcesView.UNUSED_COLOR }}>&#x25cf;&nbsp;</span>
Unused
</span>
);
}
const segmentsEl = (
<a onClick={() => goToSegments(datasource)}>
{pluralIfNeeded(num_segments, 'segment')}
</a>
);
if (typeof num_segments_to_load !== 'number' || typeof num_segments !== 'number') {
return '-';
} else if (num_segments_to_load === 0) {
return (
<span>
<span style={{ color: DatasourcesView.FULLY_AVAILABLE_COLOR }}>
&#x25cf;&nbsp;
</span>
Fully available ({segmentsEl})
</span>
);
} else {
const numAvailableSegments = num_segments - num_segments_to_load;
const percentAvailable = (
Math.floor((numAvailableSegments / num_segments) * 1000) / 10
).toFixed(1);
return (
<span>
<span style={{ color: DatasourcesView.PARTIALLY_AVAILABLE_COLOR }}>
{numAvailableSegments ? '\u25cf' : '\u25cb'}&nbsp;
</span>
{percentAvailable}% available ({segmentsEl})
</span>
);
}
},
sortMethod: (d1, d2) => {
const percentAvailable1 = d1.num_available / d1.num_total;
const percentAvailable2 = d2.num_available / d2.num_total;
return percentAvailable1 - percentAvailable2 || d1.num_total - d2.num_total;
},
},
{
Header: twoLines('Availability', 'detail'),
show: visibleColumns.shown('Availability detail'),
accessor: 'num_segments_to_load',
filterable: false,
minWidth: 100,
Cell: ({ original }) => {
const { num_segments_to_load, num_segments_to_drop } = original as Datasource;
return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
},
},
{
Header: twoLines('Total', 'data size'),
show: visibleColumns.shown('Total data size'),
accessor: 'total_data_size',
filterable: false,
width: 100,
Cell: ({ value }) => (
<BracedText text={formatTotalDataSize(value)} braces={totalDataSizeValues} />
),
},
{
Header: twoLines('Segment rows', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment rows'),
accessor: 'avg_segment_rows',
filterable: false,
width: 220,
Cell: ({ value, original }) => {
const { min_segment_rows, max_segment_rows } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_rows) ||
isNumberLikeNaN(max_segment_rows)
)
return '-';
return (
<>
<BracedText
text={formatSegmentRows(min_segment_rows)}
braces={minSegmentRowsValues}
/>{' '}
&nbsp;{' '}
<BracedText text={formatSegmentRows(value)} braces={avgSegmentRowsValues} />{' '}
&nbsp;{' '}
<BracedText
text={formatSegmentRows(max_segment_rows)}
braces={maxSegmentRowsValues}
/>
</>
);
},
},
{
Header: twoLines('Segment size', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment size'),
accessor: 'avg_segment_size',
filterable: false,
width: 270,
Cell: ({ value, original }) => {
const { min_segment_size, max_segment_size } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_size) ||
isNumberLikeNaN(max_segment_size)
)
return '-';
return (
<>
<BracedText
text={formatSegmentSize(min_segment_size)}
braces={minSegmentSizeValues}
/>{' '}
&nbsp;{' '}
<BracedText text={formatSegmentSize(value)} braces={avgSegmentSizeValues} />{' '}
&nbsp;{' '}
<BracedText
text={formatSegmentSize(max_segment_size)}
braces={maxSegmentSizeValues}
/>
</>
);
},
},
{
Header: twoLines('Segment', 'granularity'),
show: capabilities.hasSql() && visibleColumns.shown('Segment granularity'),
id: 'segment_granularity',
accessor: segmentGranularityCountsToRank,
filterable: false,
width: 100,
Cell: ({ original }) => {
const {
num_segments,
minute_aligned_segments,
hour_aligned_segments,
day_aligned_segments,
month_aligned_segments,
year_aligned_segments,
all_granularity_segments,
} = original as Datasource;
const segmentGranularities: string[] = [];
if (!num_segments || isNumberLikeNaN(year_aligned_segments)) return '-';
if (all_granularity_segments) {
segmentGranularities.push('All');
}
if (year_aligned_segments) {
segmentGranularities.push('Year');
}
if (month_aligned_segments !== year_aligned_segments) {
segmentGranularities.push('Month');
}
if (day_aligned_segments !== month_aligned_segments) {
segmentGranularities.push('Day');
}
if (hour_aligned_segments !== day_aligned_segments) {
segmentGranularities.push('Hour');
}
if (minute_aligned_segments !== hour_aligned_segments) {
segmentGranularities.push('Minute');
}
if (
Number(num_segments) - Number(all_granularity_segments) !==
Number(minute_aligned_segments)
) {
segmentGranularities.push('Sub minute');
}
return segmentGranularities.join(', ');
},
},
{
Header: twoLines('Total', 'rows'),
show: capabilities.hasSql() && visibleColumns.shown('Total rows'),
accessor: 'total_rows',
filterable: false,
width: 100,
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatTotalRows(value)}
braces={totalRowsValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Avg. row size', '(bytes)'),
show: capabilities.hasSql() && visibleColumns.shown('Avg. row size'),
accessor: 'avg_row_size',
filterable: false,
width: 100,
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatAvgRowSize(value)}
braces={avgRowSizeValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Replicated', 'size'),
show: capabilities.hasSql() && visibleColumns.shown('Replicated size'),
accessor: 'replicated_size',
filterable: false,
width: 100,
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText text={formatReplicatedSize(value)} braces={replicatedSizeValues} />
);
},
},
{
Header: 'Compaction',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Compaction'),
id: 'compactionStatus',
accessor: row => Boolean(row.compactionStatus),
filterable: false,
width: 150,
Cell: ({ original }) => {
const { datasource, compactionConfig, compactionStatus } = original as Datasource;
return (
<span
className="clickable-cell"
onClick={() =>
this.setState({
compactionDialogOpenOn: {
datasource,
compactionConfig,
},
})
}
>
{formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}&nbsp;
<ActionIcon icon={IconNames.EDIT} />
<span>
<span style={{ color: DatasourcesView.UNUSED_COLOR }}>&#x25cf;&nbsp;</span>
Unused
</span>
);
},
},
{
Header: twoLines('% Compacted', 'bytes / segments / intervals'),
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('% Compacted'),
id: 'percentCompacted',
width: 200,
accessor: ({ compactionStatus }) =>
compactionStatus && compactionStatus.bytesCompacted
? compactionStatus.bytesCompacted /
(compactionStatus.bytesAwaitingCompaction + compactionStatus.bytesCompacted)
: 0,
filterable: false,
Cell: ({ original }) => {
const { compactionStatus } = original as Datasource;
if (!compactionStatus || zeroCompactionStatus(compactionStatus)) {
return (
<>
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} />
</>
);
}
}
const segmentsEl = (
<a onClick={() => goToSegments(datasource)}>
{pluralIfNeeded(num_segments, 'segment')}
</a>
);
if (typeof num_segments_to_load !== 'number' || typeof num_segments !== 'number') {
return '-';
} else if (num_segments_to_load === 0) {
return (
<>
<BracedText
text={formatPercent(
progress(
compactionStatus.bytesCompacted,
compactionStatus.bytesAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.segmentCountCompacted,
compactionStatus.segmentCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.intervalCountCompacted,
compactionStatus.intervalCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>
</>
);
},
},
{
Header: twoLines('Left to be', 'compacted'),
show:
capabilities.hasCoordinatorAccess() && visibleColumns.shown('Left to be compacted'),
id: 'leftToBeCompacted',
width: 100,
accessor: ({ compactionStatus }) =>
(compactionStatus && compactionStatus.bytesAwaitingCompaction) || 0,
filterable: false,
Cell: ({ original }) => {
const { compactionStatus } = original as Datasource;
if (!compactionStatus) {
return <BracedText text="-" braces={leftToBeCompactedValues} />;
}
return (
<BracedText
text={formatLeftToBeCompacted(compactionStatus.bytesAwaitingCompaction)}
braces={leftToBeCompactedValues}
/>
);
},
},
{
Header: 'Retention',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Retention'),
id: 'retention',
accessor: row => row.rules.length,
filterable: false,
minWidth: 100,
Cell: ({ original }) => {
const { datasource, rules } = original as Datasource;
return (
<span
onClick={() =>
this.setState({
retentionDialogOpenOn: {
datasource,
rules,
},
})
}
className="clickable-cell"
>
{rules.length
? DatasourcesView.formatRules(rules)
: `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
&nbsp;
<ActionIcon icon={IconNames.EDIT} />
<span>
<span style={{ color: DatasourcesView.FULLY_AVAILABLE_COLOR }}>
&#x25cf;&nbsp;
</span>
Fully available ({segmentsEl})
</span>
);
},
},
{
Header: ACTION_COLUMN_LABEL,
show: visibleColumns.shown(ACTION_COLUMN_LABEL),
accessor: 'datasource',
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: ({ value: datasource, original }) => {
const { unused, rules, compactionConfig } = original as Datasource;
const datasourceActions = this.getDatasourceActions(
datasource,
unused,
rules,
compactionConfig,
);
} else {
const numAvailableSegments = num_segments - num_segments_to_load;
const percentAvailable = (
Math.floor((numAvailableSegments / num_segments) * 1000) / 10
).toFixed(1);
return (
<ActionCell
onDetail={() => {
this.setState({
datasourceTableActionDialogId: datasource,
actions: datasourceActions,
});
}}
actions={datasourceActions}
/>
<span>
<span style={{ color: DatasourcesView.PARTIALLY_AVAILABLE_COLOR }}>
{numAvailableSegments ? '\u25cf' : '\u25cb'}&nbsp;
</span>
{percentAvailable}% available ({segmentsEl})
</span>
);
},
}
},
]}
/>
{this.renderUnuseAction()}
{this.renderUseAction()}
{this.renderUseUnuseActionByInterval()}
{this.renderKillAction()}
{this.renderRetentionDialog()}
{this.renderCompactionDialog()}
{this.renderForceCompactAction()}
</>
sortMethod: (d1, d2) => {
const percentAvailable1 = d1.num_available / d1.num_total;
const percentAvailable2 = d2.num_available / d2.num_total;
return percentAvailable1 - percentAvailable2 || d1.num_total - d2.num_total;
},
},
{
Header: twoLines('Availability', 'detail'),
show: visibleColumns.shown('Availability detail'),
accessor: 'num_segments_to_load',
filterable: false,
width: 180,
className: 'padded',
Cell: ({ original }) => {
const { num_segments_to_load, num_segments_to_drop } = original as Datasource;
return formatLoadDrop(num_segments_to_load, num_segments_to_drop);
},
},
{
Header: twoLines('Total', 'data size'),
show: visibleColumns.shown('Total data size'),
accessor: 'total_data_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => (
<BracedText text={formatTotalDataSize(value)} braces={totalDataSizeValues} />
),
},
{
Header: twoLines('Segment rows', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment rows'),
accessor: 'avg_segment_rows',
filterable: false,
width: 220,
className: 'padded',
Cell: ({ value, original }) => {
const { min_segment_rows, max_segment_rows } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_rows) ||
isNumberLikeNaN(max_segment_rows)
)
return '-';
return (
<>
<BracedText
text={formatSegmentRows(min_segment_rows)}
braces={minSegmentRowsValues}
/>{' '}
&nbsp;{' '}
<BracedText text={formatSegmentRows(value)} braces={avgSegmentRowsValues} />{' '}
&nbsp;{' '}
<BracedText
text={formatSegmentRows(max_segment_rows)}
braces={maxSegmentRowsValues}
/>
</>
);
},
},
{
Header: twoLines('Segment size', 'minimum / average / maximum'),
show: capabilities.hasSql() && visibleColumns.shown('Segment size'),
accessor: 'avg_segment_size',
filterable: false,
width: 270,
className: 'padded',
Cell: ({ value, original }) => {
const { min_segment_size, max_segment_size } = original as Datasource;
if (
isNumberLikeNaN(value) ||
isNumberLikeNaN(min_segment_size) ||
isNumberLikeNaN(max_segment_size)
)
return '-';
return (
<>
<BracedText
text={formatSegmentSize(min_segment_size)}
braces={minSegmentSizeValues}
/>{' '}
&nbsp;{' '}
<BracedText text={formatSegmentSize(value)} braces={avgSegmentSizeValues} />{' '}
&nbsp;{' '}
<BracedText
text={formatSegmentSize(max_segment_size)}
braces={maxSegmentSizeValues}
/>
</>
);
},
},
{
Header: twoLines('Segment', 'granularity'),
show: capabilities.hasSql() && visibleColumns.shown('Segment granularity'),
id: 'segment_granularity',
accessor: segmentGranularityCountsToRank,
filterable: false,
width: 100,
className: 'padded',
Cell: ({ original }) => {
const {
num_segments,
minute_aligned_segments,
hour_aligned_segments,
day_aligned_segments,
month_aligned_segments,
year_aligned_segments,
all_granularity_segments,
} = original as Datasource;
const segmentGranularities: string[] = [];
if (!num_segments || isNumberLikeNaN(year_aligned_segments)) return '-';
if (all_granularity_segments) {
segmentGranularities.push('All');
}
if (year_aligned_segments) {
segmentGranularities.push('Year');
}
if (month_aligned_segments !== year_aligned_segments) {
segmentGranularities.push('Month');
}
if (day_aligned_segments !== month_aligned_segments) {
segmentGranularities.push('Day');
}
if (hour_aligned_segments !== day_aligned_segments) {
segmentGranularities.push('Hour');
}
if (minute_aligned_segments !== hour_aligned_segments) {
segmentGranularities.push('Minute');
}
if (
Number(num_segments) - Number(all_granularity_segments) !==
Number(minute_aligned_segments)
) {
segmentGranularities.push('Sub minute');
}
return segmentGranularities.join(', ');
},
},
{
Header: twoLines('Total', 'rows'),
show: capabilities.hasSql() && visibleColumns.shown('Total rows'),
accessor: 'total_rows',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatTotalRows(value)}
braces={totalRowsValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Avg. row size', '(bytes)'),
show: capabilities.hasSql() && visibleColumns.shown('Avg. row size'),
accessor: 'avg_row_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText
text={formatAvgRowSize(value)}
braces={avgRowSizeValues}
unselectableThousandsSeparator
/>
);
},
},
{
Header: twoLines('Replicated', 'size'),
show: capabilities.hasSql() && visibleColumns.shown('Replicated size'),
accessor: 'replicated_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
<BracedText text={formatReplicatedSize(value)} braces={replicatedSizeValues} />
);
},
},
{
Header: 'Compaction',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Compaction'),
id: 'compactionStatus',
accessor: row => Boolean(row.compactionStatus),
filterable: false,
width: 150,
Cell: ({ original }) => {
const { datasource, compactionConfig, compactionStatus } = original as Datasource;
return (
<TableClickableCell
onClick={() =>
this.setState({
compactionDialogOpenOn: {
datasource,
compactionConfig,
},
})
}
hoverIcon={IconNames.EDIT}
>
{formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}
</TableClickableCell>
);
},
},
{
Header: twoLines('% Compacted', 'bytes / segments / intervals'),
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('% Compacted'),
id: 'percentCompacted',
width: 200,
accessor: ({ compactionStatus }) =>
compactionStatus && compactionStatus.bytesCompacted
? compactionStatus.bytesCompacted /
(compactionStatus.bytesAwaitingCompaction + compactionStatus.bytesCompacted)
: 0,
filterable: false,
className: 'padded',
Cell: ({ original }) => {
const { compactionStatus } = original as Datasource;
if (!compactionStatus || zeroCompactionStatus(compactionStatus)) {
return (
<>
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} /> &nbsp;{' '}
<BracedText text="-" braces={PERCENT_BRACES} />
</>
);
}
return (
<>
<BracedText
text={formatPercent(
progress(
compactionStatus.bytesCompacted,
compactionStatus.bytesAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.segmentCountCompacted,
compactionStatus.segmentCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>{' '}
&nbsp;{' '}
<BracedText
text={formatPercent(
progress(
compactionStatus.intervalCountCompacted,
compactionStatus.intervalCountAwaitingCompaction,
),
)}
braces={PERCENT_BRACES}
/>
</>
);
},
},
{
Header: twoLines('Left to be', 'compacted'),
show:
capabilities.hasCoordinatorAccess() && visibleColumns.shown('Left to be compacted'),
id: 'leftToBeCompacted',
width: 100,
accessor: ({ compactionStatus }) =>
(compactionStatus && compactionStatus.bytesAwaitingCompaction) || 0,
filterable: false,
className: 'padded',
Cell: ({ original }) => {
const { compactionStatus } = original as Datasource;
if (!compactionStatus) {
return <BracedText text="-" braces={leftToBeCompactedValues} />;
}
return (
<BracedText
text={formatLeftToBeCompacted(compactionStatus.bytesAwaitingCompaction)}
braces={leftToBeCompactedValues}
/>
);
},
},
{
Header: 'Retention',
show: capabilities.hasCoordinatorAccess() && visibleColumns.shown('Retention'),
id: 'retention',
accessor: row => row.rules.length,
filterable: false,
width: 200,
Cell: ({ original }) => {
const { datasource, rules } = original as Datasource;
return (
<TableClickableCell
onClick={() =>
this.setState({
retentionDialogOpenOn: {
datasource,
rules,
},
})
}
hoverIcon={IconNames.EDIT}
>
{rules.length
? DatasourcesView.formatRules(rules)
: `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
</TableClickableCell>
);
},
},
{
Header: ACTION_COLUMN_LABEL,
show: visibleColumns.shown(ACTION_COLUMN_LABEL),
accessor: 'datasource',
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: ({ value: datasource, original }) => {
const { unused, rules, compactionConfig } = original as Datasource;
const datasourceActions = this.getDatasourceActions(
datasource,
unused,
rules,
compactionConfig,
);
return (
<ActionCell
onDetail={() => {
this.onDetail(original);
}}
actions={datasourceActions}
/>
);
},
},
]}
/>
);
}
@ -1466,7 +1464,7 @@ ORDER BY 1`;
return (
<div
className={classNames('datasource-view app-view', {
className={classNames('datasources-view app-view', {
'show-segment-timeline': showSegmentTimeline,
})}
>
@ -1505,7 +1503,7 @@ ORDER BY 1`;
/>
</ViewControlBar>
{showSegmentTimeline && <SegmentTimeline capabilities={capabilities} />}
{this.renderDatasourceTable()}
{this.renderDatasourcesTable()}
{datasourceTableActionDialogId && (
<DatasourceTableActionDialog
datasourceId={datasourceTableActionDialogId}
@ -1513,6 +1511,13 @@ ORDER BY 1`;
onClose={() => this.setState({ datasourceTableActionDialogId: undefined })}
/>
)}
{this.renderUnuseAction()}
{this.renderUseAction()}
{this.renderUseUnuseActionByInterval()}
{this.renderKillAction()}
{this.renderRetentionDialog()}
{this.renderCompactionDialog()}
{this.renderForceCompactAction()}
</div>
);
}

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
export * from './datasource-view/datasource-view';
export * from './datasources-view/datasources-view';
export * from './home-view/home-view';
export * from './ingestion-view/ingestion-view';
export * from './load-data-view/load-data-view';

View File

@ -154,6 +154,7 @@ exports[`IngestionView matches snapshot 1`] = `
columns={
Array [
Object {
"Cell": [Function],
"Header": "Datasource",
"accessor": "supervisor_id",
"id": "datasource",
@ -161,21 +162,23 @@ exports[`IngestionView matches snapshot 1`] = `
"width": 300,
},
Object {
"Cell": [Function],
"Header": "Type",
"accessor": [Function],
"id": "type",
"accessor": "type",
"show": true,
"width": 100,
},
Object {
"Cell": [Function],
"Header": "Topic/Stream",
"accessor": [Function],
"id": "source",
"accessor": "source",
"show": true,
"width": 300,
},
Object {
"Cell": [Function],
"Header": "Status",
"accessor": [Function],
"accessor": "detailed_state",
"id": "status",
"show": true,
"width": 300,
@ -426,10 +429,11 @@ exports[`IngestionView matches snapshot 1`] = `
Array [
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Task ID",
"accessor": "task_id",
"show": true,
"width": 500,
"width": 440,
},
Object {
"Aggregated": [Function],
@ -451,16 +455,19 @@ exports[`IngestionView matches snapshot 1`] = `
"Header": "Datasource",
"accessor": "datasource",
"show": true,
"width": 200,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Location",
"accessor": "location",
"filterMethod": [Function],
"show": true,
"width": 200,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Created time",
"accessor": "created_time",
"show": true,
@ -470,6 +477,7 @@ exports[`IngestionView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Status",
"accessor": [Function],
"className": "padded",
"filterMethod": [Function],
"id": "status",
"show": true,
@ -481,9 +489,10 @@ exports[`IngestionView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Duration",
"accessor": "duration",
"className": "padded",
"filterable": false,
"show": true,
"width": 70,
"width": 80,
},
Object {
"Aggregated": [Function],

View File

@ -29,7 +29,9 @@ import {
ActionCell,
MoreButton,
RefreshButton,
TableClickableCell,
TableColumnSelector,
TableFilterableCell,
ViewControlBar,
} from '../../components';
import {
@ -38,11 +40,14 @@ import {
SupervisorTableActionDialog,
TaskTableActionDialog,
} from '../../dialogs';
import {
booleanCustomTableFilter,
SMALL_TABLE_PAGE_SIZE,
SMALL_TABLE_PAGE_SIZE_OPTIONS,
syncFilterClauseById,
} from '../../react-table';
import { Api, AppToaster } from '../../singletons';
import {
addFilter,
addFilterRaw,
booleanCustomTableFilter,
Capabilities,
deepGet,
formatDuration,
@ -56,8 +61,6 @@ import {
queryDruidSql,
QueryManager,
QueryState,
SMALL_TABLE_PAGE_SIZE,
SMALL_TABLE_PAGE_SIZE_OPTIONS,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
@ -88,7 +91,7 @@ interface SupervisorQueryResultRow {
source: string;
state: string;
detailed_state: string;
suspended: number;
suspended: boolean;
}
interface TaskQueryResultRow {
@ -196,7 +199,7 @@ export class IngestionView extends React.PureComponent<IngestionViewProps, Inges
};
static SUPERVISOR_SQL = `SELECT
"supervisor_id", "type", "source", "state", "detailed_state", "suspended"
"supervisor_id", "type", "source", "state", "detailed_state", "suspended" = 1 AS "suspended"
FROM sys.supervisors
ORDER BY "supervisor_id"`;
@ -266,7 +269,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
'n/a',
state: deepGet(sup, 'state'),
detailed_state: deepGet(sup, 'detailedState'),
suspended: Number(deepGet(sup, 'suspended')),
suspended: Boolean(deepGet(sup, 'suspended')),
};
});
} else {
@ -552,100 +555,129 @@ ORDER BY "rank" DESC, "created_time" DESC`;
);
}
renderSupervisorTable() {
private renderSupervisorFilterableCell(field: string) {
const { supervisorFilter } = this.state;
return (row: { value: any }) => (
<TableFilterableCell
field={field}
value={row.value}
filters={supervisorFilter}
onFiltersChange={filters => this.setState({ supervisorFilter: filters })}
>
{row.value}
</TableFilterableCell>
);
}
private onSupervisorDetail(supervisor: SupervisorQueryResultRow) {
this.setState({
supervisorTableActionDialogId: supervisor.supervisor_id,
supervisorTableActionDialogActions: this.getSupervisorActions(
supervisor.supervisor_id,
supervisor.suspended,
supervisor.type,
),
});
}
private renderSupervisorTable() {
const { supervisorsState, hiddenSupervisorColumns, taskFilter, supervisorFilter } = this.state;
const supervisors = supervisorsState.data || [];
return (
<>
<ReactTable
data={supervisors}
loading={supervisorsState.loading}
noDataText={
supervisorsState.isEmpty() ? 'No supervisors' : supervisorsState.getErrorMessage() || ''
}
filtered={supervisorFilter}
onFilteredChange={filtered => {
const datasourceFilter = filtered.find(filter => filter.id === 'datasource');
let newTaskFilter = taskFilter.filter(filter => filter.id !== 'datasource');
if (datasourceFilter) {
newTaskFilter = addFilterRaw(
newTaskFilter,
datasourceFilter.id,
datasourceFilter.value,
);
}
this.setState({ supervisorFilter: filtered, taskFilter: newTaskFilter });
}}
filterable
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={supervisors.length > SMALL_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Datasource',
id: 'datasource',
accessor: 'supervisor_id',
width: 300,
show: hiddenSupervisorColumns.shown('Datasource'),
},
{
Header: 'Type',
id: 'type',
accessor: row => row.type,
show: hiddenSupervisorColumns.shown('Type'),
},
{
Header: 'Topic/Stream',
id: 'source',
accessor: row => row.source,
show: hiddenSupervisorColumns.shown('Topic/Stream'),
},
{
Header: 'Status',
id: 'status',
width: 300,
accessor: row => row.detailed_state,
Cell: row => (
<ReactTable
data={supervisors}
loading={supervisorsState.loading}
noDataText={
supervisorsState.isEmpty() ? 'No supervisors' : supervisorsState.getErrorMessage() || ''
}
filtered={supervisorFilter}
onFilteredChange={(filtered, column) => {
this.setState({
supervisorFilter: filtered,
taskFilter:
column.id === 'datasource'
? syncFilterClauseById(taskFilter, filtered, 'datasource')
: taskFilter,
});
}}
filterable
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={supervisors.length > SMALL_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Datasource',
id: 'datasource',
accessor: 'supervisor_id',
width: 300,
show: hiddenSupervisorColumns.shown('Datasource'),
Cell: ({ value, original }) => (
<TableClickableCell
onClick={() => this.onSupervisorDetail(original)}
hoverIcon={IconNames.EDIT}
>
{value}
</TableClickableCell>
),
},
{
Header: 'Type',
accessor: 'type',
width: 100,
Cell: this.renderSupervisorFilterableCell('type'),
show: hiddenSupervisorColumns.shown('Type'),
},
{
Header: 'Topic/Stream',
accessor: 'source',
width: 300,
Cell: this.renderSupervisorFilterableCell('source'),
show: hiddenSupervisorColumns.shown('Topic/Stream'),
},
{
Header: 'Status',
id: 'status',
width: 300,
accessor: 'detailed_state',
Cell: row => (
<TableFilterableCell
field="status"
value={row.value}
filters={supervisorFilter}
onFiltersChange={filters => this.setState({ supervisorFilter: filters })}
>
<span>
<span style={{ color: stateToColor(row.original.state) }}>&#x25cf;&nbsp;</span>
{row.value}
</span>
),
show: hiddenSupervisorColumns.shown('Status'),
</TableFilterableCell>
),
show: hiddenSupervisorColumns.shown('Status'),
},
{
Header: ACTION_COLUMN_LABEL,
id: ACTION_COLUMN_ID,
accessor: 'supervisor_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: row => {
const id = row.value;
const type = row.original.type;
const supervisorSuspended = row.original.suspended;
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
return (
<ActionCell
onDetail={() => this.onSupervisorDetail(row.original)}
actions={supervisorActions}
/>
);
},
{
Header: ACTION_COLUMN_LABEL,
id: ACTION_COLUMN_ID,
accessor: 'supervisor_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: row => {
const id = row.value;
const type = row.original.type;
const supervisorSuspended = row.original.suspended;
const supervisorActions = this.getSupervisorActions(id, supervisorSuspended, type);
return (
<ActionCell
onDetail={() =>
this.setState({
supervisorTableActionDialogId: id,
supervisorTableActionDialogActions: supervisorActions,
})
}
actions={supervisorActions}
/>
);
},
show: hiddenSupervisorColumns.shown(ACTION_COLUMN_LABEL),
},
]}
/>
{this.renderResumeSupervisorAction()}
{this.renderSuspendSupervisorAction()}
{this.renderResetSupervisorAction()}
{this.renderTerminateSupervisorAction()}
</>
show: hiddenSupervisorColumns.shown(ACTION_COLUMN_LABEL),
},
]}
/>
);
}
@ -712,208 +744,198 @@ ORDER BY "rank" DESC, "created_time" DESC`;
);
}
renderTaskTable() {
private renderTaskFilterableCell(field: string) {
const { taskFilter } = this.state;
return (row: { value: any }) => (
<TableFilterableCell
field={field}
value={row.value}
filters={taskFilter}
onFiltersChange={filters => this.setState({ taskFilter: filters })}
>
{row.value}
</TableFilterableCell>
);
}
private onTaskDetail(task: TaskQueryResultRow) {
this.setState({
taskTableActionDialogId: task.task_id,
taskTableActionDialogStatus: task.status,
taskTableActionDialogActions: this.getTaskActions(
task.task_id,
task.datasource,
task.status,
task.type,
),
});
}
private renderTaskTable() {
const { tasksState, taskFilter, groupTasksBy, hiddenTaskColumns, supervisorFilter } =
this.state;
const tasks = tasksState.data || [];
return (
<>
<ReactTable
data={tasks}
loading={tasksState.loading}
noDataText={tasksState.isEmpty() ? 'No tasks' : tasksState.getErrorMessage() || ''}
filterable
filtered={taskFilter}
onFilteredChange={filtered => {
const datasourceFilter = filtered.find(filter => filter.id === 'datasource');
let newSupervisorFilter = supervisorFilter.filter(filter => filter.id !== 'datasource');
if (datasourceFilter) {
newSupervisorFilter = addFilterRaw(
newSupervisorFilter,
datasourceFilter.id,
datasourceFilter.value,
<ReactTable
data={tasks}
loading={tasksState.loading}
noDataText={tasksState.isEmpty() ? 'No tasks' : tasksState.getErrorMessage() || ''}
filterable
filtered={taskFilter}
onFilteredChange={(filtered, column) => {
this.setState({
supervisorFilter:
column.id === 'datasource'
? syncFilterClauseById(supervisorFilter, filtered, 'datasource')
: supervisorFilter,
taskFilter: filtered,
});
}}
defaultSorted={[{ id: 'status', desc: true }]}
pivotBy={groupTasksBy ? [groupTasksBy] : []}
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={tasks.length > SMALL_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Task ID',
accessor: 'task_id',
width: 440,
Cell: ({ value, original }) => (
<TableClickableCell
onClick={() => this.onTaskDetail(original)}
hoverIcon={IconNames.EDIT}
>
{value}
</TableClickableCell>
),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Task ID'),
},
{
Header: 'Group ID',
accessor: 'group_id',
width: 300,
Cell: this.renderTaskFilterableCell('group_id'),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Group ID'),
},
{
Header: 'Type',
accessor: 'type',
width: 140,
Cell: this.renderTaskFilterableCell('type'),
show: hiddenTaskColumns.shown('Type'),
},
{
Header: 'Datasource',
accessor: 'datasource',
width: 200,
Cell: this.renderTaskFilterableCell('datasource'),
show: hiddenTaskColumns.shown('Datasource'),
},
{
Header: 'Location',
accessor: 'location',
width: 200,
Cell: this.renderTaskFilterableCell('location'),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Location'),
},
{
Header: 'Created time',
accessor: 'created_time',
width: 190,
Cell: this.renderTaskFilterableCell('created_time'),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Created time'),
},
{
Header: 'Status',
id: 'status',
width: 110,
className: 'padded',
accessor: row => ({
status: row.status,
created_time: row.created_time,
toString: () => row.status,
}),
Cell: row => {
if (row.aggregated) return '';
const { status } = row.original;
const errorMsg = row.original.error_msg;
return (
<span>
<span style={{ color: statusToColor(status) }}>&#x25cf;&nbsp;</span>
{status}
{errorMsg && (
<a onClick={() => this.setState({ alertErrorMsg: errorMsg })} title={errorMsg}>
&nbsp;?
</a>
)}
</span>
);
}
this.setState({ supervisorFilter: newSupervisorFilter, taskFilter: filtered });
}}
defaultSorted={[{ id: 'status', desc: true }]}
pivotBy={groupTasksBy ? [groupTasksBy] : []}
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={tasks.length > SMALL_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Task ID',
accessor: 'task_id',
width: 500,
Aggregated: () => '',
show: hiddenTaskColumns.shown('Task ID'),
},
{
Header: 'Group ID',
accessor: 'group_id',
width: 300,
Aggregated: () => '',
Cell: row => {
const value = row.value;
return (
<a
onClick={() => {
this.setState({ taskFilter: addFilter(taskFilter, 'group_id', value) });
}}
>
{value}
</a>
);
},
show: hiddenTaskColumns.shown('Group ID'),
},
{
Header: 'Type',
accessor: 'type',
width: 140,
Cell: row => {
const value = row.value;
return (
<a
onClick={() => {
this.setState({ taskFilter: addFilter(taskFilter, 'type', value) });
}}
>
{value}
</a>
);
},
show: hiddenTaskColumns.shown('Type'),
},
{
Header: 'Datasource',
accessor: 'datasource',
Cell: row => {
const value = row.value;
return (
<a
onClick={() => {
this.setState({ taskFilter: addFilter(taskFilter, 'datasource', value) });
}}
>
{value}
</a>
);
},
show: hiddenTaskColumns.shown('Datasource'),
},
sortMethod: (d1, d2) => {
const typeofD1 = typeof d1;
const typeofD2 = typeof d2;
if (typeofD1 !== typeofD2) return 0;
switch (typeofD1) {
case 'string':
return IngestionView.statusRanking[d1] - IngestionView.statusRanking[d2];
{
Header: 'Location',
accessor: 'location',
Aggregated: () => '',
filterMethod: (filter: Filter, row: any) => {
return booleanCustomTableFilter(filter, row.location);
},
show: hiddenTaskColumns.shown('Location'),
},
{
Header: 'Created time',
accessor: 'created_time',
width: 190,
Aggregated: () => '',
show: hiddenTaskColumns.shown('Created time'),
},
{
Header: 'Status',
id: 'status',
width: 110,
accessor: row => ({
status: row.status,
created_time: row.created_time,
toString: () => row.status,
}),
Cell: row => {
if (row.aggregated) return '';
const { status } = row.original;
const errorMsg = row.original.error_msg;
return (
<span>
<span style={{ color: statusToColor(status) }}>&#x25cf;&nbsp;</span>
{status}
{errorMsg && (
<a
onClick={() => this.setState({ alertErrorMsg: errorMsg })}
title={errorMsg}
>
&nbsp;?
</a>
)}
</span>
);
},
sortMethod: (d1, d2) => {
const typeofD1 = typeof d1;
const typeofD2 = typeof d2;
if (typeofD1 !== typeofD2) return 0;
switch (typeofD1) {
case 'string':
return IngestionView.statusRanking[d1] - IngestionView.statusRanking[d2];
case 'object':
return (
IngestionView.statusRanking[d1.status] -
IngestionView.statusRanking[d2.status] ||
d1.created_time.localeCompare(d2.created_time)
);
case 'object':
return (
IngestionView.statusRanking[d1.status] -
IngestionView.statusRanking[d2.status] ||
d1.created_time.localeCompare(d2.created_time)
);
default:
return 0;
}
},
filterMethod: (filter: Filter, row: any) => {
return booleanCustomTableFilter(filter, row.status.status);
},
show: hiddenTaskColumns.shown('Status'),
default:
return 0;
}
},
{
Header: 'Duration',
accessor: 'duration',
width: 70,
filterable: false,
Cell: row => (row.value > 0 ? formatDuration(row.value) : ''),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Duration'),
filterMethod: (filter: Filter, row: any) => {
return booleanCustomTableFilter(filter, row.status.status);
},
{
Header: ACTION_COLUMN_LABEL,
id: ACTION_COLUMN_ID,
accessor: 'task_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: row => {
if (row.aggregated) return '';
const id = row.value;
const type = row.row.type;
const { datasource, status } = row.original;
const taskActions = this.getTaskActions(id, datasource, status, type);
return (
<ActionCell
onDetail={() =>
this.setState({
taskTableActionDialogId: id,
taskTableActionDialogStatus: status,
taskTableActionDialogActions: taskActions,
})
}
actions={taskActions}
/>
);
},
Aggregated: () => '',
show: hiddenTaskColumns.shown(ACTION_COLUMN_LABEL),
show: hiddenTaskColumns.shown('Status'),
},
{
Header: 'Duration',
accessor: 'duration',
width: 80,
filterable: false,
className: 'padded',
Cell: row => (row.value > 0 ? formatDuration(row.value) : ''),
Aggregated: () => '',
show: hiddenTaskColumns.shown('Duration'),
},
{
Header: ACTION_COLUMN_LABEL,
id: ACTION_COLUMN_ID,
accessor: 'task_id',
width: ACTION_COLUMN_WIDTH,
filterable: false,
Cell: row => {
if (row.aggregated) return '';
const id = row.value;
const type = row.row.type;
const { datasource, status } = row.original;
const taskActions = this.getTaskActions(id, datasource, status, type);
return (
<ActionCell
onDetail={() => this.onTaskDetail(row.original)}
actions={taskActions}
/>
);
},
]}
/>
{this.renderKillTaskAction()}
</>
Aggregated: () => '',
show: hiddenTaskColumns.shown(ACTION_COLUMN_LABEL),
},
]}
/>
);
}
@ -1164,6 +1186,11 @@ ORDER BY "rank" DESC, "created_time" DESC`;
{this.renderTaskTable()}
</div>
</SplitterLayout>
{this.renderResumeSupervisorAction()}
{this.renderSuspendSupervisorAction()}
{this.renderResetSupervisorAction()}
{this.renderTerminateSupervisorAction()}
{this.renderKillTaskAction()}
{supervisorSpecDialogOpen && (
<SpecDialog
onClose={this.closeSpecDialogs}

View File

@ -2,7 +2,7 @@
exports[`FilterTable matches snapshot 1`] = `
<div
class="ReactTable filter-table -striped -highlight"
class="ReactTable filter-table -striped -highlight padded-header"
>
<div
class="rt-table"
@ -10,7 +10,7 @@ exports[`FilterTable matches snapshot 1`] = `
>
<div
class="rt-thead -header"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr"
@ -19,7 +19,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-th rt-resizable-header"
role="columnheader"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
tabindex="-1"
>
<div
@ -49,7 +49,7 @@ exports[`FilterTable matches snapshot 1`] = `
</div>
<div
class="rt-tbody"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr-group"
@ -62,13 +62,13 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span
<div
class="table-cell plain"
>
hello
</span>
</div>
</div>
</div>
</div>
@ -83,7 +83,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -102,7 +102,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -121,7 +121,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -140,7 +140,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -159,7 +159,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -178,7 +178,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -197,7 +197,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -216,7 +216,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -235,7 +235,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -254,7 +254,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -273,7 +273,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -292,7 +292,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -311,7 +311,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -330,7 +330,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -349,7 +349,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -368,7 +368,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -387,7 +387,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -406,7 +406,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -425,7 +425,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -444,7 +444,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -463,7 +463,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -482,7 +482,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -501,7 +501,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -520,7 +520,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -539,7 +539,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -558,7 +558,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -577,7 +577,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -596,7 +596,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -615,7 +615,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -634,7 +634,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -653,7 +653,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -672,7 +672,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -691,7 +691,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -710,7 +710,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -729,7 +729,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -748,7 +748,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -767,7 +767,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -786,7 +786,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -805,7 +805,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -824,7 +824,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -843,7 +843,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -862,7 +862,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -881,7 +881,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -900,7 +900,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -919,7 +919,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -938,7 +938,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -957,7 +957,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -976,7 +976,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -995,7 +995,7 @@ exports[`FilterTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 

View File

@ -23,11 +23,11 @@ import ReactTable from 'react-table';
import { TableCell } from '../../../components';
import { DruidFilter, getFilterDimension } from '../../../druid-models';
import {
caseInsensitiveContains,
filterMap,
DEFAULT_TABLE_CLASS_NAME,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../../utils';
} from '../../../react-table';
import { caseInsensitiveContains, filterMap } from '../../../utils';
import { SampleEntry, SampleHeaderAndRows } from '../../../utils/sampler';
import './filter-table.scss';
@ -55,7 +55,7 @@ export const FilterTable = React.memo(function FilterTable(props: FilterTablePro
return (
<ReactTable
className="filter-table -striped -highlight"
className={classNames('filter-table', DEFAULT_TABLE_CLASS_NAME)}
data={sampleData.rows}
sortable={false}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
@ -96,6 +96,7 @@ export const FilterTable = React.memo(function FilterTable(props: FilterTablePro
className: columnClassName,
id: String(i),
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
width: 140,
Cell: function FilterTableCell(row) {
return <TableCell value={timestamp ? new Date(row.value) : row.value} />;
},

View File

@ -2,7 +2,7 @@
exports[`ParseDataTable matches snapshot 1`] = `
<div
class="ReactTable parse-data-table -striped -highlight"
class="ReactTable parse-data-table -striped -highlight padded-header"
>
<div
class="rt-table"
@ -10,7 +10,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
>
<div
class="rt-thead -header"
style="min-width: 135px;"
style="min-width: 175px;"
>
<div
class="rt-tr"
@ -29,7 +29,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-th rt-resizable-header"
role="columnheader"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
tabindex="-1"
>
<div
@ -59,7 +59,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
</div>
<div
class="rt-tbody"
style="min-width: 135px;"
style="min-width: 175px;"
>
<div
class="rt-tr-group"
@ -83,13 +83,13 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span
<div
class="table-cell plain"
>
hello
</span>
</div>
</div>
</div>
</div>
@ -113,7 +113,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -141,7 +141,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -169,7 +169,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -197,7 +197,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -225,7 +225,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -253,7 +253,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -281,7 +281,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -309,7 +309,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -337,7 +337,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -365,7 +365,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -393,7 +393,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -421,7 +421,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -449,7 +449,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -477,7 +477,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -505,7 +505,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -533,7 +533,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -561,7 +561,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -589,7 +589,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -617,7 +617,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -645,7 +645,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -673,7 +673,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -701,7 +701,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -729,7 +729,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -757,7 +757,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -785,7 +785,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -813,7 +813,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -841,7 +841,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -869,7 +869,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -897,7 +897,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -925,7 +925,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -953,7 +953,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -981,7 +981,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1009,7 +1009,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1037,7 +1037,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1065,7 +1065,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1093,7 +1093,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1121,7 +1121,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1149,7 +1149,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1177,7 +1177,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1205,7 +1205,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1233,7 +1233,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1261,7 +1261,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1289,7 +1289,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1317,7 +1317,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1345,7 +1345,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1373,7 +1373,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1401,7 +1401,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1429,7 +1429,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -1457,7 +1457,7 @@ exports[`ParseDataTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 

View File

@ -25,11 +25,11 @@ import { TableCell } from '../../../components';
import { TableCellUnparseable } from '../../../components/table-cell-unparseable/table-cell-unparseable';
import { FlattenField } from '../../../druid-models';
import {
caseInsensitiveContains,
filterMap,
DEFAULT_TABLE_CLASS_NAME,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../../utils';
} from '../../../react-table';
import { caseInsensitiveContains, filterMap } from '../../../utils';
import { SampleEntry, SampleHeaderAndRows } from '../../../utils/sampler';
import './parse-data-table.scss';
@ -58,7 +58,7 @@ export const ParseDataTable = React.memo(function ParseDataTable(props: ParseDat
const key = useInput ? 'input' : 'parsed';
return (
<ReactTable
className="parse-data-table -striped -highlight"
className={classNames('parse-data-table', DEFAULT_TABLE_CLASS_NAME)}
data={sampleData.rows}
sortable={false}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
@ -86,6 +86,7 @@ export const ParseDataTable = React.memo(function ParseDataTable(props: ParseDat
),
id: String(i),
accessor: (row: SampleEntry) => (row[key] ? row[key]![columnName] : null),
width: 140,
Cell: function ParseDataTableCell(row) {
if (row.original.unparseable) {
return <TableCellUnparseable />;

View File

@ -2,7 +2,7 @@
exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="ReactTable parse-time-table -striped -highlight"
class="ReactTable parse-time-table -striped -highlight padded-header"
>
<div
class="rt-table"
@ -10,7 +10,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
>
<div
class="rt-thead -header"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr"
@ -19,7 +19,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-th rt-resizable-header"
role="columnheader"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
tabindex="-1"
>
<div
@ -49,7 +49,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
</div>
<div
class="rt-tbody"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr-group"
@ -62,13 +62,13 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span
<div
class="table-cell plain"
>
hello
</span>
</div>
</div>
</div>
</div>
@ -83,7 +83,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -102,7 +102,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -121,7 +121,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -140,7 +140,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -159,7 +159,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -178,7 +178,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -197,7 +197,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -216,7 +216,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -235,7 +235,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -254,7 +254,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -273,7 +273,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -292,7 +292,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -311,7 +311,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -330,7 +330,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -349,7 +349,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -368,7 +368,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -387,7 +387,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -406,7 +406,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -425,7 +425,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -444,7 +444,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -463,7 +463,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -482,7 +482,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -501,7 +501,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -520,7 +520,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -539,7 +539,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -558,7 +558,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -577,7 +577,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -596,7 +596,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -615,7 +615,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -634,7 +634,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -653,7 +653,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -672,7 +672,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -691,7 +691,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -710,7 +710,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -729,7 +729,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -748,7 +748,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -767,7 +767,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -786,7 +786,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -805,7 +805,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -824,7 +824,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -843,7 +843,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -862,7 +862,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -881,7 +881,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -900,7 +900,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -919,7 +919,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -938,7 +938,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -957,7 +957,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -976,7 +976,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -995,7 +995,7 @@ exports[`ParseTimeTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 

View File

@ -30,11 +30,11 @@ import {
TimestampSpec,
} from '../../../druid-models';
import {
caseInsensitiveContains,
filterMap,
DEFAULT_TABLE_CLASS_NAME,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../../utils';
} from '../../../react-table';
import { caseInsensitiveContains, filterMap } from '../../../utils';
import { SampleEntry, SampleHeaderAndRows } from '../../../utils/sampler';
import './parse-time-table.scss';
@ -74,7 +74,7 @@ export const ParseTimeTable = React.memo(function ParseTimeTable(props: ParseTim
return (
<ReactTable
className="parse-time-table -striped -highlight"
className={classNames('parse-time-table', DEFAULT_TABLE_CLASS_NAME)}
data={headerAndRows.rows}
sortable={false}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
@ -133,7 +133,7 @@ export const ParseTimeTable = React.memo(function ParseTimeTable(props: ParseTim
}
return <TableCell value={isTimestamp ? new Date(row.value) : row.value} />;
},
minWidth: isTimestamp ? 200 : 100,
width: isTimestamp ? 200 : 140,
resizable: !isTimestamp,
};
},

View File

@ -2,7 +2,7 @@
exports[`SchemaTable matches snapshot 1`] = `
<div
class="ReactTable schema-table -striped -highlight"
class="ReactTable schema-table -striped -highlight padded-header"
>
<div
class="rt-table"
@ -10,7 +10,7 @@ exports[`SchemaTable matches snapshot 1`] = `
>
<div
class="rt-thead -header"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr"
@ -19,7 +19,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-th dimension string rt-resizable-header"
role="columnheader"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
tabindex="-1"
>
<div
@ -49,7 +49,7 @@ exports[`SchemaTable matches snapshot 1`] = `
</div>
<div
class="rt-tbody"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr-group"
@ -62,13 +62,13 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span
<div
class="table-cell plain"
>
hello
</span>
</div>
</div>
</div>
</div>
@ -83,7 +83,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -102,7 +102,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -121,7 +121,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -140,7 +140,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -159,7 +159,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -178,7 +178,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -197,7 +197,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -216,7 +216,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -235,7 +235,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -254,7 +254,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -273,7 +273,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -292,7 +292,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -311,7 +311,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -330,7 +330,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -349,7 +349,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -368,7 +368,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -387,7 +387,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -406,7 +406,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -425,7 +425,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -444,7 +444,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -463,7 +463,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -482,7 +482,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -501,7 +501,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -520,7 +520,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -539,7 +539,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -558,7 +558,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -577,7 +577,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -596,7 +596,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -615,7 +615,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -634,7 +634,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -653,7 +653,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -672,7 +672,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -691,7 +691,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -710,7 +710,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -729,7 +729,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -748,7 +748,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -767,7 +767,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -786,7 +786,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -805,7 +805,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -824,7 +824,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -843,7 +843,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -862,7 +862,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -881,7 +881,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -900,7 +900,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -919,7 +919,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -938,7 +938,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -957,7 +957,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -976,7 +976,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -995,7 +995,7 @@ exports[`SchemaTable matches snapshot 1`] = `
<div
class="rt-td dimension string"
role="gridcell"
style="flex: 100 0 auto; width: 100px; max-width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 

View File

@ -30,11 +30,11 @@ import {
MetricSpec,
} from '../../../druid-models';
import {
caseInsensitiveContains,
filterMap,
DEFAULT_TABLE_CLASS_NAME,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../../utils';
} from '../../../react-table';
import { caseInsensitiveContains, filterMap } from '../../../utils';
import { SampleEntry, SampleHeaderAndRows } from '../../../utils/sampler';
import './schema-table.scss';
@ -69,7 +69,7 @@ export const SchemaTable = React.memo(function SchemaTable(props: SchemaTablePro
return (
<ReactTable
className="schema-table -striped -highlight"
className={classNames('schema-table', DEFAULT_TABLE_CLASS_NAME)}
data={headerAndRows.rows}
sortable={false}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
@ -101,6 +101,7 @@ export const SchemaTable = React.memo(function SchemaTable(props: SchemaTablePro
className: columnClassName,
id: String(i),
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
width: 120,
Cell: function SchemaTableCell({ value }) {
return <TableCell value={value} />;
},
@ -145,7 +146,7 @@ export const SchemaTable = React.memo(function SchemaTable(props: SchemaTablePro
headerClassName: columnClassName,
className: columnClassName,
id: String(i),
width: isTimestamp ? 200 : 100,
width: isTimestamp ? 200 : 140,
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
Cell: function SchemaTableCell(row) {
return <TableCell value={isTimestamp ? new Date(row.value) : row.value} />;

View File

@ -2,7 +2,7 @@
exports[`TransformTable matches snapshot 1`] = `
<div
class="ReactTable transform-table -striped -highlight"
class="ReactTable transform-table -striped -highlight padded-header"
>
<div
class="rt-table"
@ -10,7 +10,7 @@ exports[`TransformTable matches snapshot 1`] = `
>
<div
class="rt-thead -header"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr"
@ -19,7 +19,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-th rt-resizable-header"
role="columnheader"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
tabindex="-1"
>
<div
@ -49,7 +49,7 @@ exports[`TransformTable matches snapshot 1`] = `
</div>
<div
class="rt-tbody"
style="min-width: 100px;"
style="min-width: 140px;"
>
<div
class="rt-tr-group"
@ -62,13 +62,13 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span
<div
class="table-cell plain"
>
hello
</span>
</div>
</div>
</div>
</div>
@ -83,7 +83,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -102,7 +102,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -121,7 +121,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -140,7 +140,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -159,7 +159,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -178,7 +178,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -197,7 +197,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -216,7 +216,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -235,7 +235,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -254,7 +254,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -273,7 +273,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -292,7 +292,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -311,7 +311,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -330,7 +330,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -349,7 +349,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -368,7 +368,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -387,7 +387,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -406,7 +406,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -425,7 +425,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -444,7 +444,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -463,7 +463,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -482,7 +482,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -501,7 +501,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -520,7 +520,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -539,7 +539,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -558,7 +558,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -577,7 +577,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -596,7 +596,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -615,7 +615,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -634,7 +634,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -653,7 +653,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -672,7 +672,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -691,7 +691,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -710,7 +710,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -729,7 +729,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -748,7 +748,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -767,7 +767,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -786,7 +786,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -805,7 +805,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -824,7 +824,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -843,7 +843,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -862,7 +862,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -881,7 +881,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -900,7 +900,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -919,7 +919,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -938,7 +938,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -957,7 +957,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -976,7 +976,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 
@ -995,7 +995,7 @@ exports[`TransformTable matches snapshot 1`] = `
<div
class="rt-td"
role="gridcell"
style="flex: 100 0 auto; width: 100px;"
style="flex: 140 0 auto; width: 140px; max-width: 140px;"
>
<span>
 

View File

@ -23,11 +23,11 @@ import ReactTable from 'react-table';
import { TableCell } from '../../../components';
import { Transform } from '../../../druid-models';
import {
caseInsensitiveContains,
filterMap,
DEFAULT_TABLE_CLASS_NAME,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../../utils';
} from '../../../react-table';
import { caseInsensitiveContains, filterMap } from '../../../utils';
import { escapeColumnName } from '../../../utils/druid-expression';
import { SampleEntry, SampleHeaderAndRows } from '../../../utils/sampler';
@ -64,7 +64,7 @@ export const TransformTable = React.memo(function TransformTable(props: Transfor
return (
<ReactTable
className="transform-table -striped -highlight"
className={classNames('transform-table', DEFAULT_TABLE_CLASS_NAME)}
data={sampleData.rows}
sortable={false}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
@ -110,6 +110,7 @@ export const TransformTable = React.memo(function TransformTable(props: Transfor
className: columnClassName,
id: String(i),
accessor: (row: SampleEntry) => (row.parsed ? row.parsed[columnName] : null),
width: 140,
Cell: function TransformTableCell(row) {
return <TableCell value={timestamp ? new Date(row.value) : row.value} />;
},

View File

@ -90,6 +90,7 @@ exports[`LookupsView matches snapshot 1`] = `
columns={
Array [
Object {
"Cell": [Function],
"Header": "Lookup name",
"accessor": "id",
"filterable": true,
@ -98,6 +99,7 @@ exports[`LookupsView matches snapshot 1`] = `
"width": 200,
},
Object {
"Cell": [Function],
"Header": "Lookup tier",
"accessor": "tier",
"filterable": true,
@ -106,6 +108,7 @@ exports[`LookupsView matches snapshot 1`] = `
"width": 100,
},
Object {
"Cell": [Function],
"Header": "Type",
"accessor": "spec.type",
"filterable": true,
@ -114,6 +117,7 @@ exports[`LookupsView matches snapshot 1`] = `
"width": 150,
},
Object {
"Cell": [Function],
"Header": "Version",
"accessor": "version",
"filterable": true,
@ -125,15 +129,18 @@ exports[`LookupsView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Poll period",
"accessor": [Function],
"className": "padded",
"id": "poolPeriod",
"show": true,
"width": 150,
},
Object {
"Cell": [Function],
"Header": "Summary",
"accessor": [Function],
"id": "summary",
"show": true,
"width": 600,
},
Object {
"Cell": [Function],
@ -172,6 +179,7 @@ exports[`LookupsView matches snapshot 1`] = `
}
}
filterable={true}
filtered={Array []}
freezeWhenExpanded={false}
getLoadingProps={[Function]}
getNoDataProps={[Function]}
@ -205,6 +213,7 @@ exports[`LookupsView matches snapshot 1`] = `
noDataText=""
ofText="of"
onFetchData={[Function]}
onFilteredChange={[Function]}
originalKey="_original"
pageJumpText="jump to page"
pageSizeOptions={

View File

@ -19,7 +19,7 @@
import { Button, Icon, Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import ReactTable from 'react-table';
import ReactTable, { Filter } from 'react-table';
import {
ACTION_COLUMN_ID,
@ -27,12 +27,15 @@ import {
ACTION_COLUMN_WIDTH,
ActionCell,
RefreshButton,
TableClickableCell,
TableColumnSelector,
TableFilterableCell,
ViewControlBar,
} from '../../components';
import { AsyncActionDialog, LookupEditDialog } from '../../dialogs/';
import { LookupTableActionDialog } from '../../dialogs/lookup-table-action-dialog/lookup-table-action-dialog';
import { LookupSpec, lookupSpecSummary } from '../../druid-models';
import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { Api, AppToaster } from '../../singletons';
import {
deepGet,
@ -43,8 +46,6 @@ import {
LocalStorageKeys,
QueryManager,
QueryState,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
@ -89,6 +90,7 @@ export interface LookupsViewProps {}
export interface LookupsViewState {
lookupEntriesAndTiersState: QueryState<LookupEntriesAndTiers>;
lookupFilter: Filter[];
lookupEdit?: LookupEditInfo;
isEdit: boolean;
@ -109,6 +111,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
super(props);
this.state = {
lookupEntriesAndTiersState: QueryState.INIT,
lookupFilter: [],
isEdit: false,
actions: [],
@ -276,7 +279,7 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
];
}
renderDeleteLookupAction() {
private renderDeleteLookupAction() {
const { deleteLookupTier, deleteLookupName } = this.state;
if (!deleteLookupTier || !deleteLookupName) return;
@ -305,8 +308,32 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
);
}
renderLookupsTable() {
const { lookupEntriesAndTiersState, visibleColumns } = this.state;
private onDetail(lookup: LookupEntry): void {
const lookupId = lookup.id;
const lookupTier = lookup.tier;
this.setState({
lookupTableActionDialogId: lookupId,
actions: this.getLookupActions(lookupTier, lookupId),
});
}
private renderFilterableCell(field: string) {
const { lookupFilter } = this.state;
return (row: { value: any }) => (
<TableFilterableCell
field={field}
value={row.value}
filters={lookupFilter}
onFiltersChange={filters => this.setState({ lookupFilter: filters })}
>
{row.value}
</TableFilterableCell>
);
}
private renderLookupsTable() {
const { lookupEntriesAndTiersState, lookupFilter, visibleColumns } = this.state;
const lookupEntriesAndTiers = lookupEntriesAndTiersState.data;
const lookups = lookupEntriesAndTiers ? lookupEntriesAndTiers.lookupEntries : [];
@ -323,106 +350,119 @@ export class LookupsView extends React.PureComponent<LookupsViewProps, LookupsVi
}
return (
<>
<ReactTable
data={lookups}
loading={lookupEntriesAndTiersState.loading}
noDataText={
!lookupEntriesAndTiersState.loading && !lookups.length
? 'No lookups'
: lookupEntriesAndTiersState.getErrorMessage() || ''
}
filterable
defaultSorted={[{ id: 'lookup_name', desc: false }]}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={lookups.length > STANDARD_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Lookup name',
show: visibleColumns.shown('Lookup name'),
id: 'lookup_name',
accessor: 'id',
filterable: true,
width: 200,
},
{
Header: 'Lookup tier',
show: visibleColumns.shown('Lookup tier'),
id: 'tier',
accessor: 'tier',
filterable: true,
width: 100,
},
{
Header: 'Type',
show: visibleColumns.shown('Type'),
id: 'type',
accessor: 'spec.type',
filterable: true,
width: 150,
},
{
Header: 'Version',
show: visibleColumns.shown('Version'),
id: 'version',
accessor: 'version',
filterable: true,
width: 190,
},
{
Header: 'Poll period',
show: visibleColumns.shown('Poll period'),
id: 'poolPeriod',
width: 150,
accessor: row => deepGet(row, 'spec.extractionNamespace.pollPeriod'),
Cell: ({ original }) => {
if (original.spec.type === 'map') return 'Static map';
const pollPeriod = deepGet(original, 'spec.extractionNamespace.pollPeriod');
if (!pollPeriod) {
return (
<>
<Icon icon={IconNames.WARNING_SIGN} intent={Intent.WARNING} /> No poll period
set
</>
);
}
return pollPeriod;
},
},
{
Header: 'Summary',
show: visibleColumns.shown('Summary'),
id: 'summary',
accessor: row => lookupSpecSummary(row.spec),
},
{
Header: ACTION_COLUMN_LABEL,
show: visibleColumns.shown(ACTION_COLUMN_LABEL),
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
accessor: 'id',
Cell: ({ original }) => {
const lookupId = original.id;
const lookupTier = original.tier;
const lookupActions = this.getLookupActions(lookupTier, lookupId);
<ReactTable
data={lookups}
loading={lookupEntriesAndTiersState.loading}
noDataText={
!lookupEntriesAndTiersState.loading && !lookups.length
? 'No lookups'
: lookupEntriesAndTiersState.getErrorMessage() || ''
}
filterable
filtered={lookupFilter}
onFilteredChange={filtered => {
this.setState({ lookupFilter: filtered });
}}
defaultSorted={[{ id: 'lookup_name', desc: false }]}
defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
showPagination={lookups.length > STANDARD_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Lookup name',
show: visibleColumns.shown('Lookup name'),
id: 'lookup_name',
accessor: 'id',
filterable: true,
width: 200,
Cell: ({ value, original }) => (
<TableClickableCell
onClick={() => this.onDetail(original)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{value}
</TableClickableCell>
),
},
{
Header: 'Lookup tier',
show: visibleColumns.shown('Lookup tier'),
id: 'tier',
accessor: 'tier',
filterable: true,
width: 100,
Cell: this.renderFilterableCell('tier'),
},
{
Header: 'Type',
show: visibleColumns.shown('Type'),
id: 'type',
accessor: 'spec.type',
filterable: true,
width: 150,
Cell: this.renderFilterableCell('type'),
},
{
Header: 'Version',
show: visibleColumns.shown('Version'),
id: 'version',
accessor: 'version',
filterable: true,
width: 190,
Cell: this.renderFilterableCell('version'),
},
{
Header: 'Poll period',
show: visibleColumns.shown('Poll period'),
id: 'poolPeriod',
width: 150,
className: 'padded',
accessor: row => deepGet(row, 'spec.extractionNamespace.pollPeriod'),
Cell: ({ original }) => {
if (original.spec.type === 'map') return 'Static map';
const pollPeriod = deepGet(original, 'spec.extractionNamespace.pollPeriod');
if (!pollPeriod) {
return (
<ActionCell
onDetail={() => {
this.setState({
lookupTableActionDialogId: lookupId,
actions: lookupActions,
});
}}
actions={lookupActions}
/>
<>
<Icon icon={IconNames.WARNING_SIGN} intent={Intent.WARNING} /> No poll period
set
</>
);
},
}
return pollPeriod;
},
]}
/>
</>
},
{
Header: 'Summary',
show: visibleColumns.shown('Summary'),
id: 'summary',
accessor: row => lookupSpecSummary(row.spec),
width: 600,
Cell: this.renderFilterableCell('summary'),
},
{
Header: ACTION_COLUMN_LABEL,
show: visibleColumns.shown(ACTION_COLUMN_LABEL),
id: ACTION_COLUMN_ID,
width: ACTION_COLUMN_WIDTH,
filterable: false,
accessor: 'id',
Cell: ({ original }) => {
const lookupId = original.id;
const lookupTier = original.tier;
const lookupActions = this.getLookupActions(lookupTier, lookupId);
return (
<ActionCell
onDetail={() => {
this.onDetail(original);
}}
actions={lookupActions}
/>
);
},
},
]}
/>
);
}

View File

@ -33,7 +33,7 @@ import React, { ChangeEvent } from 'react';
import { Loader } from '../../../components';
import { Deferred } from '../../../components/deferred/deferred';
import { ColumnMetadata, copyAndAlert, groupBy, oneOf, prettyPrintSql } from '../../../utils';
import { dataTypeToIcon } from '../query-utils';
import { dataTypeToIcon } from '../../../utils/data-type-utils';
import { NumberMenuItems, StringMenuItems, TimeMenuItems } from './column-tree-menu';

View File

@ -20,11 +20,6 @@
@import '../../../blueprint-overrides/common/colors';
.query-output {
&.more-results .-totalPages {
// Hide the total page counter as it can be confusing due to the auto limit
display: none;
}
.ReactTable {
position: absolute;
top: 0;
@ -65,8 +60,10 @@
}
.clickable-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
cursor: pointer;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.#{$bp-ns}-popover2-target {

View File

@ -26,16 +26,16 @@ import ReactTable from 'react-table';
import { BracedText, Deferred, TableCell } from '../../../components';
import { ShowValueDialog } from '../../../dialogs/show-value-dialog/show-value-dialog';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../../react-table';
import {
changePage,
copyAndAlert,
dataTypeToColumnWidth,
formatNumber,
getNumericColumnBraces,
Pagination,
prettyPrintSql,
QueryAction,
SMALL_TABLE_PAGE_SIZE,
SMALL_TABLE_PAGE_SIZE_OPTIONS,
stringifyValue,
} from '../../../utils';
import { BasicAction, basicActionsToMenu } from '../../../utils/basic-action';
@ -270,12 +270,12 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro
<>
{isComparable(value) && (
<>
{filterOnMenuItem(IconNames.FILTER_KEEP, ex.greaterThanOrEqual(val), having)}
{filterOnMenuItem(IconNames.FILTER_KEEP, ex.lessThanOrEqual(val), having)}
{filterOnMenuItem(IconNames.FILTER, ex.greaterThanOrEqual(val), having)}
{filterOnMenuItem(IconNames.FILTER, ex.lessThanOrEqual(val), having)}
</>
)}
{filterOnMenuItem(IconNames.FILTER_KEEP, ex.equal(val), having)}
{filterOnMenuItem(IconNames.FILTER_REMOVE, ex.unequal(val), having)}
{filterOnMenuItem(IconNames.FILTER, ex.equal(val), having)}
{filterOnMenuItem(IconNames.FILTER, ex.unequal(val), having)}
</>
)}
{showFullValueMenuItem}
@ -351,14 +351,15 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro
return (
<div className={classNames('query-output', { 'more-results': hasMoreResults })}>
<ReactTable
className="-striped -highlight"
data={queryResult.rows as any[][]}
ofText={hasMoreResults ? '' : 'of'}
noDataText={queryResult.rows.length ? '' : 'Query returned no data'}
page={pagination.page}
pageSize={pagination.pageSize}
onPageChange={page => changePagination(changePage(pagination, page))}
onPageSizeChange={(pageSize, page) => changePagination({ page, pageSize })}
sortable={false}
ofText={hasMoreResults ? '' : 'of'}
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
showPagination={
@ -366,17 +367,16 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro
}
columns={queryResult.header.map((column, i) => {
const h = column.name;
const effectiveType = column.isTimeColumn() ? column.sqlType : column.nativeType;
return {
Header:
i === renamingColumn && parsedQuery
? () => <ColumnRenameInput initialName={h} onDone={renameColumnTo} />
: () => {
return (
<Popover2
className="clickable-cell"
content={<Deferred content={() => getHeaderMenu(h, i)} />}
>
<div>
<Popover2 content={<Deferred content={() => getHeaderMenu(h, i)} />}>
<div className="clickable-cell">
{h}
{hasFilterOnHeader(h, i) && (
<Icon icon={IconNames.FILTER} iconSize={14} />
@ -386,24 +386,24 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro
);
},
headerClassName: getHeaderClassName(h, i),
width: dataTypeToColumnWidth(effectiveType),
accessor: String(i),
Cell(row) {
const value = row.value;
return (
<div>
<Popover2 content={<Deferred content={() => getCellMenu(h, i, value)} />}>
{numericColumnBraces[i] ? (
<BracedText
text={formatNumber(value)}
braces={numericColumnBraces[i]}
padFractionalPart
unselectableThousandsSeparator
/>
) : (
<TableCell value={value} unlimited />
)}
</Popover2>
</div>
<Popover2 content={<Deferred content={() => getCellMenu(h, i, value)} />}>
{numericColumnBraces[i] ? (
<BracedText
className="table-padding"
text={formatNumber(value)}
braces={numericColumnBraces[i]}
padFractionalPart
unselectableThousandsSeparator
/>
) : (
<TableCell value={value} unlimited />
)}
</Popover2>
);
},
className:

View File

@ -138,18 +138,20 @@ exports[`SegmentsView matches snapshot 1`] = `
columns={
Array [
Object {
"Cell": [Function],
"Header": "Segment ID",
"accessor": "segment_id",
"filterable": true,
"show": true,
"sortable": true,
"width": 300,
"width": 280,
},
Object {
"Cell": [Function],
"Header": "Datasource",
"accessor": "datasource",
"show": true,
"width": 140,
},
Object {
"Cell": [Function],
@ -169,7 +171,7 @@ exports[`SegmentsView matches snapshot 1`] = `
"filterable": true,
"show": true,
"sortable": true,
"width": 120,
"width": 160,
},
Object {
"Cell": [Function],
@ -179,16 +181,17 @@ exports[`SegmentsView matches snapshot 1`] = `
"filterable": true,
"show": true,
"sortable": true,
"width": 120,
"width": 160,
},
Object {
"Cell": [Function],
"Header": "Version",
"accessor": "version",
"defaultSortDesc": true,
"filterable": true,
"show": true,
"sortable": true,
"width": 120,
"width": 160,
},
Object {
"Cell": [Function],
@ -203,6 +206,7 @@ exports[`SegmentsView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Shard type",
"accessor": [Function],
"headerClassName": "disable-comparisons",
"id": "shard_type",
"show": true,
"sortable": false,
@ -221,6 +225,7 @@ exports[`SegmentsView matches snapshot 1`] = `
Object {
"Header": "Partition",
"accessor": "partition_num",
"className": "padded",
"filterable": false,
"show": true,
"sortable": true,
@ -230,18 +235,22 @@ exports[`SegmentsView matches snapshot 1`] = `
"Cell": [Function],
"Header": "Size",
"accessor": "size",
"className": "padded",
"defaultSortDesc": true,
"filterable": false,
"show": true,
"sortable": true,
"width": 120,
},
Object {
"Cell": [Function],
"Header": "Num rows",
"accessor": "num_rows",
"className": "padded",
"defaultSortDesc": true,
"filterable": false,
"show": true,
"width": 120,
},
Object {
"Cell": [Function],
@ -251,6 +260,7 @@ exports[`SegmentsView matches snapshot 1`] = `
(bytes)
</React.Fragment>,
"accessor": "avg_row_size",
"className": "padded",
"filterable": false,
"show": true,
"width": 100,
@ -258,6 +268,7 @@ exports[`SegmentsView matches snapshot 1`] = `
Object {
"Header": "Replicas",
"accessor": "num_replicas",
"className": "padded",
"defaultSortDesc": true,
"filterable": false,
"show": true,
@ -267,29 +278,37 @@ exports[`SegmentsView matches snapshot 1`] = `
"Filter": [Function],
"Header": "Is published",
"accessor": [Function],
"className": "padded",
"id": "is_published",
"show": true,
"width": 100,
},
Object {
"Filter": [Function],
"Header": "Is realtime",
"accessor": [Function],
"className": "padded",
"id": "is_realtime",
"show": true,
"width": 100,
},
Object {
"Filter": [Function],
"Header": "Is available",
"accessor": [Function],
"className": "padded",
"id": "is_available",
"show": true,
"width": 100,
},
Object {
"Filter": [Function],
"Header": "Is overshadowed",
"accessor": [Function],
"className": "padded",
"id": "is_overshadowed",
"show": true,
"width": 100,
},
Object {
"Aggregated": [Function],

View File

@ -33,16 +33,24 @@ import {
MoreButton,
RefreshButton,
SegmentTimeline,
TableClickableCell,
TableColumnSelector,
TableFilterableCell,
ViewControlBar,
} from '../../components';
import { AsyncActionDialog } from '../../dialogs';
import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-dialog/segment-table-action-dialog';
import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog';
import {
booleanCustomTableFilter,
BooleanFilterInput,
parseFilterModeAndNeedle,
sqlQueryCustomTableFilter,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../react-table';
import { Api } from '../../singletons';
import {
addFilter,
booleanCustomTableFilter,
Capabilities,
CapabilitiesMode,
compact,
@ -50,19 +58,14 @@ import {
filterMap,
formatBytes,
formatInteger,
getNeedleAndMode,
hasPopoverOpen,
isNumberLikeNaN,
LocalStorageBackedVisibility,
LocalStorageKeys,
makeBooleanFilter,
NumberLike,
queryDruidSql,
QueryManager,
QueryState,
sqlQueryCustomTableFilter,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
twoLines,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
@ -275,12 +278,19 @@ END AS "time_span"`,
if (f.id === 'shard_type') {
// Special handling for shard_type that needs to be search in the shard_spec
// Creates filters like `shard_spec LIKE '%"type":"numbered"%'`
const needleAndMode = getNeedleAndMode(f);
const closingQuote = needleAndMode.mode === 'exact' ? '"' : '';
return SqlComparison.like(
SqlRef.column('shard_spec'),
`%"type":"${needleAndMode.needle}${closingQuote}%`,
);
const modeAndNeedle = parseFilterModeAndNeedle(f);
if (!modeAndNeedle) return;
const shardSpecRef = SqlRef.column('shard_spec');
switch (modeAndNeedle.mode) {
case '=':
return SqlComparison.like(shardSpecRef, `%"type":"${modeAndNeedle.needle}"%`);
case '!=':
return SqlComparison.notLike(shardSpecRef, `%"type":"${modeAndNeedle.needle}"%`);
default:
return SqlComparison.like(shardSpecRef, `%"type":"${modeAndNeedle.needle}%`);
}
} else if (f.id.startsWith('is_')) {
if (f.value === 'all') return;
return SqlRef.columnWithQuotes(f.id).equal(f.value === 'true' ? 1 : 0);
@ -466,6 +476,30 @@ END AS "time_span"`,
return actions;
}
private onDetail(segmentId: string, datasource: string): void {
this.setState({
segmentTableActionDialogId: segmentId,
datasourceTableActionDialogId: datasource,
actions: this.getSegmentActions(segmentId, datasource),
});
}
private renderFilterableCell(field: string, disableComparisons = false) {
const { segmentFilter } = this.state;
return (row: { value: any }) => (
<TableFilterableCell
field={field}
value={row.value}
filters={segmentFilter}
onFiltersChange={filters => this.setState({ segmentFilter: filters })}
disableComparisons={disableComparisons}
>
{row.value}
</TableFilterableCell>
);
}
renderSegmentsTable() {
const { segmentsState, segmentFilter, visibleColumns, groupByInterval } = this.state;
const { capabilities } = this.props;
@ -478,30 +512,13 @@ END AS "time_span"`,
const avgRowSizeValues = segments.map(d => formatInteger(d.avg_row_size));
const renderFilterableCell = (field: string) => {
return (row: { value: any }) => {
const value = row.value;
return (
<a
onClick={() => {
this.setState({
segmentFilter: addFilter(segmentFilter, field, value),
});
}}
>
{value}
</a>
);
};
};
const hasSql = capabilities.hasSql();
// Only allow filtering of columns other than datasource if in SQL mode or we are filtering on an exact datasource
// Only allow filtering of columns other than datasource if in SQL mode, or if we are filtering on an exact datasource
const allowGeneralFilter =
hasSql ||
segmentFilter.some(
filter => filter.id === 'datasource' && getNeedleAndMode(filter).mode === 'exact',
filter => filter.id === 'datasource' && parseFilterModeAndNeedle(filter)?.mode === '=',
);
return (
@ -513,10 +530,10 @@ END AS "time_span"`,
manual
filterable
filtered={segmentFilter}
defaultSorted={[hasSql ? { id: 'start', desc: true } : { id: 'datasource', desc: false }]}
onFilteredChange={filtered => {
this.setState({ segmentFilter: filtered });
}}
defaultSorted={[hasSql ? { id: 'start', desc: true } : { id: 'datasource', desc: false }]}
onFetchData={tableState => {
this.fetchData(groupByInterval, tableState);
}}
@ -531,15 +548,24 @@ END AS "time_span"`,
Header: 'Segment ID',
show: visibleColumns.shown('Segment ID'),
accessor: 'segment_id',
width: 300,
width: 280,
sortable: hasSql,
filterable: allowGeneralFilter,
Cell: row => (
<TableClickableCell
onClick={() => this.onDetail(row.value, row.row.datasource)}
hoverIcon={IconNames.SEARCH_TEMPLATE}
>
{row.value}
</TableClickableCell>
),
},
{
Header: 'Datasource',
show: visibleColumns.shown('Datasource'),
accessor: 'datasource',
Cell: renderFilterableCell('datasource'),
width: 140,
Cell: this.renderFilterableCell('datasource'),
},
{
Header: 'Interval',
@ -549,36 +575,37 @@ END AS "time_span"`,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: renderFilterableCell('interval'),
Cell: this.renderFilterableCell('interval'),
},
{
Header: 'Start',
show: visibleColumns.shown('Start'),
accessor: 'start',
width: 120,
width: 160,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: renderFilterableCell('start'),
Cell: this.renderFilterableCell('start'),
},
{
Header: 'End',
show: visibleColumns.shown('End'),
accessor: 'end',
width: 120,
width: 160,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: renderFilterableCell('end'),
Cell: this.renderFilterableCell('end'),
},
{
Header: 'Version',
show: visibleColumns.shown('Version'),
accessor: 'version',
width: 120,
width: 160,
sortable: hasSql,
defaultSortDesc: true,
filterable: allowGeneralFilter,
Cell: this.renderFilterableCell('version'),
},
{
Header: 'Time span',
@ -587,7 +614,7 @@ END AS "time_span"`,
width: 100,
sortable: hasSql,
filterable: allowGeneralFilter,
Cell: renderFilterableCell('time_span'),
Cell: this.renderFilterableCell('time_span'),
},
{
Header: 'Shard type',
@ -595,6 +622,7 @@ END AS "time_span"`,
id: 'shard_type',
width: 100,
sortable: false,
headerClassName: 'disable-comparisons',
accessor: d => {
let v: any;
try {
@ -604,7 +632,7 @@ END AS "time_span"`,
if (typeof v?.type !== 'string') return '-';
return v?.type;
},
Cell: renderFilterableCell('shard_type'),
Cell: this.renderFilterableCell('shard_type', true),
},
{
Header: 'Shard spec',
@ -638,14 +666,16 @@ END AS "time_span"`,
values.map((x, i) => formatRangeDimensionValue(dimensions[i], x)).join('; ');
return (
<div className="spec-detail range-detail" onClick={onShowFullShardSpec}>
<TableClickableCell
onClick={onShowFullShardSpec}
hoverIcon={IconNames.EYE_OPEN}
>
<span className="range-label">Start:</span>
{Array.isArray(v.start) ? formatEdge(v.start) : '-∞'}
<br />
<span className="range-label">End:</span>
{Array.isArray(v.end) ? formatEdge(v.end) : '∞'}
{fullShardIcon}
</div>
</TableClickableCell>
);
}
@ -703,6 +733,7 @@ END AS "time_span"`,
width: 60,
filterable: false,
sortable: hasSql,
className: 'padded',
},
{
Header: 'Size',
@ -711,6 +742,8 @@ END AS "time_span"`,
filterable: false,
sortable: hasSql,
defaultSortDesc: true,
width: 120,
className: 'padded',
Cell: row => (
<BracedText
text={
@ -728,6 +761,8 @@ END AS "time_span"`,
accessor: 'num_rows',
filterable: false,
defaultSortDesc: true,
width: 120,
className: 'padded',
Cell: row => (
<BracedText
text={row.original.is_available ? formatInteger(row.value) : '(unknown)'}
@ -742,6 +777,7 @@ END AS "time_span"`,
accessor: 'avg_row_size',
filterable: false,
width: 100,
className: 'padded',
Cell: ({ value }) => {
if (isNumberLikeNaN(value)) return '-';
return (
@ -760,34 +796,43 @@ END AS "time_span"`,
width: 60,
filterable: false,
defaultSortDesc: true,
className: 'padded',
},
{
Header: 'Is published',
show: hasSql && visibleColumns.shown('Is published'),
id: 'is_published',
accessor: row => String(Boolean(row.is_published)),
Filter: makeBooleanFilter(),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is realtime',
show: hasSql && visibleColumns.shown('Is realtime'),
id: 'is_realtime',
accessor: row => String(Boolean(row.is_realtime)),
Filter: makeBooleanFilter(),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is available',
show: hasSql && visibleColumns.shown('Is available'),
id: 'is_available',
accessor: row => String(Boolean(row.is_available)),
Filter: makeBooleanFilter(),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: 'Is overshadowed',
show: hasSql && visibleColumns.shown('Is overshadowed'),
id: 'is_overshadowed',
accessor: row => String(Boolean(row.is_overshadowed)),
Filter: makeBooleanFilter(),
Filter: BooleanFilterInput,
className: 'padded',
width: 100,
},
{
Header: ACTION_COLUMN_LABEL,
@ -803,11 +848,7 @@ END AS "time_span"`,
return (
<ActionCell
onDetail={() => {
this.setState({
segmentTableActionDialogId: id,
datasourceTableActionDialogId: datasource,
actions: this.getSegmentActions(id, datasource),
});
this.onDetail(id, datasource);
}}
actions={this.getSegmentActions(id, datasource)}
/>

View File

@ -55,7 +55,7 @@ exports[`ServicesView renders data 1`] = `
"Tier",
"Host",
"Port",
"Curr size",
"Current size",
"Max size",
"Usage",
"Detail",
@ -125,6 +125,7 @@ exports[`ServicesView renders data 1`] = `
Array [
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Service",
"accessor": "service",
"show": true,
@ -143,25 +144,31 @@ exports[`ServicesView renders data 1`] = `
"accessor": [Function],
"id": "tier",
"show": true,
},
Object {
"Aggregated": [Function],
"Header": "Host",
"accessor": "host",
"show": true,
},
Object {
"Aggregated": [Function],
"Header": "Port",
"accessor": [Function],
"id": "port",
"show": true,
"width": 180,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Curr size",
"Header": "Host",
"accessor": "host",
"show": true,
"width": 200,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Port",
"accessor": [Function],
"id": "port",
"show": true,
"width": 100,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Current size",
"accessor": "curr_size",
"className": "padded",
"filterable": false,
"id": "curr_size",
"show": true,
@ -172,6 +179,7 @@ exports[`ServicesView renders data 1`] = `
"Cell": [Function],
"Header": "Max size",
"accessor": "max_size",
"className": "padded",
"filterable": false,
"id": "max_size",
"show": true,
@ -182,6 +190,7 @@ exports[`ServicesView renders data 1`] = `
"Cell": [Function],
"Header": "Usage",
"accessor": [Function],
"className": "padded",
"filterable": false,
"id": "usage",
"show": true,
@ -192,6 +201,7 @@ exports[`ServicesView renders data 1`] = `
"Cell": [Function],
"Header": "Detail",
"accessor": [Function],
"className": "padded",
"filterable": false,
"id": "queue",
"show": true,

View File

@ -40,7 +40,7 @@
.fill-indicator {
position: relative;
width: 100%;
height: 16px;
height: 18px;
background-color: #dadada;
border-radius: 2px;

View File

@ -30,12 +30,13 @@ import {
MoreButton,
RefreshButton,
TableColumnSelector,
TableFilterableCell,
ViewControlBar,
} from '../../components';
import { AsyncActionDialog } from '../../dialogs';
import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
import { Api } from '../../singletons';
import {
addFilter,
Capabilities,
CapabilitiesMode,
deepGet,
@ -51,8 +52,6 @@ import {
queryDruidSql,
QueryManager,
QueryState,
STANDARD_TABLE_PAGE_SIZE,
STANDARD_TABLE_PAGE_SIZE_OPTIONS,
} from '../../utils';
import { BasicAction } from '../../utils/basic-action';
@ -64,7 +63,7 @@ const allColumns: string[] = [
'Tier',
'Host',
'Port',
'Curr size',
'Current size',
'Max size',
'Usage',
'Detail',
@ -74,7 +73,7 @@ const allColumns: string[] = [
const tableColumns: Record<CapabilitiesMode, string[]> = {
'full': allColumns,
'no-sql': allColumns,
'no-proxy': ['Service', 'Type', 'Tier', 'Host', 'Port', 'Curr size', 'Max size', 'Usage'],
'no-proxy': ['Service', 'Type', 'Tier', 'Host', 'Port', 'Current size', 'Max size', 'Usage'],
};
function formatQueues(
@ -304,6 +303,21 @@ ORDER BY
this.serviceQueryManager.terminate();
}
private renderFilterableCell(field: string) {
const { serviceFilter } = this.state;
return (row: { value: any }) => (
<TableFilterableCell
field={field}
value={row.value}
filters={serviceFilter}
onFiltersChange={filters => this.setState({ serviceFilter: filters })}
>
{row.value}
</TableFilterableCell>
);
}
renderServicesTable() {
const { capabilities } = this.props;
const { servicesState, serviceFilter, groupServicesBy, visibleColumns } = this.state;
@ -342,6 +356,7 @@ ORDER BY
show: visibleColumns.shown('Service'),
accessor: 'service',
width: 300,
Cell: this.renderFilterableCell('service'),
Aggregated: () => '',
},
{
@ -349,49 +364,31 @@ ORDER BY
show: visibleColumns.shown('Type'),
accessor: 'service_type',
width: 150,
Cell: ({ value }) => {
return (
<a
onClick={() => {
this.setState({
serviceFilter: addFilter(serviceFilter, 'service_type', value),
});
}}
>
{value}
</a>
);
},
Cell: this.renderFilterableCell('service_type'),
},
{
Header: 'Tier',
show: visibleColumns.shown('Tier'),
id: 'tier',
width: 180,
accessor: row => {
return row.tier ? row.tier : row.worker ? row.worker.category : null;
},
Cell: ({ value }) => {
return (
<a
onClick={() => {
this.setState({ serviceFilter: addFilter(serviceFilter, 'tier', value) });
}}
>
{value}
</a>
);
},
Cell: this.renderFilterableCell('tier'),
},
{
Header: 'Host',
show: visibleColumns.shown('Host'),
accessor: 'host',
width: 200,
Cell: this.renderFilterableCell('host'),
Aggregated: () => '',
},
{
Header: 'Port',
show: visibleColumns.shown('Port'),
id: 'port',
width: 100,
accessor: row => {
const ports: string[] = [];
if (row.plaintext_port !== -1) {
@ -402,15 +399,17 @@ ORDER BY
}
return ports.join(', ') || 'No port';
},
Cell: this.renderFilterableCell('port'),
Aggregated: () => '',
},
{
Header: 'Curr size',
show: visibleColumns.shown('Curr size'),
Header: 'Current size',
show: visibleColumns.shown('Current size'),
id: 'curr_size',
width: 100,
filterable: false,
accessor: 'curr_size',
className: 'padded',
Aggregated: row => {
if (row.row._pivotVal !== 'historical') return '';
const originals = row.subRows.map(r => r._original);
@ -430,6 +429,7 @@ ORDER BY
width: 100,
filterable: false,
accessor: 'max_size',
className: 'padded',
Aggregated: row => {
if (row.row._pivotVal !== 'historical') return '';
const originals = row.subRows.map(r => r._original);
@ -448,6 +448,7 @@ ORDER BY
id: 'usage',
width: 140,
filterable: false,
className: 'padded',
accessor: row => {
if (oneOf(row.service_type, 'middle_manager', 'indexer')) {
return row.worker
@ -515,6 +516,7 @@ ORDER BY
id: 'queue',
width: 400,
filterable: false,
className: 'padded',
accessor: row => {
if (oneOf(row.service_type, 'middle_manager', 'indexer')) {
if (deepGet(row, 'worker.version') === '') return 'Disabled';
@ -550,8 +552,6 @@ ORDER BY
case 'indexer':
case 'middle_manager':
return row.value;
case 'coordinator':
case 'overlord':
return row.value;