diff --git a/server/pom.xml b/server/pom.xml
index 0fe3f075fb50..cefb209e5859 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -50,6 +50,7 @@
runtime
+
org.apache.druid
druid-console
${project.parent.version}
diff --git a/web-console/README.md b/web-console/README.md
index 918f7efea79d..4e16369d3436 100644
--- a/web-console/README.md
+++ b/web-console/README.md
@@ -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
diff --git a/web-console/e2e-tests/component/datasources/overview.ts b/web-console/e2e-tests/component/datasources/overview.ts
index 660d22cfdf18..fb525811122e 100644
--- a/web-console/e2e-tests/component/datasources/overview.ts
+++ b/web-console/e2e-tests/component/datasources/overview.ts
@@ -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();
}
diff --git a/web-console/lib/react-table.styl b/web-console/lib/react-table.styl
deleted file mode 100644
index 709bd142044c..000000000000
--- a/web-console/lib/react-table.styl
+++ /dev/null
@@ -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
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 64b73c39c68f..95b3c0149b46 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -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",
diff --git a/web-console/package.json b/web-console/package.json
index f9cdf7f8cf99..ce59e3f39eaa 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -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",
diff --git a/web-console/script/build b/web-console/script/build
index 95877202ae1b..0d83cd29fc42 100755
--- a/web-console/script/build
+++ b/web-console/script/build
@@ -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
diff --git a/web-console/script/druid b/web-console/script/druid
index 064290bdca53..192d43281d69 100755
--- a/web-console/script/druid
+++ b/web-console/script/druid
@@ -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() {
diff --git a/web-console/src/bootstrap/react-table-custom-pagination.tsx b/web-console/src/bootstrap/react-table-custom-pagination.tsx
deleted file mode 100644
index bcc900f130c5..000000000000
--- a/web-console/src/bootstrap/react-table-custom-pagination.tsx
+++ /dev/null
@@ -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;
-}
-
-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 (
-
-
- {
- if (!canPrevious) return;
- this.changePage(page - 1);
- }}
- disabled={!canPrevious}
- >
- {previousText}
-
-
-
-
- {pageText}{' '}
- {showPageJump ? (
-
- {
- 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);
- }
- }}
- />
-
- ) : (
- {page + 1}
- )}{' '}
- {ofText} {pages || 1}
-
- {showPageSizeOptions && (
-
- onPageSizeChange(Number(e.target.value))} value={pageSize}>
- {pageSizeOptions.map((option: any, i: number) => (
-
- {`${option} ${rowsText}`}
-
- ))}
-
-
- )}
-
-
- {
- if (!canNext) return;
- this.changePage(page + 1);
- }}
- disabled={!canNext}
- >
- {nextText}
-
-
-
- );
- }
-}
diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx
index 7cb6e11ce911..9058b95839d0 100644
--- a/web-console/src/bootstrap/react-table-defaults.tsx
+++ b/web-console/src/bootstrap/react-table-defaults.tsx
@@ -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
diff --git a/web-console/src/components/action-cell/action-cell.scss b/web-console/src/components/action-cell/action-cell.scss
index 52838723edd9..722ca8929667 100644
--- a/web-console/src/components/action-cell/action-cell.scss
+++ b/web-console/src/components/action-cell/action-cell.scss
@@ -16,7 +16,11 @@
* limitations under the License.
*/
+@import '../../variables';
+
.action-cell {
+ padding: $table-cell-v-padding $table-cell-h-padding;
+
& > * {
margin-right: 10px;
diff --git a/web-console/src/components/action-cell/action-cell.tsx b/web-console/src/components/action-cell/action-cell.tsx
index 9863e616aabb..0433eca2b08c 100644
--- a/web-console/src/components/action-cell/action-cell.tsx
+++ b/web-console/src/components/action-cell/action-cell.tsx
@@ -44,7 +44,7 @@ export const ActionCell = React.memo(function ActionCell(props: ActionCellProps)
{onDetail && }
{actionsMenu && (
-
+
)}
diff --git a/web-console/src/components/braced-text/braced-text.tsx b/web-console/src/components/braced-text/braced-text.tsx
index 8dd6cf54cc6b..7416653d9838 100644
--- a/web-console/src/components/braced-text/braced-text.tsx
+++ b/web-console/src/components/braced-text/braced-text.tsx
@@ -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 (
-
+
{findMostNumbers(effectiveBraces)}
{unselectableThousandsSeparator ? hideThousandsSeparator(text) : text}
diff --git a/web-console/src/components/datasource-columns-table/__snapshots__/datasource-columns-table.spec.tsx.snap b/web-console/src/components/datasource-columns-table/__snapshots__/datasource-columns-table.spec.tsx.snap
index 1a711fc298c3..dca59389af97 100644
--- a/web-console/src/components/datasource-columns-table/__snapshots__/datasource-columns-table.spec.tsx.snap
+++ b/web-console/src/components/datasource-columns-table/__snapshots__/datasource-columns-table.spec.tsx.snap
@@ -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,
},
]
}
diff --git a/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx b/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
index bd4149aa081c..007e54919be5 100644
--- a/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
+++ b/web-console/src/components/datasource-columns-table/datasource-columns-table.tsx
@@ -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'}
diff --git a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
index bfd6aa4c3daa..b3943e556a92 100644
--- a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
+++ b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
@@ -76,6 +76,7 @@ exports[`HeaderBar matches snapshot 1`] = `
"queryType": "nativeAndSql",
}
}
+ onUnrestrict={[Function]}
/>
{
it('matches snapshot', () => {
- const headerBar = shallow( );
+ const headerBar = shallow(
+ {}} />,
+ );
expect(headerBar).toMatchSnapshot();
});
});
diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx
index 217ff807571f..53042d91a22d 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -85,10 +85,11 @@ const DruidLogo = React.memo(function DruidLogo() {
interface RestrictedModeProps {
capabilities: Capabilities;
+ onUnrestrict(capabilities: Capabilities): void;
}
const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeProps) {
- const { capabilities } = props;
+ const { capabilities, onUnrestrict } = props;
const mode = capabilities.getModeExtended();
let label: string;
@@ -136,7 +137,8 @@ const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeP
It appears that you are accessing the console on the Coordinator/Overlord shared service.
Due to the lack of access to some APIs on this service the console will operate in a
- limited mode. The full version of the console can be accessed on the Router service.
+ limited mode. The unrestricted version of the console can be accessed on the Router
+ service.
);
break;
@@ -157,8 +159,8 @@ const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeP
message = (
It appears that you are accessing the console on the Overlord service. Due to the lack of
- access to some APIs on this service the console will operate in a limited mode. The full
- version of the console can be accessed on the Router service.
+ access to some APIs on this service the console will operate in a limited mode. The
+ unrestricted version of the console can be accessed on the Router service.
);
break;
@@ -168,7 +170,8 @@ const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeP
message = (
Due to the lack of access to some APIs on this service the console will operate in a
- limited mode. The full version of the console can be accessed on the Router service.
+ limited mode. The unrestricted version of the console can be accessed on the Router
+ service.
);
break;
@@ -187,6 +190,27 @@ const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeP
.
+
+ It is possible that there is an issue with the capability detection. You can enable the
+ unrestricted console but certain features might not work if the underlying APIs are not
+ available.
+
+
+ onUnrestrict(Capabilities.FULL)}
+ />
+
+ {!capabilities.hasSql() && (
+
+ onUnrestrict(Capabilities.NO_SQL)}
+ />
+
+ )}
}
position={Position.BOTTOM_RIGHT}
@@ -199,10 +223,11 @@ const RestrictedMode = React.memo(function RestrictedMode(props: RestrictedModeP
export interface HeaderBarProps {
active: HeaderActiveTab;
capabilities: Capabilities;
+ onUnrestrict(capabilities: Capabilities): void;
}
export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
- const { active, capabilities } = props;
+ const { active, capabilities, onUnrestrict } = props;
const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
const [doctorDialogOpen, setDoctorDialogOpen] = useState(false);
const [coordinatorDynamicConfigDialogOpen, setCoordinatorDynamicConfigDialogOpen] =
@@ -371,7 +396,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
/>
-
+
diff --git a/web-console/src/components/index.ts b/web-console/src/components/index.ts
index 1324578c3ae3..d3418fdc718a 100644
--- a/web-console/src/components/index.ts
+++ b/web-console/src/components/index.ts
@@ -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';
diff --git a/web-console/src/components/lookup-values-table/lookup-values-table.tsx b/web-console/src/components/lookup-values-table/lookup-values-table.tsx
index 716dfc41647e..faa7810fd455 100644
--- a/web-console/src/components/lookup-values-table/lookup-values-table.tsx
+++ b/web-console/src/components/lookup-values-table/lookup-values-table.tsx
@@ -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
+ 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',
},
]}
- noDataText={
- entriesState.getErrorMessage() ||
- 'Lookup data not found. If this is a new lookup it might not have propagated yet.'
- }
/>
);
}
diff --git a/web-console/src/components/segment-timeline/bar-group.tsx b/web-console/src/components/segment-timeline/bar-group.tsx
index 11b02523c89d..442df4ba73c6 100644
--- a/web-console/src/components/segment-timeline/bar-group.tsx
+++ b/web-console/src/components/segment-timeline/bar-group.tsx
@@ -56,6 +56,7 @@ export class BarGroup extends React.Component {
datasource: entry.datasource,
xValue: entry.x,
yValue: entry.y,
+ dailySize: entry.dailySize,
};
return (
Datasource: {hoverOn.datasource}
Time: {hoverOn.xValue}
+
+ {`${
+ activeDataType === 'countData' ? 'Daily total count:' : 'Daily total size:'
+ } ${formatTick(hoverOn.dailySize!)}`}
+
{`${activeDataType === 'countData' ? 'Count:' : 'Size:'} ${formatTick(
hoverOn.yValue!,
diff --git a/web-console/src/components/supervisor-statistics-table/__snapshots__/supervisor-statistics-table.spec.tsx.snap b/web-console/src/components/supervisor-statistics-table/__snapshots__/supervisor-statistics-table.spec.tsx.snap
index 8b93d8d9ec66..614224de77a5 100644
--- a/web-console/src/components/supervisor-statistics-table/__snapshots__/supervisor-statistics-table.spec.tsx.snap
+++ b/web-console/src/components/supervisor-statistics-table/__snapshots__/supervisor-statistics-table.spec.tsx.snap
@@ -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,
},
]
}
diff --git a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
index d36e1aba8577..fe66c4efb675 100644
--- a/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
+++ b/web-console/src/components/supervisor-statistics-table/supervisor-statistics-table.tsx
@@ -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}`);
},
diff --git a/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap b/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap
index cd0a4e368b06..c019c38fa226 100644
--- a/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap
+++ b/web-console/src/components/table-cell/__snapshots__/table-cell.spec.tsx.snap
@@ -1,25 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TableCell matches snapshot Date (invalid) 1`] = `
-
Unusable date
-
+
`;
exports[`TableCell matches snapshot Date 1`] = `
-
2022-02-24T01:02:03.000Z
-
+
`;
exports[`TableCell matches snapshot array long 1`] = `
-
[0, 1, 2, 3, 4, 5, 6, 7
@@ -46,43 +46,43 @@ exports[`TableCell matches snapshot array long 1`] = `
/>
-
+
`;
exports[`TableCell matches snapshot array short 1`] = `
-
[a, b, c]
-
+
`;
exports[`TableCell matches snapshot null 1`] = `
-
null
-
+
`;
exports[`TableCell matches snapshot object 1`] = `
-
{"hello":"world"}
-
+
`;
exports[`TableCell matches snapshot simple 1`] = `
-
Hello World
-
+
`;
exports[`TableCell matches snapshot truncate 1`] = `
-
test_test_test_test_tes
@@ -109,21 +109,21 @@ exports[`TableCell matches snapshot truncate 1`] = `
/>
-
+
`;
exports[`TableCell matches snapshot unlimited (absolute max) 1`] = `
-
test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_...
-
+
`;
exports[`TableCell matches snapshot unlimited 1`] = `
-
test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_test_
-
+
`;
diff --git a/web-console/src/components/table-cell/table-cell.scss b/web-console/src/components/table-cell/table-cell.scss
index 3b3ded93d4ad..3a7b4db6950c 100644
--- a/web-console/src/components/table-cell/table-cell.scss
+++ b/web-console/src/components/table-cell/table-cell.scss
@@ -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;
}
}
diff --git a/web-console/src/components/table-cell/table-cell.tsx b/web-console/src/components/table-cell/table-cell.tsx
index 7d20b717fc66..3895a805a702 100644
--- a/web-console/src/components/table-cell/table-cell.tsx
+++ b/web-console/src/components/table-cell/table-cell.tsx
@@ -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 {str} ;
+ return {str}
;
}
if (unlimited) {
return (
-
+
{str.length < ABSOLUTE_MAX_CHARS_TO_SHOW
? str
: `${str.substr(0, ABSOLUTE_MAX_CHARS_TO_SHOW)}...`}
-
+
);
}
const { prefix, omitted, suffix } = shortenString(str);
return (
-
+
{prefix}
{omitted}
{suffix}
setShowValue(str)} />
{renderShowValueDialog()}
-
+
);
}
@@ -93,9 +93,9 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) {
if (value instanceof Date) {
const dateValue = value.valueOf();
return (
-
+
{isNaN(dateValue) ? 'Unusable date' : value.toISOString()}
-
+
);
} 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 null ;
+ return null
;
}
});
diff --git a/web-console/src/bootstrap/react-table-custom-pagination.scss b/web-console/src/components/table-clickable-cell/table-clickable-cell.scss
similarity index 65%
rename from web-console/src/bootstrap/react-table-custom-pagination.scss
rename to web-console/src/components/table-clickable-cell/table-clickable-cell.scss
index 4bfacb31e80f..d6f6f8b2d7f7 100644
--- a/web-console/src/bootstrap/react-table-custom-pagination.scss
+++ b/web-console/src/components/table-clickable-cell/table-clickable-cell.scss
@@ -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;
}
}
diff --git a/web-console/src/components/table-clickable-cell/table-clickable-cell.tsx b/web-console/src/components/table-clickable-cell/table-clickable-cell.tsx
new file mode 100644
index 000000000000..e2e79b1fcdb5
--- /dev/null
+++ b/web-console/src/components/table-clickable-cell/table-clickable-cell.tsx
@@ -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;
+ hoverIcon?: IconName;
+ children?: ReactNode;
+}
+
+export const TableClickableCell = React.memo(function TableClickableCell(
+ props: TableClickableCellProps,
+) {
+ const { onClick, hoverIcon, children } = props;
+
+ return (
+
+ {children}
+ {hoverIcon && }
+
+ );
+});
diff --git a/web-console/src/components/table-filterable-cell/table-filterable-cell.scss b/web-console/src/components/table-filterable-cell/table-filterable-cell.scss
new file mode 100644
index 000000000000..bb3d61cd05ec
--- /dev/null
+++ b/web-console/src/components/table-filterable-cell/table-filterable-cell.scss
@@ -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
+ }
+}
diff --git a/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
new file mode 100644
index 000000000000..d3387fd37e81
--- /dev/null
+++ b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
@@ -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 (
+ (
+
+
+ {(disableComparisons ? FILTER_MODES_NO_COMPARISONS : FILTER_MODES).map((mode, i) => (
+ onFiltersChange(addFilter(filters, field, mode, value))}
+ />
+ ))}
+
+ )}
+ />
+ }
+ >
+ {children}
+
+ );
+});
diff --git a/web-console/src/console-application.tsx b/web-console/src/console-application.tsx
index 2283deac68d6..731002632c03 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -55,7 +55,7 @@ export class ConsoleApplication extends React.PureComponent<
> {
private readonly capabilitiesQueryManager: QueryManager;
- static shownNotifications() {
+ static shownServiceNotification() {
AppToaster.show({
icon: IconNames.ERROR,
intent: Intent.DANGER,
@@ -87,7 +87,7 @@ export class ConsoleApplication extends React.PureComponent<
this.capabilitiesQueryManager = new QueryManager({
processQuery: async () => {
const capabilities = await Capabilities.detectCapabilities();
- if (!capabilities) ConsoleApplication.shownNotifications();
+ if (!capabilities) ConsoleApplication.shownServiceNotification();
return capabilities || Capabilities.FULL;
},
onStateChange: ({ data, loading }) => {
@@ -107,6 +107,10 @@ export class ConsoleApplication extends React.PureComponent<
this.capabilitiesQueryManager.terminate();
}
+ private readonly handleUnrestrict = (capabilities: Capabilities) => {
+ this.setState({ capabilities });
+ };
+
private resetInitialsWithDelay() {
setTimeout(() => {
this.taskId = undefined;
@@ -168,7 +172,11 @@ export class ConsoleApplication extends React.PureComponent<
return (
<>
-
+
{el}
>
);
diff --git a/web-console/src/dialogs/status-dialog/status-dialog.tsx b/web-console/src/dialogs/status-dialog/status-dialog.tsx
index d23fadfe3a45..3ae476d6753e 100644
--- a/web-console/src/dialogs/status-dialog/status-dialog.tsx
+++ b/web-console/src/dialogs/status-dialog/status-dialog.tsx
@@ -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',
},
],
},
diff --git a/web-console/src/druid-models/input-source.tsx b/web-console/src/druid-models/input-source.tsx
index 116ef48a8398..eb989de4f07e 100644
--- a/web-console/src/druid-models/input-source.tsx
+++ b/web-console/src/druid-models/input-source.tsx
@@ -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;
diff --git a/web-console/src/entry.scss b/web-console/src/entry.scss
index 44ce87b05928..99f7ef57d85e 100644
--- a/web-console/src/entry.scss
+++ b/web-console/src/entry.scss
@@ -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 {
diff --git a/web-console/src/react-table/index.ts b/web-console/src/react-table/index.ts
new file mode 100644
index 000000000000..3f1900fb6109
--- /dev/null
+++ b/web-console/src/react-table/index.ts
@@ -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';
diff --git a/web-console/src/react-table/react-table-base-styles.scss b/web-console/src/react-table/react-table-base-styles.scss
new file mode 100644
index 000000000000..2b1d0d20324a
--- /dev/null
+++ b/web-console/src/react-table/react-table-base-styles.scss
@@ -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;
+ }
+ }
+}
diff --git a/web-console/src/react-table/react-table-extra.scss b/web-console/src/react-table/react-table-extra.scss
new file mode 100644
index 000000000000..0b57b7967109
--- /dev/null
+++ b/web-console/src/react-table/react-table-extra.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/web-console/src/react-table/react-table-inputs.tsx b/web-console/src/react-table/react-table-inputs.tsx
new file mode 100644
index 000000000000..d7e02c3dc37e
--- /dev/null
+++ b/web-console/src/react-table/react-table-inputs.tsx
@@ -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 (
+
+ {(disableComparisons ? FILTER_MODES_NO_COMPARISON : FILTER_MODES).map((m, i) => (
+ onChange(combineModeAndNeedle(m, needle))}
+ labelElement={m === mode ? : undefined}
+ />
+ ))}
+
+ }
+ >
+
+
+ }
+ value={needle}
+ onChange={e => onChange(combineModeAndNeedle(mode, e.target.value))}
+ rightElement={
+ filter ? 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 (
+ onChange(event.target.value)}
+ value={filterValue || 'all'}
+ fill
+ >
+ Show all
+ true
+ false
+
+ );
+}
diff --git a/web-console/src/react-table/react-table-pagination/page-jump-dialog/page-jump-dialog.tsx b/web-console/src/react-table/react-table-pagination/page-jump-dialog/page-jump-dialog.tsx
new file mode 100644
index 000000000000..519abba735ec
--- /dev/null
+++ b/web-console/src/react-table/react-table-pagination/page-jump-dialog/page-jump-dialog.tsx
@@ -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(initPage + 1));
+ const pageAsNumber = parseInt(page, 10);
+ const validPage = pageAsNumber >= 1;
+
+ return (
+
+
+
+ setPage(vs)}
+ autoFocus
+ selectAllOnFocus
+ min={1}
+ max={maxPage}
+ minorStepSize={1}
+ fill
+ />
+
+
+
+
+
+ {
+ onJump(pageAsNumber - 1);
+ onClose();
+ }}
+ />
+
+
+
+ );
+});
diff --git a/web-console/src/react-table/react-table-pagination/react-table-pagination.scss b/web-console/src/react-table/react-table-pagination/react-table-pagination.scss
new file mode 100644
index 000000000000..2c185a81c237
--- /dev/null
+++ b/web-console/src/react-table/react-table-pagination/react-table-pagination.scss
@@ -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;
+}
diff --git a/web-console/src/react-table/react-table-pagination/react-table-pagination.tsx b/web-console/src/react-table/react-table-pagination/react-table-pagination.tsx
new file mode 100644
index 000000000000..df9b51b316b3
--- /dev/null
+++ b/web-console/src/react-table/react-table-pagination/react-table-pagination.tsx
@@ -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;
+}
+
+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 setShowPageJumpDialog(true)} />;
+ }
+
+ function renderPageSizeChangeMenuItem() {
+ if (!showPageSizeOptions) return;
+ return (
+
+ {pageSizeOptions.map((option, i) => (
+ {
+ if (option === pageSize) return;
+ onPageSizeChange(option);
+ }}
+ />
+ ))}
+
+ );
+ }
+
+ 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 (
+
+
+ changePage(page - 1)}
+ />
+ changePage(page + 1)}
+ />
+
+ {pageJumpMenuItem}
+ {pageSizeChangeMenuItem}
+
+ }
+ >
+
+
+
+ {showPageJumpDialog && (
+
setShowPageJumpDialog(false)}
+ />
+ )}
+
+ );
+});
diff --git a/web-console/src/react-table/react-table-utils.spec.ts b/web-console/src/react-table/react-table-utils.spec.ts
new file mode 100644
index 000000000000..70de19ed93c9
--- /dev/null
+++ b/web-console/src/react-table/react-table-utils.spec.ts
@@ -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'`);
+ });
+ });
+});
diff --git a/web-console/src/react-table/react-table-utils.ts b/web-console/src/react-table/react-table-utils.ts
new file mode 100644
index 000000000000..7cdaddbad3d8
--- /dev/null
+++ b/web-console/src/react-table/react-table-utils.ts
@@ -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()}%`),
+ );
+ }
+}
diff --git a/web-console/src/utils/capabilities.ts b/web-console/src/utils/capabilities.ts
index 406115dfc996..033b39842525 100644
--- a/web-console/src/utils/capabilities.ts
+++ b/web-console/src/utils/capabilities.ts
@@ -40,8 +40,9 @@ export interface CapabilitiesOptions {
}
export class Capabilities {
- static STATUS_TIMEOUT = 2000;
+ static STATUS_TIMEOUT = 15000;
static FULL: Capabilities;
+ static NO_SQL: Capabilities;
static COORDINATOR_OVERLORD: Capabilities;
static COORDINATOR: Capabilities;
static OVERLORD: Capabilities;
@@ -55,7 +56,7 @@ export class Capabilities {
// Check SQL endpoint
try {
await Api.instance.post(
- '/druid/v2/sql',
+ '/druid/v2/sql?capabilities',
{ query: 'SELECT 1337', context: { timeout: Capabilities.STATUS_TIMEOUT } },
{ timeout: Capabilities.STATUS_TIMEOUT },
);
@@ -65,7 +66,7 @@ export class Capabilities {
return; // other failure
}
try {
- await Api.instance.get('/status', { timeout: Capabilities.STATUS_TIMEOUT });
+ await Api.instance.get('/status?capabilities', { timeout: Capabilities.STATUS_TIMEOUT });
} catch (e) {
return; // total failure
}
@@ -73,7 +74,7 @@ export class Capabilities {
try {
await Api.instance.post(
- '/druid/v2',
+ '/druid/v2?capabilities',
{
queryType: 'dataSourceMetadata',
dataSource: '__web_console_probe__',
@@ -95,9 +96,9 @@ export class Capabilities {
return 'nativeAndSql';
}
- static async detectNode(node: 'coordinator' | 'overlord'): Promise {
+ static async detectManagementProxy(): Promise {
try {
- await Api.instance.get(`/proxy/${node}/status`, {
+ await Api.instance.get(`/proxy/coordinator/status?capabilities`, {
timeout: Capabilities.STATUS_TIMEOUT,
});
} catch (e) {
@@ -107,6 +108,21 @@ export class Capabilities {
return true;
}
+ static async detectNode(node: 'coordinator' | 'overlord'): Promise {
+ try {
+ await Api.instance.get(
+ `/druid/${node === 'overlord' ? 'indexer' : node}/v1/isLeader?capabilities`,
+ {
+ timeout: Capabilities.STATUS_TIMEOUT,
+ },
+ );
+ } catch (e) {
+ return false;
+ }
+
+ return true;
+ }
+
static async detectCapabilities(): Promise {
const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
if (capabilitiesOverride) return new Capabilities(capabilitiesOverride);
@@ -114,11 +130,16 @@ export class Capabilities {
const queryType = await Capabilities.detectQueryType();
if (typeof queryType === 'undefined') return;
- const coordinator = await Capabilities.detectNode('coordinator');
- if (typeof coordinator === 'undefined') return;
-
- const overlord = await Capabilities.detectNode('overlord');
- if (typeof overlord === 'undefined') return;
+ let coordinator: boolean;
+ let overlord: boolean;
+ if (queryType === 'none') {
+ // must not be running on the router, figure out what node the console is on (or both?)
+ coordinator = await Capabilities.detectNode('coordinator');
+ overlord = await Capabilities.detectNode('overlord');
+ } else {
+ // must be running on the router, figure out if the management proxy is working
+ coordinator = overlord = await Capabilities.detectManagementProxy();
+ }
return new Capabilities({
queryType,
@@ -204,6 +225,11 @@ Capabilities.FULL = new Capabilities({
coordinator: true,
overlord: true,
});
+Capabilities.NO_SQL = new Capabilities({
+ queryType: 'nativeOnly',
+ coordinator: true,
+ overlord: true,
+});
Capabilities.COORDINATOR_OVERLORD = new Capabilities({
queryType: 'none',
coordinator: true,
diff --git a/web-console/src/views/query-view/query-utils.ts b/web-console/src/utils/data-type-utils.ts
similarity index 69%
rename from web-console/src/views/query-view/query-utils.ts
rename to web-console/src/utils/data-type-utils.ts
index 770a68c7f5af..d9b387873657 100644
--- a/web-console/src/views/query-view/query-utils.ts
+++ b/web-console/src/utils/data-type-utils.ts
@@ -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':
+ return 200;
+
+ case 'ARRAY':
+ case 'ARRAY':
+ case 'ARRAY':
+ return 180;
+
+ case 'COMPLEX':
+ return 300;
+
+ default:
+ if (typeUpper.startsWith('ARRAY')) return 200;
+ if (typeUpper.startsWith('COMPLEX')) return 150;
+ return 180;
+ }
+}
diff --git a/web-console/src/utils/general.spec.ts b/web-console/src/utils/general.spec.ts
index fcb6e8b33809..fa11e1c87146 100644
--- a/web-console/src/utils/general.spec.ts
+++ b/web-console/src/utils/general.spec.ts
@@ -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'];
diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx
index e1c064a00595..b71ae28087c1 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -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 {
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(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 (
- onChange(e.target.value)}
- value={filterValue}
- rightElement={
- filterValue && onChange('')} />
- }
- placeholder={placeholder}
- />
- );
- };
-}
-
-export function makeBooleanFilter(): FilterRender {
- return function BooleanFilter({ filter, onChange, key }) {
- const filterValue = filter ? filter.value : '';
- return (
- onChange(event.target.value)}
- value={filterValue || 'all'}
- fill
- >
- Show all
- true
- false
-
- );
- };
-}
-
-// ----------------------------
-
-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;
}
// ----------------------------
diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx
index 77a093530dd1..35cc766d5814 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/utils/index.tsx
@@ -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';
diff --git a/web-console/src/utils/query-manager.tsx b/web-console/src/utils/query-manager.tsx
index 934ad495a27f..9504e8af9d67 100644
--- a/web-console/src/utils/query-manager.tsx
+++ b/web-console/src/utils/query-manager.tsx
@@ -185,18 +185,17 @@ export class QueryManager {
}
private trigger() {
- const currentlyLoading = Boolean(this.currentRunCancelFn);
-
- this.setState(
- new QueryState({
- loading: true,
- lastData: this.state.getSomeData(),
- }),
- );
-
- if (currentlyLoading) {
+ if (this.currentRunCancelFn) {
+ // Currently loading
this.runWhenLoading();
} else {
+ this.setState(
+ new QueryState({
+ loading: true,
+ lastData: this.state.getSomeData(),
+ }),
+ );
+
this.runWhenIdle();
}
}
diff --git a/web-console/src/variables.scss b/web-console/src/variables.scss
index 86528e156611..1dc4d0124cf3 100644
--- a/web-console/src/variables.scss
+++ b/web-console/src/variables.scss
@@ -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;
diff --git a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
old mode 100755
new mode 100644
similarity index 95%
rename from web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
rename to web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
index ed0016b10218..9b5240377e5a
--- a/web-console/src/views/datasource-view/__snapshots__/datasource-view.spec.tsx.snap
+++ b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
@@ -2,7 +2,7 @@
exports[`DatasourcesView matches snapshot 1`] = `
,
"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
,
"accessor": "total_data_size",
+ "className": "padded",
"filterable": false,
"show": true,
"width": 100,
@@ -201,6 +204,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
minimum / average / maximum
,
"accessor": "avg_segment_rows",
+ "className": "padded",
"filterable": false,
"show": true,
"width": 220,
@@ -213,6 +217,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
minimum / average / maximum
,
"accessor": "avg_segment_size",
+ "className": "padded",
"filterable": false,
"show": false,
"width": 270,
@@ -225,6 +230,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
granularity
,
"accessor": [Function],
+ "className": "padded",
"filterable": false,
"id": "segment_granularity",
"show": false,
@@ -238,6 +244,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
rows
,
"accessor": "total_rows",
+ "className": "padded",
"filterable": false,
"show": true,
"width": 100,
@@ -250,6 +257,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
(bytes)
,
"accessor": "avg_row_size",
+ "className": "padded",
"filterable": false,
"show": true,
"width": 100,
@@ -262,6 +270,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
size
,
"accessor": "replicated_size",
+ "className": "padded",
"filterable": false,
"show": true,
"width": 100,
@@ -283,6 +292,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
bytes / segments / intervals
,
"accessor": [Function],
+ "className": "padded",
"filterable": false,
"id": "percentCompacted",
"show": true,
@@ -296,6 +306,7 @@ exports[`DatasourcesView matches snapshot 1`] = `
compacted
,
"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],
diff --git a/web-console/src/views/datasource-view/datasource-view.scss b/web-console/src/views/datasources-view/datasources-view.scss
similarity index 98%
rename from web-console/src/views/datasource-view/datasource-view.scss
rename to web-console/src/views/datasources-view/datasources-view.scss
index b141d64b05e5..497be39b3c58 100644
--- a/web-console/src/views/datasource-view/datasource-view.scss
+++ b/web-console/src/views/datasources-view/datasources-view.scss
@@ -18,7 +18,7 @@
@import '../../variables';
-.datasource-view {
+.datasources-view {
height: 100%;
width: 100%;
overflow: auto;
diff --git a/web-console/src/views/datasource-view/datasource-view.spec.tsx b/web-console/src/views/datasources-view/datasources-view.spec.tsx
similarity index 95%
rename from web-console/src/views/datasource-view/datasource-view.spec.tsx
rename to web-console/src/views/datasources-view/datasources-view.spec.tsx
index b44948363bcd..e4231bceb33a 100644
--- a/web-console/src/views/datasource-view/datasource-view.spec.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.spec.tsx
@@ -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', () => {
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasources-view/datasources-view.tsx
similarity index 68%
rename from web-console/src/views/datasource-view/datasource-view.tsx
rename to web-console/src/views/datasources-view/datasources-view.tsx
index b894f7d91c1c..6e28bb62db9f 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -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 = {
'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 (
- <>
- {
- 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 }) => {
+ {
+ 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 => (
+ this.onDetail(row.original)}
+ hoverIcon={IconNames.SEARCH_TEMPLATE}
+ >
+ {row.value}
+
+ ),
+ },
+ {
+ 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 (
- {
- this.setState({
- datasourceFilter: addFilter(datasourceFilter, 'datasource', value),
- });
- }}
- >
- {value}
-
+
+ ●
+ Unused
+
);
- },
- },
- {
- 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 (
-
- ●
- Unused
+ }
+
+ const segmentsEl = (
+ goToSegments(datasource)}>
+ {pluralIfNeeded(num_segments, 'segment')}
+
+ );
+ if (typeof num_segments_to_load !== 'number' || typeof num_segments !== 'number') {
+ return '-';
+ } else if (num_segments_to_load === 0) {
+ return (
+
+
+ ●
- );
- }
-
- const segmentsEl = (
- goToSegments(datasource)}>
- {pluralIfNeeded(num_segments, 'segment')}
-
+ Fully available ({segmentsEl})
+
);
- if (typeof num_segments_to_load !== 'number' || typeof num_segments !== 'number') {
- return '-';
- } else if (num_segments_to_load === 0) {
- return (
-
-
- ●
-
- Fully available ({segmentsEl})
-
- );
- } else {
- const numAvailableSegments = num_segments - num_segments_to_load;
- const percentAvailable = (
- Math.floor((numAvailableSegments / num_segments) * 1000) / 10
- ).toFixed(1);
- return (
-
-
- {numAvailableSegments ? '\u25cf' : '\u25cb'}
-
- {percentAvailable}% available ({segmentsEl})
-
- );
- }
- },
- 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 }) => (
-
- ),
- },
- {
- 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 '-';
+ } else {
+ const numAvailableSegments = num_segments - num_segments_to_load;
+ const percentAvailable = (
+ Math.floor((numAvailableSegments / num_segments) * 1000) / 10
+ ).toFixed(1);
return (
- <>
- {' '}
- {' '}
- {' '}
- {' '}
-
- >
+
+
+ {numAvailableSegments ? '\u25cf' : '\u25cb'}
+
+ {percentAvailable}% available ({segmentsEl})
+
);
- },
+ }
},
- {
- 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 (
- <>
- {' '}
- {' '}
- {' '}
- {' '}
-
- >
- );
- },
+ 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('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('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', 'rows'),
- show: capabilities.hasSql() && visibleColumns.shown('Total rows'),
- accessor: 'total_rows',
- filterable: false,
- width: 100,
- Cell: ({ value }) => {
- if (isNumberLikeNaN(value)) return '-';
- return (
+ },
+ {
+ Header: twoLines('Total', 'data size'),
+ show: visibleColumns.shown('Total data size'),
+ accessor: 'total_data_size',
+ filterable: false,
+ width: 100,
+ className: 'padded',
+ Cell: ({ value }) => (
+
+ ),
+ },
+ {
+ 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 (
+ <>
{' '}
+ {' '}
+ {' '}
+ {' '}
+
- );
- },
+ >
+ );
},
- {
- 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 (
+ },
+ {
+ 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 (
+ <>
+ {' '}
+ {' '}
+ {' '}
+ {' '}
- );
- },
+ >
+ );
},
- {
- 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 (
-
- );
- },
+ },
+ {
+ 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: '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 (
-
- this.setState({
- compactionDialogOpenOn: {
- datasource,
- compactionConfig,
- },
- })
- }
- >
- {formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}
-
-
- );
- },
+ },
+ {
+ 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 (
+
+ );
},
- {
- 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 (
- <>
- {' '}
- {' '}
-
- >
- );
- }
-
+ },
+ {
+ 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 (
+
+ );
+ },
+ },
+ {
+ 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 (
+
+ );
+ },
+ },
+ {
+ 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 (
+
+ this.setState({
+ compactionDialogOpenOn: {
+ datasource,
+ compactionConfig,
+ },
+ })
+ }
+ hoverIcon={IconNames.EDIT}
+ >
+ {formatCompactionConfigAndStatus(compactionConfig, compactionStatus)}
+
+ );
+ },
+ },
+ {
+ 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 (
<>
- {' '}
- {' '}
- {' '}
- {' '}
-
+ {' '}
+ {' '}
+
>
);
- },
- },
- {
- 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 ;
- }
+ }
- return (
+ return (
+ <>
{' '}
+ {' '}
+ {' '}
+ {' '}
+
- );
- },
+ >
+ );
},
- {
- 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 (
-
- this.setState({
- retentionDialogOpenOn: {
- datasource,
- rules,
- },
- })
- }
- className="clickable-cell"
- >
- {rules.length
- ? DatasourcesView.formatRules(rules)
- : `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
-
-
-
- );
- },
+ },
+ {
+ 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 ;
+ }
+
+ return (
+
+ );
},
- {
- 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 (
- {
- this.setState({
- datasourceTableActionDialogId: datasource,
- actions: datasourceActions,
- });
- }}
- actions={datasourceActions}
- />
- );
- },
+ },
+ {
+ 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 (
+
+ this.setState({
+ retentionDialogOpenOn: {
+ datasource,
+ rules,
+ },
+ })
+ }
+ hoverIcon={IconNames.EDIT}
+ >
+ {rules.length
+ ? DatasourcesView.formatRules(rules)
+ : `Cluster default: ${DatasourcesView.formatRules(defaultRules)}`}
+
+ );
},
- ]}
- />
- {this.renderUnuseAction()}
- {this.renderUseAction()}
- {this.renderUseUnuseActionByInterval()}
- {this.renderKillAction()}
- {this.renderRetentionDialog()}
- {this.renderCompactionDialog()}
- {this.renderForceCompactAction()}
- >
+ },
+ {
+ 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 (
+ {
+ this.onDetail(original);
+ }}
+ actions={datasourceActions}
+ />
+ );
+ },
+ },
+ ]}
+ />
);
}
@@ -1466,7 +1464,7 @@ ORDER BY 1`;
return (
@@ -1505,7 +1503,7 @@ ORDER BY 1`;
/>
{showSegmentTimeline && }
- {this.renderDatasourceTable()}
+ {this.renderDatasourcesTable()}
{datasourceTableActionDialogId && (
this.setState({ datasourceTableActionDialogId: undefined })}
/>
)}
+ {this.renderUnuseAction()}
+ {this.renderUseAction()}
+ {this.renderUseUnuseActionByInterval()}
+ {this.renderKillAction()}
+ {this.renderRetentionDialog()}
+ {this.renderCompactionDialog()}
+ {this.renderForceCompactAction()}
);
}
diff --git a/web-console/src/views/index.ts b/web-console/src/views/index.ts
index a5e47e682b1e..8037ac29e911 100644
--- a/web-console/src/views/index.ts
+++ b/web-console/src/views/index.ts
@@ -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';
diff --git a/web-console/src/views/ingestion-view/__snapshots__/ingestion-view.spec.tsx.snap b/web-console/src/views/ingestion-view/__snapshots__/ingestion-view.spec.tsx.snap
index 3ae3f3026a36..41bf8fb33c30 100644
--- a/web-console/src/views/ingestion-view/__snapshots__/ingestion-view.spec.tsx.snap
+++ b/web-console/src/views/ingestion-view/__snapshots__/ingestion-view.spec.tsx.snap
@@ -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],
diff --git a/web-console/src/views/ingestion-view/ingestion-view.tsx b/web-console/src/views/ingestion-view/ingestion-view.tsx
index ac4a8afe929b..743850117588 100644
--- a/web-console/src/views/ingestion-view/ingestion-view.tsx
+++ b/web-console/src/views/ingestion-view/ingestion-view.tsx
@@ -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 { Api, AppToaster } from '../../singletons';
import {
- addFilter,
- addFilterRaw,
booleanCustomTableFilter,
+ SMALL_TABLE_PAGE_SIZE,
+ SMALL_TABLE_PAGE_SIZE_OPTIONS,
+ syncFilterClauseById,
+} from '../../react-table';
+import { Api, AppToaster } from '../../singletons';
+import {
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 (
+ this.setState({ supervisorFilter: filters })}
+ >
+ {row.value}
+
+ );
+ }
+
+ 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 (
- <>
- {
- 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 => (
+ {
+ 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 }) => (
+ this.onSupervisorDetail(original)}
+ hoverIcon={IconNames.EDIT}
+ >
+ {value}
+
+ ),
+ },
+ {
+ 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 => (
+ this.setState({ supervisorFilter: filters })}
+ >
●
{row.value}
- ),
- 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 (
-
- this.setState({
- supervisorTableActionDialogId: id,
- supervisorTableActionDialogActions: supervisorActions,
- })
- }
- actions={supervisorActions}
- />
- );
- },
- show: hiddenSupervisorColumns.shown(ACTION_COLUMN_LABEL),
+
+ ),
+ 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 (
+ this.onSupervisorDetail(row.original)}
+ actions={supervisorActions}
+ />
+ );
},
- ]}
- />
- {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 }) => (
+ this.setState({ taskFilter: filters })}
+ >
+ {row.value}
+
+ );
+ }
+
+ 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 (
- <>
- {
- 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,
+ {
+ 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 }) => (
+ this.onTaskDetail(original)}
+ hoverIcon={IconNames.EDIT}
+ >
+ {value}
+
+ ),
+ 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 (
+
+ ●
+ {status}
+ {errorMsg && (
+ this.setState({ alertErrorMsg: errorMsg })} title={errorMsg}>
+ ?
+
+ )}
+
);
- }
- 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 (
- {
- this.setState({ taskFilter: addFilter(taskFilter, 'group_id', value) });
- }}
- >
- {value}
-
- );
- },
- show: hiddenTaskColumns.shown('Group ID'),
},
- {
- Header: 'Type',
- accessor: 'type',
- width: 140,
- Cell: row => {
- const value = row.value;
- return (
- {
- this.setState({ taskFilter: addFilter(taskFilter, 'type', value) });
- }}
- >
- {value}
-
- );
- },
- show: hiddenTaskColumns.shown('Type'),
+ 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)
+ );
+
+ default:
+ return 0;
+ }
},
- {
- Header: 'Datasource',
- accessor: 'datasource',
- Cell: row => {
- const value = row.value;
- return (
- {
- this.setState({ taskFilter: addFilter(taskFilter, 'datasource', value) });
- }}
- >
- {value}
-
- );
- },
- show: hiddenTaskColumns.shown('Datasource'),
+ filterMethod: (filter: Filter, row: any) => {
+ return booleanCustomTableFilter(filter, row.status.status);
},
-
- {
- 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 (
-
- ●
- {status}
- {errorMsg && (
- this.setState({ alertErrorMsg: errorMsg })}
- title={errorMsg}
- >
- ?
-
- )}
-
- );
- },
- 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)
- );
-
- default:
- return 0;
- }
- },
- filterMethod: (filter: Filter, row: any) => {
- return booleanCustomTableFilter(filter, row.status.status);
- },
- show: hiddenTaskColumns.shown('Status'),
- },
- {
- Header: 'Duration',
- accessor: 'duration',
- width: 70,
- filterable: false,
- 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 (
-
- 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 (
+ 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()}
+ {this.renderResumeSupervisorAction()}
+ {this.renderSuspendSupervisorAction()}
+ {this.renderResetSupervisorAction()}
+ {this.renderTerminateSupervisorAction()}
+ {this.renderKillTaskAction()}
{supervisorSpecDialogOpen && (