diff --git a/server/pom.xml b/server/pom.xml index 339ac566123..febee7035d3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -48,6 +48,13 @@ druid-gcp-common ${project.parent.version} runtime + + + + org.apache.druid + druid-console + ${project.parent.version} + runtime org.apache.druid diff --git a/web-console/README.md b/web-console/README.md index 918f7efea79..4e16369d343 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 660d22cfdf1..fb525811122 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 709bd142044..00000000000 --- 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 51f8881363c..70c0a4c6c0b 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 c0e403c5b96..26364cd51bb 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 95877202ae1..0d83cd29fc4 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 064290bdca5..192d43281d6 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 bcc900f130c..00000000000 --- 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 ( -
-
- -
-
- - {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) => ( - - ))} - - - )} -
-
- -
-
- ); - } -} diff --git a/web-console/src/bootstrap/react-table-defaults.tsx b/web-console/src/bootstrap/react-table-defaults.tsx index 7cb6e11ce91..9058b95839d 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 52838723edd..722ca892966 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 9863e616aab..0433eca2b08 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 8dd6cf54cc6..7416653d983 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 1a711fc298c..dca59389af9 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 bd4149aa081..007e54919be 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/index.ts b/web-console/src/components/index.ts index 1324578c3ae..d3418fdc718 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 716dfc41647..faa7810fd45 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 - columns={[ - { - Header: 'Key', - accessor: 'k', - }, - { - Header: 'Value', - accessor: 'v', - }, - ]} noDataText={ entriesState.getErrorMessage() || 'Lookup data not found. If this is a new lookup it might not have propagated yet.' } + columns={[ + { + Header: 'Key', + accessor: 'k', + className: 'padded', + width: 300, + }, + { + Header: 'Value', + accessor: 'v', + className: 'padded', + }, + ]} /> ); } 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 8b93d8d9ec6..614224de77a 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 d36e1aba857..fe66c4efb67 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 cd0a4e368b0..c019c38fa22 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 3b3ded93d4a..3a7b4db6950 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 7d20b717fc6..3895a805a70 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 4bfacb31e80..d6f6f8b2d7f 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 00000000000..e2e79b1fcdb --- /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 00000000000..bb3d61cd05e --- /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 00000000000..d3387fd37e8 --- /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/dialogs/status-dialog/status-dialog.tsx b/web-console/src/dialogs/status-dialog/status-dialog.tsx index d23fadfe3a4..3ae476d6753 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 116ef48a839..eb989de4f07 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 44ce87b0592..99f7ef57d85 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 00000000000..3f1900fb610 --- /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 00000000000..2b1d0d20324 --- /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 00000000000..0b57b796710 --- /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 00000000000..d7e02c3dc37 --- /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} + /> + ))} + + } + > +