From 2e7ea1ac0c6f5d0e8b1b558869221aa30523c123 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 14 May 2018 16:04:27 -0700 Subject: [PATCH 01/14] Convert QueryTabList to TypeScript --- .../components/QueryMakerTab.tsx | 7 +-- ui/src/shared/components/QueryTabList.js | 51 ----------------- ui/src/shared/components/QueryTabList.tsx | 55 +++++++++++++++++++ 3 files changed, 57 insertions(+), 56 deletions(-) delete mode 100644 ui/src/shared/components/QueryTabList.js create mode 100644 ui/src/shared/components/QueryTabList.tsx diff --git a/ui/src/data_explorer/components/QueryMakerTab.tsx b/ui/src/data_explorer/components/QueryMakerTab.tsx index d5d913de29..539af199cb 100644 --- a/ui/src/data_explorer/components/QueryMakerTab.tsx +++ b/ui/src/data_explorer/components/QueryMakerTab.tsx @@ -1,14 +1,11 @@ import React, {PureComponent} from 'react' import classnames from 'classnames' import {ErrorHandling} from 'src/shared/decorators/errors' - -interface Query { - rawText: string -} +import {QueryConfig} from 'src/types/query' interface Props { isActive: boolean - query: Query + query: QueryConfig onSelect: (index: number) => void onDelete: (index: number) => void queryTabText: string diff --git a/ui/src/shared/components/QueryTabList.js b/ui/src/shared/components/QueryTabList.js deleted file mode 100644 index 32ad35d201..0000000000 --- a/ui/src/shared/components/QueryTabList.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import QueryMakerTab from 'src/data_explorer/components/QueryMakerTab' -import buildInfluxQLQuery from 'utils/influxql' - -const QueryTabList = ({ - queries, - timeRange, - onAddQuery, - onDeleteQuery, - activeQueryIndex, - setActiveQueryIndex, -}) => ( -
- {queries.map((q, i) => ( - - ))} -
- -
-
-) - -const {arrayOf, func, number, shape, string} = PropTypes - -QueryTabList.propTypes = { - queries: arrayOf(shape({})).isRequired, - timeRange: shape({ - upper: string, - lower: string, - }).isRequired, - onAddQuery: func.isRequired, - onDeleteQuery: func.isRequired, - activeQueryIndex: number.isRequired, - setActiveQueryIndex: func.isRequired, -} - -export default QueryTabList diff --git a/ui/src/shared/components/QueryTabList.tsx b/ui/src/shared/components/QueryTabList.tsx new file mode 100644 index 0000000000..1e3ee9cae8 --- /dev/null +++ b/ui/src/shared/components/QueryTabList.tsx @@ -0,0 +1,55 @@ +import React, {PureComponent} from 'react' +import QueryMakerTab from 'src/data_explorer/components/QueryMakerTab' +import buildInfluxQLQuery from 'src/utils/influxql' +import {QueryConfig, TimeRange} from 'src/types/query' + +interface Props { + queries: QueryConfig[] + onAddQuery: () => void + onDeleteQuery: (index: number) => void + activeQueryIndex: number + setActiveQueryIndex: (index: number) => void + timeRange: TimeRange +} + +export default class QueryTabList extends PureComponent { + public render() { + const { + queries, + onAddQuery, + onDeleteQuery, + activeQueryIndex, + setActiveQueryIndex, + } = this.props + + return ( +
+ {queries.map((q, i) => ( + + ))} +
+ +
+
+ ) + } + + private queryTabText = (i: number, query: QueryConfig): string => { + const {timeRange} = this.props + + return ( + query.rawText || buildInfluxQLQuery(timeRange, query) || `Query ${i + 1}` + ) + } +} From 47f7d4b3121cd8b5a37ef4b65c04af0a254eb1a9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 14 May 2018 16:22:39 -0700 Subject: [PATCH 02/14] Consolidate Notification type definitions --- ui/src/kapacitor/components/AlertTabs.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ui/src/kapacitor/components/AlertTabs.tsx b/ui/src/kapacitor/components/AlertTabs.tsx index 734f58f091..4fbe08787c 100644 --- a/ui/src/kapacitor/components/AlertTabs.tsx +++ b/ui/src/kapacitor/components/AlertTabs.tsx @@ -43,6 +43,7 @@ import DeprecationWarning from 'src/admin/components/DeprecationWarning' import {ErrorHandling} from 'src/shared/decorators/errors' import {Source, Kapacitor} from 'src/types' +import {Notification} from 'src/types/notifications' interface Service { link: Link @@ -112,14 +113,6 @@ interface SupportedConfig { victorops: Config } -interface Notification { - id?: string - type: string - icon: string - duration: number - message: string -} - interface Props { source: Source kapacitor: Kapacitor From 7175d37b7e2664c2c508ea24377ecf4938f0f9a8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 14 May 2018 16:22:52 -0700 Subject: [PATCH 03/14] Convert Notifications components to TypeScript --- .../{Notification.js => Notification.tsx} | 96 ++++++++++--------- ui/src/shared/components/Notifications.js | 44 --------- ui/src/shared/components/Notifications.tsx | 47 +++++++++ 3 files changed, 96 insertions(+), 91 deletions(-) rename ui/src/shared/components/{Notification.js => Notification.tsx} (65%) delete mode 100644 ui/src/shared/components/Notifications.js create mode 100644 ui/src/shared/components/Notifications.tsx diff --git a/ui/src/shared/components/Notification.js b/ui/src/shared/components/Notification.tsx similarity index 65% rename from ui/src/shared/components/Notification.js rename to ui/src/shared/components/Notification.tsx index e185e6d53c..9dc3bb5872 100644 --- a/ui/src/shared/components/Notification.js +++ b/ui/src/shared/components/Notification.tsx @@ -1,17 +1,32 @@ import React, {Component} from 'react' -import PropTypes from 'prop-types' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' +import {Notification as NotificationType} from 'src/types/notifications' import classnames from 'classnames' -import {dismissNotification as dismissNotificationAction} from 'shared/actions/notifications' +import {dismissNotification as dismissNotificationAction} from 'src/shared/actions/notifications' -import {NOTIFICATION_TRANSITION} from 'shared/constants/index' +import {NOTIFICATION_TRANSITION} from 'src/shared/constants/index' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + notification: NotificationType + dismissNotification: (id: string) => void +} + +interface State { + opacity: number + height: number + dismissed: boolean +} + @ErrorHandling -class Notification extends Component { +class Notification extends Component { + private notificationRef: HTMLElement + private dismissalTimer: number + private deletionTimer: number + constructor(props) { super(props) @@ -22,7 +37,7 @@ class Notification extends Component { } } - componentDidMount() { + public componentDidMount() { const { notification: {duration}, } = this.props @@ -31,41 +46,16 @@ class Notification extends Component { if (duration >= 0) { // Automatically dismiss notification after duration prop - this.dismissTimer = setTimeout(this.handleDismiss, duration) + this.dismissalTimer = window.setTimeout(this.handleDismiss, duration) } } - updateHeight() { - if (this.notificationRef) { - const {height} = this.notificationRef.getBoundingClientRect() - this.setState({height}) - } + public componentWillUnmount() { + clearTimeout(this.dismissalTimer) + clearTimeout(this.deletionTimer) } - componentWillUnmount() { - clearTimeout(this.dismissTimer) - clearTimeout(this.deleteTimer) - } - - handleDismiss = () => { - const { - notification: {id}, - dismissNotification, - } = this.props - - this.setState({dismissed: true}) - this.deleteTimer = setTimeout( - () => dismissNotification(id), - NOTIFICATION_TRANSITION - ) - } - - onNotificationRef = ref => { - this.notificationRef = ref - this.updateHeight() - } - - render() { + public render() { const { notification: {type, message, icon}, } = this.props @@ -81,7 +71,7 @@ class Notification extends Component { return (
-
+
{message}
) } -} -const {func, number, shape, string} = PropTypes + private updateHeight = (): void => { + if (this.notificationRef) { + const {height} = this.notificationRef.getBoundingClientRect() + this.setState({height}) + } + } -Notification.propTypes = { - notification: shape({ - id: string.isRequired, - type: string.isRequired, - message: string.isRequired, - duration: number.isRequired, - icon: string.isRequired, - }).isRequired, - dismissNotification: func.isRequired, + private handleDismiss = (): void => { + const { + notification: {id}, + dismissNotification, + } = this.props + + this.setState({dismissed: true}) + this.deletionTimer = window.setTimeout( + () => dismissNotification(id), + NOTIFICATION_TRANSITION + ) + } + + private handleNotificationRef = (ref: HTMLElement): void => { + this.notificationRef = ref + this.updateHeight() + } } const mapDispatchToProps = dispatch => ({ diff --git a/ui/src/shared/components/Notifications.js b/ui/src/shared/components/Notifications.js deleted file mode 100644 index f08bec6245..0000000000 --- a/ui/src/shared/components/Notifications.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' - -import Notification from 'shared/components/Notification' - -const Notifications = ({notifications, inPresentationMode}) => ( -
- {notifications.map(n => )} -
-) - -const {arrayOf, bool, number, shape, string} = PropTypes - -Notifications.propTypes = { - notifications: arrayOf( - shape({ - id: string.isRequired, - type: string.isRequired, - message: string.isRequired, - duration: number.isRequired, - icon: string, - }) - ), - inPresentationMode: bool, -} - -const mapStateToProps = ({ - notifications, - app: { - ephemeral: {inPresentationMode}, - }, -}) => ({ - notifications, - inPresentationMode, -}) - -export default connect(mapStateToProps, null)(Notifications) diff --git a/ui/src/shared/components/Notifications.tsx b/ui/src/shared/components/Notifications.tsx new file mode 100644 index 0000000000..aafe2e1e8b --- /dev/null +++ b/ui/src/shared/components/Notifications.tsx @@ -0,0 +1,47 @@ +import React, {PureComponent} from 'react' +import {connect} from 'react-redux' +import {Notification as NotificationType} from 'src/types/notifications' +import Notification from 'src/shared/components/Notification' + +interface Props { + inPresentationMode?: boolean + notifications: NotificationType[] +} + +class Notifications extends PureComponent { + public static defaultProps: Partial = { + inPresentationMode: false, + } + + public render() { + const {notifications} = this.props + + return ( +
+ {notifications.map(n => )} +
+ ) + } + + private get className(): string { + const {inPresentationMode} = this.props + + if (inPresentationMode) { + return 'notification-center__presentation-mode' + } + + return 'notification-center' + } +} + +const mapStateToProps = ({ + notifications, + app: { + ephemeral: {inPresentationMode}, + }, +}) => ({ + notifications, + inPresentationMode, +}) + +export default connect(mapStateToProps, null)(Notifications) From 5b6f806cefec4d43268a4d3c716806f56d9292ee Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 14 May 2018 16:27:25 -0700 Subject: [PATCH 04/14] Clean up Notification component via getters --- ui/src/shared/components/Notification.tsx | 45 ++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/ui/src/shared/components/Notification.tsx b/ui/src/shared/components/Notification.tsx index 9dc3bb5872..b8c6dedd4a 100644 --- a/ui/src/shared/components/Notification.tsx +++ b/ui/src/shared/components/Notification.tsx @@ -1,4 +1,4 @@ -import React, {Component} from 'react' +import React, {Component, CSSProperties} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import {Notification as NotificationType} from 'src/types/notifications' @@ -57,21 +57,15 @@ class Notification extends Component { public render() { const { - notification: {type, message, icon}, + notification: {message, icon}, } = this.props - const {height, dismissed} = this.state - - const notificationContainerClass = classnames('notification-container', { - show: !!height, - 'notification-dismissed': dismissed, - }) - const notificationClass = `notification notification-${type}` - const notificationMargin = 4 - const style = {height: height + notificationMargin} return ( -
-
+
+
{message}
- -
- )} -
-
- ) - } -} - -const {arrayOf, bool, func, node, number, shape, string} = PropTypes - -LayoutCell.propTypes = { - cell: shape({ - i: string.isRequired, - name: string.isRequired, - isEditing: bool, - x: number.isRequired, - y: number.isRequired, - queries: arrayOf(shape()), - }).isRequired, - children: node.isRequired, - onDeleteCell: func, - onCloneCell: func, - onSummonOverlayTechnologies: func, - isEditable: bool, - onCancelEditCell: func, - cellData: arrayOf(shape({})), -} - -export default LayoutCell diff --git a/ui/src/shared/components/LayoutCell.tsx b/ui/src/shared/components/LayoutCell.tsx new file mode 100644 index 0000000000..bef6c838db --- /dev/null +++ b/ui/src/shared/components/LayoutCell.tsx @@ -0,0 +1,124 @@ +import React, {Component, ReactElement} from 'react' +import _ from 'lodash' + +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' + +import LayoutCellMenu from 'src/shared/components/LayoutCellMenu' +import LayoutCellHeader from 'src/shared/components/LayoutCellHeader' +import {notify} from 'src/shared/actions/notifications' +import {notifyCSVDownloadFailed} from 'src/shared/copy/notifications' +import download from 'src/external/download.js' +import {ErrorHandling} from 'src/shared/decorators/errors' +import {dataToCSV} from 'src/shared/parsing/dataToCSV' +import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers' +import {Cell} from 'src/types/dashboard' + +interface Series { + columns: string[] + name: string + values: number[][] +} + +interface Result { + statement_id: number + series: Series[] +} + +interface Response { + results: Result[] +} + +interface Data { + response: Response[] +} + +interface Props { + cell: Cell + children: ReactElement + onDeleteCell: (cell: Cell) => void + onCloneCell: (cell: Cell) => void + onSummonOverlayTechnologies: (cell: Cell) => void + isEditable: boolean + onCancelEditCell: () => void + cellData: Data[] +} + +@ErrorHandling +export default class LayoutCell extends Component { + public render() { + const {cell, isEditable, cellData, onCloneCell} = this.props + + return ( +
+ + + + +
{this.renderGraph}
+
+ ) + } + + private get queries() { + const {cell} = this.props + return _.get(cell, ['queries'], []) + } + + private get renderGraph(): JSX.Element { + const {cell, children} = this.props + + if (this.queries.length) { + const child = React.Children.only(children) + return React.cloneElement(child, {cellID: cell.id}) + } + + return this.emptyGraph + } + + private get emptyGraph(): JSX.Element { + const {cell} = this.props + + return ( +
+ + + +
+ ) + } + + private handleDeleteCell = (cell: Cell) => (): void => { + this.props.onDeleteCell(cell) + } + + private handleSummonOverlay = (cell: Cell) => (): void => { + this.props.onSummonOverlayTechnologies(cell) + } + + private handleCSVDownload = (cell: Cell) => (): void => { + const {cellData} = this.props + const joinedName = cell.name.split(' ').join('_') + const {data} = timeSeriesToTableGraph(cellData) + + try { + download(dataToCSV(data), `${joinedName}.csv`, 'text/plain') + } catch (error) { + notify(notifyCSVDownloadFailed()) + console.error(error) + } + } +} From f025065ca2866fae16efb1ce7891a8dd0fa42e4b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 May 2018 09:58:19 -0700 Subject: [PATCH 08/14] Convert layout cell and related components to TypeScript --- ui/src/dashboards/containers/DashboardPage.js | 2 +- .../shared/components/CustomTimeIndicator.tsx | 8 +- ui/src/shared/components/LayoutCell.tsx | 21 +- ui/src/shared/components/LayoutCellMenu.js | 156 ------------- ui/src/shared/components/LayoutCellMenu.tsx | 210 ++++++++++++++++++ .../shared/components/MenuTooltipButton.tsx | 2 +- 6 files changed, 226 insertions(+), 173 deletions(-) delete mode 100644 ui/src/shared/components/LayoutCellMenu.js create mode 100644 ui/src/shared/components/LayoutCellMenu.tsx diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 19f64d7956..6190b43687 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -223,7 +223,7 @@ class DashboardPage extends Component { dashboardActions.addDashboardCellAsync(dashboard) } - handleCloneCell = cell => () => { + handleCloneCell = cell => { const {dashboardActions, dashboard} = this.props dashboardActions.cloneDashboardCellAsync(dashboard, cell) } diff --git a/ui/src/shared/components/CustomTimeIndicator.tsx b/ui/src/shared/components/CustomTimeIndicator.tsx index 339bf46901..9b40dc3cd0 100644 --- a/ui/src/shared/components/CustomTimeIndicator.tsx +++ b/ui/src/shared/components/CustomTimeIndicator.tsx @@ -2,9 +2,11 @@ import React, {SFC} from 'react' import _ from 'lodash' import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' +import {QueryConfig} from 'src/types/query' interface Query { - query: string + config: QueryConfig + text: string } interface Props { @@ -12,7 +14,9 @@ interface Props { } const CustomTimeIndicator: SFC = ({queries}) => { - const q = queries.find(({query}) => !query.includes(TEMP_VAR_DASHBOARD_TIME)) + const q = queries.find( + query => query.text.includes(TEMP_VAR_DASHBOARD_TIME) === false + ) const customLower = _.get(q, ['queryConfig', 'range', 'lower'], null) const customUpper = _.get(q, ['queryConfig', 'range', 'upper'], null) diff --git a/ui/src/shared/components/LayoutCell.tsx b/ui/src/shared/components/LayoutCell.tsx index bef6c838db..1fdb122245 100644 --- a/ui/src/shared/components/LayoutCell.tsx +++ b/ui/src/shared/components/LayoutCell.tsx @@ -46,7 +46,7 @@ interface Props { @ErrorHandling export default class LayoutCell extends Component { public render() { - const {cell, isEditable, cellData, onCloneCell} = this.props + const {cell, isEditable, cellData, onDeleteCell, onCloneCell} = this.props return (
@@ -56,7 +56,7 @@ export default class LayoutCell extends Component { queries={this.queries} dataExists={!!cellData.length} isEditable={isEditable} - onDelete={this.handleDeleteCell} + onDelete={onDeleteCell} onEdit={this.handleSummonOverlay} onClone={onCloneCell} onCSVDownload={this.handleCSVDownload} @@ -85,14 +85,12 @@ export default class LayoutCell extends Component { } private get emptyGraph(): JSX.Element { - const {cell} = this.props - return (
@@ -101,16 +99,13 @@ export default class LayoutCell extends Component { ) } - private handleDeleteCell = (cell: Cell) => (): void => { - this.props.onDeleteCell(cell) + private handleSummonOverlay = (): void => { + const {cell, onSummonOverlayTechnologies} = this.props + onSummonOverlayTechnologies(cell) } - private handleSummonOverlay = (cell: Cell) => (): void => { - this.props.onSummonOverlayTechnologies(cell) - } - - private handleCSVDownload = (cell: Cell) => (): void => { - const {cellData} = this.props + private handleCSVDownload = (): void => { + const {cellData, cell} = this.props const joinedName = cell.name.split(' ').join('_') const {data} = timeSeriesToTableGraph(cellData) diff --git a/ui/src/shared/components/LayoutCellMenu.js b/ui/src/shared/components/LayoutCellMenu.js deleted file mode 100644 index 182fdc7bc1..0000000000 --- a/ui/src/shared/components/LayoutCellMenu.js +++ /dev/null @@ -1,156 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import classnames from 'classnames' - -import MenuTooltipButton from 'src/shared/components/MenuTooltipButton' -import CustomTimeIndicator from 'src/shared/components/CustomTimeIndicator' -import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' -import {EDITING} from 'src/shared/annotations/helpers' -import {cellSupportsAnnotations} from 'src/shared/constants/index' - -import { - addingAnnotation, - editingAnnotation, - dismissEditingAnnotation, -} from 'src/shared/actions/annotations' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class LayoutCellMenu extends Component { - state = { - subMenuIsOpen: false, - } - - handleToggleSubMenu = () => { - this.setState({subMenuIsOpen: !this.state.subMenuIsOpen}) - } - - render() { - const {subMenuIsOpen} = this.state - const { - mode, - cell, - onEdit, - onClone, - queries, - onDelete, - isEditable, - dataExists, - onCSVDownload, - onStartAddingAnnotation, - onStartEditingAnnotation, - onDismissEditingAnnotation, - } = this.props - - const menuOptions = [ - { - text: 'Configure', - action: onEdit(cell), - }, - { - text: 'Add Annotation', - action: onStartAddingAnnotation, - disabled: !cellSupportsAnnotations(cell.type), - }, - { - text: 'Edit Annotations', - action: onStartEditingAnnotation, - disabled: !cellSupportsAnnotations(cell.type), - }, - { - text: 'Download CSV', - action: onCSVDownload(cell), - disabled: !dataExists, - }, - ] - - return ( -
-
- {queries && } -
- {isEditable && - mode !== EDITING && ( -
- {queries.length ? ( - - ) : null} - - - - -
- )} - {mode === 'editing' && - cellSupportsAnnotations(cell.type) && ( -
-
- Done Editing -
-
- )} -
- ) - } -} - -const {arrayOf, bool, func, shape, string} = PropTypes - -LayoutCellMenu.propTypes = { - mode: string, - onEdit: func, - onClone: func, - onDelete: func, - cell: shape(), - isEditable: bool, - dataExists: bool, - onCSVDownload: func, - queries: arrayOf(shape()), - onStartAddingAnnotation: func.isRequired, - onStartEditingAnnotation: func.isRequired, - onDismissEditingAnnotation: func.isRequired, -} - -const mapStateToProps = ({annotations: {mode}}) => ({ - mode, -}) - -const mapDispatchToProps = dispatch => ({ - onStartAddingAnnotation: bindActionCreators(addingAnnotation, dispatch), - onStartEditingAnnotation: bindActionCreators(editingAnnotation, dispatch), - onDismissEditingAnnotation: bindActionCreators( - dismissEditingAnnotation, - dispatch - ), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(LayoutCellMenu) diff --git a/ui/src/shared/components/LayoutCellMenu.tsx b/ui/src/shared/components/LayoutCellMenu.tsx new file mode 100644 index 0000000000..903ce538cf --- /dev/null +++ b/ui/src/shared/components/LayoutCellMenu.tsx @@ -0,0 +1,210 @@ +import React, {Component} from 'react' +import {connect} from 'react-redux' +import {bindActionCreators} from 'redux' + +import classnames from 'classnames' + +import MenuTooltipButton, { + MenuOption, +} from 'src/shared/components/MenuTooltipButton' +import CustomTimeIndicator from 'src/shared/components/CustomTimeIndicator' +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' +import {EDITING} from 'src/shared/annotations/helpers' +import {cellSupportsAnnotations} from 'src/shared/constants/index' +import {Cell} from 'src/types/dashboard' +import {QueryConfig} from 'src/types/query' + +import { + addingAnnotation, + editingAnnotation, + dismissEditingAnnotation, +} from 'src/shared/actions/annotations' +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Query { + text: string + config: QueryConfig +} + +interface Props { + cell: Cell + isEditable: boolean + dataExists: boolean + mode: string + onEdit: () => void + onClone: (cell: Cell) => void + onDelete: (cell: Cell) => void + onCSVDownload: () => void + onStartAddingAnnotation: () => void + onStartEditingAnnotation: () => void + onDismissEditingAnnotation: () => void + queries: Query[] +} + +interface State { + subMenuIsOpen: boolean +} + +@ErrorHandling +class LayoutCellMenu extends Component { + constructor(props: Props) { + super(props) + + this.state = { + subMenuIsOpen: false, + } + } + + public render() { + const {queries} = this.props + + return ( +
+
+ {queries && } +
+ {this.renderMenu} +
+ ) + } + + private get renderMenu(): JSX.Element { + const { + isEditable, + mode, + cell, + onDismissEditingAnnotation, + queries, + } = this.props + + if (mode === EDITING && cellSupportsAnnotations(cell.type)) { + return ( +
+
+ Done Editing +
+
+ ) + } + + if (isEditable && mode !== EDITING) { + return ( +
+ {queries.length ? ( + + ) : null} + + + + +
+ ) + } + } + + private get contextMenuClassname(): string { + const {subMenuIsOpen} = this.state + + return classnames('dash-graph-context', { + 'dash-graph-context__open': subMenuIsOpen, + }) + } + private get customIndicatorsClassname(): string { + const {isEditable} = this.props + + return classnames('dash-graph--custom-indicators', { + 'dash-graph--draggable': isEditable, + }) + } + + private get editMenuOptions(): MenuOption[] { + const { + cell, + dataExists, + onStartAddingAnnotation, + onStartEditingAnnotation, + onCSVDownload, + } = this.props + + return [ + { + text: 'Configure', + action: this.handleEditCell, + disabled: false, + }, + { + text: 'Add Annotation', + action: onStartAddingAnnotation, + disabled: !cellSupportsAnnotations(cell.type), + }, + { + text: 'Edit Annotations', + action: onStartEditingAnnotation, + disabled: !cellSupportsAnnotations(cell.type), + }, + { + text: 'Download CSV', + action: onCSVDownload, + disabled: !dataExists, + }, + ] + } + + private get cloneMenuOptions(): MenuOption[] { + return [{text: 'Clone Cell', action: this.handleCloneCell, disabled: false}] + } + + private get deleteMenuOptions(): MenuOption[] { + return [{text: 'Confirm', action: this.handleDeleteCell, disabled: false}] + } + + private handleEditCell = (): void => { + const {onEdit} = this.props + onEdit() + } + + private handleDeleteCell = (): void => { + const {onDelete, cell} = this.props + onDelete(cell) + } + + private handleCloneCell = (): void => { + const {onClone, cell} = this.props + onClone(cell) + } + + private handleToggleSubMenu = (): void => { + this.setState({subMenuIsOpen: !this.state.subMenuIsOpen}) + } +} + +const mapStateToProps = ({annotations: {mode}}) => ({ + mode, +}) + +const mapDispatchToProps = dispatch => ({ + onStartAddingAnnotation: bindActionCreators(addingAnnotation, dispatch), + onStartEditingAnnotation: bindActionCreators(editingAnnotation, dispatch), + onDismissEditingAnnotation: bindActionCreators( + dismissEditingAnnotation, + dispatch + ), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(LayoutCellMenu) diff --git a/ui/src/shared/components/MenuTooltipButton.tsx b/ui/src/shared/components/MenuTooltipButton.tsx index 72b01fc19f..fdea945dc7 100644 --- a/ui/src/shared/components/MenuTooltipButton.tsx +++ b/ui/src/shared/components/MenuTooltipButton.tsx @@ -3,7 +3,7 @@ import {ClickOutside} from 'src/shared/components/ClickOutside' import classnames from 'classnames' import {ErrorHandling} from 'src/shared/decorators/errors' -interface MenuOption { +export interface MenuOption { text: string action: () => void disabled?: boolean From 0a856863e4703a813a159e6fcf8cbaba0d4f92b0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 May 2018 10:37:09 -0700 Subject: [PATCH 09/14] Fix double legend css bug --- ui/src/style/components/dygraphs.scss | 2 +- ui/src/style/layout/page.scss | 4 ++++ ui/src/style/pages/cell-editor-overlay.scss | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index 043e559d83..38aad37f48 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -171,7 +171,7 @@ display: block !important; position: absolute; padding: 11px; - z-index: 500; + z-index: $dygraph-legend-z; border-radius: 3px; min-width: 350px; user-select: text; diff --git a/ui/src/style/layout/page.scss b/ui/src/style/layout/page.scss index 5caed2b229..ccef658ce1 100644 --- a/ui/src/style/layout/page.scss +++ b/ui/src/style/layout/page.scss @@ -2,6 +2,10 @@ Page Layout ---------------------------------------------------------------------------- */ + +$dygraph-legend-z: 500; +$dash-ceo-z: $dygraph-legend-z + 10; + .chronograf-root { display: flex; align-items: stretch; diff --git a/ui/src/style/pages/cell-editor-overlay.scss b/ui/src/style/pages/cell-editor-overlay.scss index 16fa6a9c5e..09ac0c53f7 100644 --- a/ui/src/style/pages/cell-editor-overlay.scss +++ b/ui/src/style/pages/cell-editor-overlay.scss @@ -5,7 +5,6 @@ $overlay-controls-height: 60px; $overlay-controls-bg: $g2-kevlar; -$overlay-z: 100; // Make Overlay Technology full screen @@ -26,7 +25,7 @@ $overlay-z: 100; top: 0; bottom: 0; right: 0; - z-index: $overlay-z; + z-index: $dash-ceo-z; padding: 0 30px; /* From 14225f67d96360615a52033dc6852afc4b6120ed Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 May 2018 10:41:50 -0700 Subject: [PATCH 10/14] Remove empty ruleset --- ui/src/style/components/dygraphs.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index 38aad37f48..5e0d8a6481 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -57,8 +57,6 @@ text-align: right !important; user-select: none; } -.graph-container > div > div > div > div { -} /* Vertical Axis Labels */ .dygraph-ylabel, From cd73ef416dfa6bccc80256134bfe2b1e7218682f Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 May 2018 10:53:37 -0700 Subject: [PATCH 11/14] Create type for menu item action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also renaming “MenuOption” to be “MenuItem” --- ui/src/shared/components/LayoutCellMenu.tsx | 14 +++++++------- ui/src/shared/components/MenuTooltipButton.tsx | 16 +++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/ui/src/shared/components/LayoutCellMenu.tsx b/ui/src/shared/components/LayoutCellMenu.tsx index 903ce538cf..6853b6a443 100644 --- a/ui/src/shared/components/LayoutCellMenu.tsx +++ b/ui/src/shared/components/LayoutCellMenu.tsx @@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux' import classnames from 'classnames' import MenuTooltipButton, { - MenuOption, + MenuItem, } from 'src/shared/components/MenuTooltipButton' import CustomTimeIndicator from 'src/shared/components/CustomTimeIndicator' import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' @@ -96,21 +96,21 @@ class LayoutCellMenu extends Component { {queries.length ? ( ) : null}
@@ -133,7 +133,7 @@ class LayoutCellMenu extends Component { }) } - private get editMenuOptions(): MenuOption[] { + private get editMenuItems(): MenuItem[] { const { cell, dataExists, @@ -166,11 +166,11 @@ class LayoutCellMenu extends Component { ] } - private get cloneMenuOptions(): MenuOption[] { + private get cloneMenuItems(): MenuItem[] { return [{text: 'Clone Cell', action: this.handleCloneCell, disabled: false}] } - private get deleteMenuOptions(): MenuOption[] { + private get deleteMenuItems(): MenuItem[] { return [{text: 'Confirm', action: this.handleDeleteCell, disabled: false}] } diff --git a/ui/src/shared/components/MenuTooltipButton.tsx b/ui/src/shared/components/MenuTooltipButton.tsx index fdea945dc7..01b8eec22d 100644 --- a/ui/src/shared/components/MenuTooltipButton.tsx +++ b/ui/src/shared/components/MenuTooltipButton.tsx @@ -3,9 +3,11 @@ import {ClickOutside} from 'src/shared/components/ClickOutside' import classnames from 'classnames' import {ErrorHandling} from 'src/shared/decorators/errors' -export interface MenuOption { +type MenuItemAction = () => void + +export interface MenuItem { text: string - action: () => void + action: MenuItemAction disabled?: boolean } @@ -13,7 +15,7 @@ interface Props { theme?: string icon: string informParent: () => void - menuOptions: MenuOption[] + menuItems: MenuItem[] } interface State { @@ -54,11 +56,11 @@ export default class MenuTooltipButton extends Component { informParent() } - private handleMenuItemClick = menuItemAction => (): void => { + private handleMenuItemClick = (action: MenuItemAction) => (): void => { const {informParent} = this.props this.setState({expanded: false}) - menuItemAction() + action() informParent() } @@ -87,7 +89,7 @@ export default class MenuTooltipButton extends Component { } private get renderMenu(): JSX.Element { - const {menuOptions, theme} = this.props + const {menuItems, theme} = this.props const {expanded} = this.state if (expanded === false) { @@ -96,7 +98,7 @@ export default class MenuTooltipButton extends Component { return (
- {menuOptions.map((option, i) => ( + {menuItems.map((option, i) => (
Date: Thu, 17 May 2018 11:00:21 -0700 Subject: [PATCH 12/14] Type query getter response --- ui/src/shared/components/LayoutCell.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/shared/components/LayoutCell.tsx b/ui/src/shared/components/LayoutCell.tsx index 1fdb122245..b4fda7c0d3 100644 --- a/ui/src/shared/components/LayoutCell.tsx +++ b/ui/src/shared/components/LayoutCell.tsx @@ -11,7 +11,7 @@ import download from 'src/external/download.js' import {ErrorHandling} from 'src/shared/decorators/errors' import {dataToCSV} from 'src/shared/parsing/dataToCSV' import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers' -import {Cell} from 'src/types/dashboard' +import {Cell, CellQuery} from 'src/types/dashboard' interface Series { columns: string[] @@ -68,7 +68,7 @@ export default class LayoutCell extends Component { ) } - private get queries() { + private get queries(): CellQuery[] { const {cell} = this.props return _.get(cell, ['queries'], []) } From 6476812a1dedb0f25779cecc09a58341743e0fbd Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 May 2018 11:04:46 -0700 Subject: [PATCH 13/14] Make pencil menu into getter to remove ternary --- ui/src/shared/components/LayoutCellMenu.tsx | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/ui/src/shared/components/LayoutCellMenu.tsx b/ui/src/shared/components/LayoutCellMenu.tsx index 6853b6a443..8253ad04e5 100644 --- a/ui/src/shared/components/LayoutCellMenu.tsx +++ b/ui/src/shared/components/LayoutCellMenu.tsx @@ -69,13 +69,7 @@ class LayoutCellMenu extends Component { } private get renderMenu(): JSX.Element { - const { - isEditable, - mode, - cell, - onDismissEditingAnnotation, - queries, - } = this.props + const {isEditable, mode, cell, onDismissEditingAnnotation} = this.props if (mode === EDITING && cellSupportsAnnotations(cell.type)) { return ( @@ -93,13 +87,7 @@ class LayoutCellMenu extends Component { if (isEditable && mode !== EDITING) { return (
- {queries.length ? ( - - ) : null} + {this.pencilMenu} { } } + private get pencilMenu(): JSX.Element { + const {queries} = this.props + + if (!queries.length) { + return + } + + return ( + + ) + } + private get contextMenuClassname(): string { const {subMenuIsOpen} = this.state From e319cf590e630749915daffe1cfab9caa6695780 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 May 2018 11:13:15 -0700 Subject: [PATCH 14/14] Type map state to props --- ui/src/shared/components/Notifications.tsx | 2 +- .../shared/components/ThresholdsListTypeToggle.tsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/Notifications.tsx b/ui/src/shared/components/Notifications.tsx index aafe2e1e8b..41df444d44 100644 --- a/ui/src/shared/components/Notifications.tsx +++ b/ui/src/shared/components/Notifications.tsx @@ -39,7 +39,7 @@ const mapStateToProps = ({ app: { ephemeral: {inPresentationMode}, }, -}) => ({ +}): Props => ({ notifications, inPresentationMode, }) diff --git a/ui/src/shared/components/ThresholdsListTypeToggle.tsx b/ui/src/shared/components/ThresholdsListTypeToggle.tsx index 865d418711..7873d4e320 100644 --- a/ui/src/shared/components/ThresholdsListTypeToggle.tsx +++ b/ui/src/shared/components/ThresholdsListTypeToggle.tsx @@ -10,12 +10,16 @@ import { } from 'src/shared/constants/thresholds' import {ErrorHandling} from 'src/shared/decorators/errors' -interface Props { - containerClass: string +interface PropsFromRedux { thresholdsListType: string +} +interface PropsFromParent { + containerClass: string handleUpdateThresholdsListType: (newType: string) => void } +type Props = PropsFromRedux & PropsFromParent + @ErrorHandling class ThresholdsListTypeToggle extends Component { public render() { @@ -69,7 +73,9 @@ class ThresholdsListTypeToggle extends Component { } } -const mapStateToProps = ({cellEditorOverlay: {thresholdsListType}}) => ({ +const mapStateToProps = ({ + cellEditorOverlay: {thresholdsListType}, +}): PropsFromRedux => ({ thresholdsListType, })