From 196f8503a8e5e7724ebaed5dc098e78267048a8b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 11 Sep 2019 09:00:14 +0200 Subject: [PATCH] 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 --- .../grafana-data/src/utils/moment_wrapper.ts | 8 ++- packages/grafana-ui/package.json | 1 + .../TimePicker/TimeOfDayPicker.story.tsx | 53 +++++++++++++++++++ .../components/TimePicker/TimeOfDayPicker.tsx | 26 +++++++++ .../TimePicker/TimePicker.story.tsx | 4 +- .../TimePicker/_TimeOfDayPicker.scss | 39 ++++++++++++++ packages/grafana-ui/src/components/index.scss | 1 + packages/grafana-ui/src/components/index.ts | 1 + .../utils/storybook/withRightAlignedStory.tsx | 2 +- .../components/Select/DashboardPicker.tsx | 7 ++- .../sass/_variables.dark.generated.scss.rej | 6 +++ .../sass/_variables.light.generated.scss.rej | 6 +++ yarn.lock | 16 +++++- 13 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.story.tsx create mode 100644 packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx create mode 100644 packages/grafana-ui/src/components/TimePicker/_TimeOfDayPicker.scss create mode 100644 public/sass/_variables.dark.generated.scss.rej create mode 100644 public/sass/_variables.light.generated.scss.rej diff --git a/packages/grafana-data/src/utils/moment_wrapper.ts b/packages/grafana-data/src/utils/moment_wrapper.ts index 814db6fe480..32e58f80ca5 100644 --- a/packages/grafana-data/src/utils/moment_wrapper.ts +++ b/packages/grafana-data/src/utils/moment_wrapper.ts @@ -1,7 +1,6 @@ import { TimeZone } from '../types/time'; /* tslint:disable:import-blacklist ban ban-types */ -import moment, { MomentInput, DurationInputArg1 } from 'moment'; - +import moment, { Moment, MomentInput, DurationInputArg1 } from 'moment'; export interface DateTimeBuiltinFormat { __momentBuiltinFormatBrand: any; } @@ -72,6 +71,7 @@ export interface DateTime extends Object { utc: () => DateTime; utcOffset: () => number; hour?: () => number; + minute?: () => number; } 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; }; +export const dateTimeAsMoment = (input?: DateTimeInput) => { + return dateTime(input) as Moment; +}; + export const dateTimeForTimeZone = ( timezone?: TimeZone, input?: DateTimeInput, diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 826b9e0aca2..63de94851a0 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -34,6 +34,7 @@ "lodash": "4.17.14", "moment": "2.24.0", "papaparse": "4.6.3", + "rc-time-picker": "^3.7.2", "react": "16.8.6", "react-calendar": "2.18.1", "react-color": "2.17.0", diff --git a/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.story.tsx new file mode 100644 index 00000000000..8697782e528 --- /dev/null +++ b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.story.tsx @@ -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 ( + + {(value, updateValue) => { + return ( + { + action('on selected')(newValue); + updateValue({ value: newValue }); + }} + value={value.value} + /> + ); + }} + + ); +}); + +TimeOfDayPickerStories.add('only minutes', () => { + return ( + + {(value, updateValue) => { + return ( + { + action('on selected')(newValue); + updateValue({ value: newValue }); + }} + value={value.value} + showHour={false} + /> + ); + }} + + ); +}); diff --git a/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx new file mode 100644 index 00000000000..fdd8130d97b --- /dev/null +++ b/packages/grafana-ui/src/components/TimePicker/TimeOfDayPicker.tsx @@ -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 = ({ minuteStep = 1, showHour = true, onChange, value }) => { + return ( +
+ onChange(dateTime(value))} + allowEmpty={false} + showSecond={false} + value={dateTimeAsMoment(value)} + showHour={showHour} + minuteStep={minuteStep} + /> +
+ ); +}; diff --git a/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx index 4c2123b9c4d..59f6c97a857 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx @@ -4,12 +4,12 @@ import { action } from '@storybook/addon-actions'; import { TimePicker } from './TimePicker'; import { UseState } from '../../utils/storybook/UseState'; -import { withRighAlignedStory } from '../../utils/storybook/withRightAlignedStory'; +import { withRightAlignedStory } from '../../utils/storybook/withRightAlignedStory'; import { TimeFragment, dateTime } from '@grafana/data'; const TimePickerStories = storiesOf('UI/TimePicker', module); -TimePickerStories.addDecorator(withRighAlignedStory); +TimePickerStories.addDecorator(withRightAlignedStory); TimePickerStories.add('default', () => { return ( diff --git a/packages/grafana-ui/src/components/TimePicker/_TimeOfDayPicker.scss b/packages/grafana-ui/src/components/TimePicker/_TimeOfDayPicker.scss new file mode 100644 index 00000000000..6dbefe4a531 --- /dev/null +++ b/packages/grafana-ui/src/components/TimePicker/_TimeOfDayPicker.scss @@ -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; +} diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss index f9af38c877e..0a9adb3650e 100644 --- a/packages/grafana-ui/src/components/index.scss +++ b/packages/grafana-ui/src/components/index.scss @@ -14,3 +14,4 @@ @import 'BarGauge/BarGauge'; @import 'RefreshPicker/RefreshPicker'; @import 'TimePicker/TimePicker'; +@import 'TimePicker/TimeOfDayPicker'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index b730bd2fbe6..c47bb3a10d6 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -35,6 +35,7 @@ export { StatsPicker } from './StatsPicker/StatsPicker'; export { Input, InputStatus } from './Input/Input'; export { RefreshPicker } from './RefreshPicker/RefreshPicker'; export { TimePicker } from './TimePicker/TimePicker'; +export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker'; export { List } from './List/List'; // Renderless diff --git a/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx b/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx index 85c024d0249..3fca1a7a718 100644 --- a/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx +++ b/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx @@ -17,4 +17,4 @@ const RightAlignedStory: React.FunctionComponent<{}> = ({ children }) => { ); }; -export const withRighAlignedStory = (story: RenderFunction) => {story()}; +export const withRightAlignedStory = (story: RenderFunction) => {story()}; diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index ffb4eece11c..924f4141751 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import { AsyncSelect } from '@grafana/ui'; +import { SelectableValue } from '@grafana/data'; import { debounce } from 'lodash'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { DashboardSearchHit, DashboardDTO } from 'app/types'; @@ -7,6 +8,7 @@ import { DashboardSearchHit, DashboardDTO } from 'app/types'; export interface Props { className?: string; onSelected: (dashboard: DashboardDTO) => void; + currentDashboardId: SelectableValue; } export interface State { @@ -36,7 +38,7 @@ export class DashboardPicker extends PureComponent { .then((result: DashboardSearchHit[]) => { const dashboards = result.map((item: DashboardSearchHit) => { return { - id: item.uid, + id: item.id, value: item.id, label: `${item.folderTitle ? item.folderTitle : 'General'}/${item.title}`, }; @@ -48,7 +50,7 @@ export class DashboardPicker extends PureComponent { }; render() { - const { className, onSelected } = this.props; + const { className, onSelected, currentDashboardId } = this.props; const { isLoading } = this.state; return ( @@ -63,6 +65,7 @@ export class DashboardPicker extends PureComponent { onChange={onSelected} placeholder="Select dashboard" noOptionsMessage={() => 'No dashboards found'} + value={currentDashboardId} /> diff --git a/public/sass/_variables.dark.generated.scss.rej b/public/sass/_variables.dark.generated.scss.rej new file mode 100644 index 00000000000..100525b5ed6 --- /dev/null +++ b/public/sass/_variables.dark.generated.scss.rej @@ -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%); diff --git a/public/sass/_variables.light.generated.scss.rej b/public/sass/_variables.light.generated.scss.rej new file mode 100644 index 00000000000..e16225be503 --- /dev/null +++ b/public/sass/_variables.light.generated.scss.rej @@ -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%); diff --git a/yarn.lock b/yarn.lock index 17726f040e4..1a4fa4be653 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12068,7 +12068,7 @@ module-alias@2.2.0: version "2.2.0" 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" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -14629,7 +14629,7 @@ qw@~1.0.1: version "1.0.1" 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" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" dependencies: @@ -14722,6 +14722,18 @@ rc-cascader@0.14.0: shallow-equal "^1.0.0" 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: version "2.6.2" resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.6.2.tgz#a9c09ba5fad63af3b2ec46349c7db6cb46657001"