From e1a972fdf1ee4af62607662730b6e4856c3a5a74 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:10:33 +0800 Subject: [PATCH 01/12] Display time in local time --- web-console/package-lock.json | 12 ++++++ web-console/package.json | 1 + web-console/src/utils/date.ts | 1 + .../src/views/services-view/services-view.tsx | 13 +++++-- .../src/views/tasks-view/tasks-view.tsx | 39 +++++++++++-------- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/web-console/package-lock.json b/web-console/package-lock.json index fa00329611ef..71ae0fa6f37a 100644 --- a/web-console/package-lock.json +++ b/web-console/package-lock.json @@ -30,6 +30,7 @@ "d3-shape": "^3.2.0", "d3-time-format": "^4.1.0", "date-fns": "^2.28.0", + "dayjs": "^1.11.15", "druid-query-toolkit": "^1.1.5", "echarts": "^5.5.1", "file-saver": "^2.0.5", @@ -6978,6 +6979,12 @@ "date-fns": "2.x" } }, + "node_modules/dayjs": { + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", + "license": "MIT" + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -23479,6 +23486,11 @@ "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.1.tgz", "integrity": "sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==" }, + "dayjs": { + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==" + }, "debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", diff --git a/web-console/package.json b/web-console/package.json index 370bf979c02d..9a050ff49e52 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -72,6 +72,7 @@ "d3-shape": "^3.2.0", "d3-time-format": "^4.1.0", "date-fns": "^2.28.0", + "dayjs": "^1.11.15", "druid-query-toolkit": "^1.1.5", "echarts": "^5.5.1", "file-saver": "^2.0.5", diff --git a/web-console/src/utils/date.ts b/web-console/src/utils/date.ts index e240932d92b4..fbbc2489ee49 100644 --- a/web-console/src/utils/date.ts +++ b/web-console/src/utils/date.ts @@ -21,6 +21,7 @@ import { fromDate, toTimeZone } from '@internationalized/date'; import type { Timezone } from 'chronoshift'; const CURRENT_YEAR = new Date().getUTCFullYear(); +export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss:SSSZ'; export function isNonNullRange(range: DateRange): range is NonNullDateRange { return range[0] != null && range[1] != null; diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 408d4ddb2747..1c5cb42b2e1e 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -49,6 +49,7 @@ import { Api, AppToaster } from '../../singletons'; import type { AuxiliaryQueryFn, NumberLike } from '../../utils'; import { assemble, + DATE_FORMAT, deepGet, filterMap, formatBytes, @@ -72,6 +73,7 @@ import type { BasicAction } from '../../utils/basic-action'; import { FillIndicator } from './fill-indicator/fill-indicator'; import './services-view.scss'; +import dayjs from 'dayjs'; const TABLE_COLUMNS_BY_MODE: Record = { 'full': [ @@ -143,6 +145,7 @@ interface ServiceResultRow { readonly plaintext_port: number; readonly tls_port: number; readonly start_time: string; + display_start_time: string; } interface ServicesWithAuxiliaryInfo { @@ -271,6 +274,9 @@ ORDER BY let services: ServiceResultRow[]; if (capabilities.hasSql()) { services = await queryDruidSql({ query: ServicesView.SERVICE_SQL }, cancelToken); + services.forEach(s => { + s.display_start_time = dayjs(s.start_time).format(DATE_FORMAT); + }); } else if (capabilities.hasCoordinatorAccess()) { services = (await getApiArray('/druid/coordinator/v1/servers?simple', cancelToken)).map( (s: any): ServiceResultRow => { @@ -286,6 +292,7 @@ ORDER BY curr_size: s.currSize, max_size: s.maxSize, start_time: '1970:01:01T00:00:00Z', + display_start_time: '1970:01:01T00:00:00+00:00', is_leader: 0, }; }, @@ -611,9 +618,9 @@ ORDER BY { Header: 'Start time', show: visibleColumns.shown('Start time'), - accessor: 'start_time', - width: 200, - Cell: this.renderFilterableCell('start_time'), + accessor: 'display_start_time', + width: 220, + Cell: this.renderFilterableCell('display_start_time'), Aggregated: () => '', }, { diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 216345d8aaa3..3725d8f538af 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -18,7 +18,8 @@ import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { formatDistanceToNow } from 'date-fns'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime' import React, { type ReactNode } from 'react'; import type { Filter } from 'react-table'; import ReactTable from 'react-table'; @@ -50,6 +51,7 @@ import { } from '../../react-table'; import { Api, AppToaster } from '../../singletons'; import { + DATE_FORMAT, formatDuration, getApiArray, getDruidErrorMessage, @@ -66,6 +68,8 @@ import { ExecutionDetailsDialog } from '../workbench-view/execution-details-dial import './tasks-view.scss'; +dayjs.extend(relativeTime); + const taskTableColumns: string[] = [ 'Task ID', 'Group ID', @@ -179,12 +183,16 @@ ORDER BY this.taskQueryManager = new QueryManager({ processQuery: async (capabilities, cancelToken) => { if (capabilities.hasSql()) { - return await queryDruidSql( + const tasks = await queryDruidSql( { query: TasksView.TASK_SQL, }, cancelToken, ); + tasks.forEach(t => { + t.display_created_time = dayjs(t.created_time).format(DATE_FORMAT); + }); + return tasks; } else if (capabilities.hasOverlordAccess()) { return (await getApiArray(`/druid/indexer/v1/tasks`, cancelToken)).map(d => { return { @@ -192,6 +200,7 @@ ORDER BY group_id: d.groupId, type: d.type, created_time: d.createdTime, + display_created_time: dayjs(d.createdTime).format(DATE_FORMAT), datasource: d.dataSource, duration: d.duration ? d.duration : 0, error_msg: d.errorMsg, @@ -493,18 +502,18 @@ ORDER BY }, { Header: 'Created time', - accessor: 'created_time', - width: 190, - Cell: this.renderTaskFilterableCell('created_time', true, value => { - const valueAsDate = new Date(value); - return isNaN(valueAsDate.valueOf()) ? ( + accessor: 'display_created_time', + width: 220, + Cell: this.renderTaskFilterableCell('display_created_time', true, value => { + const parsedDate = dayjs(value); + return !parsedDate.isValid() ? ( String(value) ) : ( - - {value} + + {parsedDate.format(DATE_FORMAT)} ); - }), + }, ), Aggregated: () => '', show: visibleColumns.shown('Created time'), }, @@ -519,15 +528,13 @@ ORDER BY if (value > 0) { const shownDuration = formatDuration(value); - const start = new Date(original.created_time); - if (isNaN(start.valueOf())) return shownDuration; + const start = dayjs(original.created_time); + if (!start.isValid()) return shownDuration; - const end = new Date(start.valueOf() + value); + const end = start.add(value, 'ms'); return ( {shownDuration} From 31dcbf89541d76e939a8125266d0a573b3cf3be6 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:18:42 +0800 Subject: [PATCH 02/12] Add config for displaying local time --- .../src/components/header-bar/header-bar.tsx | 10 +++ .../web-console-config-dialog.tsx | 67 +++++++++++++++++++ .../web-console-config.mock.tsx | 23 +++++++ .../web-console-config/web-console-config.tsx | 36 ++++++++++ web-console/src/utils/local-storage-keys.tsx | 2 + .../src/views/services-view/services-view.tsx | 16 +++-- .../src/views/tasks-view/tasks-view.tsx | 25 +++---- 7 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 web-console/src/dialogs/web-console-config-dialog/web-console-config-dialog.tsx create mode 100644 web-console/src/druid-models/web-console-config/web-console-config.mock.tsx create mode 100644 web-console/src/druid-models/web-console-config/web-console-config.tsx diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index cfa10d83a5be..8c4823c5812c 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -41,6 +41,7 @@ import { DoctorDialog, OverlordDynamicConfigDialog, } from '../../dialogs'; +import { WebConsoleConfigDialog } from '../../dialogs/web-console-config-dialog/web-console-config-dialog'; import type { ConsoleViewId } from '../../druid-models'; import { getConsoleViewIcon } from '../../druid-models'; import { Capabilities } from '../../helpers'; @@ -76,6 +77,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { useState(false); const [overlordDynamicConfigDialogOpen, setOverlordDynamicConfigDialogOpen] = useState(false); const [compactionDynamicConfigDialogOpen, setCompactionDynamicConfigDialogOpen] = useState(false); + const [webConsoleConfigDialogOpen, setWebConsoleConfigDialogOpen] = useState(false); const showSplitDataLoaderMenu = capabilities.hasMultiStageQueryTask(); @@ -194,6 +196,11 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { onClick={() => setCompactionDynamicConfigDialogOpen(true)} disabled={!capabilities.hasCoordinatorAccess()} /> + setWebConsoleConfigDialogOpen(true)} + /> setCompactionDynamicConfigDialogOpen(false)} /> )} + {webConsoleConfigDialogOpen && ( + setWebConsoleConfigDialogOpen(false)} /> + )} ); }); diff --git a/web-console/src/dialogs/web-console-config-dialog/web-console-config-dialog.tsx b/web-console/src/dialogs/web-console-config-dialog/web-console-config-dialog.tsx new file mode 100644 index 000000000000..bc3afbe60002 --- /dev/null +++ b/web-console/src/dialogs/web-console-config-dialog/web-console-config-dialog.tsx @@ -0,0 +1,67 @@ +/* + * 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, Intent } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; +import React, { useState } from 'react'; + +import { AutoForm } from '../../components'; +import { + WEB_CONSOLE_CONFIG_FIELDS, + type WebConsoleConfig, +} from '../../druid-models/web-console-config/web-console-config'; +import { DEFAULT_WEB_CONSOLE_CONFIG } from '../../druid-models/web-console-config/web-console-config.mock'; +import { AppToaster } from '../../singletons'; +import { localStorageGetJson, LocalStorageKeys, localStorageSetJson } from '../../utils'; + +export interface WebConsoleConfigDialogProps { + onClose(): void; +} + +export const WebConsoleConfigDialog = React.memo(function WebConsoleConfigDialog( + props: WebConsoleConfigDialogProps, +) { + const { onClose } = props; + const [config, setConfig] = useState( + localStorageGetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS) || DEFAULT_WEB_CONSOLE_CONFIG, + ); + + function save() { + localStorageSetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS, config); + AppToaster.show({ + message: 'Saved web console config', + intent: Intent.SUCCESS, + }); + onClose(); + location.reload(); + } + + return ( + +
+

Sets the local web console configuration.

+ +
+
+
+
+
+
+ ); +}); diff --git a/web-console/src/druid-models/web-console-config/web-console-config.mock.tsx b/web-console/src/druid-models/web-console-config/web-console-config.mock.tsx new file mode 100644 index 000000000000..13e65b0e2ef3 --- /dev/null +++ b/web-console/src/druid-models/web-console-config/web-console-config.mock.tsx @@ -0,0 +1,23 @@ +/* + * 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 type { WebConsoleConfig } from './web-console-config'; + +export const DEFAULT_WEB_CONSOLE_CONFIG: WebConsoleConfig = { + showLocalTime: false, +}; diff --git a/web-console/src/druid-models/web-console-config/web-console-config.tsx b/web-console/src/druid-models/web-console-config/web-console-config.tsx new file mode 100644 index 000000000000..8a609d846d58 --- /dev/null +++ b/web-console/src/druid-models/web-console-config/web-console-config.tsx @@ -0,0 +1,36 @@ +/* + * 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 type { Field } from '../../components'; + +export interface WebConsoleConfig { + showLocalTime?: boolean; +} + +export const WEB_CONSOLE_CONFIG_FIELDS: Field[] = [ + { + name: 'showLocalTime', + type: 'boolean', + defaultValue: false, + info: ( + <> + Boolean flag for whether we show local time in the "Tasks", "Segments" and "Services" views. + + ), + }, +]; diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx index 9181e4be0d31..de1118c7ae19 100644 --- a/web-console/src/utils/local-storage-keys.tsx +++ b/web-console/src/utils/local-storage-keys.tsx @@ -61,6 +61,8 @@ export const LocalStorageKeys = { EXPLORE_STATE: 'explore-state' as const, EXPLORE_STICKY: 'explore-sticky' as const, + + WEB_CONSOLE_CONFIGS: 'web-console-configs' as const, }; export type LocalStorageKeys = (typeof LocalStorageKeys)[keyof typeof LocalStorageKeys]; diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 1c5cb42b2e1e..7e34ad5b7196 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -19,6 +19,7 @@ import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { max, sum } from 'd3-array'; +import dayjs from 'dayjs'; import memoize from 'memoize-one'; import React, { createContext, useContext } from 'react'; import type { Column, Filter } from 'react-table'; @@ -39,6 +40,7 @@ import { import { AsyncActionDialog } from '../../dialogs'; import type { QueryWithContext } from '../../druid-models'; import { getConsoleViewIcon } from '../../druid-models'; +import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities, CapabilitiesMode } from '../../helpers'; import { STANDARD_TABLE_PAGE_SIZE, @@ -58,6 +60,7 @@ import { getApiArray, hasOverlayOpen, LocalStorageBackedVisibility, + localStorageGetJson, LocalStorageKeys, lookupBy, oneOf, @@ -73,7 +76,6 @@ import type { BasicAction } from '../../utils/basic-action'; import { FillIndicator } from './fill-indicator/fill-indicator'; import './services-view.scss'; -import dayjs from 'dayjs'; const TABLE_COLUMNS_BY_MODE: Record = { 'full': [ @@ -145,7 +147,7 @@ interface ServiceResultRow { readonly plaintext_port: number; readonly tls_port: number; readonly start_time: string; - display_start_time: string; + local_start_time: string; } interface ServicesWithAuxiliaryInfo { @@ -275,7 +277,7 @@ ORDER BY if (capabilities.hasSql()) { services = await queryDruidSql({ query: ServicesView.SERVICE_SQL }, cancelToken); services.forEach(s => { - s.display_start_time = dayjs(s.start_time).format(DATE_FORMAT); + s.local_start_time = dayjs(s.start_time).format(DATE_FORMAT); }); } else if (capabilities.hasCoordinatorAccess()) { services = (await getApiArray('/druid/coordinator/v1/servers?simple', cancelToken)).map( @@ -292,7 +294,7 @@ ORDER BY curr_size: s.currSize, max_size: s.maxSize, start_time: '1970:01:01T00:00:00Z', - display_start_time: '1970:01:01T00:00:00+00:00', + local_start_time: '1970:01:01T00:00:00+00:00', is_leader: 0, }; }, @@ -441,6 +443,8 @@ ORDER BY workerInfoLookup: Record, ): Column[] => { const { capabilities } = this.props; + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS); + const showLocalTime = webConsoleConfig?.showLocalTime; return [ { Header: 'Service', @@ -618,9 +622,9 @@ ORDER BY { Header: 'Start time', show: visibleColumns.shown('Start time'), - accessor: 'display_start_time', + accessor: showLocalTime ? 'local_start_time' : 'start_time', width: 220, - Cell: this.renderFilterableCell('display_start_time'), + Cell: this.renderFilterableCell(showLocalTime ? 'local_start_time' : 'start_time'), Aggregated: () => '', }, { diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 3725d8f538af..08c4c95a2f94 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -19,7 +19,7 @@ import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime' +import relativeTime from 'dayjs/plugin/relativeTime'; import React, { type ReactNode } from 'react'; import type { Filter } from 'react-table'; import ReactTable from 'react-table'; @@ -43,6 +43,7 @@ import { TASK_CANCELED_ERROR_MESSAGES, TASK_CANCELED_PREDICATE, } from '../../druid-models'; +import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities } from '../../helpers'; import { SMALL_TABLE_PAGE_SIZE, @@ -57,6 +58,7 @@ import { getDruidErrorMessage, hasOverlayOpen, LocalStorageBackedVisibility, + localStorageGetJson, LocalStorageKeys, oneOf, queryDruidSql, @@ -87,6 +89,7 @@ interface TaskQueryResultRow { group_id: string; type: string; created_time: string; + local_created_time: string; datasource: string; duration: number; error_msg: string | null; @@ -190,7 +193,7 @@ ORDER BY cancelToken, ); tasks.forEach(t => { - t.display_created_time = dayjs(t.created_time).format(DATE_FORMAT); + t.local_created_time = dayjs(t.created_time).format(DATE_FORMAT); }); return tasks; } else if (capabilities.hasOverlordAccess()) { @@ -200,7 +203,7 @@ ORDER BY group_id: d.groupId, type: d.type, created_time: d.createdTime, - display_created_time: dayjs(d.createdTime).format(DATE_FORMAT), + local_created_time: dayjs(d.createdTime).format(DATE_FORMAT), datasource: d.dataSource, duration: d.duration ? d.duration : 0, error_msg: d.errorMsg, @@ -378,6 +381,8 @@ ORDER BY const { tasksState, groupTasksBy, visibleColumns } = this.state; const tasks = tasksState.data || []; + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS); + const showLocalTime = webConsoleConfig?.showLocalTime; return ( { + Cell: this.renderTaskFilterableCell(showLocalTime ? 'local_created_time' : 'created_time', true, value => { const parsedDate = dayjs(value); return !parsedDate.isValid() ? ( String(value) ) : ( - - {parsedDate.format(DATE_FORMAT)} - + {value} ); - }, ), + }), Aggregated: () => '', show: visibleColumns.shown('Created time'), }, @@ -533,9 +536,7 @@ ORDER BY const end = start.add(value, 'ms'); return ( - + {shownDuration} ); From 84444c61d74f58f5ecbf32b84f44b03342583b96 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:41:21 +0800 Subject: [PATCH 03/12] Format code --- .../__snapshots__/header-bar.spec.tsx.snap | 10 ++++++++ .../web-console-config-dialog.tsx | 2 +- .../web-console-config/web-console-config.tsx | 3 ++- .../__snapshots__/services-view.spec.tsx.snap | 2 +- .../src/views/services-view/services-view.tsx | 4 +++- .../__snapshots__/tasks-view.spec.tsx.snap | 2 +- .../src/views/tasks-view/tasks-view.tsx | 24 ++++++++++++------- 7 files changed, 33 insertions(+), 14 deletions(-) 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 af55b025da42..351d0e90bf06 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 @@ -274,6 +274,16 @@ exports[`HeaderBar matches snapshot 1`] = ` shouldDismissPopover={true} text="Compaction dynamic config" /> +
-
diff --git a/web-console/src/druid-models/web-console-config/web-console-config.tsx b/web-console/src/druid-models/web-console-config/web-console-config.tsx index 8a609d846d58..1e6ebbf1d9f7 100644 --- a/web-console/src/druid-models/web-console-config/web-console-config.tsx +++ b/web-console/src/druid-models/web-console-config/web-console-config.tsx @@ -29,7 +29,8 @@ export const WEB_CONSOLE_CONFIG_FIELDS: Field[] = [ defaultValue: false, info: ( <> - Boolean flag for whether we show local time in the "Tasks", "Segments" and "Services" views. + Boolean flag for whether we show local time in the "Tasks", "Segments" + and "Services" views. ), }, diff --git a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap index 329a7fd3562a..0c1322d5026f 100644 --- a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap +++ b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap @@ -206,7 +206,7 @@ exports[`ServicesView renders data 1`] = ` "Header": "Start time", "accessor": "start_time", "show": true, - "width": 200, + "width": 220, }, { "Aggregated": [Function], diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 7e34ad5b7196..081646c43773 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -443,7 +443,9 @@ ORDER BY workerInfoLookup: Record, ): Column[] => { const { capabilities } = this.props; - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS); + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( + LocalStorageKeys.WEB_CONSOLE_CONFIGS, + ); const showLocalTime = webConsoleConfig?.showLocalTime; return [ { diff --git a/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap b/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap index 6fc2a4d3386a..c253cd3a5672 100644 --- a/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap +++ b/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap @@ -202,7 +202,7 @@ exports[`TasksView matches snapshot 1`] = ` "Header": "Created time", "accessor": "created_time", "show": true, - "width": 190, + "width": 220, }, { "Aggregated": [Function], diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 08c4c95a2f94..0711f835cb38 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -381,7 +381,9 @@ ORDER BY const { tasksState, groupTasksBy, visibleColumns } = this.state; const tasks = tasksState.data || []; - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson(LocalStorageKeys.WEB_CONSOLE_CONFIGS); + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( + LocalStorageKeys.WEB_CONSOLE_CONFIGS, + ); const showLocalTime = webConsoleConfig?.showLocalTime; return ( { - const parsedDate = dayjs(value); - return !parsedDate.isValid() ? ( - String(value) - ) : ( - {value} - ); - }), + Cell: this.renderTaskFilterableCell( + showLocalTime ? 'local_created_time' : 'created_time', + true, + value => { + const parsedDate = dayjs(value); + return !parsedDate.isValid() ? ( + String(value) + ) : ( + {value} + ); + }, + ), Aggregated: () => '', show: visibleColumns.shown('Created time'), }, From b7763b5b5dfd0434f5f2f32cb9c4fc90be5c3b1f Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:56:49 +0800 Subject: [PATCH 04/12] Fix date format --- web-console/src/utils/date.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-console/src/utils/date.ts b/web-console/src/utils/date.ts index fbbc2489ee49..91dd3d5a67aa 100644 --- a/web-console/src/utils/date.ts +++ b/web-console/src/utils/date.ts @@ -21,7 +21,7 @@ import { fromDate, toTimeZone } from '@internationalized/date'; import type { Timezone } from 'chronoshift'; const CURRENT_YEAR = new Date().getUTCFullYear(); -export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss:SSSZ'; +export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; export function isNonNullRange(range: DateRange): range is NonNullDateRange { return range[0] != null && range[1] != null; From 93a63f3d57f6cef6b941ca11bf41d5129307ebfc Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:15:11 +0800 Subject: [PATCH 05/12] Support local time in segments tab --- .../src/views/segments-view/segments-view.tsx | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 967b3e55755f..9690b8ee2d62 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -18,6 +18,7 @@ import { Button, ButtonGroup, Intent, Label, MenuItem, Switch, Tag } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import dayjs from 'dayjs'; import { C, L, SqlComparison, SqlExpression } from 'druid-query-toolkit'; import * as JSONBig from 'json-bigint-native'; import type { ReactNode } from 'react'; @@ -46,6 +47,7 @@ import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-di import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog'; import type { QueryContext, QueryWithContext, ShardSpec } from '../../druid-models'; import { computeSegmentTimeSpan, getConsoleViewIcon, getDatasourceColor } from '../../druid-models'; +import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities, CapabilitiesMode } from '../../helpers'; import { booleanCustomTableFilter, @@ -62,6 +64,7 @@ import { assemble, compact, countBy, + DATE_FORMAT, filterMap, findMap, formatBytes, @@ -70,6 +73,7 @@ import { hasOverlayOpen, isNumberLikeNaN, LocalStorageBackedVisibility, + localStorageGetJson, LocalStorageKeys, oneOf, queryDruidSql, @@ -156,8 +160,22 @@ function formatRangeDimensionValue(dimension: any, value: any): string { } function segmentFiltersToExpression(filters: Filter[]): SqlExpression { + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( + LocalStorageKeys.WEB_CONSOLE_CONFIGS, + ); + const showLocalTime = webConsoleConfig?.showLocalTime; return SqlExpression.and( ...filterMap(filters, filter => { + if ((filter.id === 'start' || filter.id === 'end') && showLocalTime) { + // Local times need to be converted to UTC for the SQL query + const newFilter = { ...filter }; + const modeAndNeedle = parseFilterModeAndNeedle(newFilter); + if (!modeAndNeedle) return; + const parsedDate = dayjs(modeAndNeedle.needle); + if (!parsedDate.isValid()) return; + newFilter.value = `${modeAndNeedle.mode}${parsedDate.toISOString()}`; + return sqlQueryCustomTableFilter(newFilter); + } if (filter.id === 'shard_type') { // Special handling for shard_type that needs to be searched for in the shard_spec // Creates filters like `shard_spec LIKE '%"type":"numbered"%'` @@ -570,10 +588,19 @@ export class SegmentsView extends React.PureComponent { + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( + LocalStorageKeys.WEB_CONSOLE_CONFIGS, + ); + const showLocalTime = webConsoleConfig?.showLocalTime; + return showLocalTime ? dayjs(value).format(DATE_FORMAT) : value; + } + private renderFilterableCell( field: string, enableComparisons = false, - valueFn: (value: string) => ReactNode = String, + displayFn: (value: string) => ReactNode = String, + valueFn: (value: string) => string = String, ) { const { filters } = this.props; const { handleFilterChange } = this; @@ -582,12 +609,12 @@ export class SegmentsView extends React.PureComponent - {valueFn(row.value)} + {displayFn(row.value)} ); }; @@ -698,20 +725,20 @@ export class SegmentsView extends React.PureComponent Date: Thu, 4 Sep 2025 17:46:36 +0800 Subject: [PATCH 06/12] Update menu icon --- web-console/src/components/header-bar/header-bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-console/src/components/header-bar/header-bar.tsx b/web-console/src/components/header-bar/header-bar.tsx index 8c4823c5812c..d2e2bb23b13d 100644 --- a/web-console/src/components/header-bar/header-bar.tsx +++ b/web-console/src/components/header-bar/header-bar.tsx @@ -197,7 +197,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) { disabled={!capabilities.hasCoordinatorAccess()} /> setWebConsoleConfigDialogOpen(true)} /> From e84dc5ee63c85ed5666cd28630cfccb2b4a67a4c Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Thu, 4 Sep 2025 18:41:06 +0800 Subject: [PATCH 07/12] Fix bugs --- .../table-filterable-cell.tsx | 5 +- web-console/src/utils/date.ts | 13 +++++ .../src/views/segments-view/segments-view.tsx | 48 ++++++++----------- .../src/views/services-view/services-view.tsx | 42 +++++++++------- .../src/views/tasks-view/tasks-view.tsx | 48 +++++++++++-------- 5 files changed, 89 insertions(+), 67 deletions(-) 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 index 80d9cc83d594..7654492752b3 100644 --- a/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx +++ b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx @@ -37,12 +37,13 @@ export interface TableFilterableCellProps { onFiltersChange(filters: Filter[]): void; enableComparisons?: boolean; children?: ReactNode; + displayValue?: string; } export const TableFilterableCell = React.memo(function TableFilterableCell( props: TableFilterableCellProps, ) { - const { field, value, children, filters, enableComparisons, onFiltersChange } = props; + const { field, value, children, filters, enableComparisons, onFiltersChange, displayValue } = props; return ( onFiltersChange( addOrUpdateFilter(filters, { diff --git a/web-console/src/utils/date.ts b/web-console/src/utils/date.ts index 91dd3d5a67aa..d426dd2cea8c 100644 --- a/web-console/src/utils/date.ts +++ b/web-console/src/utils/date.ts @@ -19,6 +19,11 @@ import type { DateRange, NonNullDateRange } from '@blueprintjs/datetime'; import { fromDate, toTimeZone } from '@internationalized/date'; import type { Timezone } from 'chronoshift'; +import dayjs from 'dayjs'; + +import type { WebConsoleConfig } from '../druid-models/web-console-config/web-console-config'; + +import { localStorageGetJson,LocalStorageKeys } from './local-storage-keys'; const CURRENT_YEAR = new Date().getUTCFullYear(); export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -95,3 +100,11 @@ export function maxDate(a: Date, b: Date): Date { export function minDate(a: Date, b: Date): Date { return a < b ? a : b; } + +export function formatDate(value: string) { + const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( + LocalStorageKeys.WEB_CONSOLE_CONFIGS, + ); + const showLocalTime = webConsoleConfig?.showLocalTime; + return showLocalTime ? dayjs(value).format(DATE_FORMAT) : dayjs(value).toISOString(); +} diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 9690b8ee2d62..c0d8f3d48d7a 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -47,11 +47,11 @@ import { SegmentTableActionDialog } from '../../dialogs/segments-table-action-di import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog'; import type { QueryContext, QueryWithContext, ShardSpec } from '../../druid-models'; import { computeSegmentTimeSpan, getConsoleViewIcon, getDatasourceColor } from '../../druid-models'; -import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities, CapabilitiesMode } from '../../helpers'; import { booleanCustomTableFilter, BooleanFilterInput, + combineModeAndNeedle, parseFilterModeAndNeedle, sqlQueryCustomTableFilter, STANDARD_TABLE_PAGE_SIZE, @@ -64,16 +64,15 @@ import { assemble, compact, countBy, - DATE_FORMAT, filterMap, findMap, formatBytes, + formatDate, formatInteger, getApiArray, hasOverlayOpen, isNumberLikeNaN, LocalStorageBackedVisibility, - localStorageGetJson, LocalStorageKeys, oneOf, queryDruidSql, @@ -160,21 +159,21 @@ function formatRangeDimensionValue(dimension: any, value: any): string { } function segmentFiltersToExpression(filters: Filter[]): SqlExpression { - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( - LocalStorageKeys.WEB_CONSOLE_CONFIGS, - ); - const showLocalTime = webConsoleConfig?.showLocalTime; return SqlExpression.and( ...filterMap(filters, filter => { - if ((filter.id === 'start' || filter.id === 'end') && showLocalTime) { - // Local times need to be converted to UTC for the SQL query - const newFilter = { ...filter }; - const modeAndNeedle = parseFilterModeAndNeedle(newFilter); + if (filter.id === 'start' || filter.id === 'end') { + // Dates need to be converted to ISO string for the SQL query + const modeAndNeedle = parseFilterModeAndNeedle(filter); if (!modeAndNeedle) return; - const parsedDate = dayjs(modeAndNeedle.needle); - if (!parsedDate.isValid()) return; - newFilter.value = `${modeAndNeedle.mode}${parsedDate.toISOString()}`; - return sqlQueryCustomTableFilter(newFilter); + if (modeAndNeedle.mode === '~') { + return sqlQueryCustomTableFilter(filter); + } + const internalFilter = { ...filter }; + const formattedDate = formatDate(modeAndNeedle.needle); + const filterDate = dayjs(formattedDate).toISOString(); + filter.value = combineModeAndNeedle(modeAndNeedle.mode,formattedDate); + internalFilter.value = combineModeAndNeedle(modeAndNeedle.mode, filterDate); + return sqlQueryCustomTableFilter(internalFilter); } if (filter.id === 'shard_type') { // Special handling for shard_type that needs to be searched for in the shard_spec @@ -588,19 +587,11 @@ export class SegmentsView extends React.PureComponent { - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( - LocalStorageKeys.WEB_CONSOLE_CONFIGS, - ); - const showLocalTime = webConsoleConfig?.showLocalTime; - return showLocalTime ? dayjs(value).format(DATE_FORMAT) : value; - } - private renderFilterableCell( field: string, enableComparisons = false, displayFn: (value: string) => ReactNode = String, - valueFn: (value: string) => string = String, + filterDisplayFn: (value: string) => string = String, ) { const { filters } = this.props; const { handleFilterChange } = this; @@ -609,10 +600,11 @@ export class SegmentsView extends React.PureComponent {displayFn(row.value)} @@ -728,7 +720,7 @@ export class SegmentsView extends React.PureComponent computeSegmentTimeSpan(start, end), + accessor: ({ start, end }) => computeSegmentTimeSpan(dayjs(start).toISOString(), dayjs(end).toISOString()), width: 100, sortable: false, filterable: false, diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 081646c43773..2dd8a6a6b0ca 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -19,7 +19,6 @@ import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { max, sum } from 'd3-array'; -import dayjs from 'dayjs'; import memoize from 'memoize-one'; import React, { createContext, useContext } from 'react'; import type { Column, Filter } from 'react-table'; @@ -40,9 +39,11 @@ import { import { AsyncActionDialog } from '../../dialogs'; import type { QueryWithContext } from '../../druid-models'; import { getConsoleViewIcon } from '../../druid-models'; -import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities, CapabilitiesMode } from '../../helpers'; import { + booleanCustomTableFilter, + combineModeAndNeedle, + parseFilterModeAndNeedle, STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS, suggestibleFilterInput, @@ -51,16 +52,15 @@ import { Api, AppToaster } from '../../singletons'; import type { AuxiliaryQueryFn, NumberLike } from '../../utils'; import { assemble, - DATE_FORMAT, deepGet, filterMap, formatBytes, formatBytesCompact, + formatDate, formatDurationWithMsIfNeeded, getApiArray, hasOverlayOpen, LocalStorageBackedVisibility, - localStorageGetJson, LocalStorageKeys, lookupBy, oneOf, @@ -147,7 +147,6 @@ interface ServiceResultRow { readonly plaintext_port: number; readonly tls_port: number; readonly start_time: string; - local_start_time: string; } interface ServicesWithAuxiliaryInfo { @@ -276,9 +275,6 @@ ORDER BY let services: ServiceResultRow[]; if (capabilities.hasSql()) { services = await queryDruidSql({ query: ServicesView.SERVICE_SQL }, cancelToken); - services.forEach(s => { - s.local_start_time = dayjs(s.start_time).format(DATE_FORMAT); - }); } else if (capabilities.hasCoordinatorAccess()) { services = (await getApiArray('/druid/coordinator/v1/servers?simple', cancelToken)).map( (s: any): ServiceResultRow => { @@ -294,7 +290,6 @@ ORDER BY curr_size: s.currSize, max_size: s.maxSize, start_time: '1970:01:01T00:00:00Z', - local_start_time: '1970:01:01T00:00:00+00:00', is_leader: 0, }; }, @@ -389,7 +384,7 @@ ORDER BY this.serviceQueryManager.runQuery({ capabilities, visibleColumns }); }; - private renderFilterableCell(field: string) { + private renderFilterableCell(field: string, displayFn: (value: string) => string = String) { const { filters, onFiltersChange } = this.props; return function FilterableCell(row: { value: any }) { @@ -399,7 +394,10 @@ ORDER BY value={row.value} filters={filters} onFiltersChange={onFiltersChange} - /> +displayValue={displayFn(row.value)} + > + {displayFn(row.value)} + ); }; } @@ -443,10 +441,7 @@ ORDER BY workerInfoLookup: Record, ): Column[] => { const { capabilities } = this.props; - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( - LocalStorageKeys.WEB_CONSOLE_CONFIGS, - ); - const showLocalTime = webConsoleConfig?.showLocalTime; + return [ { Header: 'Service', @@ -529,6 +524,7 @@ ORDER BY if (value === null) return ''; return formatBytes(value); }, + }, { Header: 'Max size', @@ -624,10 +620,22 @@ ORDER BY { Header: 'Start time', show: visibleColumns.shown('Start time'), - accessor: showLocalTime ? 'local_start_time' : 'start_time', + accessor: 'start_time', + id: 'start_time', width: 220, - Cell: this.renderFilterableCell(showLocalTime ? 'local_start_time' : 'start_time'), + Cell: this.renderFilterableCell('start_time', formatDate), Aggregated: () => '', + filterMethod: (filter: Filter, row: ServiceResultRow) => { + const modeAndNeedle = parseFilterModeAndNeedle(filter) + if (!modeAndNeedle) return true; + const parsedRowTime = formatDate(row.start_time); + if (modeAndNeedle.mode === '~') { + return booleanCustomTableFilter(filter, parsedRowTime); + } + const parsedFilterTime = formatDate(modeAndNeedle.needle); + filter.value = combineModeAndNeedle(modeAndNeedle.mode, parsedFilterTime); + return booleanCustomTableFilter(filter, parsedRowTime); + }, }, { Header: 'Detail', diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 0711f835cb38..68c53e2026fc 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -43,9 +43,11 @@ import { TASK_CANCELED_ERROR_MESSAGES, TASK_CANCELED_PREDICATE, } from '../../druid-models'; -import type { WebConsoleConfig } from '../../druid-models/web-console-config/web-console-config'; import type { Capabilities } from '../../helpers'; import { + booleanCustomTableFilter, + combineModeAndNeedle, + parseFilterModeAndNeedle, SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS, suggestibleFilterInput, @@ -53,12 +55,12 @@ import { import { Api, AppToaster } from '../../singletons'; import { DATE_FORMAT, + formatDate, formatDuration, getApiArray, getDruidErrorMessage, hasOverlayOpen, LocalStorageBackedVisibility, - localStorageGetJson, LocalStorageKeys, oneOf, queryDruidSql, @@ -89,7 +91,6 @@ interface TaskQueryResultRow { group_id: string; type: string; created_time: string; - local_created_time: string; datasource: string; duration: number; error_msg: string | null; @@ -192,9 +193,6 @@ ORDER BY }, cancelToken, ); - tasks.forEach(t => { - t.local_created_time = dayjs(t.created_time).format(DATE_FORMAT); - }); return tasks; } else if (capabilities.hasOverlordAccess()) { return (await getApiArray(`/druid/indexer/v1/tasks`, cancelToken)).map(d => { @@ -203,7 +201,6 @@ ORDER BY group_id: d.groupId, type: d.type, created_time: d.createdTime, - local_created_time: dayjs(d.createdTime).format(DATE_FORMAT), datasource: d.dataSource, duration: d.duration ? d.duration : 0, error_msg: d.errorMsg, @@ -341,7 +338,8 @@ ORDER BY private renderTaskFilterableCell( field: string, enableComparisons = false, - valueFn: (value: string) => ReactNode = String, + displayFn: (value: string ) => ReactNode = String, + filterDisplayFn: (value: string) => string = String, ) { const { filters, onFiltersChange } = this.props; @@ -353,8 +351,9 @@ ORDER BY filters={filters} onFiltersChange={onFiltersChange} enableComparisons={enableComparisons} + displayValue={filterDisplayFn(row.value)} > - {valueFn(row.value)} + {displayFn(row.value)} ); }; @@ -381,10 +380,6 @@ ORDER BY const { tasksState, groupTasksBy, visibleColumns } = this.state; const tasks = tasksState.data || []; - const webConsoleConfig: WebConsoleConfig | undefined = localStorageGetJson( - LocalStorageKeys.WEB_CONSOLE_CONFIGS, - ); - const showLocalTime = webConsoleConfig?.showLocalTime; return ( { - const parsedDate = dayjs(value); - return !parsedDate.isValid() ? ( - String(value) + const day = dayjs(value); + return day.isValid() ? ( + {formatDate(value)} ) : ( - {value} + String(value) ); }, + formatDate, ), Aggregated: () => '', show: visibleColumns.shown('Created time'), + filterMethod: (filter: Filter, row: TaskQueryResultRow) => { + const modeAndNeedle = parseFilterModeAndNeedle(filter) + if (!modeAndNeedle) return true; + const parsedRowDate = formatDate(row.created_time); + if (modeAndNeedle.mode === '~') { + return booleanCustomTableFilter(filter, parsedRowDate); + } + const parsedFilterDate = formatDate(modeAndNeedle.needle); + filter.value = combineModeAndNeedle(modeAndNeedle.mode, parsedFilterDate); + return booleanCustomTableFilter(filter, parsedRowDate); + }, }, { Header: 'Duration', @@ -533,6 +540,7 @@ ORDER BY filterable: false, className: 'padded', Cell({ value, original, aggregated }) { + console.log(value, original, aggregated); if (aggregated) return ''; if (value > 0) { const shownDuration = formatDuration(value); From 96a1fa6f68a280d553750e23658376d1ed422ae0 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:19:11 +0800 Subject: [PATCH 08/12] Fix formatting --- .../header-bar/__snapshots__/header-bar.spec.tsx.snap | 2 +- .../table-filterable-cell/table-filterable-cell.tsx | 3 ++- web-console/src/utils/date.ts | 2 +- .../segments-view/__snapshots__/segments-view.spec.tsx.snap | 4 ++-- web-console/src/views/segments-view/segments-view.tsx | 5 +++-- web-console/src/views/services-view/services-view.tsx | 5 ++--- web-console/src/views/tasks-view/tasks-view.tsx | 4 ++-- 7 files changed, 13 insertions(+), 12 deletions(-) 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 351d0e90bf06..49866cc08391 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 @@ -277,7 +277,7 @@ exports[`HeaderBar matches snapshot 1`] = ` computeSegmentTimeSpan(dayjs(start).toISOString(), dayjs(end).toISOString()), + accessor: ({ start, end }) => + computeSegmentTimeSpan(dayjs(start).toISOString(), dayjs(end).toISOString()), width: 100, sortable: false, filterable: false, diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 2dd8a6a6b0ca..29b114e36c96 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -394,7 +394,7 @@ ORDER BY value={row.value} filters={filters} onFiltersChange={onFiltersChange} -displayValue={displayFn(row.value)} + displayValue={displayFn(row.value)} > {displayFn(row.value)} @@ -524,7 +524,6 @@ displayValue={displayFn(row.value)} if (value === null) return ''; return formatBytes(value); }, - }, { Header: 'Max size', @@ -626,7 +625,7 @@ displayValue={displayFn(row.value)} Cell: this.renderFilterableCell('start_time', formatDate), Aggregated: () => '', filterMethod: (filter: Filter, row: ServiceResultRow) => { - const modeAndNeedle = parseFilterModeAndNeedle(filter) + const modeAndNeedle = parseFilterModeAndNeedle(filter); if (!modeAndNeedle) return true; const parsedRowTime = formatDate(row.start_time); if (modeAndNeedle.mode === '~') { diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 68c53e2026fc..361ed17a3dd7 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -338,7 +338,7 @@ ORDER BY private renderTaskFilterableCell( field: string, enableComparisons = false, - displayFn: (value: string ) => ReactNode = String, + displayFn: (value: string) => ReactNode = String, filterDisplayFn: (value: string) => string = String, ) { const { filters, onFiltersChange } = this.props; @@ -522,7 +522,7 @@ ORDER BY Aggregated: () => '', show: visibleColumns.shown('Created time'), filterMethod: (filter: Filter, row: TaskQueryResultRow) => { - const modeAndNeedle = parseFilterModeAndNeedle(filter) + const modeAndNeedle = parseFilterModeAndNeedle(filter); if (!modeAndNeedle) return true; const parsedRowDate = formatDate(row.created_time); if (modeAndNeedle.mode === '~') { From d21843c735b26a850c5799672abfb072b4093f2d Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:29:41 +0800 Subject: [PATCH 09/12] Update snapshots --- .../__snapshots__/timezone-menu-items.spec.tsx.snap | 2 +- .../services-view/__snapshots__/services-view.spec.tsx.snap | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap b/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap index 313c9721c1f2..19dca4e0bced 100644 --- a/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap +++ b/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap @@ -81,7 +81,7 @@ exports[`TimezoneMenuItems matches snapshot 1`] = ` active={false} disabled={false} icon="blank" - label="UTC-6:00" + label="UTC-5:00" multiline={false} onClick={[Function]} popoverProps={{}} diff --git a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap index 0c1322d5026f..1aa0506f8f6a 100644 --- a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap +++ b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap @@ -205,6 +205,8 @@ exports[`ServicesView renders data 1`] = ` "Cell": [Function], "Header": "Start time", "accessor": "start_time", + "filterMethod": [Function], + "id": "start_time", "show": true, "width": 220, }, From 46f9c4fdb5a096c23af3210848d0167863ed25a7 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:45:52 +0800 Subject: [PATCH 10/12] Clean up files --- web-console/src/views/tasks-view/tasks-view.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 361ed17a3dd7..90485e8b6153 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -187,13 +187,12 @@ ORDER BY this.taskQueryManager = new QueryManager({ processQuery: async (capabilities, cancelToken) => { if (capabilities.hasSql()) { - const tasks = await queryDruidSql( + return await queryDruidSql( { query: TasksView.TASK_SQL, }, cancelToken, ); - return tasks; } else if (capabilities.hasOverlordAccess()) { return (await getApiArray(`/druid/indexer/v1/tasks`, cancelToken)).map(d => { return { @@ -484,7 +483,7 @@ ORDER BY case 'object': return ( TasksView.statusRanking[d1.status] - TasksView.statusRanking[d2.status] || - dayjs(d1.created_time).diff(d2.created_time) + d1.created_time.localeCompare(d2.created_time) ); default: @@ -540,7 +539,6 @@ ORDER BY filterable: false, className: 'padded', Cell({ value, original, aggregated }) { - console.log(value, original, aggregated); if (aggregated) return ''; if (value > 0) { const shownDuration = formatDuration(value); From 06574b1f5f1bf840cb0b8eaeaa5145416df5044f Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:04:23 +0800 Subject: [PATCH 11/12] Update snapshots --- .../__snapshots__/timezone-menu-items.spec.tsx.snap | 2 +- .../src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap b/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap index 19dca4e0bced..313c9721c1f2 100644 --- a/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap +++ b/web-console/src/components/timezone-menu-items/__snapshots__/timezone-menu-items.spec.tsx.snap @@ -81,7 +81,7 @@ exports[`TimezoneMenuItems matches snapshot 1`] = ` active={false} disabled={false} icon="blank" - label="UTC-5:00" + label="UTC-6:00" multiline={false} onClick={[Function]} popoverProps={{}} diff --git a/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap b/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap index c253cd3a5672..87744c0ac833 100644 --- a/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap +++ b/web-console/src/views/tasks-view/__snapshots__/tasks-view.spec.tsx.snap @@ -201,6 +201,7 @@ exports[`TasksView matches snapshot 1`] = ` "Cell": [Function], "Header": "Created time", "accessor": "created_time", + "filterMethod": [Function], "show": true, "width": 220, }, From 46031997423c4fa22f8fd9f33dcdd8914f4e2d51 Mon Sep 17 00:00:00 2001 From: Gabriel Chang <77312579+GabrielCWT@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:25:48 +0800 Subject: [PATCH 12/12] Format lastCompletedTaskTime and blacklistedUntil --- .../src/views/services-view/services-view.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index a896b8b70cb8..4891ba248718 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -65,7 +65,6 @@ import { lookupBy, oneOf, pluralIfNeeded, - prettyFormatIsoDateWithMsIfNeeded, queryDruidSql, QueryManager, QueryState, @@ -202,6 +201,11 @@ function aggregateLoadQueueInfos(loadQueueInfos: LoadQueueInfo[]): LoadQueueInfo }; } +function defaultDisplayFn(value: any): string { + if (value === undefined || value === null) return ''; + return String(value); +} + interface WorkerInfo { readonly availabilityGroups: string[]; readonly blacklistedUntil: string | null; @@ -384,7 +388,10 @@ ORDER BY this.serviceQueryManager.runQuery({ capabilities, visibleColumns }); }; - private renderFilterableCell(field: string, displayFn: (value: string) => string = String) { + private renderFilterableCell( + field: string, + displayFn: (value: string) => string = defaultDisplayFn, + ) { const { filters, onFiltersChange } = this.props; return function FilterableCell(row: { value: any }) { @@ -659,17 +666,11 @@ ORDER BY const details: string[] = []; if (workerInfo.lastCompletedTaskTime) { details.push( - `Last completed task: ${prettyFormatIsoDateWithMsIfNeeded( - workerInfo.lastCompletedTaskTime, - )}`, + `Last completed task: ${formatDate(workerInfo.lastCompletedTaskTime)}`, ); } if (workerInfo.blacklistedUntil) { - details.push( - `Blacklisted until: ${prettyFormatIsoDateWithMsIfNeeded( - workerInfo.blacklistedUntil, - )}`, - ); + details.push(`Blacklisted until: ${formatDate(workerInfo.blacklistedUntil)}`); } return details.join(' ') || null; }