Merge pull request #11990 from influxdata/feat/time-range-calendar
feat(ui): Add custom time range option in time range dropdownpull/12001/head
commit
d895f5aeb7
|
@ -2,6 +2,7 @@
|
|||
|
||||
### Features
|
||||
1. [11954](https://github.com/influxdata/influxdb/pull/11954): Add the ability to run a task manually from tasks page
|
||||
1. [11990](https://github.com/influxdata/influxdb/pull/11990): Add the ability to select a custom time range in explorer and dashboard
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -777,7 +777,6 @@
|
|||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz",
|
||||
"integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.12.0"
|
||||
},
|
||||
|
@ -785,8 +784,7 @@
|
|||
"regenerator-runtime": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
|
||||
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3696,6 +3694,15 @@
|
|||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"create-react-context": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz",
|
||||
"integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==",
|
||||
"requires": {
|
||||
"fbjs": "^0.8.0",
|
||||
"gud": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
|
@ -6589,6 +6596,11 @@
|
|||
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
|
||||
"dev": true
|
||||
},
|
||||
"gud": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
|
||||
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz",
|
||||
|
@ -11336,6 +11348,11 @@
|
|||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||
"dev": true
|
||||
},
|
||||
"popper.js": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz",
|
||||
"integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ=="
|
||||
},
|
||||
"posix-character-classes": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
||||
|
@ -12488,6 +12505,25 @@
|
|||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-datepicker": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.1.0.tgz",
|
||||
"integrity": "sha512-zsPqierShVc0NN+JCyJO18jMFDTbGNSgmekQm+Zr5JYH/aZShsjOBGQmjNiQmIw7nJNQDRzh1oQUND3TY/9Swg==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.5",
|
||||
"date-fns": "^2.0.0-alpha.23",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-onclickoutside": "^6.7.1",
|
||||
"react-popper": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": {
|
||||
"version": "2.0.0-alpha.27",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.27.tgz",
|
||||
"integrity": "sha512-cqfVLS+346P/Mpj2RpDrBv0P4p2zZhWWvfY5fuWrXNR/K38HaAGEkeOwb47hIpQP9Jr/TIxjZ2/sNMQwdXuGMg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dimensions": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
|
||||
|
@ -12574,6 +12610,34 @@
|
|||
"xtend": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-onclickoutside": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz",
|
||||
"integrity": "sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg=="
|
||||
},
|
||||
"react-popper": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.3.tgz",
|
||||
"integrity": "sha512-ynMZBPkXONPc5K4P5yFWgZx5JGAUIP3pGGLNs58cfAPgK67olx7fmLp+AdpZ0+GoQ+ieFDa/z4cdV6u7sioH6w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"create-react-context": "<=0.2.2",
|
||||
"popper.js": "^1.14.4",
|
||||
"prop-types": "^15.6.1",
|
||||
"typed-styles": "^0.0.7",
|
||||
"warning": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "http://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz",
|
||||
|
@ -14630,204 +14694,6 @@
|
|||
"loader-utils": "^1.0.2",
|
||||
"micromatch": "^3.1.4",
|
||||
"semver": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-property": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
|
||||
"integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^1.0.2",
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^2.3.3",
|
||||
"define-property": "^0.2.5",
|
||||
"extend-shallow": "^2.0.1",
|
||||
"posix-character-classes": "^0.1.0",
|
||||
"regex-not": "^1.0.0",
|
||||
"snapdragon": "^0.8.1",
|
||||
"to-regex": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-property": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"is-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
|
||||
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-accessor-descriptor": "^0.1.6",
|
||||
"is-data-descriptor": "^0.1.4",
|
||||
"kind-of": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
|
||||
"dev": true
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
|
||||
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
|
||||
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assign-symbols": "^1.0.0",
|
||||
"is-extendable": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"extglob": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
||||
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-unique": "^0.3.2",
|
||||
"define-property": "^1.0.0",
|
||||
"expand-brackets": "^2.1.4",
|
||||
"extend-shallow": "^2.0.1",
|
||||
"fragment-cache": "^0.2.1",
|
||||
"regex-not": "^1.0.0",
|
||||
"snapdragon": "^0.8.1",
|
||||
"to-regex": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-accessor-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-data-descriptor": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
||||
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-object": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
"braces": "^2.3.1",
|
||||
"define-property": "^2.0.2",
|
||||
"extend-shallow": "^3.0.2",
|
||||
"extglob": "^2.0.4",
|
||||
"fragment-cache": "^0.2.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"nanomatch": "^1.2.9",
|
||||
"object.pick": "^1.3.0",
|
||||
"regex-not": "^1.0.0",
|
||||
"snapdragon": "^0.8.1",
|
||||
"to-regex": "^3.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
|
@ -14931,6 +14797,11 @@
|
|||
"mime-types": "~2.1.18"
|
||||
}
|
||||
},
|
||||
"typed-styles": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
|
||||
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
"react": "^16.8.0",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-datepicker": "^2.1.0",
|
||||
"react-dimensions": "^1.2.0",
|
||||
"react-dnd": "^2.6.0",
|
||||
"react-dnd-html5-backend": "^2.6.0",
|
||||
|
|
|
@ -10,11 +10,11 @@ interface Props {
|
|||
@ErrorHandling
|
||||
export class ClickOutside extends PureComponent<Props> {
|
||||
public componentDidMount() {
|
||||
document.addEventListener('click', this.handleClickOutside, true)
|
||||
document.addEventListener('mousedown', this.handleClickOutside, true)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleClickOutside, true)
|
||||
document.removeEventListener('mousedown', this.handleClickOutside, true)
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import React, {PureComponent, createRef} from 'react'
|
||||
import {get} from 'lodash'
|
||||
import moment from 'moment'
|
||||
|
||||
// Components
|
||||
import {Dropdown} from 'src/clockface'
|
||||
import DateRangePicker from 'src/shared/components/dateRangePicker/DateRangePicker'
|
||||
|
||||
// Constants
|
||||
import {TIME_RANGES} from 'src/shared/constants/timeRanges'
|
||||
import {
|
||||
TIME_RANGES,
|
||||
CUSTOM_TIME_RANGE,
|
||||
TIME_RANGE_FORMAT,
|
||||
} from 'src/shared/constants/timeRanges'
|
||||
|
||||
// Types
|
||||
import {TimeRange} from 'src/types'
|
||||
|
@ -15,34 +22,134 @@ interface Props {
|
|||
onSetTimeRange: (timeRange: TimeRange) => void
|
||||
}
|
||||
|
||||
class TimeRangeDropdown extends PureComponent<Props> {
|
||||
interface State {
|
||||
isDatePickerOpen: boolean
|
||||
dropdownPosition: {top: number; right: number}
|
||||
}
|
||||
|
||||
class TimeRangeDropdown extends PureComponent<Props, State> {
|
||||
private dropdownRef = createRef<HTMLDivElement>()
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {isDatePickerOpen: false, dropdownPosition: undefined}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const timeRange = this.timeRange
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.isDatePickerVisible && (
|
||||
<DateRangePicker
|
||||
timeRange={timeRange}
|
||||
onSetTimeRange={this.handleApplyTimeRange}
|
||||
onClose={this.handleHideDatePicker}
|
||||
position={this.state.dropdownPosition}
|
||||
/>
|
||||
)}
|
||||
<div ref={this.dropdownRef}>
|
||||
<Dropdown
|
||||
selectedID={timeRange.label}
|
||||
onChange={this.handleChange}
|
||||
widthPixels={this.dropdownWidth}
|
||||
titleText={this.formattedCustomTimeRange}
|
||||
>
|
||||
{TIME_RANGES.map(({label}) => (
|
||||
<Dropdown.Item key={label} value={label} id={label}>
|
||||
{label}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private get dropdownWidth(): number {
|
||||
if (this.isCustomTimeRange) {
|
||||
return 250
|
||||
}
|
||||
|
||||
return 100
|
||||
}
|
||||
|
||||
private get isCustomTimeRange(): boolean {
|
||||
const {timeRange} = this.props
|
||||
return (
|
||||
get(timeRange, 'label', '') === CUSTOM_TIME_RANGE || !!timeRange.upper
|
||||
)
|
||||
}
|
||||
|
||||
private get formattedCustomTimeRange(): string {
|
||||
const {timeRange} = this.props
|
||||
if (!this.isCustomTimeRange) {
|
||||
return timeRange.label
|
||||
}
|
||||
|
||||
return `${moment(timeRange.lower).format(TIME_RANGE_FORMAT)} - ${moment(
|
||||
timeRange.upper
|
||||
).format(TIME_RANGE_FORMAT)}`
|
||||
}
|
||||
|
||||
private get timeRange(): TimeRange {
|
||||
const {timeRange} = this.props
|
||||
const {isDatePickerOpen} = this.state
|
||||
|
||||
if (isDatePickerOpen) {
|
||||
const date = new Date().toISOString()
|
||||
const upper =
|
||||
timeRange.upper && this.isCustomTimeRange ? timeRange.upper : date
|
||||
const lower =
|
||||
timeRange.lower && this.isCustomTimeRange ? timeRange.lower : date
|
||||
return {
|
||||
label: CUSTOM_TIME_RANGE,
|
||||
lower,
|
||||
upper,
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isCustomTimeRange) {
|
||||
return {
|
||||
...timeRange,
|
||||
label: this.formattedCustomTimeRange,
|
||||
}
|
||||
}
|
||||
|
||||
const selectedTimeRange = TIME_RANGES.find(t => t.lower === timeRange.lower)
|
||||
|
||||
if (!selectedTimeRange) {
|
||||
throw new Error('TimeRangeDropdown passed unknown TimeRange')
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
selectedID={selectedTimeRange.label}
|
||||
onChange={this.handleChange}
|
||||
widthPixels={100}
|
||||
>
|
||||
{TIME_RANGES.map(({label}) => (
|
||||
<Dropdown.Item key={label} value={label} id={label}>
|
||||
{label}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
)
|
||||
return selectedTimeRange
|
||||
}
|
||||
|
||||
private get isDatePickerVisible() {
|
||||
return this.state.isDatePickerOpen
|
||||
}
|
||||
|
||||
private handleApplyTimeRange = (timeRange: TimeRange) => {
|
||||
this.props.onSetTimeRange(timeRange)
|
||||
this.handleHideDatePicker()
|
||||
}
|
||||
|
||||
private handleHideDatePicker = () => {
|
||||
this.setState({isDatePickerOpen: false, dropdownPosition: null})
|
||||
}
|
||||
|
||||
private handleChange = (label: string): void => {
|
||||
const {onSetTimeRange} = this.props
|
||||
const timeRange = TIME_RANGES.find(t => t.label === label)
|
||||
|
||||
if (label === CUSTOM_TIME_RANGE) {
|
||||
const {top, left} = this.dropdownRef.current.getBoundingClientRect()
|
||||
const right = window.innerWidth - left
|
||||
this.setState({isDatePickerOpen: true, dropdownPosition: {top, right}})
|
||||
return
|
||||
}
|
||||
|
||||
onSetTimeRange(timeRange)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import ReactDatePicker from 'react-datepicker'
|
||||
|
||||
// Styles
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
import {Input} from 'src/clockface'
|
||||
import {ComponentSize} from '@influxdata/clockface'
|
||||
import FormLabel from 'src/clockface/components/form_layout/FormLabel'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
dateTime: string
|
||||
onSelectDate: (date: string) => void
|
||||
}
|
||||
|
||||
class DatePicker extends PureComponent<Props> {
|
||||
private inCurrentMonth: boolean = false
|
||||
|
||||
public render() {
|
||||
const {dateTime, label} = this.props
|
||||
const date = new Date(dateTime)
|
||||
|
||||
return (
|
||||
<FormLabel label={label}>
|
||||
<div className="range-picker--date-picker">
|
||||
<ReactDatePicker
|
||||
selected={date}
|
||||
onChange={this.handleSelectDate}
|
||||
startOpen={true}
|
||||
dateFormat="yyyy-MM-dd HH:mm"
|
||||
showTimeSelect={true}
|
||||
timeFormat="HH:mm"
|
||||
shouldCloseOnSelect={false}
|
||||
disabledKeyboardNavigation={true}
|
||||
customInput={this.customInput}
|
||||
popperContainer={this.popperContainer}
|
||||
popperClassName="range-picker--popper"
|
||||
calendarClassName="range-picker--calendar"
|
||||
dayClassName={this.dayClassName}
|
||||
timeIntervals={60}
|
||||
fixedHeight={true}
|
||||
/>
|
||||
</div>
|
||||
</FormLabel>
|
||||
)
|
||||
}
|
||||
|
||||
private get customInput() {
|
||||
return (
|
||||
<Input
|
||||
widthPixels={314}
|
||||
size={ComponentSize.Medium}
|
||||
customClass="range-picker--input react-datepicker-ignore-onclickoutside"
|
||||
titleText="Start"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private dayClassName = (date: Date) => {
|
||||
const day = date.getDate()
|
||||
|
||||
if (day === 1) {
|
||||
this.inCurrentMonth = !this.inCurrentMonth
|
||||
}
|
||||
|
||||
if (this.inCurrentMonth) {
|
||||
return 'range-picker--day-in-month'
|
||||
}
|
||||
|
||||
return 'range-picker--day'
|
||||
}
|
||||
|
||||
private popperContainer({children}): JSX.Element {
|
||||
return <div className="range-picker--popper-container">{children}</div>
|
||||
}
|
||||
|
||||
private handleSelectDate = (date: Date): void => {
|
||||
const {onSelectDate} = this.props
|
||||
|
||||
onSelectDate(date.toISOString())
|
||||
}
|
||||
}
|
||||
|
||||
export default DatePicker
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
Date Range Picker Styles
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@import 'src/style/modules';
|
||||
|
||||
.range-picker {
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
background-color: $g1-raven;
|
||||
border: $ix-border solid $c-pool;
|
||||
padding: $ix-marg-b;
|
||||
border-radius: $ix-radius;
|
||||
z-index: 9999;
|
||||
height: 410px;
|
||||
|
||||
.react-datepicker {
|
||||
font-family: $ix-text-font;
|
||||
font-size: $ix-text-base-1;
|
||||
}
|
||||
|
||||
.range-picker--date-pickers {
|
||||
flex-wrap: nowrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: $ix-marg-b 0;
|
||||
|
||||
.range-picker--date-picker {
|
||||
.range-picker--popper-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.range-picker--popper {
|
||||
position: relative !important;
|
||||
transform: none !important;
|
||||
@include no-user-select();
|
||||
|
||||
.range-picker--calendar {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: $c-pool;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
.react-datepicker__navigation {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation--next {
|
||||
border-left-color: $g18-cloud;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation--previous {
|
||||
border-right-color: $g18-cloud;
|
||||
}
|
||||
|
||||
.range-picker--day {
|
||||
color: $c-void;
|
||||
font-weight: 400;
|
||||
|
||||
&:hover {
|
||||
background-color: $c-laser;
|
||||
color: $g20-white;
|
||||
}
|
||||
}
|
||||
|
||||
.range-picker--day-in-month {
|
||||
color: $c-star;
|
||||
|
||||
&:hover {
|
||||
background-color: $c-laser;
|
||||
color: $g20-white;
|
||||
}
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected {
|
||||
background-color: $c-pool;
|
||||
color: $g18-cloud;
|
||||
}
|
||||
|
||||
.react-datepicker__triangle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
.react-datepicker__day-name {
|
||||
color: $c-rainforest;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month {
|
||||
width: 100%;
|
||||
border-radius: $ix-radius $ix-radius 0 0;
|
||||
background-color: $g4-onyx;
|
||||
color: $g18-cloud;
|
||||
font-weight: 700;
|
||||
height: $ix-marg-d;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.react-datepicker__time-container {
|
||||
width: 70px;
|
||||
border: none;
|
||||
margin-left: $ix-marg-a;
|
||||
border-radius: $ix-radius;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
.react-datepicker__header--time {
|
||||
width: 100%;
|
||||
border-radius: $ix-radius $ix-radius 0 0;
|
||||
background-color: $g4-onyx;
|
||||
font-weight: 700;
|
||||
height: $ix-marg-d;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.react-datepicker-time__header {
|
||||
color: $g18-cloud;
|
||||
}
|
||||
|
||||
.react-datepicker__time {
|
||||
background-color: transparent;
|
||||
|
||||
.react-datepicker__time-box {
|
||||
width: 100%;
|
||||
background-color: $g2-kevlar;
|
||||
color: $g18-cloud;
|
||||
|
||||
.react-datepicker__time-list {
|
||||
font-size: $ix-text-base;
|
||||
|
||||
.react-datepicker__time-list-item:hover {
|
||||
background-color: $c-laser;
|
||||
color: $g20-white;
|
||||
}
|
||||
|
||||
.react-datepicker__time-list-item--selected {
|
||||
background-color: $c-pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.range-picker--dismiss {
|
||||
position: absolute;
|
||||
z-index: 5000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(50%,-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
outline: none;
|
||||
border-radius: 50%;
|
||||
background-color: $c-pool;
|
||||
transition: background-color 0.25s ease;
|
||||
border: 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 13px;
|
||||
height: 3px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-radius: 1px;
|
||||
background-color: $g20-white;
|
||||
}
|
||||
|
||||
&:before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
&:after {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $c-laser;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, createRef, CSSProperties} from 'react'
|
||||
|
||||
// Components
|
||||
import DatePicker from 'src/shared/components/dateRangePicker/DatePicker'
|
||||
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||
|
||||
// Styles
|
||||
import 'src/shared/components/dateRangePicker/DateRangePicker.scss'
|
||||
|
||||
// Types
|
||||
import {TimeRange} from 'src/types'
|
||||
import {Button, ComponentColor, ComponentSize} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
timeRange: TimeRange
|
||||
onSetTimeRange: (timeRange: TimeRange) => void
|
||||
position?: {top: number; right: number}
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
lower: string
|
||||
upper: string
|
||||
bottomPosition?: number
|
||||
topPosition?: number
|
||||
}
|
||||
|
||||
const PICKER_HEIGHT = 410
|
||||
const HORIZONTAL_PADDING = 2
|
||||
const VERTICAL_PADDING = 15
|
||||
|
||||
class DateRangePicker extends PureComponent<Props, State> {
|
||||
private rangePickerRef = createRef<HTMLDivElement>()
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
const {
|
||||
timeRange: {lower, upper},
|
||||
} = props
|
||||
|
||||
this.state = {lower, upper, bottomPosition: null}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {
|
||||
bottom,
|
||||
top,
|
||||
height,
|
||||
} = this.rangePickerRef.current.getBoundingClientRect()
|
||||
|
||||
if (bottom > window.innerHeight) {
|
||||
this.setState({bottomPosition: height / 2})
|
||||
} else if (top < 0) {
|
||||
this.setState({topPosition: height / 2})
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {onClose} = this.props
|
||||
const {upper, lower} = this.state
|
||||
|
||||
return (
|
||||
<ClickOutside onClickOutside={onClose}>
|
||||
<div
|
||||
className="range-picker react-datepicker-ignore-onclickoutside"
|
||||
ref={this.rangePickerRef}
|
||||
style={this.stylePosition}
|
||||
>
|
||||
<button className="range-picker--dismiss" onClick={onClose} />
|
||||
<div className="range-picker--date-pickers">
|
||||
<DatePicker
|
||||
dateTime={lower}
|
||||
onSelectDate={this.handleSelectLower}
|
||||
label="Start"
|
||||
/>
|
||||
<DatePicker
|
||||
dateTime={upper}
|
||||
onSelectDate={this.handleSelectUpper}
|
||||
label="Stop"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
color={ComponentColor.Primary}
|
||||
size={ComponentSize.Small}
|
||||
onClick={this.handleSetTimeRange}
|
||||
text="Apply Time Range"
|
||||
/>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)
|
||||
}
|
||||
|
||||
private get stylePosition(): CSSProperties {
|
||||
const {position} = this.props
|
||||
const {bottomPosition, topPosition} = this.state
|
||||
|
||||
if (!position) {
|
||||
return
|
||||
}
|
||||
|
||||
const {top, right} = position
|
||||
|
||||
if (topPosition) {
|
||||
return {
|
||||
top: '14px',
|
||||
right: `${right + HORIZONTAL_PADDING}px`,
|
||||
}
|
||||
}
|
||||
|
||||
const bottomPx =
|
||||
(bottomPosition || window.innerHeight - top - VERTICAL_PADDING) -
|
||||
PICKER_HEIGHT / 2
|
||||
return {
|
||||
bottom: `${bottomPx}px`,
|
||||
right: `${right + HORIZONTAL_PADDING}px`,
|
||||
}
|
||||
}
|
||||
|
||||
private handleSetTimeRange = (): void => {
|
||||
const {onSetTimeRange, timeRange} = this.props
|
||||
const {upper, lower} = this.state
|
||||
|
||||
onSetTimeRange({...timeRange, lower, upper})
|
||||
}
|
||||
|
||||
private handleSelectLower = (lower: string): void => {
|
||||
this.setState({lower})
|
||||
}
|
||||
|
||||
private handleSelectUpper = (upper: string): void => {
|
||||
this.setState({upper})
|
||||
}
|
||||
}
|
||||
|
||||
export default DateRangePicker
|
|
@ -410,6 +410,7 @@ export const LAYOUT_MARGIN = 4
|
|||
export const DASHBOARD_LAYOUT_ROW_HEIGHT = 83.5
|
||||
|
||||
export const TIME_RANGE_START = 'timeRangeStart'
|
||||
export const TIME_RANGE_STOP = 'timeRangeStop'
|
||||
export const WINDOW_PERIOD = 'windowPeriod'
|
||||
|
||||
export const DYGRAPH_CONTAINER_H_MARGIN = 16
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import {TimeRange} from 'src/types'
|
||||
|
||||
export const CUSTOM_TIME_RANGE = 'Custom Time Range'
|
||||
export const TIME_RANGE_FORMAT = 'YYYY-MM-DD HH:mm'
|
||||
|
||||
export const TIME_RANGES: TimeRange[] = [
|
||||
{
|
||||
lower: '',
|
||||
label: CUSTOM_TIME_RANGE,
|
||||
},
|
||||
{
|
||||
seconds: 300,
|
||||
lower: 'now() - 5m',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {TimeRange} from 'src/types/v2'
|
||||
import {TIME_RANGE_START} from 'src/shared/constants'
|
||||
import {TIME_RANGE_START, TIME_RANGE_STOP} from 'src/shared/constants'
|
||||
|
||||
export const timeRangeVariables = (
|
||||
timeRange: TimeRange
|
||||
|
@ -10,5 +10,11 @@ export const timeRangeVariables = (
|
|||
.replace('now()', '')
|
||||
.replace(/\s/g, '')
|
||||
|
||||
if (timeRange.upper) {
|
||||
result[TIME_RANGE_STOP] = timeRange.upper
|
||||
} else {
|
||||
result[TIME_RANGE_STOP] = 'now()'
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -121,45 +121,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--tooltip-dismiss {
|
||||
position: absolute;
|
||||
z-index: 5000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(0,-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
outline: none;
|
||||
border-radius: 50%;
|
||||
background-color: $c-pool;
|
||||
transition: background-color 0.25s ease;
|
||||
border: 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 13px;
|
||||
height: 3px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-radius: 1px;
|
||||
background-color: $g20-white;
|
||||
}
|
||||
|
||||
&:before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
&:after {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $c-laser;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--tooltip-dismiss {
|
||||
position: absolute;
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('buildQuery', () => {
|
|||
}
|
||||
|
||||
const expected = `from(bucket: "b0")
|
||||
|> range(start: timeRangeStart)
|
||||
|> range(start: timeRangeStart, stop: timeRangeStop)
|
||||
|> filter(fn: (r) => r._measurement == "m0")`
|
||||
|
||||
const actual = buildQuery(config)
|
||||
|
@ -30,7 +30,7 @@ describe('buildQuery', () => {
|
|||
}
|
||||
|
||||
const expected = `from(bucket: "b0")
|
||||
|> range(start: timeRangeStart)
|
||||
|> range(start: timeRangeStart, stop: timeRangeStop)
|
||||
|> filter(fn: (r) => r._measurement == "m0" or r._measurement == "m1")
|
||||
|> filter(fn: (r) => r._field == "f0" or r._field == "f1")`
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe('buildQuery', () => {
|
|||
}
|
||||
|
||||
const expected = `from(bucket: "b0")
|
||||
|> range(start: timeRangeStart)
|
||||
|> range(start: timeRangeStart, stop: timeRangeStop)
|
||||
|> filter(fn: (r) => r._measurement == "m0")
|
||||
|> window(period: windowPeriod)
|
||||
|> mean()
|
||||
|
@ -55,7 +55,7 @@ describe('buildQuery', () => {
|
|||
|> yield(name: "mean")
|
||||
|
||||
from(bucket: "b0")
|
||||
|> range(start: timeRangeStart)
|
||||
|> range(start: timeRangeStart, stop: timeRangeStop)
|
||||
|> filter(fn: (r) => r._measurement == "m0")
|
||||
|> window(period: windowPeriod)
|
||||
|> toFloat()
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import {BuilderConfig} from 'src/types/v2'
|
||||
import {FUNCTIONS} from 'src/timeMachine/constants/queryBuilder'
|
||||
import {TIME_RANGE_START, WINDOW_PERIOD} from 'src/shared/constants'
|
||||
import {
|
||||
TIME_RANGE_START,
|
||||
WINDOW_PERIOD,
|
||||
TIME_RANGE_STOP,
|
||||
} from 'src/shared/constants'
|
||||
|
||||
export function isConfigValid(builderConfig: BuilderConfig): boolean {
|
||||
const {buckets, tags} = builderConfig
|
||||
|
@ -35,7 +39,7 @@ function buildQueryHelper(
|
|||
const fnCall = fn ? formatFunctionCall(fn) : ''
|
||||
|
||||
const query = `from(bucket: "${bucket}")
|
||||
|> range(start: ${TIME_RANGE_START})${tagFilterCall}${fnCall}`
|
||||
|> range(start: ${TIME_RANGE_START}, stop: ${TIME_RANGE_STOP})${tagFilterCall}${fnCall}`
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue