From d7e11b51ae5a4dec9b8073e692ac326c47d72e29 Mon Sep 17 00:00:00 2001 From: Zach Lite Date: Tue, 28 Mar 2023 15:01:55 -0400 Subject: [PATCH] ui: display configurable timezone in DB Console This commit makes it so all timestamps displayed in DB Console (with the exception of Advanced Debug pages) use the timezone set by the cluster setting `ui.display_timezone`. Only Coordinated Universal Time and America/New_York are supported. Supporting additional timezones can be achieved by adding more timezones to the enum passed to `ui.display_timezone` when the setting is registered, and modifying the webpack configs to make sure the relevant timezone data is included. See #99848 for more details. Epic: https://cockroachlabs.atlassian.net/browse/CRDB-5536 Release note (ui): Added the ability for users to view timestamps in DB Console in their preferred timezone via the cluster setting `ui.display_timezone`. Currently supported timezones are Coordinated Universal Time and America/New_York. --- .../cluster-ui/src/columnsSelector/utils.ts | 3 +- .../src/contexts/timezoneContext.tsx | 4 +- .../databaseDetailsPage.tsx | 16 ++++-- .../databaseTablePage/databaseTablePage.tsx | 45 ++++++++++----- .../src/dateRangeMenu/dateRangeMenu.tsx | 19 +++++-- .../cluster-ui/src/graphs/bargraph/bars.ts | 5 +- .../cluster-ui/src/graphs/bargraph/index.tsx | 6 +- .../cluster-ui/src/graphs/bargraph/plugins.ts | 20 +++++-- .../cluster-ui/src/graphs/utils/domain.ts | 45 ++++++++------- pkg/ui/workspaces/cluster-ui/src/index.ts | 1 + .../src/indexDetailsPage/indexDetailsPage.tsx | 17 +++--- .../insightDetailsTables.tsx | 10 +++- .../statementInsightDetailsOverviewTab.tsx | 21 ++++--- .../transactionInsightDetailsOverviewTab.tsx | 25 ++++++--- .../transactionInsightDetailsStmtsTab.tsx | 31 +++++++++-- .../statementInsightsTable.tsx | 10 +++- .../transactionInsightsTable.tsx | 16 +++++- .../workloadInsights/util/insightsColumns.tsx | 14 ++++- .../src/jobs/jobDetailsPage/jobDetails.tsx | 23 +++++--- .../src/jobs/jobsPage/jobsPage.spec.tsx | 4 +- .../cluster-ui/src/jobs/jobsPage/jobsPage.tsx | 17 ++++-- .../src/jobs/jobsPage/jobsTable.tsx | 42 ++++++++++---- .../src/jobs/util/highwaterTimestamp.tsx | 7 ++- .../src/recentExecutions/execTableCommon.tsx | 13 ++++- .../execContentionTable.tsx | 5 +- .../scheduleDetailsPage/scheduleDetails.tsx | 25 ++++++++- .../schedules/schedulesPage/scheduleTable.tsx | 25 +++++++-- .../schedulesPage/schedulesPage.spec.tsx | 8 +-- .../src/sessions/sessionDetails.tsx | 26 +++++++-- .../cluster-ui/src/sessions/sessionsTable.tsx | 21 ++++--- .../diagnostics/diagnosticsView.tsx | 10 ++-- .../planDetails/planDetails.tsx | 12 ++-- .../planDetails/plansTable.tsx | 17 ++++-- .../recentStatementDetailsOverviewTab.tsx | 12 +++- .../src/statementDetails/statementDetails.tsx | 29 ++++++---- .../src/statementsPage/statementsPage.tsx | 4 +- .../src/statementsTable/statementsTable.tsx | 9 ++- .../src/statsTableUtil/statsTableUtil.tsx | 19 +++++-- .../timeScaleDropdown/formattedTimeScale.tsx | 39 +++++++++++++ .../src/timeScaleDropdown/rangeSelect.tsx | 7 ++- .../timeScaleDropdown.spec.tsx | 9 ++- .../timeScaleDropdown/timeScaleDropdown.tsx | 16 ++++-- .../cluster-ui/src/timeScaleDropdown/utils.ts | 15 ----- .../cluster-ui/src/timestamp/index.ts | 11 ++++ .../cluster-ui/src/timestamp/timestamp.tsx | 39 +++++++++++++ .../recentTransactionDetails.tsx | 12 +++- .../transactionDetails/transactionDetails.tsx | 4 +- .../src/transactionsPage/transactionsPage.tsx | 4 +- .../workspaces/cluster-ui/src/util/format.ts | 9 ++- .../cluster/components/linegraph/index.tsx | 12 +++- .../components/linegraph/linegraph.spec.tsx | 27 ++++++--- .../cluster/containers/events/events.spec.tsx | 2 +- .../views/cluster/containers/events/index.tsx | 55 +++++++++++-------- .../nodeGraphs/dashboards/changefeeds.tsx | 2 +- .../dashboards/crossClusterReplication.tsx | 2 +- .../nodeGraphs/dashboards/distributed.tsx | 2 +- .../nodeGraphs/dashboards/hardware.tsx | 2 +- .../nodeGraphs/dashboards/overload.tsx | 2 +- .../nodeGraphs/dashboards/overview.tsx | 2 +- .../nodeGraphs/dashboards/queues.tsx | 2 +- .../nodeGraphs/dashboards/replication.tsx | 2 +- .../nodeGraphs/dashboards/requests.tsx | 2 +- .../nodeGraphs/dashboards/runtime.tsx | 2 +- .../containers/nodeGraphs/dashboards/sql.tsx | 2 +- .../nodeGraphs/dashboards/storage.tsx | 2 +- .../containers/nodeGraphs/dashboards/ttl.tsx | 2 +- .../cluster/containers/nodeLogs/index.tsx | 17 ++++-- .../cluster/containers/nodeOverview/index.tsx | 13 +++-- .../containers/nodesOverview/index.tsx | 9 ++- .../containers/raftMessages/messages.tsx | 2 +- .../db-console/src/views/hotRanges/index.tsx | 18 +++++- .../reports/containers/customChart/index.tsx | 2 +- .../nodeHistory/decommissionedNodeHistory.tsx | 8 ++- .../reports/containers/settings/index.tsx | 12 ++-- .../statementDiagnosticsHistory/index.tsx | 6 +- 75 files changed, 715 insertions(+), 295 deletions(-) create mode 100644 pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/formattedTimeScale.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/timestamp/index.ts create mode 100644 pkg/ui/workspaces/cluster-ui/src/timestamp/timestamp.tsx diff --git a/pkg/ui/workspaces/cluster-ui/src/columnsSelector/utils.ts b/pkg/ui/workspaces/cluster-ui/src/columnsSelector/utils.ts index 70001ae60694..492ceeea02f4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/columnsSelector/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/columnsSelector/utils.ts @@ -8,6 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +import { isEmpty } from "lodash"; import { ColumnDescriptor } from "src/sortedtable/sortedtable"; // We show a column if: @@ -20,7 +21,7 @@ export const isSelectedColumn = ( c: ColumnDescriptor, ): boolean => { return ( - (selectedColumns == null && c.showByDefault !== false) || + (isEmpty(selectedColumns) && c.showByDefault !== false) || (selectedColumns !== null && selectedColumns.includes(c.name)) || c.alwaysShow === true ); diff --git a/pkg/ui/workspaces/cluster-ui/src/contexts/timezoneContext.tsx b/pkg/ui/workspaces/cluster-ui/src/contexts/timezoneContext.tsx index dfe6deb91f9a..16f2353e0457 100644 --- a/pkg/ui/workspaces/cluster-ui/src/contexts/timezoneContext.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/contexts/timezoneContext.tsx @@ -14,7 +14,7 @@ import { createContext, useContext } from "react"; export const CoordinatedUniversalTime = "Etc/UTC"; export const TimezoneContext = createContext(CoordinatedUniversalTime); -interface WithTimezoneProps { +export interface WithTimezoneProps { timezone: string; } @@ -23,7 +23,7 @@ interface WithTimezoneProps { export function WithTimezone( Component: React.ComponentType, ) { - return (props: T) => { + return (props: React.PropsWithChildren) => { // This lambda is a React function component. // It is safe to call a hook here. // eslint-disable-next-line diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx index 8ce59b2cc78e..3224dbb929a3 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React from "react"; +import React, { useContext } from "react"; import { Link, RouteComponentProps } from "react-router-dom"; import { Tooltip } from "antd"; import "antd/lib/tooltip/style"; @@ -51,6 +51,7 @@ import { } from "src/queryFilter"; import { UIConfigState } from "src/store"; import { TableStatistics } from "src/tableStatistics"; +import { Timestamp, Timezone } from "../timestamp"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -611,13 +612,16 @@ export class DatabaseDetailsPage extends React.Component< placement="bottom" title="The last time table statistics were created or updated." > - Table Stats Last Updated (UTC) + Table Stats Last Updated ), - cell: table => - !table.details.statsLastUpdated - ? "No table statistics found" - : table.details.statsLastUpdated.format(DATE_FORMAT), + cell: table => ( + + ), sort: table => table.details.statsLastUpdated, className: cx("database-table__col--table-stats"), name: "tableStatsUpdated", diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseTablePage/databaseTablePage.tsx b/pkg/ui/workspaces/cluster-ui/src/databaseTablePage/databaseTablePage.tsx index c4bf25c32535..4a7bfff37550 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseTablePage/databaseTablePage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databaseTablePage/databaseTablePage.tsx @@ -33,7 +33,7 @@ import { import * as format from "src/util/format"; import { DATE_FORMAT, - DATE_FORMAT_24_UTC, + DATE_FORMAT_24_TZ, EncodeDatabaseTableUri, EncodeDatabaseUri, EncodeUriName, @@ -61,6 +61,7 @@ import LoadingError from "../sqlActivity/errorComponent"; import { Loading } from "../loading"; import { UIConfigState } from "../store"; import { QuoteIdentifier } from "../api/safesql"; +import { Timestamp, Timezone } from "../timestamp"; const cx = classNames.bind(styles); const booleanSettingCx = classnames.bind(booleanSettingStyles); @@ -303,24 +304,31 @@ export class DatabaseTablePage extends React.Component< history.replace(history.location); } - private getLastResetString() { + private getLastReset() { const lastReset = this.props.indexStats.lastReset; if (lastReset.isSame(this.minDate)) { - return "Last reset: Never"; + return <>Last reset: Never; } else { - return "Last reset: " + lastReset.format(DATE_FORMAT_24_UTC); + return ( + <> + Last reset: + + ); } } - private getLastUsedString(indexStat: IndexStat) { + private getLastUsed(indexStat: IndexStat) { // This case only occurs when we have no reads, resets, or creation time on // the index. if (indexStat.lastUsed.isSame(this.minDate)) { - return "Never"; + return <>Never; } - return `Last ${indexStat.lastUsedType}: ${indexStat.lastUsed.format( - DATE_FORMAT, - )}`; + return ( + <> + Last {indexStat.lastUsedType}:{" "} + + + ); } private renderIndexRecommendations = ( @@ -420,10 +428,14 @@ export class DatabaseTablePage extends React.Component< }, { name: "last used", - title: "Last Used (UTC)", + title: ( + <> + Last Used + + ), hideTitleUnderline: true, className: cx("index-stats-table__col-last-used"), - cell: indexStat => this.getLastUsedString(indexStat), + cell: indexStat => this.getLastUsed(indexStat), sort: indexStat => indexStat.lastUsed, }, { @@ -566,9 +578,12 @@ export class DatabaseTablePage extends React.Component< {this.props.details.statsLastUpdated && ( + } /> )} {this.props.automaticStatsCollectionEnabled != @@ -640,7 +655,7 @@ export class DatabaseTablePage extends React.Component< "underline", )} > - {this.getLastResetString()} + {this.getLastReset()} {hasAdminRole && ( diff --git a/pkg/ui/workspaces/cluster-ui/src/dateRangeMenu/dateRangeMenu.tsx b/pkg/ui/workspaces/cluster-ui/src/dateRangeMenu/dateRangeMenu.tsx index 86fe826f08b0..d1dc736ff842 100644 --- a/pkg/ui/workspaces/cluster-ui/src/dateRangeMenu/dateRangeMenu.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/dateRangeMenu/dateRangeMenu.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { Alert, DatePicker, Icon, TimePicker } from "antd"; import "antd/lib/time-picker/style"; import "antd/lib/icon/style"; @@ -21,6 +21,8 @@ import { Button } from "src/button"; import { Text, TextTypes } from "src/text"; import styles from "./dateRangeMenu.module.scss"; +import { TimezoneContext } from "../contexts"; +import { Timezone } from "src/timestamp"; const cx = classNames.bind(styles); @@ -44,6 +46,8 @@ export function DateRangeMenu({ onCancel, onReturnToPresetOptionsClick, }: DateRangeMenuProps): React.ReactElement { + const timezone = useContext(TimezoneContext); + /** * Local startMoment and endMoment state are stored here so that users can change the time before clicking "Apply". * They are re-initialized to startInit and endInit by re-mounting this component. It is thus the responsibility of @@ -66,9 +70,11 @@ export function DateRangeMenu({ * the parent component to re-initialize this. */ const [startMoment, setStartMoment] = useState( - startInit || moment.utc(), + startInit ? startInit.tz(timezone) : moment.tz(timezone), + ); + const [endMoment, setEndMoment] = useState( + endInit ? endInit.tz(timezone) : moment.tz(timezone), ); - const [endMoment, setEndMoment] = useState(endInit || moment.utc()); const onChangeStart = (m?: Moment) => { m && setStartMoment(m); @@ -99,7 +105,8 @@ export function DateRangeMenu({ const isValid = errorMessage === undefined; const onApply = (): void => { - onSubmit(startMoment, endMoment); + // Idempotently set the start and end moments to UTC. + onSubmit(startMoment.utc(), endMoment.utc()); }; return ( @@ -111,7 +118,7 @@ export function DateRangeMenu({ - Start (UTC) + Start
- End (UTC) + Start { const options = getBarChartOpts( userOptions, @@ -95,6 +96,7 @@ export const getStackedBarOpts = ( yAxisDomain, yyAxisUnits, colourPalette, + timezone, ); options.bands = getStackedBands(unstackedData, () => false); @@ -141,6 +143,7 @@ export const getBarChartOpts = ( yAxisDomain: AxisDomain, yAxisUnits: AxisUnits, colourPalette = seriesPalette, + timezone: string, ): Options => { const { series, ...providedOpts } = userOptions; const defaultBars = getBarsBuilder(0.9, 80); @@ -191,7 +194,7 @@ export const getBarChartOpts = ( ...s, })), ], - plugins: [barTooltipPlugin(yAxisUnits)], + plugins: [barTooltipPlugin(yAxisUnits, timezone)], }; const combinedOpts = merge(opts, providedOpts); diff --git a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/index.tsx b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/index.tsx index 12280349ef89..8f6470ccc5d2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/index.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/index.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React, { useEffect, useRef } from "react"; +import React, { useContext, useEffect, useRef } from "react"; import classNames from "classnames/bind"; import { getStackedBarOpts, stack } from "./bars"; import uPlot, { AlignedData } from "uplot"; @@ -20,6 +20,7 @@ import { calculateYAxisDomain, } from "../utils/domain"; import { Options } from "uplot"; +import { TimezoneContext } from "../../contexts"; const cx = classNames.bind(styles); @@ -46,6 +47,7 @@ export const BarGraphTimeSeries: React.FC = ({ const graphRef = useRef(null); const samplingIntervalMillis = alignedData[0].length > 1 ? alignedData[0][1] - alignedData[0][0] : 1e3; + const timezone = useContext(TimezoneContext); useEffect(() => { if (!alignedData) return; @@ -69,6 +71,7 @@ export const BarGraphTimeSeries: React.FC = ({ yAxisDomain, yAxisUnits, colourPalette, + timezone, ); const plot = new uPlot(opts, stackedData, graphRef.current); @@ -82,6 +85,7 @@ export const BarGraphTimeSeries: React.FC = ({ uPlotOptions, yAxisUnits, samplingIntervalMillis, + timezone, ]); return ( diff --git a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/plugins.ts b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/plugins.ts index aad3690adeed..1964b703cb85 100644 --- a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/plugins.ts +++ b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/plugins.ts @@ -9,8 +9,16 @@ // licenses/APL.txt. import uPlot, { Plugin } from "uplot"; -import { AxisUnits, formatTimeStamp } from "../utils/domain"; -import { Bytes, Duration, Percentage, Count } from "../../util"; +import { AxisUnits } from "../utils/domain"; +import { + Bytes, + Duration, + Percentage, + Count, + FormatWithTimezone, + DATE_WITH_SECONDS_FORMAT_24_TZ, +} from "../../util"; +import moment from "moment-timezone"; // Fallback color for series stroke if one is not defined. const DEFAULT_STROKE = "#7e89a9"; @@ -96,7 +104,7 @@ function getFormattedValue(value: number, yAxisUnits: AxisUnits): string { } // Tooltip legend plugin for bar charts. -export function barTooltipPlugin(yAxis: AxisUnits): Plugin { +export function barTooltipPlugin(yAxis: AxisUnits, timezone: string): Plugin { const cursorToolTip = { tooltip: document.createElement("div"), timeStamp: document.createElement("div"), @@ -110,7 +118,11 @@ export function barTooltipPlugin(yAxis: AxisUnits): Plugin { // get the current timestamp from the x axis and formatting as // the Tooltip header. const closestDataPointTimeMillis = u.data[0][u.posToIdx(left)]; - timeStamp.textContent = formatTimeStamp(closestDataPointTimeMillis); + timeStamp.textContent = FormatWithTimezone( + moment(closestDataPointTimeMillis), + DATE_WITH_SECONDS_FORMAT_24_TZ, + timezone, + ); // Generating the series legend based on current state of µPlot generateSeriesLegend(u, seriesLegend, yAxis); diff --git a/pkg/ui/workspaces/cluster-ui/src/graphs/utils/domain.ts b/pkg/ui/workspaces/cluster-ui/src/graphs/utils/domain.ts index eba1b12fbe5c..7642a7c70637 100644 --- a/pkg/ui/workspaces/cluster-ui/src/graphs/utils/domain.ts +++ b/pkg/ui/workspaces/cluster-ui/src/graphs/utils/domain.ts @@ -15,8 +15,9 @@ import { BytesFitScale, ComputeByteScale, ComputeDurationScale, - DATE_WITH_SECONDS_FORMAT_24_UTC, + DATE_WITH_SECONDS_FORMAT_24_TZ, DurationFitScale, + FormatWithTimezone, } from "src/util/format"; /** @@ -264,11 +265,7 @@ const timeIncrements: number[] = timeIncrementDurations.map(inc => inc.asMilliseconds(), ); -export function formatTimeStamp(timeMillis: number): string { - return moment.utc(timeMillis).format(DATE_WITH_SECONDS_FORMAT_24_UTC); -} - -function ComputeTimeAxisDomain(extent: Extent): AxisDomain { +function ComputeTimeAxisDomain(extent: Extent, timezone: string): AxisDomain { // Compute increment; for time scales, this is taken from a table of allowed // values. let increment = 0; @@ -288,17 +285,25 @@ function ComputeTimeAxisDomain(extent: Extent): AxisDomain { axisDomain.label = "time"; - let tickDateFormatter: (d: Date) => string; - if (increment < moment.duration(24, "hours").asMilliseconds()) { - tickDateFormatter = (d: Date) => moment.utc(d).format("H:mm"); - } else { - tickDateFormatter = (d: Date) => moment.utc(d).format("MM/DD H:mm"); - } + const tickDateFormatter = (d: Date, format: string) => + moment(d).tz(timezone).format(format); + + const format = + increment < moment.duration(24, "hours").asMilliseconds() + ? "H:mm" + : "MM/DD H:mm"; + axisDomain.tickFormat = (n: number) => { - return tickDateFormatter(new Date(n)); + return tickDateFormatter(new Date(n), format); }; - axisDomain.guideFormat = formatTimeStamp; + axisDomain.guideFormat = millis => { + return FormatWithTimezone( + moment(millis), + DATE_WITH_SECONDS_FORMAT_24_TZ, + timezone, + ); + }; return axisDomain; } @@ -324,19 +329,21 @@ export function calculateYAxisDomain( export function calculateXAxisDomain( startMillis: number, endMillis: number, + timezone = "UTC", ): AxisDomain { - return ComputeTimeAxisDomain([startMillis, endMillis] as Extent); + return ComputeTimeAxisDomain([startMillis, endMillis] as Extent, timezone); } export function calculateXAxisDomainBarChart( startMillis: number, endMillis: number, samplingIntervalMillis: number, + timezone = "UTC", ): AxisDomain { // For bar charts, we want to render past endMillis to fully render the // last bar. We should extend the x axis to the next sampling interval. - return ComputeTimeAxisDomain([ - startMillis, - endMillis + samplingIntervalMillis, - ] as Extent); + return ComputeTimeAxisDomain( + [startMillis, endMillis + samplingIntervalMillis] as Extent, + timezone, + ); } diff --git a/pkg/ui/workspaces/cluster-ui/src/index.ts b/pkg/ui/workspaces/cluster-ui/src/index.ts index eee101fdb08f..325ed2e36316 100644 --- a/pkg/ui/workspaces/cluster-ui/src/index.ts +++ b/pkg/ui/workspaces/cluster-ui/src/index.ts @@ -54,3 +54,4 @@ export * from "./recentExecutions"; export * from "./graphs"; export * from "./selectors"; export * from "./contexts"; +export * from "./timestamp"; diff --git a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx index 09d793053b10..e0b4613ce9e9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx @@ -34,7 +34,7 @@ import { Anchor } from "../anchor"; import { calculateTotalWorkload, Count, - DATE_FORMAT_24_UTC, + DATE_FORMAT_24_TZ, EncodeDatabaseTableIndexUri, EncodeDatabaseTableUri, EncodeDatabaseUri, @@ -72,7 +72,7 @@ import { Filters, } from "../queryFilter"; import { commonStyles } from "../common"; -import { Loading } from "src"; +import { Loading, Timestamp } from "src"; import LoadingError from "../sqlActivity/errorComponent"; import { INTERNAL_APP_NAME_PREFIX } from "src/util/constants"; import { filteredStatementsData } from "../sqlActivity/util"; @@ -303,12 +303,12 @@ export class IndexDetailsPage extends React.Component< } } - private getTimestampString(timestamp: Moment): string { + private getTimestamp(timestamp: Moment) { const minDate = moment.utc("0001-01-01"); // minimum value as per UTC if (timestamp.isSame(minDate)) { - return "Never"; + return <>Never; } else { - return timestamp.format(DATE_FORMAT_24_UTC); + return ; } } @@ -490,8 +490,7 @@ export class IndexDetailsPage extends React.Component< title="Index stats accumulate from the time the index was created or had its stats reset.. Clicking ‘Reset all index stats’ will reset index stats for the entire cluster. Last reset is the timestamp at which the last reset started." >
- Last reset:{" "} - {this.getTimestampString(this.props.details.lastReset)} + Last reset: {this.getTimestamp(this.props.details.lastReset)}
{hasAdminRole && ( @@ -559,9 +558,7 @@ export class IndexDetailsPage extends React.Component<

- {this.getTimestampString( - this.props.details.lastRead, - )} + {this.getTimestamp(this.props.details.lastRead)}

diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx index ba282b5b57fb..96473b640d6c 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx @@ -19,6 +19,7 @@ import { TransactionDetailsLink, } from "../workloadInsights/util"; import { TimeScale } from "../../timeScaleDropdown"; +import { Timestamp, Timezone } from "../../timestamp"; interface InsightDetailsTableProps { data: ContentionEvent[]; @@ -69,8 +70,13 @@ export function makeInsightDetailsColumns( { name: "contentionStartTime", title: insightsTableTitles.contentionStartTime(execType), - cell: (item: ContentionEvent) => - item.startTime?.format(DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT), + cell: (item: ContentionEvent) => ( + + ), sort: (item: ContentionEvent) => item.startTime.unix(), }, { diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetailsOverviewTab.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetailsOverviewTab.tsx index 41a3c899d938..fa50ab5ef7cf 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetailsOverviewTab.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetailsOverviewTab.tsx @@ -17,7 +17,7 @@ import { SummaryCard, SummaryCardItem } from "src/summaryCard"; import { capitalize, Duration } from "src/util"; import { Count, - DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_UTC, + DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_TZ, } from "src/util/format"; import { StmtInsightEvent } from "../types"; import classNames from "classnames/bind"; @@ -39,6 +39,7 @@ import { ContentionStatementDetailsTable } from "./insightDetailsTables"; import { WaitTimeInsightsLabels } from "../../detailsPanels/waitTimeInsightsPanel"; import { Heading } from "@cockroachlabs/ui-components"; import { SortSetting } from "../../sortedtable"; +import { Timestamp } from "../../timestamp"; const cx = classNames.bind(insightsDetailsStyles); const tableCx = classNames.bind(insightTableStyles); @@ -102,15 +103,21 @@ export const StatementInsightDetailsOverviewTab: React.FC< + } /> + } /> + } /> + } /> [] = [ { @@ -52,16 +53,38 @@ const stmtColumns: ColumnDescriptor[] = [ }, { name: "startTime", - title: "Start Time (UTC)", + title: ( + <> + Start Time + + ), cell: (item: StmtInsightEvent) => - item.startTime?.format(DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT), + item.startTime ? ( + + ) : ( + <>N/A + ), sort: (item: StmtInsightEvent) => item.startTime.unix(), }, { name: "endTime", - title: "End Time (UTC)", + title: ( + <> + End Time + + ), cell: (item: StmtInsightEvent) => - item.endTime?.format(DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT), + item.endTime ? ( + + ) : ( + <>N/A + ), sort: (item: StmtInsightEvent) => item.endTime.unix(), }, { diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsTable.tsx index e0b6130788e2..ec9ca9935224 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsTable.tsx @@ -38,6 +38,7 @@ import classNames from "classnames/bind"; import styles from "../util/workloadInsights.module.scss"; import { TimeScale } from "../../../timeScaleDropdown"; import { Badge } from "src/badge"; +import { Timestamp, Timezone } from "../../../timestamp"; const cx = classNames.bind(styles); @@ -117,7 +118,14 @@ export function makeStatementInsightsColumns(): ColumnDescriptor - item.startTime?.format(DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT), + item.startTime ? ( + + ) : ( + <>N/A + ), sort: (item: StmtInsightEvent) => item.startTime.unix(), showByDefault: true, }, diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsTable.tsx index e1ee23fe335d..8b5efb50ccca 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsTable.tsx @@ -15,7 +15,10 @@ import { SortedTable, SortSetting, } from "src/sortedtable"; -import { DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT, Duration } from "src/util"; +import { + DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_TZ, + Duration, +} from "src/util"; import { InsightExecEnum, TransactionStatus, @@ -30,6 +33,7 @@ import { import { Link } from "react-router-dom"; import { TimeScale } from "../../../timeScaleDropdown"; import { Badge } from "src/badge"; +import { Timestamp } from "../../../timestamp"; function txnStatusToString(status: TransactionStatus) { switch (status) { @@ -99,8 +103,14 @@ export function makeTransactionInsightsColumns(): ColumnDescriptor - item.startTime?.format(DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT) ?? - "N/A", + item.startTime ? ( + + ) : ( + <>N/A + ), sort: item => item.startTime?.unix() || 0, }, { diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx index a71f009712ee..2a9f4e432456 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx @@ -14,6 +14,7 @@ import { InsightExecEnum } from "src/insights"; import { contentModifiers } from "../../../statsTableUtil/statsTableUtil"; import { Anchor } from "../../../anchor"; import { contentionTime, readFromDisk, writtenToDisk } from "../../../util"; +import { Timezone } from "src/timestamp"; export const insightsColumnLabels = { executionID: "Execution ID", @@ -23,7 +24,7 @@ export const insightsColumnLabels = { query: "Execution", status: "Status", insights: "Insights", - startTime: "Start Time (UTC)", + startTime: "Start Time", elapsedTime: "Elapsed Time", applicationName: "Application Name", username: "User Name", @@ -31,7 +32,7 @@ export const insightsColumnLabels = { numRetries: "Retries", isFullScan: "Full Scan", contention: "Contention Time", - contentionStartTime: "Contention Start Time (UTC)", + contentionStartTime: "Contention Start Time", rowsProcessed: "Rows Processed", schemaName: "Schema Name", databaseName: "Database Name", @@ -70,10 +71,13 @@ function makeToolTip( content: JSX.Element, columnKey: InsightsTableColumnKeys, execType?: InsightExecEnum, + timezone?: JSX.Element, ): ReactElement { return ( - {getLabel(columnKey, execType)} + <> + {getLabel(columnKey, execType)} {timezone} + ); } @@ -138,12 +142,16 @@ export const insightsTableTitles: InsightsTableTitleType = { return makeToolTip(

The timestamp at which the {execType} started.

, "startTime", + undefined, + , ); }, contentionStartTime: (execType: InsightExecEnum) => { return makeToolTip(

The timestamp at which contention was detected for the {execType}.

, "contentionStartTime", + undefined, + , ); }, elapsedTime: (execType: InsightExecEnum) => { diff --git a/pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetails.tsx index 72507998bb0f..586afe7fc727 100644 --- a/pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetails.tsx @@ -24,7 +24,7 @@ import { SummaryCard, SummaryCardItem } from "src/summaryCard"; import { TimestampToMoment, idAttr, - DATE_FORMAT_24_UTC, + DATE_FORMAT_24_TZ, getMatchParamByName, } from "src/util"; @@ -37,6 +37,7 @@ import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; import jobStyles from "src/jobs/jobs.module.scss"; import classNames from "classnames/bind"; +import { Timestamp } from "../../timestamp"; const cardCx = classNames.bind(summaryCardStyles); const jobCx = classNames.bind(jobStyles); @@ -116,7 +117,7 @@ export class JobDetails extends React.Component {

Next Planned Execution Time:

- {nextRun.format(DATE_FORMAT_24_UTC)} + )}
@@ -125,15 +126,21 @@ export class JobDetails extends React.Component { + } /> + } /> ): JobsPageProps => { const history = H.createHashHistory(); @@ -80,6 +81,7 @@ describe("Jobs", () => { "User Name", "Creation Time (UTC)", "Last Execution Time (UTC)", + "Last Modified Time (UTC)", "Execution Count", ]; diff --git a/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.tsx index 722e29682ccc..494c3ba1d19f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.tsx @@ -25,7 +25,7 @@ import ColumnsSelector, { } from "src/columnsSelector/columnsSelector"; import { Pagination, ResultsPerPageLabel } from "src/pagination"; import { isSelectedColumn } from "src/columnsSelector/utils"; -import { DATE_FORMAT_24_UTC, syncHistory, TimestampToMoment } from "src/util"; +import { DATE_FORMAT_24_TZ, syncHistory, TimestampToMoment } from "src/util"; import { jobsColumnLabels, JobsTable, makeJobsColumns } from "./jobsTable"; import { showOptions, @@ -40,6 +40,7 @@ import { commonStyles } from "src/common"; import sortableTableStyles from "src/sortedtable/sortedtable.module.scss"; import styles from "../jobs.module.scss"; import classNames from "classnames/bind"; +import { Timestamp } from "../../timestamp"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -240,10 +241,16 @@ export class JobsPage extends React.Component { ); }; - formatJobsRetentionMessage = (earliestRetainedTime: ITimestamp): string => { - return `Since ${TimestampToMoment(earliestRetainedTime).format( - DATE_FORMAT_24_UTC, - )}`; + formatJobsRetentionMessage = (earliestRetainedTime: ITimestamp) => { + return ( + <> + Since{" "} + + + ); }; render(): React.ReactElement { diff --git a/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsTable.tsx index 5f9c72ca6109..6e8f7df9511b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsTable.tsx @@ -26,13 +26,14 @@ import { pauseJob, resumeJob, } from "src/util/docs"; -import { DATE_FORMAT_24_UTC } from "src/util/format"; +import { DATE_FORMAT } from "src/util/format"; import { HighwaterTimestamp, JobStatusCell } from "../util"; import { JobDescriptionCell } from "./jobDescriptionCell"; import styles from "../jobs.module.scss"; import classNames from "classnames/bind"; +import { Timestamp, Timezone } from "../../timestamp"; const cx = classNames.bind(styles); type Job = cockroach.server.serverpb.IJobResponse; @@ -52,9 +53,9 @@ export const jobsColumnLabels: any = { status: "Status", jobId: "Job ID", users: "User Name", - creationTime: "Creation Time (UTC)", - lastModifiedTime: "Last Modified Time (UTC)", - lastExecutionTime: "Last Execution Time (UTC)", + creationTime: "Creation Time", + lastModifiedTime: "Last Modified Time", + lastExecutionTime: "Last Execution Time", executionCount: "Execution Count", highWaterTimestamp: "High-water Timestamp", coordinatorID: "Coordinator Node", @@ -163,10 +164,17 @@ export function makeJobsColumns(): ColumnDescriptor[] { style="tableTitle" content={

Date and time the job was created.

} > - {jobsColumnLabels.creationTime} + <> + {jobsColumnLabels.creationTime} + ), - cell: job => TimestampToMoment(job?.created).format(DATE_FORMAT_24_UTC), + cell: job => ( + + ), sort: job => TimestampToMoment(job?.created).valueOf(), showByDefault: true, }, @@ -178,10 +186,17 @@ export function makeJobsColumns(): ColumnDescriptor[] { style="tableTitle" content={

Date and time the job was last modified.

} > - {jobsColumnLabels.lastModifiedTime} + <> + {jobsColumnLabels.lastModifiedTime} + ), - cell: job => TimestampToMoment(job?.modified).format(DATE_FORMAT_24_UTC), + cell: job => ( + + ), sort: job => TimestampToMoment(job?.modified).valueOf(), showByDefault: true, }, @@ -193,10 +208,17 @@ export function makeJobsColumns(): ColumnDescriptor[] { style="tableTitle" content={

Date and time the job was last executed.

} > - {jobsColumnLabels.lastExecutionTime} + <> + {jobsColumnLabels.lastExecutionTime} + ), - cell: job => TimestampToMoment(job?.last_run).format(DATE_FORMAT_24_UTC), + cell: job => ( + + ), sort: job => TimestampToMoment(job?.last_run).valueOf(), showByDefault: true, }, diff --git a/pkg/ui/workspaces/cluster-ui/src/jobs/util/highwaterTimestamp.tsx b/pkg/ui/workspaces/cluster-ui/src/jobs/util/highwaterTimestamp.tsx index ab5ccd0fa34d..c0b47e1c7417 100644 --- a/pkg/ui/workspaces/cluster-ui/src/jobs/util/highwaterTimestamp.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/jobs/util/highwaterTimestamp.tsx @@ -11,7 +11,8 @@ import { google } from "@cockroachlabs/crdb-protobuf-client"; import { Tooltip } from "@cockroachlabs/ui-components"; import moment from "moment-timezone"; import React from "react"; -import { DATE_FORMAT_24_UTC } from "src/util/format"; +import { DATE_FORMAT_24_TZ } from "src/util/format"; +import { Timestamp } from "../../timestamp"; type ITimestamp = google.protobuf.ITimestamp; @@ -39,7 +40,9 @@ export class HighwaterTimestamp extends React.PureComponent { + } > {this.props.decimalString} diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx index 260c1a109812..4f808b084cfc 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx @@ -15,6 +15,7 @@ import { ColumnDescriptor } from "src/sortedtable"; import { Link } from "react-router-dom"; import { capitalize, DATE_FORMAT, Duration } from "src/util"; import { StatusIcon } from "./statusIcon"; +import { Timestamp, Timezone } from "../timestamp"; export type ExecutionsColumn = | "applicationName" @@ -51,7 +52,7 @@ export const executionsColumnLabels: Record< } }, retries: () => "Retries", - startTime: () => "Start Time (UTC)", + startTime: () => "Start Time", statementCount: () => "Statements", status: () => "Status", timeSpentBlocking: () => "Time Spent Blocking", @@ -181,8 +182,14 @@ function makeRecentExecutionColumns( }, startTime: { name: "startTime", - title: executionsTableTitles.startTime(execType), - cell: (item: RecentExecution) => item.start.format(DATE_FORMAT), + title: ( + <> + {executionsTableTitles.startTime(execType)} + + ), + cell: (item: RecentExecution) => ( + + ), sort: (item: RecentExecution) => item.start.unix(), }, elapsedTime: { diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsTable/execContentionTable.tsx b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsTable/execContentionTable.tsx index 6fb4f948649d..083c6341c2f2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsTable/execContentionTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsTable/execContentionTable.tsx @@ -14,8 +14,9 @@ import { ContendedExecution, ExecutionType } from "../types"; import { Link } from "react-router-dom"; import { StatusIcon } from "../statusIcon"; import { executionsTableTitles } from "../execTableCommon"; -import { DATE_FORMAT, Duration, limitText } from "../../util"; +import { DATE_FORMAT_24_TZ, Duration, limitText } from "../../util"; import { Tooltip } from "@cockroachlabs/ui-components"; +import { Timestamp } from "../../timestamp"; const getID = (item: ContendedExecution, execType: ExecutionType) => execType === "transaction" @@ -78,7 +79,7 @@ export function makeContentionColumns( { name: "startTime", title: executionsTableTitles.startTime(execType), - cell: item => item.start.format(DATE_FORMAT), + cell: item => , sort: item => item.start.unix(), }, { diff --git a/pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/scheduleDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/scheduleDetails.tsx index 253ddf5e6707..ced3bd0ffcd5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/scheduleDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/scheduleDetails.tsx @@ -20,13 +20,14 @@ import { Button } from "src/button"; import { Loading } from "src/loading"; import { SqlBox, SqlBoxSize } from "src/sql"; import { SummaryCard, SummaryCardItem } from "src/summaryCard"; -import { DATE_FORMAT_24_UTC, idAttr, getMatchParamByName } from "src/util"; +import { DATE_FORMAT_24_TZ, idAttr, getMatchParamByName } from "src/util"; import { commonStyles } from "src/common"; import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; import scheduleStyles from "src/schedules/schedules.module.scss"; import classNames from "classnames/bind"; +import { Timestamp } from "../../timestamp"; const cardCx = classNames.bind(summaryCardStyles); const scheduleCx = classNames.bind(scheduleStyles); @@ -75,11 +76,29 @@ export const ScheduleDetails: React.FC = props => { + ) : ( + <>N/A + ) + } /> + ) : ( + <>N/A + ) + } /> {} @@ -101,10 +102,17 @@ const schedulesTableColumns: ColumnDescriptor[] = [ style="tableTitle" content={

Date and time the schedule will next execute.

} > - {"Next Execution Time (UTC)"} + <> + Next Execution Time + ), - cell: schedule => schedule.nextRun?.format(DATE_FORMAT_24_UTC), + cell: schedule => + schedule.nextRun ? ( + + ) : ( + <>N/A + ), sort: schedule => schedule.nextRun?.valueOf(), }, { @@ -157,10 +165,17 @@ const schedulesTableColumns: ColumnDescriptor[] = [ style="tableTitle" content={

Date and time the schedule was created.

} > - {"Creation Time (UTC)"} + <> + Creation Time + ), - cell: schedule => schedule.created?.format(DATE_FORMAT_24_UTC), + cell: schedule => + schedule.created ? ( + + ) : ( + <>N/A + ), sort: schedule => schedule.created?.valueOf(), }, ]; diff --git a/pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.spec.tsx index e37f5bd533c8..fbebd2aee4d2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.spec.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/schedules/schedulesPage/schedulesPage.spec.tsx @@ -8,8 +8,6 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import moment from "moment-timezone"; -import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; import { SchedulesPage, SchedulesPageProps } from "./schedulesPage"; import { allSchedulesFixture } from "./schedulesPage.fixture"; import { render } from "@testing-library/react"; @@ -45,7 +43,7 @@ const getMockSchedulesPageProps = ( }; describe("Schedules", () => { - it("renders expected schedules table columns", () => { + it.only("renders expected schedules table columns", () => { const { getByText } = render( @@ -57,8 +55,8 @@ describe("Schedules", () => { "Schedule ID", "Owner", "Recurrence", - "Creation Time (UTC)", - "Next Execution Time (UTC)", + "Creation Time", + "Next Execution Time", "Jobs Running", ]; diff --git a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionDetails.tsx index f4e2984f1497..f85c3b08639a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionDetails.tsx @@ -26,7 +26,7 @@ import { SummaryCard, SummaryCardItem } from "../summaryCard"; import LoadingError from "../sqlActivity/errorComponent"; import { DurationToMomentDuration, TimestampToMoment } from "src/util/convert"; -import { Bytes, DATE_FORMAT, Count } from "src/util/format"; +import { Bytes, DATE_FORMAT_24_TZ, Count } from "src/util/format"; import { Col, Row } from "antd"; import "antd/lib/col/style"; import "antd/lib/row/style"; @@ -56,6 +56,7 @@ import { commonStyles } from "src/common"; import { CircleFilled } from "../icon"; import { createTimeScaleFromDateRange, TimeScale } from "src/timeScaleDropdown"; import moment from "moment-timezone"; +import { Timestamp } from "../timestamp"; const cx = classNames.bind(styles); const statementsPageCx = classNames.bind(statementsPageStyles); @@ -283,7 +284,7 @@ export class SessionDetails extends React.Component { } /> { + } className={cx("details-item")} /> @@ -364,12 +370,22 @@ export class SessionDetails extends React.Component { + } /> {session.end && ( + } /> )} { ); }; -function formatSessionStart(session: ISession): string { - const formatStr = "MMM DD, YYYY [at] H:mm"; +function formatSessionStart(session: ISession) { const start = moment.unix(Number(session.start.seconds)).utc(); - - return start.format(formatStr); + return ; } -function formatStatementStart(session: ISession): string { +function formatStatementStart(session: ISession) { if (session.active_queries.length == 0) { - return "N/A"; + return <>N/A; } - const formatStr = "MMM DD, YYYY [at] H:mm"; const start = moment .unix(Number(session.active_queries[0].start.seconds)) .utc(); - return start.format(formatStr); + return ; } export function getStatusString(status: Status): string { diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/diagnostics/diagnosticsView.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/diagnostics/diagnosticsView.tsx index c4a24fc9ed3c..161e06602ca5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/diagnostics/diagnosticsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/diagnostics/diagnosticsView.tsx @@ -10,7 +10,6 @@ import React from "react"; import { Link } from "react-router-dom"; -import moment from "moment-timezone"; import classnames from "classnames/bind"; import { Button, Icon } from "@cockroachlabs/ui-components"; import { Button as CancelButton } from "src/button"; @@ -24,13 +23,15 @@ import { filterByTimeScale, getDiagnosticsStatus } from "./diagnosticsUtils"; import { EmptyTable } from "src/empty"; import styles from "./diagnosticsView.module.scss"; import { getBasePath, StatementDiagnosticsReport } from "../../api"; -import { DATE_FORMAT_24_UTC } from "../../util"; import { TimeScale, timeScale1hMinOptions, TimeScaleDropdown, } from "src/timeScaleDropdown"; import { ColumnDescriptor, SortedTable, SortSetting } from "src/sortedtable"; +import { DATE_FORMAT_24_TZ } from "../../util"; +import { Timestamp } from "../../timestamp"; +import moment from "moment-timezone"; export interface DiagnosticsViewStateProps { hasData: boolean; @@ -126,8 +127,9 @@ export class DiagnosticsView extends React.Component< name: "activatedOn", title: "Activated on", hideTitleUnderline: true, - cell: (diagnostic: StatementDiagnosticsReport) => - moment.utc(diagnostic.requested_at).format(DATE_FORMAT_24_UTC), + cell: (diagnostic: StatementDiagnosticsReport) => ( + + ), sort: (diagnostic: StatementDiagnosticsReport) => moment(diagnostic.requested_at)?.unix(), }, diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx index 6a5c16606b8e..8c21647c0349 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx @@ -33,7 +33,7 @@ import { InsightRecommendation, InsightType } from "../../insights"; import { SummaryCard, SummaryCardItem } from "../../summaryCard"; import { Count, - DATE_FORMAT_24_UTC, + DATE_FORMAT_24_TZ, Duration, formatNumberForDisplay, longToInt, @@ -41,6 +41,7 @@ import { TimestampToMoment, } from "../../util"; import { formatIndexes } from "./plansTable"; +import { Timestamp } from "../../timestamp"; const cx = classNames.bind(styles); @@ -163,9 +164,12 @@ function ExplainPlan({ + } /> - {planDetailsColumnLabels.lastExecTime} + <> + {planDetailsColumnLabels.lastExecTime} + ); }, @@ -294,10 +297,12 @@ export function makeExplainPlanColumns( { name: "lastExecTime", title: planDetailsTableTitles.lastExecTime(), - cell: (item: PlanHashStats) => - TimestampToMoment(item.stats.last_exec_timestamp).format( - DATE_FORMAT_24_UTC, - ), + cell: (item: PlanHashStats) => ( + + ), sort: (item: PlanHashStats) => TimestampToMoment(item.stats.last_exec_timestamp).unix(), }, diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/recentStatementDetailsOverviewTab.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/recentStatementDetailsOverviewTab.tsx index 0cb599e3a629..84863540352f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/recentStatementDetailsOverviewTab.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/recentStatementDetailsOverviewTab.tsx @@ -19,7 +19,7 @@ import { } from "src/recentExecutions"; import { WaitTimeInsightsPanel } from "src/detailsPanels/waitTimeInsightsPanel"; import { StatusIcon } from "src/recentExecutions/statusIcon"; -import { DATE_FORMAT_24_UTC, Duration } from "src/util"; +import { DATE_FORMAT_24_TZ, Duration } from "src/util"; import "antd/lib/col/style"; import "antd/lib/row/style"; @@ -28,6 +28,7 @@ import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; const summaryCardStylesCx = classNames.bind(summaryCardStyles); import styles from "./statementDetails.module.scss"; +import { Timestamp } from "../timestamp"; const cx = classNames.bind(styles); type Props = { @@ -48,8 +49,13 @@ export const RecentStatementDetailsOverviewTab = ({ + } /> 0; - const period = timeScaleToString(this.props.timeScale); return ( - {this.renderOverviewTabContent(hasTimeout, hasData, period)} + {this.renderOverviewTabContent(hasTimeout, hasData)} - {this.renderExplainPlanTabContent(hasTimeout, hasData, period)} + {this.renderExplainPlanTabContent(hasTimeout, hasData)} {!this.props.isTenant && !this.props.hasViewActivityRedactedRole && ( { if (!hasData) { return this.renderNoDataWithTimeScaleAndSqlBoxTabContent(hasTimeout); @@ -575,9 +574,12 @@ export class StatementDetails extends React.Component< : nodes.map(node => nodeRegions[node]).filter(r => r), // Remove undefined / unknown regions. ).sort(); - const lastExec = - stats.last_exec_timestamp && - TimestampToMoment(stats.last_exec_timestamp).format(DATE_FORMAT_24_UTC); + const lastExec = stats.last_exec_timestamp && ( + + ); const statementSampled = stats.exec_stats.count > Long.fromNumber(0); const unavailableTooltip = !statementSampled && ( @@ -694,7 +696,9 @@ export class StatementDetails extends React.Component<

Showing aggregated stats from{" "} - {period} + + +

@@ -839,7 +843,6 @@ export class StatementDetails extends React.Component< renderExplainPlanTabContent = ( hasTimeout: boolean, hasData: boolean, - period: string, ): React.ReactElement => { if (!hasData) { return this.renderNoDataWithTimeScaleAndSqlBoxTabContent(hasTimeout); @@ -859,7 +862,9 @@ export class StatementDetails extends React.Component<

Showing explain plans from{" "} - {period} + + +

diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index 73779a54dde4..e9099e08693b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -67,7 +67,6 @@ import { getValidOption, TimeScale, timeScale1hMinOptions, - timeScaleToString, toRoundedDateRange, } from "../timeScaleDropdown"; @@ -96,6 +95,7 @@ import { import { SearchCriteria } from "src/searchCriteria/searchCriteria"; import timeScaleStyles from "../timeScaleDropdown/timeScale.module.scss"; import { RequestState } from "src/api/types"; +import { FormattedTimescale } from "../timeScaleDropdown/formattedTimeScale"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -580,7 +580,7 @@ export class StatementsPage extends React.Component< isSelectedColumn(userSelectedColumnsToShow, c), ); - const period = timeScaleToString(this.props.timeScale); + const period = ; const sortSettingLabel = getSortLabel( this.props.reqSortSetting, "Statement", diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx index b7f4e111e006..c5189acd7577 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx @@ -53,6 +53,7 @@ type ICollectedStatementStatistics = cockroach.server.serverpb.StatementsResponse.ICollectedStatementStatistics; import styles from "./statementsTable.module.scss"; import { StatementDiagnosticsReport } from "../api"; +import { Timestamp } from "../timestamp"; const cx = classNames.bind(styles); export interface AggregateStatistics { @@ -318,8 +319,12 @@ export function makeStatementsColumns( { name: "lastExecTimestamp", title: statisticsTableTitles.lastExecTimestamp(statType), - cell: (stmt: AggregateStatistics) => - TimestampToMoment(stmt.stats.last_exec_timestamp).format(DATE_FORMAT), + cell: (stmt: AggregateStatistics) => ( + + ), sort: (stmt: AggregateStatistics) => TimestampToNumber(stmt.stats.last_exec_timestamp), showByDefault: false, diff --git a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx index 0bbeae40c7c6..d9bd248a301b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx @@ -23,6 +23,7 @@ import { statementsSql, writtenToDisk, } from "src/util"; +import { Timezone } from "src/timestamp"; export type NodeNames = { [nodeId: string]: string }; @@ -37,7 +38,7 @@ export const statisticsColumnLabels = { database: "Database", diagnostics: "Diagnostics", executionCount: "Execution Count", - lastExecTimestamp: "Last Execution Time (UTC)", + lastExecTimestamp: "Last Execution Time", latencyMax: "Max Latency", latencyMin: "Min Latency", latencyP50: "P50 Latency", @@ -56,10 +57,10 @@ export const statisticsColumnLabels = { rowsProcessed: "Rows Processed", sessionActiveDuration: "Session Active Duration", sessionDuration: "Session Duration", - sessionStart: "Session Start Time (UTC)", + sessionStart: "Session Start Time", sessionTxnCount: "Transaction Count", statementFingerprintId: "Statement Fingerprint ID", - statementStartTime: "Statement Start Time (UTC)", + statementStartTime: "Statement Start Time", statements: "Statements", statementsCount: "Statements", status: "Status", @@ -129,7 +130,9 @@ export const statisticsTableTitles: StatisticTableTitleType = { placement="bottom" content={"The timestamp at which the session started."} > - {getLabel("sessionStart")} + <> + {getLabel("sessionStart")} + ); }, @@ -201,7 +204,9 @@ export const statisticsTableTitles: StatisticTableTitleType = { placement="bottom" content={"The timestamp at which the statement started."} > - {getLabel("statementStartTime")} + <> + {getLabel("statementStartTime")} + ); }, @@ -469,7 +474,9 @@ export const statisticsTableTitles: StatisticTableTitleType = {

Last time stamp on which the {contentModifier} was executed.

} > - {getLabel("lastExecTimestamp")} + <> + {getLabel("lastExecTimestamp")} + ); }, diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/formattedTimeScale.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/formattedTimeScale.tsx new file mode 100644 index 000000000000..8f169c4bd1a6 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/formattedTimeScale.tsx @@ -0,0 +1,39 @@ +// Copyright 2023 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { useContext } from "react"; +import moment from "moment-timezone"; +import { dateFormat, timeFormat } from "./timeScaleDropdown"; +import { TimezoneContext } from "../contexts"; +import { toRoundedDateRange } from "./utils"; +import { TimeScale } from "./timeScaleTypes"; +import { Timezone } from "src/timestamp"; + +export const FormattedTimescale = (props: { ts: TimeScale }) => { + const timezone = useContext(TimezoneContext); + + const [start, end] = toRoundedDateRange(props.ts); + const startTz = start.tz(timezone); + const endTz = end.tz(timezone); + const endDayIsToday = endTz.isSame(moment.tz(timezone), "day"); + const startEndOnSameDay = endTz.isSame(startTz, "day"); + const omitDayFormat = endDayIsToday && startEndOnSameDay; + const dateStart = omitDayFormat ? "" : startTz.format(dateFormat); + const dateEnd = + omitDayFormat || startEndOnSameDay ? "" : endTz.format(dateFormat); + const timeStart = startTz.format(timeFormat); + const timeEnd = endTz.format(timeFormat); + + return ( + <> + {dateStart} {timeStart} to {dateEnd} {timeEnd} + + ); +}; diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx index 87adb4f4ca40..131ba76a93d9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useContext } from "react"; import { Button, Dropdown } from "antd"; import "antd/lib/button/style"; import "antd/lib/dropdown/style"; @@ -19,6 +19,8 @@ import classNames from "classnames/bind"; import styles from "./rangeSelector.module.scss"; import { TimeWindow } from "./timeScaleTypes"; +import { TimezoneContext } from "../contexts"; +import { Timezone } from "src/timestamp"; const cx = classNames.bind(styles); @@ -88,6 +90,7 @@ const RangeSelect = ({ selected, }: RangeSelectProps): React.ReactElement => { const [isVisible, setIsVisible] = useState(false); + const timezone = useContext(TimezoneContext); /** * customDropdownOptionWasJustSelected holds whether the user had just clicked the "Custom time interval" option in * the dropdown menu. @@ -224,7 +227,7 @@ const RangeSelect = ({ {selected.timeEnd} {" "} - (UTC) + )} diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx index cbcbfd93b983..fad47c4abc68 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx @@ -290,6 +290,7 @@ describe("TimeScaleDropdown functions", function () { const title = formatRangeSelectSelected( currentWindow, state.currentScale, + "UTC", ); assert.deepEqual(title, { key: "Past 10 Minutes", @@ -300,7 +301,11 @@ describe("TimeScaleDropdown functions", function () { it("returns custom Title with Time part only for current day", () => { const currentScale = { ...state.currentScale, key: "Custom" }; - const title = formatRangeSelectSelected(currentWindow, currentScale); + const title = formatRangeSelectSelected( + currentWindow, + currentScale, + "UTC", + ); const timeStart = moment .utc(currentWindow.start) .format(dropdownTimeFormat); @@ -335,7 +340,7 @@ describe("TimeScaleDropdown functions", function () { ), key: "Custom", }; - const title = formatRangeSelectSelected(window, currentScale); + const title = formatRangeSelectSelected(window, currentScale, "UTC"); const timeStart = moment.utc(window.start).format(dropdownTimeFormat); const timeEnd = moment.utc(window.end).format(dropdownTimeFormat); const dateStart = moment.utc(window.start).format(dropdownDateFormat); diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx index 676b26662c58..f6e0c6c98ad9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import React, { useMemo } from "react"; +import React, { useContext, useMemo } from "react"; import moment from "moment-timezone"; import classNames from "classnames/bind"; import { @@ -25,6 +25,8 @@ import RangeSelect, { import { defaultTimeScaleOptions, findClosestTimeScale } from "./utils"; import styles from "./timeScale.module.scss"; +import { TimezoneContext } from "../contexts"; +import { FormatWithTimezone } from "../util"; const cx = classNames.bind(styles); @@ -74,6 +76,7 @@ export const getTimeLabel = ( export const formatRangeSelectSelected = ( currentWindow: TimeWindow, currentScale: TimeScale, + timezone: string, ): RangeSelectSelected => { const selected = { timeLabel: getTimeLabel(currentWindow), @@ -92,8 +95,8 @@ export const formatRangeSelectSelected = ( ...selected, dateStart: omitDayFormat ? "" : start.format(dateFormat), dateEnd: omitDayFormat || startEndOnSameDay ? "" : end.format(dateFormat), - timeStart: moment.utc(start).format(timeFormat), - timeEnd: moment.utc(end).format(timeFormat), + timeStart: FormatWithTimezone(start, timeFormat, timezone), + timeEnd: FormatWithTimezone(end, timeFormat, timezone), }; } else { return selected; @@ -139,6 +142,7 @@ export const TimeScaleDropdown: React.FC = ({ start: moment.utc(end).subtract(currentScale.windowSize), end, }; + const timezone = useContext(TimezoneContext); const onPresetOptionSelect = (rangeOption: RangeOption) => { let timeScale: TimeScale = { @@ -257,7 +261,11 @@ export const TimeScaleDropdown: React.FC = ({ return (
{ - const [start, end] = toRoundedDateRange(ts); - const endDayIsToday = moment.utc(end).isSame(moment.utc(), "day"); - const startEndOnSameDay = end.isSame(start, "day"); - const omitDayFormat = endDayIsToday && startEndOnSameDay; - const dateStart = omitDayFormat ? "" : start.format(dateFormat); - const dateEnd = - omitDayFormat || startEndOnSameDay ? "" : end.format(dateFormat); - const timeStart = start.format(timeFormat); - const timeEnd = end.format(timeFormat); - - return `${dateStart} ${timeStart} to ${dateEnd} ${timeEnd} (UTC)`; -}; - /** * Create a fixed TimeScale object from a date range. * @param range date range containing start and end as moment objects diff --git a/pkg/ui/workspaces/cluster-ui/src/timestamp/index.ts b/pkg/ui/workspaces/cluster-ui/src/timestamp/index.ts new file mode 100644 index 000000000000..7aca95b466a2 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timestamp/index.ts @@ -0,0 +1,11 @@ +// Copyright 2023 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +export * from "./timestamp"; diff --git a/pkg/ui/workspaces/cluster-ui/src/timestamp/timestamp.tsx b/pkg/ui/workspaces/cluster-ui/src/timestamp/timestamp.tsx new file mode 100644 index 000000000000..51c9608b3344 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timestamp/timestamp.tsx @@ -0,0 +1,39 @@ +// Copyright 2023 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import { Moment } from "moment-timezone"; +import React, { useContext } from "react"; +import { FormatWithTimezone } from "../util"; +import { CoordinatedUniversalTime, TimezoneContext } from "../contexts"; + +export function Timezone(props: any) { + const timezone = useContext(TimezoneContext); + return ( + <> + {timezone.toLowerCase() === CoordinatedUniversalTime.toLowerCase() + ? "(UTC)" // People prefer to read "UTC" instead of the IANA standard "etc/UTC". + : `(${timezone})`} + + ); +} + +export function Timestamp(props: { + time?: Moment; + format: string; + fallback?: string; +}) { + const timezone = useContext(TimezoneContext); + const { time, format, fallback } = props; + return time ? ( + <>{FormatWithTimezone(time, format, timezone)} + ) : ( + <>{fallback} + ); +} diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/recentTransactionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/recentTransactionDetails.tsx index 162e55a27ca9..b123137bb2fa 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/recentTransactionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/recentTransactionDetails.tsx @@ -28,11 +28,12 @@ import { import { StatusIcon } from "src/recentExecutions/statusIcon"; import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; import { getMatchParamByName } from "src/util/query"; -import { executionIdAttr, DATE_FORMAT_24_UTC } from "src/util"; +import { executionIdAttr, DATE_FORMAT_24_TZ } from "src/util"; import styles from "../statementDetails/statementDetails.module.scss"; import { WaitTimeInsightsPanel } from "src/detailsPanels/waitTimeInsightsPanel"; import { capitalize, Duration } from "../util/format"; +import { Timestamp } from "../timestamp"; const cx = classNames.bind(styles); const summaryCardStylesCx = classNames.bind(summaryCardStyles); @@ -50,7 +51,7 @@ const BACK_TO_RECENT_TXNS_BUTTON_LABEL = "Recent Transactions"; const TXN_EXECUTION_ID_LABEL = "Transaction Execution ID"; export const RecentTxnInsightsLabels = { - START_TIME: "Start Time (UTC)", + START_TIME: "Start Time", ELAPSED_TIME: "Elapsed Time", STATUS: "Status", RETRY_COUNT: "Internal Retries", @@ -119,7 +120,12 @@ export const RecentTransactionDetails: React.FC< + } /> ; return (
diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx index f581e7596af1..258b17787f6b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx @@ -66,7 +66,6 @@ import LoadingError from "../sqlActivity/errorComponent"; import { commonStyles } from "../common"; import { TimeScale, - timeScaleToString, timeScale1hMinOptions, getValidOption, toRoundedDateRange, @@ -85,6 +84,7 @@ import { } from "src/util/sqlActivityConstants"; import { SearchCriteria } from "src/searchCriteria/searchCriteria"; import timeScaleStyles from "../timeScaleDropdown/timeScale.module.scss"; +import { FormattedTimescale } from "../timeScaleDropdown/formattedTimeScale"; type IStatementsResponse = protos.cockroach.server.serverpb.IStatementsResponse; @@ -536,7 +536,7 @@ export class TransactionsPage extends React.Component< isSelectedColumn(userSelectedColumnsToShow, c), ); - const period = timeScaleToString(this.props.timeScale); + const period = ; const sortSettingLabel = getSortLabel( this.props.reqSortSetting, "Transaction", diff --git a/pkg/ui/workspaces/cluster-ui/src/util/format.ts b/pkg/ui/workspaces/cluster-ui/src/util/format.ts index 526890b40b91..9ef4eb088135 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/format.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/format.ts @@ -189,13 +189,12 @@ export const DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT = "MMM DD, YYYY [at] H:mm:ss:ms"; /** - * Alternate 24 hour UTC formats + * Alternate 24 hour formats */ -export const DATE_FORMAT_24_UTC = "MMM DD, YYYY [at] H:mm UTC"; -export const DATE_WITH_SECONDS_FORMAT_24_UTC = "MMM DD, YYYY [at] H:mm:ss UTC"; +export const DATE_FORMAT_24_TZ = "MMM DD, YYYY [at] H:mm z"; export const DATE_WITH_SECONDS_FORMAT_24_TZ = "MMM DD, YYYY [at] H:mm:ss z"; -export const DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_UTC = - "MMM DD, YYYY [at] H:mm:ss:ms UTC"; +export const DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_TZ = + "MMM DD, YYYY [at] H:mm:ss:ms z"; export function FormatWithTimezone( m: moment.Moment, diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/index.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/index.tsx index c311aa65651b..2c8e026ffc69 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/index.tsx @@ -34,6 +34,7 @@ import { TimeScale, Visualization, util, + WithTimezoneProps, } from "@cockroachlabs/cluster-ui"; import uPlot from "uplot"; import "uplot/dist/uPlot.min.css"; @@ -43,10 +44,11 @@ import { findClosestTimeScale, defaultTimeScaleOptions, TimeWindow, + WithTimezone, } from "@cockroachlabs/cluster-ui"; import _ from "lodash"; -export interface LineGraphProps extends MetricsDataComponentProps { +export interface OwnProps extends MetricsDataComponentProps { isKvGraph?: boolean; title?: string; subtitle?: string; @@ -59,6 +61,8 @@ export interface LineGraphProps extends MetricsDataComponentProps { preCalcGraphSize?: boolean; } +export type LineGraphProps = OwnProps & WithTimezoneProps; + // touPlot formats our timeseries data into the format // uPlot expects which is a 2-dimensional array where the // first array contains the x-values (time). @@ -153,7 +157,8 @@ export function fillGaps( // and store its ref in a global variable. // Once we receive updates to props, we push new data to the // uPlot object. -export class LineGraph extends React.Component { +// InternalLinegraph is exported for testing. +export class InternalLineGraph extends React.Component { constructor(props: LineGraphProps) { super(props); @@ -295,6 +300,7 @@ export class LineGraph extends React.Component { this.xAxisDomain = calculateXAxisDomain( util.NanoToMilli(this.props.timeInfo.start.toNumber()), util.NanoToMilli(this.props.timeInfo.end.toNumber()), + this.props.timezone, ); const prevKeys = @@ -365,3 +371,5 @@ export class LineGraph extends React.Component { ); } } + +export default WithTimezone(InternalLineGraph); diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/linegraph.spec.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/linegraph.spec.tsx index 24cd745d71f9..7e688535f6e4 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/linegraph.spec.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/components/linegraph/linegraph.spec.tsx @@ -8,12 +8,13 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import { shallow } from "enzyme"; +import { shallow, ShallowWrapper } from "enzyme"; import React from "react"; import uPlot from "uplot"; import _ from "lodash"; -import { fillGaps, LineGraph, LineGraphProps } from "./index"; +import LineGraph, { InternalLineGraph, OwnProps } from "./index"; +import { fillGaps } from "./index"; import * as timewindow from "src/redux/timeScale"; import * as protos from "src/js/protos"; import { Axis } from "src/views/shared/components/metricQuery"; @@ -26,8 +27,8 @@ import { configureUPlotLineChart } from "src/views/cluster/util/graphs"; import Long from "long"; describe("", function () { - let mockProps: LineGraphProps; - const linegraph = (props: LineGraphProps) => + let mockProps: OwnProps; + const linegraph = (props: OwnProps) => shallow( @@ -73,13 +74,17 @@ describe("", function () { it("should render a root component on mount", () => { const wrapper = linegraph({ ...mockProps }); - const root = wrapper.find(".linegraph"); + const root = wrapper.dive().find(".linegraph"); expect(root.length).toBe(1); }); it("should set a new chart on update", () => { - const wrapper = linegraph({ ...mockProps }); - const instance = wrapper.instance() as any as LineGraph; + const wrapper = linegraph({ ...mockProps }).dive() as ShallowWrapper< + any, + Readonly<{}>, + React.Component<{}, {}, any> + >; + const instance = wrapper.instance() as any as InternalLineGraph; wrapper.setProps({ data: { results: [ @@ -107,8 +112,12 @@ describe("", function () { const wrapper = linegraph({ ...mockProps, data: { results: [{}], toJSON: jest.fn() }, - }); - const instance = wrapper.instance() as unknown as LineGraph; + }).dive() as ShallowWrapper< + any, + Readonly<{}>, + React.Component<{}, {}, any> + >; + const instance = wrapper.instance() as unknown as InternalLineGraph; const mockFn = jest.fn(); const mockMetrics = [ { diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx index 6df9320ce8a0..d1d1d8ff4be4 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx @@ -96,7 +96,7 @@ describe("getEventInfo", function () { const event: clusterUiApi.EventColumns = createEventWithEventType(eventType); const eventContent = shallow( - getEventInfo(event).content as React.ReactElement, + getEventInfo(event, "UTC").content as React.ReactElement, ); expect(eventContent.text()).not.toMatch(/Unknown event type/); }); diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx index 453223667a95..d9fb753a7edf 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx @@ -10,7 +10,7 @@ import _ from "lodash"; import moment from "moment-timezone"; -import React from "react"; +import React, { useContext } from "react"; import { Helmet } from "react-helmet"; import { Link, RouteComponentProps, withRouter } from "react-router-dom"; import { connect } from "react-redux"; @@ -31,6 +31,8 @@ import { SortedTable, util, api as clusterUiApi, + TimezoneContext, + WithTimezone, } from "@cockroachlabs/cluster-ui"; import { InlineAlert } from "@cockroachlabs/ui-components"; import "./events.styl"; @@ -56,10 +58,17 @@ export interface EventRowProps { event: clusterUiApi.EventColumns; } -export function getEventInfo(e: clusterUiApi.EventColumns): SimplifiedEvent { +export function getEventInfo( + e: clusterUiApi.EventColumns, + timezone: string, +): SimplifiedEvent { return { - fromNowString: moment(e.timestamp) - .format(util.DATE_FORMAT_24_UTC) + fromNowString: util + .FormatWithTimezone( + moment.utc(e.timestamp), + util.DATE_FORMAT_24_TZ, + timezone, + ) .replace("second", "sec") .replace("minute", "min"), content: {getEventDescription(e)}, @@ -67,22 +76,21 @@ export function getEventInfo(e: clusterUiApi.EventColumns): SimplifiedEvent { }; } -export class EventRow extends React.Component { - render() { - const { event } = this.props; - const e = getEventInfo(event); - return ( - - - -
{e.content}
-
-
{e.fromNowString}
- - - ); - } -} +export const EventRow = (props: EventRowProps) => { + const { event } = props; + const timezone = useContext(TimezoneContext); + const e = getEventInfo(event, timezone); + return ( + + + +
{e.content}
+
+
{e.fromNowString}
+ + + ); +}; export interface EventBoxProps { events: clusterUiApi.EventsResponse; @@ -137,6 +145,7 @@ export interface EventPageProps { setSort: typeof eventsSortSetting.set; lastError: Error; maxSizeApiReached: boolean; + timezone: string; } export class EventPageUnconnected extends React.Component { @@ -152,7 +161,9 @@ export class EventPageUnconnected extends React.Component { renderContent() { const { events, sortSetting, maxSizeApiReached } = this.props; - const simplifiedEvents = _.map(events, getEventInfo); + const simplifiedEvents = _.map(events, event => { + return getEventInfo(event, this.props.timezone); + }); return ( <> @@ -243,7 +254,7 @@ const eventPageConnected = withRouter( refreshEvents, setSort: eventsSortSetting.set, }, - )(EventPageUnconnected), + )(WithTimezone(EventPageUnconnected)), ); export { eventBoxConnected as EventBox }; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/changefeeds.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/changefeeds.tsx index e79393d1727c..8ea5f861c824 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/changefeeds.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/changefeeds.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/crossClusterReplication.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/crossClusterReplication.tsx index ec86511b5cd9..f31970aadbaa 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/crossClusterReplication.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/crossClusterReplication.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/distributed.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/distributed.tsx index dbd5ffb8b2f3..6a53a0b5a5a8 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/distributed.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/distributed.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/hardware.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/hardware.tsx index aabec09377cb..a2c8602e1125 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/hardware.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/hardware.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overload.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overload.tsx index 1d261991a5c3..e3d63ee61e34 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overload.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overload.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overview.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overview.tsx index b8aab499a08a..f358adb8a205 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overview.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/overview.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Axis, Metric } from "src/views/shared/components/metricQuery"; import { diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/queues.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/queues.tsx index 598454cb86a8..840bf6c38775 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/queues.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/queues.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/replication.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/replication.tsx index 09656e63eefa..ef7a4d918813 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/replication.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/replication.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Axis, Metric } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/requests.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/requests.tsx index 34f8739d1064..5ec3b79df7d7 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/requests.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/requests.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { GraphDashboardProps } from "./dashboardUtils"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/runtime.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/runtime.tsx index 6fa0380f9082..17214769b189 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/runtime.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/runtime.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/sql.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/sql.tsx index a7fcca4f4ea3..d9e7c652739f 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/sql.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/sql.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/storage.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/storage.tsx index 58d30ce66a55..6adaee8a662f 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/storage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/storage.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/ttl.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/ttl.tsx index b5820ce0ca3f..a41a18d57444 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/ttl.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeGraphs/dashboards/ttl.tsx @@ -11,7 +11,7 @@ import React from "react"; import _ from "lodash"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeLogs/index.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeLogs/index.tsx index 8746558d9fe2..fb0955508250 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeLogs/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeLogs/index.tsx @@ -23,7 +23,12 @@ import { refreshLogs, refreshNodes } from "src/redux/apiReducers"; import { currentNode } from "src/views/cluster/containers/nodeOverview"; import { CachedDataReducerState } from "src/redux/cachedDataReducer"; import { getDisplayName } from "src/redux/nodes"; -import { Loading, SortedTable, util } from "@cockroachlabs/cluster-ui"; +import { + Loading, + SortedTable, + util, + Timestamp, +} from "@cockroachlabs/cluster-ui"; import { getMatchParamByName } from "src/util/query"; import "./logs.styl"; @@ -54,10 +59,12 @@ export class Logs extends React.Component { { title: "Time", name: "time", - cell: (logEntry: LogEntries) => - util - .LongToMoment(logEntry.time) - .format(util.DATE_WITH_SECONDS_FORMAT_24_UTC), + cell: (logEntry: LogEntries) => ( + + ), }, { title: "Severity", diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeOverview/index.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeOverview/index.tsx index 1b42e0cf4b56..d7ad7f85f8e6 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeOverview/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/nodeOverview/index.tsx @@ -31,7 +31,7 @@ import { SummaryLabel, SummaryValue, } from "src/views/shared/components/summaryBar"; -import { Button, util } from "@cockroachlabs/cluster-ui"; +import { Button, util, Timestamp } from "@cockroachlabs/cluster-ui"; import { ArrowLeft } from "@cockroachlabs/icons"; import "./nodeOverview.styl"; import { @@ -115,7 +115,7 @@ export class NodeOverview extends React.Component { render() { const { node, nodesSummary } = this.props; - const { Bytes, Percentage, DATE_FORMAT_24_UTC } = util; + const { Bytes, Percentage, DATE_FORMAT_24_TZ } = util; if (!node) { return (
@@ -306,9 +306,12 @@ export class NodeOverview extends React.Component { /> + } /> - record.decommissionedDate.format(util.DATE_FORMAT_24_UTC), + render: (_text: string, record: DecommissionedNodeStatusRow) => ( + + ), }, { key: "status", diff --git a/pkg/ui/workspaces/db-console/src/views/devtools/containers/raftMessages/messages.tsx b/pkg/ui/workspaces/db-console/src/views/devtools/containers/raftMessages/messages.tsx index 68f16306bbe0..9b111c167a38 100644 --- a/pkg/ui/workspaces/db-console/src/views/devtools/containers/raftMessages/messages.tsx +++ b/pkg/ui/workspaces/db-console/src/views/devtools/containers/raftMessages/messages.tsx @@ -10,7 +10,7 @@ import React from "react"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; import { AxisUnits } from "@cockroachlabs/cluster-ui"; diff --git a/pkg/ui/workspaces/db-console/src/views/hotRanges/index.tsx b/pkg/ui/workspaces/db-console/src/views/hotRanges/index.tsx index 9774cd878ed0..2bfc30b94f00 100644 --- a/pkg/ui/workspaces/db-console/src/views/hotRanges/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/hotRanges/index.tsx @@ -10,12 +10,18 @@ import { cockroach } from "src/js/protos"; import { useDispatch, useSelector } from "react-redux"; -import React, { useRef, useEffect, useState } from "react"; +import React, { useRef, useEffect, useState, useContext } from "react"; import { Helmet } from "react-helmet"; import { refreshHotRanges } from "src/redux/apiReducers"; import HotRangesTable from "./hotRangesTable"; import ErrorBoundary from "../app/components/errorMessage/errorBoundary"; -import { Loading, Text, Anchor, util } from "@cockroachlabs/cluster-ui"; +import { + Loading, + Text, + Anchor, + util, + TimezoneContext, +} from "@cockroachlabs/cluster-ui"; import classNames from "classnames/bind"; import styles from "./hotRanges.module.styl"; import { @@ -41,6 +47,7 @@ const HotRangesPage = () => { const lastSetAt = useSelector(lastSetAtSelector); const isLoading = useSelector(isLoadingSelector); const nodeIdToLocalityMap = useSelector(selectNodeLocalities); + const timezone = useContext(TimezoneContext); useEffect(() => { if (!isValid) { @@ -91,7 +98,12 @@ const HotRangesPage = () => { } diff --git a/pkg/ui/workspaces/db-console/src/views/reports/containers/customChart/index.tsx b/pkg/ui/workspaces/db-console/src/views/reports/containers/customChart/index.tsx index 4d1d88d3f34d..c06399b18c97 100644 --- a/pkg/ui/workspaces/db-console/src/views/reports/containers/customChart/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/reports/containers/customChart/index.tsx @@ -18,7 +18,7 @@ import { createSelector } from "reselect"; import { refreshMetricMetadata, refreshNodes } from "src/redux/apiReducers"; import { nodesSummarySelector, NodesSummary } from "src/redux/nodes"; import { AdminUIState } from "src/redux/state"; -import { LineGraph } from "src/views/cluster/components/linegraph"; +import LineGraph from "src/views/cluster/components/linegraph"; import { DropdownOption } from "src/views/shared/components/dropdown"; import { MetricsDataProvider } from "src/views/shared/containers/metricDataProvider"; import { Metric, Axis } from "src/views/shared/components/metricQuery"; diff --git a/pkg/ui/workspaces/db-console/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx b/pkg/ui/workspaces/db-console/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx index 393814716d63..5d6c5667aa04 100644 --- a/pkg/ui/workspaces/db-console/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx +++ b/pkg/ui/workspaces/db-console/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx @@ -29,6 +29,7 @@ import { Table, SortSetting, util, + Timestamp, } from "@cockroachlabs/cluster-ui"; import { createSelector } from "reselect"; import { BackToAdvanceDebug } from "src/views/reports/containers/util"; @@ -91,7 +92,12 @@ export class DecommissionedNodeHistory extends React.Component< title: "Decommissioned On", sorter: sortByDecommissioningDate, render: (_text, record) => { - return record.decommissionedDate.format(util.DATE_FORMAT_24_UTC); + return ( + + ); }, }, ]; diff --git a/pkg/ui/workspaces/db-console/src/views/reports/containers/settings/index.tsx b/pkg/ui/workspaces/db-console/src/views/reports/containers/settings/index.tsx index 2b72e189fb18..4ed42ab37a67 100644 --- a/pkg/ui/workspaces/db-console/src/views/reports/containers/settings/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/reports/containers/settings/index.tsx @@ -23,6 +23,7 @@ import { SortedTable, SortSetting, util, + Timestamp, } from "@cockroachlabs/cluster-ui"; import "./index.styl"; import { CachedDataReducerState } from "src/redux/cachedDataReducer"; @@ -109,10 +110,13 @@ export class Settings extends React.Component { { name: "lastUpdated", title: "Last Updated", - cell: (setting: IterableSetting) => - setting.last_updated - ? setting.last_updated.format(util.DATE_FORMAT_24_UTC) - : "No overrides", + cell: (setting: IterableSetting) => ( + + ), sort: (setting: IterableSetting) => setting.last_updated?.valueOf(), }, { diff --git a/pkg/ui/workspaces/db-console/src/views/reports/containers/statementDiagnosticsHistory/index.tsx b/pkg/ui/workspaces/db-console/src/views/reports/containers/statementDiagnosticsHistory/index.tsx index 30b570f967fa..646535e057ea 100644 --- a/pkg/ui/workspaces/db-console/src/views/reports/containers/statementDiagnosticsHistory/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/reports/containers/statementDiagnosticsHistory/index.tsx @@ -46,6 +46,7 @@ import { SortSetting, ColumnDescriptor, util, + Timestamp, } from "@cockroachlabs/cluster-ui"; import { cancelStatementDiagnosticsReportAction } from "src/redux/statements"; import { trackCancelDiagnosticsBundleAction } from "src/redux/analyticsActions"; @@ -95,8 +96,9 @@ class StatementDiagnosticsHistoryView extends React.Component< { title: "Activated on", name: "activated_on", - cell: record => - moment.utc(record.requested_at).format(util.DATE_FORMAT_24_UTC), + cell: record => ( + + ), sort: record => { return moment.utc(record.requested_at).unix(); },