mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/ui: Add Time of day picker (#18894)
* Adding DashboardPicker component * fix prop names * fix prop names pt2 * add component and modify utils * add showHour prop * add minuteStep to TimeOfDayPicker, add value to DashboardPicker * fix for dashboard picker, missed adding file * Adding story * add another story for hiding hour and style fixes * fix these generated files * fixes after review * rename current value * fix type issue on onChange * fix story
This commit is contained in:
parent
7b856ae040
commit
196f8503a8
@ -1,7 +1,6 @@
|
|||||||
import { TimeZone } from '../types/time';
|
import { TimeZone } from '../types/time';
|
||||||
/* tslint:disable:import-blacklist ban ban-types */
|
/* tslint:disable:import-blacklist ban ban-types */
|
||||||
import moment, { MomentInput, DurationInputArg1 } from 'moment';
|
import moment, { Moment, MomentInput, DurationInputArg1 } from 'moment';
|
||||||
|
|
||||||
export interface DateTimeBuiltinFormat {
|
export interface DateTimeBuiltinFormat {
|
||||||
__momentBuiltinFormatBrand: any;
|
__momentBuiltinFormatBrand: any;
|
||||||
}
|
}
|
||||||
@ -72,6 +71,7 @@ export interface DateTime extends Object {
|
|||||||
utc: () => DateTime;
|
utc: () => DateTime;
|
||||||
utcOffset: () => number;
|
utcOffset: () => number;
|
||||||
hour?: () => number;
|
hour?: () => number;
|
||||||
|
minute?: () => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setLocale = (language: string) => {
|
export const setLocale = (language: string) => {
|
||||||
@ -98,6 +98,10 @@ export const dateTime = (input?: DateTimeInput, formatInput?: FormatInput): Date
|
|||||||
return moment(input as MomentInput, formatInput) as DateTime;
|
return moment(input as MomentInput, formatInput) as DateTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dateTimeAsMoment = (input?: DateTimeInput) => {
|
||||||
|
return dateTime(input) as Moment;
|
||||||
|
};
|
||||||
|
|
||||||
export const dateTimeForTimeZone = (
|
export const dateTimeForTimeZone = (
|
||||||
timezone?: TimeZone,
|
timezone?: TimeZone,
|
||||||
input?: DateTimeInput,
|
input?: DateTimeInput,
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"lodash": "4.17.14",
|
"lodash": "4.17.14",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"papaparse": "4.6.3",
|
"papaparse": "4.6.3",
|
||||||
|
"rc-time-picker": "^3.7.2",
|
||||||
"react": "16.8.6",
|
"react": "16.8.6",
|
||||||
"react-calendar": "2.18.1",
|
"react-calendar": "2.18.1",
|
||||||
"react-color": "2.17.0",
|
"react-color": "2.17.0",
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { TimeOfDayPicker } from './TimeOfDayPicker';
|
||||||
|
import { UseState } from '../../utils/storybook/UseState';
|
||||||
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
import { dateTime } from '@grafana/data';
|
||||||
|
|
||||||
|
const TimeOfDayPickerStories = storiesOf('UI/TimeOfDayPicker', module);
|
||||||
|
|
||||||
|
TimeOfDayPickerStories.addDecorator(withCenteredStory);
|
||||||
|
|
||||||
|
TimeOfDayPickerStories.add('default', () => {
|
||||||
|
return (
|
||||||
|
<UseState
|
||||||
|
initialState={{
|
||||||
|
value: dateTime(Date.now()),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(value, updateValue) => {
|
||||||
|
return (
|
||||||
|
<TimeOfDayPicker
|
||||||
|
onChange={newValue => {
|
||||||
|
action('on selected')(newValue);
|
||||||
|
updateValue({ value: newValue });
|
||||||
|
}}
|
||||||
|
value={value.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TimeOfDayPickerStories.add('only minutes', () => {
|
||||||
|
return (
|
||||||
|
<UseState initialState={{ value: dateTime(Date.now()) }}>
|
||||||
|
{(value, updateValue) => {
|
||||||
|
return (
|
||||||
|
<TimeOfDayPicker
|
||||||
|
onChange={newValue => {
|
||||||
|
action('on selected')(newValue);
|
||||||
|
updateValue({ value: newValue });
|
||||||
|
}}
|
||||||
|
value={value.value}
|
||||||
|
showHour={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import RcTimePicker from 'rc-time-picker';
|
||||||
|
import { dateTime, DateTime, dateTimeAsMoment } from '@grafana/data';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onChange: (value: DateTime) => void;
|
||||||
|
value: DateTime;
|
||||||
|
showHour?: boolean;
|
||||||
|
minuteStep?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeOfDayPicker: FC<Props> = ({ minuteStep = 1, showHour = true, onChange, value }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RcTimePicker
|
||||||
|
defaultValue={dateTimeAsMoment()}
|
||||||
|
onChange={(value: any) => onChange(dateTime(value))}
|
||||||
|
allowEmpty={false}
|
||||||
|
showSecond={false}
|
||||||
|
value={dateTimeAsMoment(value)}
|
||||||
|
showHour={showHour}
|
||||||
|
minuteStep={minuteStep}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -4,12 +4,12 @@ import { action } from '@storybook/addon-actions';
|
|||||||
|
|
||||||
import { TimePicker } from './TimePicker';
|
import { TimePicker } from './TimePicker';
|
||||||
import { UseState } from '../../utils/storybook/UseState';
|
import { UseState } from '../../utils/storybook/UseState';
|
||||||
import { withRighAlignedStory } from '../../utils/storybook/withRightAlignedStory';
|
import { withRightAlignedStory } from '../../utils/storybook/withRightAlignedStory';
|
||||||
import { TimeFragment, dateTime } from '@grafana/data';
|
import { TimeFragment, dateTime } from '@grafana/data';
|
||||||
|
|
||||||
const TimePickerStories = storiesOf('UI/TimePicker', module);
|
const TimePickerStories = storiesOf('UI/TimePicker', module);
|
||||||
|
|
||||||
TimePickerStories.addDecorator(withRighAlignedStory);
|
TimePickerStories.addDecorator(withRightAlignedStory);
|
||||||
|
|
||||||
TimePickerStories.add('default', () => {
|
TimePickerStories.add('default', () => {
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
@import '../../node_modules/rc-time-picker/assets/index.css';
|
||||||
|
|
||||||
|
.rc-time-picker-input,
|
||||||
|
.rc-time-picker-panel-input-wrap,
|
||||||
|
.rc-time-picker-panel-inner {
|
||||||
|
background-color: $input-bg;
|
||||||
|
color: $input-color;
|
||||||
|
border-color: $input-border-color;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-time-picker-input {
|
||||||
|
padding: $input-padding;
|
||||||
|
height: $input-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-time-picker-panel {
|
||||||
|
width: 176px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-time-picker-panel-select {
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&:only-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-time-picker-panel-select-option-selected {
|
||||||
|
background-color: $menu-dropdown-hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background-color: $menu-dropdown-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-time-picker-panel-narrow {
|
||||||
|
max-width: none;
|
||||||
|
}
|
@ -14,3 +14,4 @@
|
|||||||
@import 'BarGauge/BarGauge';
|
@import 'BarGauge/BarGauge';
|
||||||
@import 'RefreshPicker/RefreshPicker';
|
@import 'RefreshPicker/RefreshPicker';
|
||||||
@import 'TimePicker/TimePicker';
|
@import 'TimePicker/TimePicker';
|
||||||
|
@import 'TimePicker/TimeOfDayPicker';
|
||||||
|
@ -35,6 +35,7 @@ export { StatsPicker } from './StatsPicker/StatsPicker';
|
|||||||
export { Input, InputStatus } from './Input/Input';
|
export { Input, InputStatus } from './Input/Input';
|
||||||
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
|
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
|
||||||
export { TimePicker } from './TimePicker/TimePicker';
|
export { TimePicker } from './TimePicker/TimePicker';
|
||||||
|
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
||||||
export { List } from './List/List';
|
export { List } from './List/List';
|
||||||
|
|
||||||
// Renderless
|
// Renderless
|
||||||
|
@ -17,4 +17,4 @@ const RightAlignedStory: React.FunctionComponent<{}> = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const withRighAlignedStory = (story: RenderFunction) => <RightAlignedStory>{story()}</RightAlignedStory>;
|
export const withRightAlignedStory = (story: RenderFunction) => <RightAlignedStory>{story()}</RightAlignedStory>;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { AsyncSelect } from '@grafana/ui';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
||||||
@ -7,6 +8,7 @@ import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
onSelected: (dashboard: DashboardDTO) => void;
|
onSelected: (dashboard: DashboardDTO) => void;
|
||||||
|
currentDashboardId: SelectableValue<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
@ -36,7 +38,7 @@ export class DashboardPicker extends PureComponent<Props, State> {
|
|||||||
.then((result: DashboardSearchHit[]) => {
|
.then((result: DashboardSearchHit[]) => {
|
||||||
const dashboards = result.map((item: DashboardSearchHit) => {
|
const dashboards = result.map((item: DashboardSearchHit) => {
|
||||||
return {
|
return {
|
||||||
id: item.uid,
|
id: item.id,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`,
|
||||||
};
|
};
|
||||||
@ -48,7 +50,7 @@ export class DashboardPicker extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, onSelected } = this.props;
|
const { className, onSelected, currentDashboardId } = this.props;
|
||||||
const { isLoading } = this.state;
|
const { isLoading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,6 +65,7 @@ export class DashboardPicker extends PureComponent<Props, State> {
|
|||||||
onChange={onSelected}
|
onChange={onSelected}
|
||||||
placeholder="Select dashboard"
|
placeholder="Select dashboard"
|
||||||
noOptionsMessage={() => 'No dashboards found'}
|
noOptionsMessage={() => 'No dashboards found'}
|
||||||
|
value={currentDashboardId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
6
public/sass/_variables.dark.generated.scss.rej
Normal file
6
public/sass/_variables.dark.generated.scss.rej
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
diff a/public/sass/_variables.dark.generated.scss b/public/sass/_variables.dark.generated.scss (rejected hunks)
|
||||||
|
@@ -94,2 +94,2 @@ $textShadow: none;
|
||||||
|
-$brand-gradient-horizontal: linear-gradient(to right, #f05a28 30%, #fbca0a 99%);
|
||||||
|
-$brand-gradient-vertical: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
||||||
|
+$brand-gradient-horizontal: linear-gradient(to right, #F05A28 30%, #FBCA0A 99%);
|
||||||
|
+$brand-gradient-vertical: linear-gradient(#F05A28 30%, #FBCA0A 99%);
|
6
public/sass/_variables.light.generated.scss.rej
Normal file
6
public/sass/_variables.light.generated.scss.rej
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
diff a/public/sass/_variables.light.generated.scss b/public/sass/_variables.light.generated.scss (rejected hunks)
|
||||||
|
@@ -86,2 +86,2 @@ $text-shadow-faint: none;
|
||||||
|
-$brand-gradient-horizontal: linear-gradient(to right, #f05a28 30%, #fbca0a 99%);
|
||||||
|
-$brand-gradient-vertical: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
||||||
|
+$brand-gradient-horizontal: linear-gradient(to right, #F05A28 30%, #FBCA0A 99%);
|
||||||
|
+$brand-gradient-vertical: linear-gradient(#F05A28 30%, #FBCA0A 99%);
|
16
yarn.lock
16
yarn.lock
@ -12068,7 +12068,7 @@ module-alias@2.2.0:
|
|||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.0.tgz#a2e32275381642252bf0c51405f7a09a367479b5"
|
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.0.tgz#a2e32275381642252bf0c51405f7a09a367479b5"
|
||||||
|
|
||||||
moment@2.24.0:
|
moment@2.24.0, moment@2.x:
|
||||||
version "2.24.0"
|
version "2.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||||
|
|
||||||
@ -14629,7 +14629,7 @@ qw@~1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
|
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
|
||||||
|
|
||||||
raf@^3.1.0, raf@^3.4.0:
|
raf@^3.1.0, raf@^3.4.0, raf@^3.4.1:
|
||||||
version "3.4.1"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -14722,6 +14722,18 @@ rc-cascader@0.14.0:
|
|||||||
shallow-equal "^1.0.0"
|
shallow-equal "^1.0.0"
|
||||||
warning "^4.0.1"
|
warning "^4.0.1"
|
||||||
|
|
||||||
|
rc-time-picker@^3.7.2:
|
||||||
|
version "3.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.7.2.tgz#fabe5501adf1374d31a2d3b47f1ba89fc2dc2467"
|
||||||
|
integrity sha512-UVWO9HXGyZoM4I2THlJsEAFcZQz+tYwdcpoHXCEFZsRLz9L2+7vV4EMp9Wa3UrtzMFEt83qSAX/90dCJeKl9sg==
|
||||||
|
dependencies:
|
||||||
|
classnames "2.x"
|
||||||
|
moment "2.x"
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
raf "^3.4.1"
|
||||||
|
rc-trigger "^2.2.0"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
rc-trigger@^2.2.0:
|
rc-trigger@^2.2.0:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.6.2.tgz#a9c09ba5fad63af3b2ec46349c7db6cb46657001"
|
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.6.2.tgz#a9c09ba5fad63af3b2ec46349c7db6cb46657001"
|
||||||
|
Loading…
Reference in New Issue
Block a user