From 9f5c3c1c0ac8aecc3fdda8c9bc39251235f93214 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 17 Feb 2021 17:05:55 -0800 Subject: [PATCH 1/9] do not load all the segments --- web-console/src/singletons/api.ts | 2 +- .../src/views/segments-view/segments-view.tsx | 401 ++++++++++-------- 2 files changed, 219 insertions(+), 184 deletions(-) diff --git a/web-console/src/singletons/api.ts b/web-console/src/singletons/api.ts index 7a05bdd398c8..e14a13b35342 100644 --- a/web-console/src/singletons/api.ts +++ b/web-console/src/singletons/api.ts @@ -47,7 +47,7 @@ export class Api { static encodePath(path: string): string { return path.replace( - /[?#%&'\[\]]/g, + /[?#%&'\[\]\\]/g, c => '%' + c diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 0df4da6cadc7..8c5b71825463 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -19,7 +19,6 @@ import { Button, ButtonGroup, Intent, Label, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { SqlExpression, SqlRef } from 'druid-query-toolkit'; -import * as JSONBig from 'json-bigint-native'; import React from 'react'; import ReactTable, { Filter } from 'react-table'; @@ -39,6 +38,7 @@ import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-di import { Api } from '../../singletons'; import { addFilter, + booleanCustomTableFilter, compact, deepGet, filterMap, @@ -124,6 +124,7 @@ interface TableState { } interface SegmentsQuery extends TableState { + capabilities: Capabilities; groupByInterval: boolean; } @@ -131,6 +132,7 @@ interface SegmentQueryResultRow { datasource: string; start: string; end: string; + interval: string; segment_id: string; version: string; time_span: string; @@ -186,8 +188,31 @@ export class SegmentsView extends React.PureComponent; - private segmentsNoSqlQueryManager: QueryManager; + static computeTimeSpan(start: string, end: string): string { + if (start.endsWith('-01-01T00:00:00.000Z') && end.endsWith('-01-01T00:00:00.000Z')) { + return 'Year'; + } + + if (start.endsWith('-01T00:00:00.000Z') && end.endsWith('-01T00:00:00.000Z')) { + return 'Month'; + } + + if (start.endsWith('T00:00:00.000Z') && end.endsWith('T00:00:00.000Z')) { + return 'Day'; + } + + if (start.endsWith(':00:00.000Z') && end.endsWith(':00:00.000Z')) { + return 'Hour'; + } + + if (start.endsWith(':00.000Z') && end.endsWith(':00.000Z')) { + return 'Minute'; + } + + return 'Sub minute'; + } + + private segmentsQueryManager: QueryManager; private lastTableState: TableState | undefined; @@ -208,196 +233,210 @@ export class SegmentsView extends React.PureComponent { - const whereParts = filterMap(query.filtered, (f: Filter) => { - if (f.id.startsWith('is_')) { - if (f.value === 'all') return; - return SqlRef.columnWithQuotes(f.id).equal(f.value === 'true' ? 1 : 0); - } else { - return sqlQueryCustomTableFilter(f); - } - }); + const { page, pageSize, filtered, sorted, capabilities, groupByInterval } = query; - let queryParts: string[]; + if (capabilities.hasSql()) { + const whereParts = filterMap(filtered, (f: Filter) => { + if (f.id.startsWith('is_')) { + if (f.value === 'all') return; + return SqlRef.columnWithQuotes(f.id).equal(f.value === 'true' ? 1 : 0); + } else { + return sqlQueryCustomTableFilter(f); + } + }); - let whereClause = ''; - if (whereParts.length) { - whereClause = SqlExpression.and(...whereParts).toString(); - } + let queryParts: string[]; - if (query.groupByInterval) { - const innerQuery = compact([ - `SELECT "start" || '/' || "end" AS "interval"`, - `FROM sys.segments`, - whereClause ? `WHERE ${whereClause}` : undefined, - `GROUP BY 1`, - `ORDER BY 1 DESC`, - `LIMIT ${query.pageSize}`, - query.page ? `OFFSET ${query.page * query.pageSize}` : undefined, - ]).join('\n'); - - const intervals: string = (await queryDruidSql({ query: innerQuery })) - .map(row => `'${row.interval}'`) - .join(', '); - - queryParts = compact([ - SegmentsView.WITH_QUERY, - `SELECT "start" || '/' || "end" AS "interval", *`, - `FROM s`, - `WHERE`, - intervals ? ` ("start" || '/' || "end") IN (${intervals})` : 'FALSE', - whereClause ? ` AND ${whereClause}` : '', - ]); - - if (query.sorted.length) { - queryParts.push( - 'ORDER BY ' + - query.sorted - .map((sort: any) => `${JSONBig.stringify(sort.id)} ${sort.desc ? 'DESC' : 'ASC'}`) - .join(', '), - ); + let whereClause = ''; + if (whereParts.length) { + whereClause = SqlExpression.and(...whereParts).toString(); } - queryParts.push(`LIMIT ${query.pageSize * 1000}`); - } else { - queryParts = [SegmentsView.WITH_QUERY, `SELECT *`, `FROM s`]; + if (groupByInterval) { + const innerQuery = compact([ + `SELECT "start" || '/' || "end" AS "interval"`, + `FROM sys.segments`, + whereClause ? `WHERE ${whereClause}` : undefined, + `GROUP BY 1`, + `ORDER BY 1 DESC`, + `LIMIT ${pageSize}`, + page ? `OFFSET ${page * pageSize}` : undefined, + ]).join('\n'); + + const intervals: string = (await queryDruidSql({ query: innerQuery })) + .map(row => `'${row.interval}'`) + .join(', '); + + queryParts = compact([ + SegmentsView.WITH_QUERY, + `SELECT "start" || '/' || "end" AS "interval", *`, + `FROM s`, + `WHERE`, + intervals ? ` ("start" || '/' || "end") IN (${intervals})` : 'FALSE', + whereClause ? ` AND ${whereClause}` : '', + ]); + + if (sorted.length) { + queryParts.push( + 'ORDER BY ' + + sorted + .map((sort: any) => `${SqlRef.column(sort.id)} ${sort.desc ? 'DESC' : 'ASC'}`) + .join(', '), + ); + } - if (whereClause) { - queryParts.push(`WHERE ${whereClause}`); - } + queryParts.push(`LIMIT ${pageSize * 1000}`); + } else { + queryParts = [SegmentsView.WITH_QUERY, `SELECT *`, `FROM s`]; + + if (whereClause) { + queryParts.push(`WHERE ${whereClause}`); + } + + if (sorted.length) { + queryParts.push( + 'ORDER BY ' + + sorted + .map((sort: any) => `${SqlRef.column(sort.id)} ${sort.desc ? 'DESC' : 'ASC'}`) + .join(', '), + ); + } + + queryParts.push(`LIMIT ${pageSize}`); - if (query.sorted.length) { - queryParts.push( - 'ORDER BY ' + - query.sorted - .map((sort: any) => `${JSONBig.stringify(sort.id)} ${sort.desc ? 'DESC' : 'ASC'}`) - .join(', '), + if (page) { + queryParts.push(`OFFSET ${page * pageSize}`); + } + } + const sqlQuery = queryParts.join('\n'); + setIntermediateQuery(sqlQuery); + return await queryDruidSql({ query: sqlQuery }); + } else if (capabilities.hasCoordinatorAccess()) { + let datasourceList: string[] = (await Api.instance.get( + '/druid/coordinator/v1/metadata/datasources', + )).data; + + const datasourceFilter = filtered.find(({ id }) => id === 'datasource'); + // let restFiltered: Filter[]; + if (datasourceFilter) { + // restFiltered = filtered.filter(({ id }) => id !== 'datasource'); + datasourceList = datasourceList.filter(datasource => + booleanCustomTableFilter(datasourceFilter, datasource), ); + } else { + // restFiltered = filtered; } - queryParts.push(`LIMIT ${query.pageSize}`); - - if (query.page) { - queryParts.push(`OFFSET ${query.page * query.pageSize}`); + if (sorted.length && sorted[0].id === 'datasource') { + datasourceList.sort( + sorted[0].desc ? (d1, d2) => d1.localeCompare(d2) : (d1, d2) => d2.localeCompare(d1), + ); } - } - const sqlQuery = queryParts.join('\n'); - setIntermediateQuery(sqlQuery); - return await queryDruidSql({ query: sqlQuery }); - }, - onStateChange: segmentsState => { - this.setState({ - segmentsState, - }); - }, - }); - this.segmentsNoSqlQueryManager = new QueryManager({ - processQuery: async () => { - const datasourceList = (await Api.instance.get( - '/druid/coordinator/v1/metadata/datasources', - )).data; - const nestedResults: SegmentQueryResultRow[][] = await Promise.all( - datasourceList.map(async (d: string) => { + const maxResults = (page + 1) * pageSize; + let results: SegmentQueryResultRow[] = []; + + const n = Math.min(datasourceList.length, maxResults); + for (let i = 0; i < n && results.length < maxResults; i++) { const segments = (await Api.instance.get( - `/druid/coordinator/v1/datasources/${Api.encodePath(d)}?full`, + `/druid/coordinator/v1/datasources/${Api.encodePath(datasourceList[i])}?full`, )).data.segments; - return segments.map( - (segment: any): SegmentQueryResultRow => { - return { - segment_id: segment.identifier, - datasource: segment.dataSource, - start: segment.interval.split('/')[0], - end: segment.interval.split('/')[1], - version: segment.version, - time_span: '-', - partitioning: '-', - partition_num: deepGet(segment, 'shardSpec.partitionNum') || 0, - size: segment.size, - num_rows: -1, - num_replicas: -1, - is_available: -1, - is_published: -1, - is_realtime: -1, - is_overshadowed: -1, - }; - }, - ); - }), - ); + if (segments) { + results = results.concat( + segments + .map( + (segment: any): SegmentQueryResultRow => { + const [start, end] = segment.interval.split('/'); + return { + segment_id: segment.identifier, + datasource: segment.dataSource, + start, + end, + interval: segment.interval, + version: segment.version, + time_span: SegmentsView.computeTimeSpan(start, end), + partitioning: deepGet(segment, 'shardSpec.type') || '-', + partition_num: deepGet(segment, 'shardSpec.partitionNum') || 0, + size: segment.size, + num_rows: -1, + num_replicas: -1, + is_available: -1, + is_published: -1, + is_realtime: -1, + is_overshadowed: -1, + }; + }, + ) + .filter(Boolean), + ); + } + } - return nestedResults.flat().sort((d1, d2) => { - return d2.start.localeCompare(d1.start); - }); + return results.slice(page * pageSize, maxResults); + } else { + throw new Error('must have SQL or coordinator access to load this view'); + } }, onStateChange: segmentsState => { this.setState({ - trimmedSegments: segmentsState.data - ? segmentsState.data.slice(0, SegmentsView.PAGE_SIZE) - : undefined, segmentsState, }); }, }); } - componentDidMount(): void { - const { capabilities } = this.props; - if (!capabilities.hasSql() && capabilities.hasCoordinatorAccess()) { - this.segmentsNoSqlQueryManager.runQuery(null); - } - } - componentWillUnmount(): void { - this.segmentsSqlQueryManager.terminate(); - this.segmentsNoSqlQueryManager.terminate(); + this.segmentsQueryManager.terminate(); } private fetchData = (groupByInterval: boolean, tableState?: TableState) => { + const { capabilities } = this.props; if (tableState) this.lastTableState = tableState; const { page, pageSize, filtered, sorted } = this.lastTableState!; - this.segmentsSqlQueryManager.runQuery({ + this.segmentsQueryManager.runQuery({ page, pageSize, filtered, sorted, - groupByInterval: groupByInterval, + capabilities, + groupByInterval, }); }; - private fetchClientSideData = (tableState?: TableState) => { - if (tableState) this.lastTableState = tableState; - const { page, pageSize, filtered, sorted } = this.lastTableState!; - - this.setState(state => { - const allSegments = state.segmentsState.data; - if (!allSegments) return {}; - const sortKey = sorted[0].id as keyof SegmentQueryResultRow; - const sortDesc = sorted[0].desc; - - return { - trimmedSegments: allSegments - .filter(d => { - return filtered.every((f: any) => { - return String(d[f.id as keyof SegmentQueryResultRow]).includes(f.value); - }); - }) - .sort((d1, d2) => { - const v1 = d1[sortKey] as any; - const v2 = d2[sortKey] as any; - if (typeof v1 === 'string') { - return sortDesc ? v2.localeCompare(v1) : v1.localeCompare(v2); - } else { - return sortDesc ? v2 - v1 : v1 - v2; - } - }) - .slice(page * pageSize, (page + 1) * pageSize), - }; - }); - }; + // private fetchClientSideData = (tableState?: TableState) => { + // if (tableState) this.lastTableState = tableState; + // const { page, pageSize, filtered, sorted } = this.lastTableState!; + // + // this.setState(state => { + // const allSegments = state.segmentsState.data; + // if (!allSegments) return {}; + // const sortKey = sorted[0].id as keyof SegmentQueryResultRow; + // const sortDesc = sorted[0].desc; + // + // return { + // trimmedSegments: allSegments + // .filter(d => { + // return filtered.every((f: any) => { + // return String(d[f.id as keyof SegmentQueryResultRow]).includes(f.value); + // }); + // }) + // .sort((d1, d2) => { + // const v1 = d1[sortKey] as any; + // const v2 = d2[sortKey] as any; + // if (typeof v1 === 'string') { + // return sortDesc ? v2.localeCompare(v1) : v1.localeCompare(v2); + // } else { + // return sortDesc ? v2 - v1 : v1 - v2; + // } + // }) + // .slice(page * pageSize, (page + 1) * pageSize), + // }; + // }); + // }; private getSegmentActions(id: string, datasource: string): BasicAction[] { const actions: BasicAction[] = []; @@ -443,6 +482,7 @@ export class SegmentsView extends React.PureComponent { this.setState({ segmentFilter: filtered }); }} onFetchData={tableState => { - if (capabilities.hasSql()) { - this.fetchData(groupByInterval, tableState); - } else if (capabilities.hasCoordinatorAccess()) { - this.fetchClientSideData(tableState); - } + this.fetchData(groupByInterval, tableState); }} showPageJump={false} ofText="" @@ -472,6 +508,7 @@ export class SegmentsView extends React.PureComponent ( String(Boolean(row.is_published)), Filter: makeBooleanFilter(), }, { Header: 'Is realtime', - show: capabilities.hasSql() && hiddenColumns.exists('Is realtime'), + show: hasSql && hiddenColumns.exists('Is realtime'), id: 'is_realtime', accessor: row => String(Boolean(row.is_realtime)), Filter: makeBooleanFilter(), }, { Header: 'Is available', - show: capabilities.hasSql() && hiddenColumns.exists('Is available'), + show: hasSql && hiddenColumns.exists('Is available'), id: 'is_available', accessor: row => String(Boolean(row.is_available)), Filter: makeBooleanFilter(), }, { Header: 'Is overshadowed', - show: capabilities.hasSql() && hiddenColumns.exists('Is overshadowed'), + show: hasSql && hiddenColumns.exists('Is overshadowed'), id: 'is_overshadowed', accessor: row => String(Boolean(row.is_overshadowed)), Filter: makeBooleanFilter(), @@ -654,8 +698,7 @@ export class SegmentsView extends React.PureComponent { - this.segmentsNoSqlQueryManager.rerunLastQuery(); - this.segmentsSqlQueryManager.rerunLastQuery(); + this.segmentsQueryManager.rerunLastQuery(); }} >

{`Are you sure you want to drop segment '${terminateSegmentId}'?`}

@@ -666,7 +709,7 @@ export class SegmentsView extends React.PureComponent @@ -700,11 +743,7 @@ export class SegmentsView extends React.PureComponent - capabilities.hasSql() - ? this.segmentsSqlQueryManager.rerunLastQuery(auto) - : this.segmentsNoSqlQueryManager.rerunLastQuery(auto) - } + onRefresh={auto => this.segmentsQueryManager.rerunLastQuery(auto)} localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE} /> @@ -713,11 +752,7 @@ export class SegmentsView extends React.PureComponent { this.setState({ groupByInterval: false }); - if (capabilities.hasSql()) { - this.fetchData(false); - } else { - this.fetchClientSideData(); - } + this.fetchData(false); }} > None From be38d1ff68594a3b9d103448d22af7b62135e2de Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 18 Feb 2021 21:17:40 -0800 Subject: [PATCH 2/9] fix filtering --- .../src/components/header-bar/header-bar.tsx | 43 ++++++- web-console/src/utils/general.tsx | 9 +- web-console/src/utils/local-storage-keys.tsx | 5 + .../src/views/segments-view/segments-view.tsx | 116 ++++++++---------- 4 files changed, 103 insertions(+), 70 deletions(-) diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index 76bb378fe781..f28810523a71 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -22,6 +22,7 @@ import { Button, Intent, Menu, + MenuDivider, MenuItem, Navbar, NavbarDivider, @@ -39,12 +40,20 @@ import { OverlordDynamicConfigDialog, } from '../../dialogs'; import { getLink } from '../../links'; -import { Capabilities } from '../../utils'; +import { + Capabilities, + localStorageGetJson, + LocalStorageKeys, + localStorageRemove, + localStorageSetJson, +} from '../../utils'; import { ExternalLink } from '../external-link/external-link'; import { PopoverText } from '../popover-text/popover-text'; import './header-bar.scss'; +const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE); + export type HeaderActiveTab = | null | 'load-data' @@ -243,6 +252,38 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { href="#lookups" disabled={!capabilities.hasCoordinatorAccess()} /> + + + {capabilitiesOverride ? ( + { + localStorageRemove(LocalStorageKeys.CAPABILITIES_OVERRIDE); + location.reload(); + }} + /> + ) : ( + <> + { + localStorageSetJson( + LocalStorageKeys.CAPABILITIES_OVERRIDE, + Capabilities.COORDINATOR, + ); + location.reload(); + }} + /> + { + localStorageSetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE, Capabilities.OVERLORD); + location.reload(); + }} + /> + + )} + ); diff --git a/web-console/src/utils/general.tsx b/web-console/src/utils/general.tsx index 8783f084841f..672c17603e82 100644 --- a/web-console/src/utils/general.tsx +++ b/web-console/src/utils/general.tsx @@ -98,7 +98,8 @@ interface NeedleAndMode { mode: 'exact' | 'includes'; } -function getNeedleAndMode(input: string): NeedleAndMode { +export function getNeedleAndMode(filter: Filter): NeedleAndMode { + const input = filter.value.toLowerCase(); if (input.startsWith(`"`) && input.endsWith(`"`)) { return { needle: input.slice(1, -1), @@ -114,7 +115,7 @@ function getNeedleAndMode(input: string): NeedleAndMode { export function booleanCustomTableFilter(filter: Filter, value: any): boolean { if (value == null) return false; const haystack = String(value).toLowerCase(); - const needleAndMode: NeedleAndMode = getNeedleAndMode(filter.value.toLowerCase()); + const needleAndMode: NeedleAndMode = getNeedleAndMode(filter); const needle = needleAndMode.needle; if (needleAndMode.mode === 'exact') { return needle === haystack; @@ -123,13 +124,13 @@ export function booleanCustomTableFilter(filter: Filter, value: any): boolean { } export function sqlQueryCustomTableFilter(filter: Filter): SqlExpression { - const needleAndMode: NeedleAndMode = getNeedleAndMode(filter.value); + 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.toLowerCase()}%`), + SqlLiteral.create(`%${needle}%`), ); } } diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx index 7ae1c6853d3e..99e788c53f0f 100644 --- a/web-console/src/utils/local-storage-keys.tsx +++ b/web-console/src/utils/local-storage-keys.tsx @@ -67,3 +67,8 @@ export function localStorageGetJson(key: LocalStorageKeys): any { return; } } + +export function localStorageRemove(key: LocalStorageKeys): void { + if (typeof localStorage === 'undefined') return; + return localStorage.removeItem(key); +} diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 8c5b71825463..70b092f3b541 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -44,6 +44,7 @@ import { filterMap, formatBytes, formatInteger, + getNeedleAndMode, LocalStorageKeys, makeBooleanFilter, queryDruidSql, @@ -320,14 +321,10 @@ export class SegmentsView extends React.PureComponent id === 'datasource'); - // let restFiltered: Filter[]; if (datasourceFilter) { - // restFiltered = filtered.filter(({ id }) => id !== 'datasource'); datasourceList = datasourceList.filter(datasource => booleanCustomTableFilter(datasourceFilter, datasource), ); - } else { - // restFiltered = filtered; } if (sorted.length && sorted[0].id === 'datasource') { @@ -344,36 +341,42 @@ export class SegmentsView extends React.PureComponent { - const [start, end] = segment.interval.split('/'); - return { - segment_id: segment.identifier, - datasource: segment.dataSource, - start, - end, - interval: segment.interval, - version: segment.version, - time_span: SegmentsView.computeTimeSpan(start, end), - partitioning: deepGet(segment, 'shardSpec.type') || '-', - partition_num: deepGet(segment, 'shardSpec.partitionNum') || 0, - size: segment.size, - num_rows: -1, - num_replicas: -1, - is_available: -1, - is_published: -1, - is_realtime: -1, - is_overshadowed: -1, - }; - }, - ) - .filter(Boolean), - ); + if (!Array.isArray(segments)) continue; + + let segmentQueryResultRows: SegmentQueryResultRow[] = segments.map((segment: any) => { + const [start, end] = segment.interval.split('/'); + return { + segment_id: segment.identifier, + datasource: segment.dataSource, + start, + end, + interval: segment.interval, + version: segment.version, + time_span: SegmentsView.computeTimeSpan(start, end), + partitioning: deepGet(segment, 'shardSpec.type') || '-', + partition_num: deepGet(segment, 'shardSpec.partitionNum') || 0, + size: segment.size, + num_rows: -1, + num_replicas: -1, + is_available: -1, + is_published: -1, + is_realtime: -1, + is_overshadowed: -1, + }; + }); + + if (filtered.length) { + segmentQueryResultRows = segmentQueryResultRows.filter((d: SegmentQueryResultRow) => { + return filtered.every(filter => { + return booleanCustomTableFilter( + filter, + d[filter.id as keyof SegmentQueryResultRow], + ); + }); + }); } + + results = results.concat(segmentQueryResultRows); } return results.slice(page * pageSize, maxResults); @@ -407,37 +410,6 @@ export class SegmentsView extends React.PureComponent { - // if (tableState) this.lastTableState = tableState; - // const { page, pageSize, filtered, sorted } = this.lastTableState!; - // - // this.setState(state => { - // const allSegments = state.segmentsState.data; - // if (!allSegments) return {}; - // const sortKey = sorted[0].id as keyof SegmentQueryResultRow; - // const sortDesc = sorted[0].desc; - // - // return { - // trimmedSegments: allSegments - // .filter(d => { - // return filtered.every((f: any) => { - // return String(d[f.id as keyof SegmentQueryResultRow]).includes(f.value); - // }); - // }) - // .sort((d1, d2) => { - // const v1 = d1[sortKey] as any; - // const v2 = d2[sortKey] as any; - // if (typeof v1 === 'string') { - // return sortDesc ? v2.localeCompare(v1) : v1.localeCompare(v2); - // } else { - // return sortDesc ? v2 - v1 : v1 - v2; - // } - // }) - // .slice(page * pageSize, (page + 1) * pageSize), - // }; - // }); - // }; - private getSegmentActions(id: string, datasource: string): BasicAction[] { const actions: BasicAction[] = []; actions.push({ @@ -483,6 +455,14 @@ export class SegmentsView extends React.PureComponent filter.id === 'datasource' && getNeedleAndMode(filter).mode === 'exact', + ); + return ( Date: Fri, 19 Feb 2021 10:49:36 -0800 Subject: [PATCH 3/9] update datasource view --- .../table-column-selector.tsx | 18 ++- .../views/datasource-view/datasource-view.tsx | 140 ++++++++++++------ .../src/views/segments-view/segments-view.tsx | 87 +++++++---- 3 files changed, 167 insertions(+), 78 deletions(-) diff --git a/web-console/src/components/table-column-selector/table-column-selector.tsx b/web-console/src/components/table-column-selector/table-column-selector.tsx index 6cdd9510fb94..5ce6db555d8b 100644 --- a/web-console/src/components/table-column-selector/table-column-selector.tsx +++ b/web-console/src/components/table-column-selector/table-column-selector.tsx @@ -18,7 +18,7 @@ import { Button, Menu, Popover, Position } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; +import React, { useState } from 'react'; import { MenuCheckbox } from '../menu-checkbox/menu-checkbox'; @@ -27,13 +27,15 @@ import './table-column-selector.scss'; interface TableColumnSelectorProps { columns: string[]; onChange: (column: string) => void; + onClose?: (added: number) => void; tableColumnsHidden: string[]; } export const TableColumnSelector = React.memo(function TableColumnSelector( props: TableColumnSelectorProps, ) { - const { columns, onChange, tableColumnsHidden } = props; + const { columns, onChange, onClose, tableColumnsHidden } = props; + const [added, setAdded] = useState(0); const isColumnShown = (column: string) => !tableColumnsHidden.includes(column); @@ -44,7 +46,12 @@ export const TableColumnSelector = React.memo(function TableColumnSelector( text={column} key={column} checked={isColumnShown(column)} - onChange={() => onChange(column)} + onChange={() => { + if (!isColumnShown(column)) { + setAdded(added + 1); + } + onChange(column); + }} /> ))} @@ -57,6 +64,11 @@ export const TableColumnSelector = React.memo(function TableColumnSelector( className="table-column-selector" content={checkboxes} position={Position.BOTTOM_RIGHT} + onOpened={() => setAdded(0)} + onClose={() => { + if (!onClose) return; + onClose(added); + }} >