mirror of https://github.com/prometheus/prometheus
React UI: Support local timezone on /graph (#6692)
* React UI: Support local timezone on /graph This partially implements https://github.com/prometheus/prometheus/issues/500 in the sense that it only addresses the /graph page, and only allows toggling between UTC and local (browser) time, but no arbitrary timezone selection yet. Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fixup: Also display TZ offset in tooltip Signed-off-by: Julius Volz <julius.volz@gmail.com> * Just show offset, not timezone name abbreviation Signed-off-by: Julius Volz <julius.volz@gmail.com>pull/6696/head
parent
fafb7940b1
commit
d996ba20ec
|
@ -19,6 +19,7 @@ export interface GraphProps {
|
||||||
result: Array<{ metric: Metric; values: [number, string][] }>;
|
result: Array<{ metric: Metric; values: [number, string][] }>;
|
||||||
};
|
};
|
||||||
stacked: boolean;
|
stacked: boolean;
|
||||||
|
useLocalTime: boolean;
|
||||||
queryParams: QueryParams | null;
|
queryParams: QueryParams | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ class Graph extends PureComponent<GraphProps, GraphState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps: GraphProps) {
|
componentDidUpdate(prevProps: GraphProps) {
|
||||||
const { data, stacked } = this.props;
|
const { data, stacked, useLocalTime } = this.props;
|
||||||
if (prevProps.data !== data) {
|
if (prevProps.data !== data) {
|
||||||
this.selectedSeriesIndexes = [];
|
this.selectedSeriesIndexes = [];
|
||||||
this.setState({ chartData: normalizeData(this.props) }, this.plot);
|
this.setState({ chartData: normalizeData(this.props) }, this.plot);
|
||||||
|
@ -57,6 +58,10 @@ class Graph extends PureComponent<GraphProps, GraphState> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.useLocalTime !== useLocalTime) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -73,7 +78,7 @@ class Graph extends PureComponent<GraphProps, GraphState> {
|
||||||
}
|
}
|
||||||
this.destroyPlot();
|
this.destroyPlot();
|
||||||
|
|
||||||
this.$chart = $.plot($(this.chartRef.current), data, getOptions(this.props.stacked));
|
this.$chart = $.plot($(this.chartRef.current), data, getOptions(this.props.stacked, this.props.useLocalTime));
|
||||||
};
|
};
|
||||||
|
|
||||||
destroyPlot = () => {
|
destroyPlot = () => {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { parseRange, formatRange } from '../../utils';
|
||||||
interface GraphControlsProps {
|
interface GraphControlsProps {
|
||||||
range: number;
|
range: number;
|
||||||
endTime: number | null;
|
endTime: number | null;
|
||||||
|
useLocalTime: boolean;
|
||||||
resolution: number | null;
|
resolution: number | null;
|
||||||
stacked: boolean;
|
stacked: boolean;
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ class GraphControls extends Component<GraphControlsProps> {
|
||||||
|
|
||||||
<TimeInput
|
<TimeInput
|
||||||
time={this.props.endTime}
|
time={this.props.endTime}
|
||||||
|
useLocalTime={this.props.useLocalTime}
|
||||||
range={this.props.range}
|
range={this.props.range}
|
||||||
placeholder="End time"
|
placeholder="End time"
|
||||||
onChangeTime={this.props.onChangeEndTime}
|
onChangeTime={this.props.onChangeEndTime}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { formatValue, getColors, parseValue, getOptions } from './GraphHelpers';
|
import { formatValue, getColors, parseValue, getOptions } from './GraphHelpers';
|
||||||
|
import moment from 'moment';
|
||||||
require('../../vendor/flot/jquery.flot'); // need for $.colors
|
require('../../vendor/flot/jquery.flot'); // need for $.colors
|
||||||
|
|
||||||
describe('GraphHelpers', () => {
|
describe('GraphHelpers', () => {
|
||||||
|
@ -98,8 +99,8 @@ describe('GraphHelpers', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Plot options', () => {
|
describe('Plot options', () => {
|
||||||
it('should configer options properly if stacked prop is true', () => {
|
it('should configure options properly if stacked prop is true', () => {
|
||||||
expect(getOptions(true)).toMatchObject({
|
expect(getOptions(true, false)).toMatchObject({
|
||||||
series: {
|
series: {
|
||||||
stack: true,
|
stack: true,
|
||||||
lines: { lineWidth: 1, steps: false, fill: true },
|
lines: { lineWidth: 1, steps: false, fill: true },
|
||||||
|
@ -107,8 +108,8 @@ describe('GraphHelpers', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should configer options properly if stacked prop is false', () => {
|
it('should configure options properly if stacked prop is false', () => {
|
||||||
expect(getOptions(false)).toMatchObject({
|
expect(getOptions(false, false)).toMatchObject({
|
||||||
series: {
|
series: {
|
||||||
stack: false,
|
stack: false,
|
||||||
lines: { lineWidth: 2, steps: false, fill: false },
|
lines: { lineWidth: 2, steps: false, fill: false },
|
||||||
|
@ -116,13 +117,51 @@ describe('GraphHelpers', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('should configure options properly if useLocalTime prop is true', () => {
|
||||||
|
expect(getOptions(true, true)).toMatchObject({
|
||||||
|
xaxis: {
|
||||||
|
mode: 'time',
|
||||||
|
showTicks: true,
|
||||||
|
showMinorTicks: true,
|
||||||
|
timeBase: 'milliseconds',
|
||||||
|
timezone: 'browser',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should configure options properly if useLocalTime prop is false', () => {
|
||||||
|
expect(getOptions(false, false)).toMatchObject({
|
||||||
|
xaxis: {
|
||||||
|
mode: 'time',
|
||||||
|
showTicks: true,
|
||||||
|
showMinorTicks: true,
|
||||||
|
timeBase: 'milliseconds',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
it('should return proper tooltip html from options', () => {
|
it('should return proper tooltip html from options', () => {
|
||||||
expect(
|
expect(
|
||||||
getOptions(true).tooltip.content('', 1572128592, 1572128592, {
|
getOptions(true, false).tooltip.content('', 1572128592, 1572128592, {
|
||||||
series: { labels: { foo: '1', bar: '2' }, color: '' },
|
series: { labels: { foo: '1', bar: '2' }, color: '' },
|
||||||
} as any)
|
} as any)
|
||||||
).toEqual(`
|
).toEqual(`
|
||||||
<div class="date">Mon, 19 Jan 1970 04:42:08 GMT</div>
|
<div class="date">1970-01-19 04:42:08 +00:00</div>
|
||||||
|
<div>
|
||||||
|
<span class="detail-swatch" style="background-color: " />
|
||||||
|
<span>value: <strong>1572128592</strong></span>
|
||||||
|
<div>
|
||||||
|
<div class="labels mt-1">
|
||||||
|
<div class="mb-1"><strong>foo</strong>: 1</div><div class="mb-1"><strong>bar</strong>: 2</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('should return proper tooltip html from options with local time', () => {
|
||||||
|
moment.tz.setDefault('America/New_York');
|
||||||
|
expect(
|
||||||
|
getOptions(true, true).tooltip.content('', 1572128592, 1572128592, {
|
||||||
|
series: { labels: { foo: '1', bar: '2' }, color: '' },
|
||||||
|
} as any)
|
||||||
|
).toEqual(`
|
||||||
|
<div class="date">1970-01-18 23:42:08 -05:00</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="detail-swatch" style="background-color: " />
|
<span class="detail-swatch" style="background-color: " />
|
||||||
<span>value: <strong>1572128592</strong></span>
|
<span>value: <strong>1572128592</strong></span>
|
||||||
|
@ -133,7 +172,7 @@ describe('GraphHelpers', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
it('should render Plot with proper options', () => {
|
it('should render Plot with proper options', () => {
|
||||||
expect(getOptions(true)).toEqual({
|
expect(getOptions(true, false)).toEqual({
|
||||||
grid: {
|
grid: {
|
||||||
hoverable: true,
|
hoverable: true,
|
||||||
clickable: true,
|
clickable: true,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import $ from 'jquery';
|
||||||
import { escapeHTML } from '../../utils';
|
import { escapeHTML } from '../../utils';
|
||||||
import { Metric } from '../../types/types';
|
import { Metric } from '../../types/types';
|
||||||
import { GraphProps, GraphSeries } from './Graph';
|
import { GraphProps, GraphSeries } from './Graph';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
export const formatValue = (y: number | null): string => {
|
export const formatValue = (y: number | null): string => {
|
||||||
if (y === null) {
|
if (y === null) {
|
||||||
|
@ -71,7 +72,7 @@ export const toHoverColor = (index: number, stacked: boolean) => (series: GraphS
|
||||||
color: getHoverColor(series.color, i !== index ? 0.3 : 1, stacked),
|
color: getHoverColor(series.color, i !== index ? 0.3 : 1, stacked),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getOptions = (stacked: boolean): jquery.flot.plotOptions => {
|
export const getOptions = (stacked: boolean, useLocalTime: boolean): jquery.flot.plotOptions => {
|
||||||
return {
|
return {
|
||||||
grid: {
|
grid: {
|
||||||
hoverable: true,
|
hoverable: true,
|
||||||
|
@ -87,6 +88,7 @@ export const getOptions = (stacked: boolean): jquery.flot.plotOptions => {
|
||||||
showTicks: true,
|
showTicks: true,
|
||||||
showMinorTicks: true,
|
showMinorTicks: true,
|
||||||
timeBase: 'milliseconds',
|
timeBase: 'milliseconds',
|
||||||
|
timezone: useLocalTime ? 'browser' : undefined,
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
tickFormatter: formatValue,
|
tickFormatter: formatValue,
|
||||||
|
@ -100,8 +102,12 @@ export const getOptions = (stacked: boolean): jquery.flot.plotOptions => {
|
||||||
cssClass: 'graph-tooltip',
|
cssClass: 'graph-tooltip',
|
||||||
content: (_, xval, yval, { series }): string => {
|
content: (_, xval, yval, { series }): string => {
|
||||||
const { labels, color } = series;
|
const { labels, color } = series;
|
||||||
|
let dateTime = moment(xval);
|
||||||
|
if (!useLocalTime) {
|
||||||
|
dateTime = dateTime.utc();
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
<div class="date">${new Date(xval).toUTCString()}</div>
|
<div class="date">${dateTime.format('YYYY-MM-DD HH:mm:ss Z')}</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="detail-swatch" style="background-color: ${color}" />
|
<span class="detail-swatch" style="background-color: ${color}" />
|
||||||
<span>${labels.__name__ || 'value'}: <strong>${yval}</strong></span>
|
<span>${labels.__name__ || 'value'}: <strong>${yval}</strong></span>
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
import React from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Alert } from 'reactstrap';
|
import { Alert } from 'reactstrap';
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
import { QueryParams } from '../../types/types';
|
import { QueryParams } from '../../types/types';
|
||||||
import { isPresent } from '../../utils';
|
import { isPresent } from '../../utils';
|
||||||
|
|
||||||
export const GraphTabContent = ({
|
interface GraphTabContentProps {
|
||||||
data,
|
|
||||||
stacked,
|
|
||||||
lastQueryParams,
|
|
||||||
}: {
|
|
||||||
data: any;
|
data: any;
|
||||||
stacked: boolean;
|
stacked: boolean;
|
||||||
|
useLocalTime: boolean;
|
||||||
lastQueryParams: QueryParams | null;
|
lastQueryParams: QueryParams | null;
|
||||||
}) => {
|
}
|
||||||
|
|
||||||
|
export const GraphTabContent: FC<GraphTabContentProps> = ({ data, stacked, useLocalTime, lastQueryParams }) => {
|
||||||
if (!isPresent(data)) {
|
if (!isPresent(data)) {
|
||||||
return <Alert color="light">No data queried yet</Alert>;
|
return <Alert color="light">No data queried yet</Alert>;
|
||||||
}
|
}
|
||||||
|
@ -24,5 +23,5 @@ export const GraphTabContent = ({
|
||||||
<Alert color="danger">Query result is of wrong type '{data.resultType}', should be 'matrix' (range vector).</Alert>
|
<Alert color="danger">Query result is of wrong type '{data.resultType}', should be 'matrix' (range vector).</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <Graph data={data} stacked={stacked} queryParams={lastQueryParams} />;
|
return <Graph data={data} stacked={stacked} useLocalTime={useLocalTime} queryParams={lastQueryParams} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { QueryParams } from '../../types/types';
|
||||||
interface PanelProps {
|
interface PanelProps {
|
||||||
options: PanelOptions;
|
options: PanelOptions;
|
||||||
onOptionsChanged: (opts: PanelOptions) => void;
|
onOptionsChanged: (opts: PanelOptions) => void;
|
||||||
|
useLocalTime: boolean;
|
||||||
pastQueries: string[];
|
pastQueries: string[];
|
||||||
metricNames: string[];
|
metricNames: string[];
|
||||||
removePanel: () => void;
|
removePanel: () => void;
|
||||||
|
@ -266,6 +267,7 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
||||||
<div className="table-controls">
|
<div className="table-controls">
|
||||||
<TimeInput
|
<TimeInput
|
||||||
time={options.endTime}
|
time={options.endTime}
|
||||||
|
useLocalTime={this.props.useLocalTime}
|
||||||
range={options.range}
|
range={options.range}
|
||||||
placeholder="Evaluation time"
|
placeholder="Evaluation time"
|
||||||
onChangeTime={this.handleChangeEndTime}
|
onChangeTime={this.handleChangeEndTime}
|
||||||
|
@ -281,6 +283,7 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
||||||
<GraphControls
|
<GraphControls
|
||||||
range={options.range}
|
range={options.range}
|
||||||
endTime={options.endTime}
|
endTime={options.endTime}
|
||||||
|
useLocalTime={this.props.useLocalTime}
|
||||||
resolution={options.resolution}
|
resolution={options.resolution}
|
||||||
stacked={options.stacked}
|
stacked={options.stacked}
|
||||||
onChangeRange={this.handleChangeRange}
|
onChangeRange={this.handleChangeRange}
|
||||||
|
@ -291,6 +294,7 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
||||||
<GraphTabContent
|
<GraphTabContent
|
||||||
data={this.state.data}
|
data={this.state.data}
|
||||||
stacked={options.stacked}
|
stacked={options.stacked}
|
||||||
|
useLocalTime={this.props.useLocalTime}
|
||||||
lastQueryParams={this.state.lastQueryParams}
|
lastQueryParams={this.state.lastQueryParams}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,16 +6,21 @@ import { Alert, Button } from 'reactstrap';
|
||||||
import Panel from './Panel';
|
import Panel from './Panel';
|
||||||
|
|
||||||
describe('PanelList', () => {
|
describe('PanelList', () => {
|
||||||
it('renders a query history checkbox', () => {
|
it('renders query history and local time checkboxes', () => {
|
||||||
const panelList = shallow(<PanelList />);
|
[
|
||||||
const checkbox = panelList.find(Checkbox);
|
{ id: 'query-history-checkbox', label: 'Enable query history' },
|
||||||
expect(checkbox.prop('id')).toEqual('query-history-checkbox');
|
{ id: 'use-local-time-checkbox', label: 'Use local time' },
|
||||||
expect(checkbox.prop('wrapperStyles')).toEqual({
|
].forEach((cb, idx) => {
|
||||||
margin: '0 0 0 15px',
|
const panelList = shallow(<PanelList />);
|
||||||
alignSelf: 'center',
|
const checkbox = panelList.find(Checkbox).at(idx);
|
||||||
|
expect(checkbox.prop('id')).toEqual(cb.id);
|
||||||
|
expect(checkbox.prop('wrapperStyles')).toEqual({
|
||||||
|
margin: '0 0 0 15px',
|
||||||
|
alignSelf: 'center',
|
||||||
|
});
|
||||||
|
expect(checkbox.prop('defaultChecked')).toBe(false);
|
||||||
|
expect(checkbox.children().text()).toBe(cb.label);
|
||||||
});
|
});
|
||||||
expect(checkbox.prop('defaultChecked')).toBe(false);
|
|
||||||
expect(checkbox.children().text()).toBe('Enable query history');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an alert when no data is queried yet', () => {
|
it('renders an alert when no data is queried yet', () => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface PanelListState {
|
||||||
metricNames: string[];
|
metricNames: string[];
|
||||||
fetchMetricsError: string | null;
|
fetchMetricsError: string | null;
|
||||||
timeDriftError: string | null;
|
timeDriftError: string | null;
|
||||||
|
useLocalTime: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelListState> {
|
class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelListState> {
|
||||||
|
@ -29,6 +30,7 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
metricNames: [],
|
metricNames: [],
|
||||||
fetchMetricsError: null,
|
fetchMetricsError: null,
|
||||||
timeDriftError: null,
|
timeDriftError: null,
|
||||||
|
useLocalTime: this.useLocalTime(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +97,13 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useLocalTime = () => JSON.parse(localStorage.getItem('use-local-time') || 'false') as boolean;
|
||||||
|
|
||||||
|
toggleUseLocalTime = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
localStorage.setItem('use-local-time', `${e.target.checked}`);
|
||||||
|
this.setState({ useLocalTime: e.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
handleExecuteQuery = (query: string) => {
|
handleExecuteQuery = (query: string) => {
|
||||||
const isSimpleMetric = this.state.metricNames.indexOf(query) !== -1;
|
const isSimpleMetric = this.state.metricNames.indexOf(query) !== -1;
|
||||||
if (isSimpleMetric || !query.length) {
|
if (isSimpleMetric || !query.length) {
|
||||||
|
@ -159,6 +168,14 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
>
|
>
|
||||||
Enable query history
|
Enable query history
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
id="use-local-time-checkbox"
|
||||||
|
wrapperStyles={{ margin: '0 0 0 15px', alignSelf: 'center' }}
|
||||||
|
onChange={this.toggleUseLocalTime}
|
||||||
|
defaultChecked={this.useLocalTime()}
|
||||||
|
>
|
||||||
|
Use local time
|
||||||
|
</Checkbox>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
|
@ -184,6 +201,7 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
key={id}
|
key={id}
|
||||||
options={options}
|
options={options}
|
||||||
onOptionsChanged={opts => this.handleOptionsChanged(id, opts)}
|
onOptionsChanged={opts => this.handleOptionsChanged(id, opts)}
|
||||||
|
useLocalTime={this.state.useLocalTime}
|
||||||
removePanel={() => this.removePanel(id)}
|
removePanel={() => this.removePanel(id)}
|
||||||
metricNames={metricNames}
|
metricNames={metricNames}
|
||||||
pastQueries={pastQueries}
|
pastQueries={pastQueries}
|
||||||
|
|
|
@ -25,6 +25,7 @@ dom.watch();
|
||||||
|
|
||||||
interface TimeInputProps {
|
interface TimeInputProps {
|
||||||
time: number | null; // Timestamp in milliseconds.
|
time: number | null; // Timestamp in milliseconds.
|
||||||
|
useLocalTime: boolean;
|
||||||
range: number; // Range in seconds.
|
range: number; // Range in seconds.
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
onChangeTime: (time: number | null) => void;
|
onChangeTime: (time: number | null) => void;
|
||||||
|
@ -54,6 +55,10 @@ class TimeInput extends Component<TimeInputProps> {
|
||||||
this.props.onChangeTime(null);
|
this.props.onChangeTime(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
timezone = (): string => {
|
||||||
|
return this.props.useLocalTime ? moment.tz.guess() : 'UTC';
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.$time = $(this.timeInputRef.current!);
|
this.$time = $(this.timeInputRef.current!);
|
||||||
|
|
||||||
|
@ -69,7 +74,7 @@ class TimeInput extends Component<TimeInputProps> {
|
||||||
sideBySide: true,
|
sideBySide: true,
|
||||||
format: 'YYYY-MM-DD HH:mm:ss',
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
timeZone: 'UTC',
|
timeZone: this.timezone(),
|
||||||
defaultDate: this.props.time,
|
defaultDate: this.props.time,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -84,8 +89,14 @@ class TimeInput extends Component<TimeInputProps> {
|
||||||
this.$time.datetimepicker('destroy');
|
this.$time.datetimepicker('destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate(prevProps: TimeInputProps) {
|
||||||
this.$time.datetimepicker('date', this.props.time ? moment(this.props.time) : null);
|
const { time, useLocalTime } = this.props;
|
||||||
|
if (prevProps.time !== time) {
|
||||||
|
this.$time.datetimepicker('date', time ? moment(time) : null);
|
||||||
|
}
|
||||||
|
if (prevProps.useLocalTime !== useLocalTime) {
|
||||||
|
this.$time.datetimepicker('options', { timeZone: this.timezone(), defaultDate: null });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
Loading…
Reference in New Issue