Merge pull request #3778 from influxdata/refactor-overlay-technology

Refactor Overlay Technology
pull/10616/head
Alex Paxton 2018-06-27 11:48:16 -07:00 committed by GitHub
commit be4b65850c
35 changed files with 583 additions and 582 deletions

View File

@ -2,7 +2,6 @@ import React, {SFC, ReactChildren} from 'react'
import SideNav from 'src/side_nav'
import Notifications from 'src/shared/components/Notifications'
import Overlay from 'src/shared/components/OverlayTechnology'
interface Props {
children: ReactChildren
@ -10,7 +9,6 @@ interface Props {
const App: SFC<Props> = ({children}) => (
<div className="chronograf-root">
<Overlay />
<Notifications />
<SideNav />
{children}

View File

@ -1,5 +1,4 @@
import React, {Component, MouseEvent} from 'react'
import {connect} from 'react-redux'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
@ -8,11 +7,7 @@ import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOve
import SearchBar from 'src/hosts/components/SearchBar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
showOverlay as showOverlayAction,
ShowOverlayActionCreator,
} from 'src/shared/actions/overlayTechnology'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import {Dashboard} from 'src/types'
import {Notification} from 'src/types/notifications'
@ -27,12 +22,12 @@ interface Props {
onExportDashboard: (dashboard: Dashboard) => () => void
onImportDashboard: (dashboard: Dashboard) => void
notify: (message: Notification) => void
showOverlay: ShowOverlayActionCreator
dashboardLink: string
}
interface State {
searchTerm: string
isOverlayVisible: boolean
}
@ErrorHandling
@ -42,6 +37,7 @@ class DashboardsPageContents extends Component<Props, State> {
this.state = {
searchTerm: '',
isOverlayVisible: false,
}
}
@ -83,31 +79,34 @@ class DashboardsPageContents extends Component<Props, State> {
const {onCreateDashboard} = this.props
return (
<div className="panel-heading">
<h2 className="panel-title">{this.panelTitle}</h2>
<div className="panel-controls">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
/>
<Authorized requiredRole={EDITOR_ROLE}>
<>
<button
className="btn btn-sm btn-default"
onClick={this.showImportOverlay}
>
<span className="icon import" /> Import Dashboard
</button>
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</>
</Authorized>
<>
<div className="panel-heading">
<h2 className="panel-title">{this.panelTitle}</h2>
<div className="panel-controls">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
/>
<Authorized requiredRole={EDITOR_ROLE}>
<>
<button
className="btn btn-sm btn-default"
onClick={this.handleToggleOverlay}
>
<span className="icon import" /> Import Dashboard
</button>
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</>
</Authorized>
</div>
</div>
</div>
{this.renderImportOverlay}
</>
)
}
@ -136,30 +135,24 @@ class DashboardsPageContents extends Component<Props, State> {
this.setState({searchTerm})
}
private showImportOverlay = (): void => {
const {showOverlay, onImportDashboard, notify} = this.props
const options = {
dismissOnClickOutside: false,
dismissOnEscape: false,
}
private handleToggleOverlay = (): void => {
this.setState({isOverlayVisible: !this.state.isOverlayVisible})
}
showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<ImportDashboardOverlay
onDismissOverlay={onDismissOverlay}
onImportDashboard={onImportDashboard}
notify={notify}
/>
)}
</OverlayContext.Consumer>,
options
private get renderImportOverlay(): JSX.Element {
const {onImportDashboard, notify} = this.props
const {isOverlayVisible} = this.state
return (
<OverlayTechnology visible={isOverlayVisible}>
<ImportDashboardOverlay
onDismissOverlay={this.handleToggleOverlay}
onImportDashboard={onImportDashboard}
notify={notify}
/>
</OverlayTechnology>
)
}
}
const mapDispatchToProps = {
showOverlay: showOverlayAction,
}
export default connect(null, mapDispatchToProps)(DashboardsPageContents)
export default DashboardsPageContents

View File

@ -1,9 +1,9 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import Container from 'src/shared/components/overlay/OverlayContainer'
import Heading from 'src/shared/components/overlay/OverlayHeading'
import Body from 'src/shared/components/overlay/OverlayBody'
import Container from 'src/reusable_ui/components/overlays/OverlayContainer'
import Heading from 'src/reusable_ui/components/overlays/OverlayHeading'
import Body from 'src/reusable_ui/components/overlays/OverlayBody'
import DragAndDrop from 'src/shared/components/DragAndDrop'
import {notifyDashboardImportFailed} from 'src/shared/copy/notifications'

View File

@ -13,7 +13,7 @@ import QueryMaker from 'src/data_explorer/components/QueryMaker'
import Visualization from 'src/data_explorer/components/Visualization'
import WriteDataForm from 'src/data_explorer/components/WriteDataForm'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import OverlayTechnologies from 'src/shared/components/OverlayTechnologies'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
@ -49,7 +49,7 @@ interface Props {
}
interface State {
showWriteForm: boolean
isWriteFormVisible: boolean
}
@ErrorHandling
@ -58,7 +58,7 @@ export class DataExplorer extends PureComponent<Props, State> {
super(props)
this.state = {
showWriteForm: false,
isWriteFormVisible: false,
}
}
@ -101,21 +101,19 @@ export class DataExplorer extends PureComponent<Props, State> {
queryConfigActions,
} = this.props
const {showWriteForm} = this.state
const {isWriteFormVisible} = this.state
return (
<>
{showWriteForm ? (
<OverlayTechnologies>
<WriteDataForm
source={source}
errorThrown={errorThrownAction}
selectedDatabase={this.selectedDatabase}
onClose={this.handleCloseWriteData}
writeLineProtocol={writeLineProtocol}
/>
</OverlayTechnologies>
) : null}
<OverlayTechnology visible={isWriteFormVisible}>
<WriteDataForm
source={source}
errorThrown={errorThrownAction}
selectedDatabase={this.selectedDatabase}
onClose={this.handleCloseWriteData}
writeLineProtocol={writeLineProtocol}
/>
</OverlayTechnology>
<PageHeader
titleText="Data Explorer"
fullWidth={true}
@ -154,11 +152,11 @@ export class DataExplorer extends PureComponent<Props, State> {
}
private handleCloseWriteData = (): void => {
this.setState({showWriteForm: false})
this.setState({isWriteFormVisible: false})
}
private handleOpenWriteData = (): void => {
this.setState({showWriteForm: true})
this.setState({isWriteFormVisible: true})
}
private handleChooseTimeRange = (timeRange: TimeRange): void => {

View File

@ -0,0 +1,25 @@
import React, {SFC} from 'react'
import PageHeader from 'src/shared/components/PageHeader'
interface Props {
onShowOverlay: () => void
overlay: JSX.Element
}
const EmptyFluxPage: SFC<Props> = ({onShowOverlay, overlay}) => (
<div className="page">
<PageHeader titleText="Flux Editor" fullWidth={true} />
<div className="page-contents">
<div className="flux-empty">
<p>You do not have a configured Flux source</p>
<button className="btn btn-primary btn-md" onClick={onShowOverlay}>
Connect to Flux
</button>
</div>
</div>
{overlay}
</div>
)
export default EmptyFluxPage

View File

@ -1,60 +1,70 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import FluxOverlay from 'src/flux/components/FluxOverlay'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import PageHeader from 'src/shared/components/PageHeader'
import {
showOverlay,
ShowOverlayActionCreator,
} from 'src/shared/actions/overlayTechnology'
import {Service} from 'src/types'
interface Props {
showOverlay: ShowOverlayActionCreator
service: Service
}
class FluxHeader extends PureComponent<Props> {
interface State {
isOverlayVisible: boolean
}
class FluxHeader extends PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
isOverlayVisible: false,
}
}
public render() {
return (
<PageHeader
titleText="Flux Editor"
fullWidth={true}
optionsComponents={this.optionsComponents}
/>
<>
<PageHeader
titleText="Flux Editor"
fullWidth={true}
optionsComponents={this.optionsComponents}
/>
{this.overlay}
</>
)
}
private handleToggleOverlay = (): void => {
this.setState({isOverlayVisible: !this.state.isOverlayVisible})
}
private get optionsComponents(): JSX.Element {
return (
<button onClick={this.overlay} className="btn btn-sm btn-default">
<button
onClick={this.handleToggleOverlay}
className="btn btn-sm btn-default"
>
Edit Connection
</button>
)
}
private overlay = () => {
private get overlay(): JSX.Element {
const {service} = this.props
const {isOverlayVisible} = this.state
this.props.showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<FluxOverlay
mode="edit"
service={service}
onDismiss={onDismissOverlay}
/>
)}
</OverlayContext.Consumer>,
{}
return (
<OverlayTechnology visible={isOverlayVisible}>
<FluxOverlay
mode="edit"
service={service}
onDismiss={this.handleToggleOverlay}
/>
</OverlayTechnology>
)
}
}
const mdtp = {
showOverlay,
}
export default connect(null, mdtp)(FluxHeader)
export default FluxHeader

View File

@ -3,8 +3,9 @@ import {connect} from 'react-redux'
import {WithRouterProps} from 'react-router'
import {FluxPage} from 'src/flux'
import EmptyFluxPage from 'src/flux/components/EmptyFluxPage'
import FluxOverlay from 'src/flux/components/FluxOverlay'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import {Source, Service, Notification} from 'src/types'
import {Links} from 'src/types/flux'
import {notify as notifyAction} from 'src/shared/actions/notifications'
@ -12,26 +13,37 @@ import {
updateScript as updateScriptAction,
UpdateScript,
} from 'src/flux/actions'
import * as a from 'src/shared/actions/overlayTechnology'
import * as b from 'src/shared/actions/services'
import * as actions from 'src/shared/actions/services'
export const NotificationContext = React.createContext()
const actions = {...a, ...b}
interface Props {
sources: Source[]
services: Service[]
children: ReactChildren
showOverlay: a.ShowOverlayActionCreator
fetchServicesAsync: b.FetchServicesAsync
fetchServicesAsync: actions.FetchServicesAsync
notify: (message: Notification) => void
updateScript: UpdateScript
script: string
links: Links
}
export class CheckServices extends PureComponent<Props & WithRouterProps> {
interface State {
isOverlayShown: boolean
}
export class CheckServices extends PureComponent<
Props & WithRouterProps,
State
> {
constructor(props: Props & WithRouterProps) {
super(props)
this.state = {
isOverlayShown: false,
}
}
public async componentDidMount() {
const source = this.props.sources.find(
s => s.id === this.props.params.sourceID
@ -44,7 +56,7 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
await this.props.fetchServicesAsync(source)
if (!this.props.services.length) {
this.overlay()
this.setState({isOverlayShown: true})
}
}
@ -52,7 +64,12 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
const {services, notify, updateScript, links, script} = this.props
if (!this.props.services.length) {
return null // put loading spinner here
return (
<EmptyFluxPage
onShowOverlay={this.handleShowOverlay}
overlay={this.renderOverlay}
/>
)
}
return (
@ -65,6 +82,7 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
notify={notify}
updateScript={updateScript}
/>
{this.renderOverlay}
</NotificationContext.Provider>
)
}
@ -75,31 +93,31 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
return sources.find(s => s.id === params.sourceID)
}
private overlay() {
const {showOverlay, services} = this.props
private get renderOverlay(): JSX.Element {
const {isOverlayShown} = this.state
if (services.length) {
return
}
showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<FluxOverlay
mode="new"
source={this.source}
onDismiss={onDismissOverlay}
/>
)}
</OverlayContext.Consumer>,
{}
return (
<OverlayTechnology visible={isOverlayShown}>
<FluxOverlay
mode="new"
source={this.source}
onDismiss={this.handleDismissOverlay}
/>
</OverlayTechnology>
)
}
private handleShowOverlay = (): void => {
this.setState({isOverlayShown: true})
}
private handleDismissOverlay = (): void => {
this.setState({isOverlayShown: false})
}
}
const mdtp = {
fetchServicesAsync: actions.fetchServicesAsync,
showOverlay: actions.showOverlay,
updateScript: updateScriptAction,
notify: notifyAction,
}

View File

@ -1,7 +1,7 @@
import React, {PureComponent, ReactChildren} from 'react'
interface Props {
children?: ReactChildren
children?: ReactChildren | JSX.Element
title: string
onDismiss?: () => void
}

View File

@ -0,0 +1,76 @@
import React, {Component} from 'react'
import classnames from 'classnames'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
children: JSX.Element
visible: boolean
}
interface State {
showChildren: boolean
}
@ErrorHandling
class OverlayTechnology extends Component<Props, State> {
public static getDerivedStateFromProps(props) {
if (props.visible) {
return {showChildren: true}
}
return {}
}
private animationTimer: number
constructor(props: Props) {
super(props)
this.state = {
showChildren: false,
}
}
public componentDidUpdate(prevProps) {
if (prevProps.visible && !this.props.visible) {
clearTimeout(this.animationTimer)
this.animationTimer = window.setTimeout(this.hideChildren, 300)
}
}
public render() {
return (
<div className={this.overlayClass}>
{this.childContainer}
<div className="overlay--mask" />
</div>
)
}
private get childContainer(): JSX.Element {
const {children} = this.props
const {showChildren} = this.state
if (showChildren) {
return (
<div className="overlay--dialog" data-test="overlay-children">
{children}
</div>
)
}
return <div className="overlay--dialog" data-test="overlay-children" />
}
private get overlayClass(): string {
const {visible} = this.props
return classnames('overlay-tech', {show: visible})
}
private hideChildren = (): void => {
this.setState({showChildren: false})
}
}
export default OverlayTechnology

View File

@ -1,34 +0,0 @@
import {ReactElement} from 'react'
type OverlayNodeType = ReactElement<any>
interface Options {
dismissOnClickOutside?: boolean
dismissOnEscape?: boolean
transitionTime?: number
}
export type ShowOverlayActionCreator = (
OverlayNode: OverlayNodeType,
options: Options
) => ShowOverlayAction
interface ShowOverlayAction {
type: 'SHOW_OVERLAY'
payload: {
OverlayNode
options
}
}
export const showOverlay = (
OverlayNode: OverlayNodeType,
options: Options
): ShowOverlayAction => ({
type: 'SHOW_OVERLAY',
payload: {OverlayNode, options},
})
export const dismissOverlay = () => ({
type: 'DISMISS_OVERLAY',
})

View File

@ -1,104 +0,0 @@
import React, {PureComponent, Component} from 'react'
import {connect} from 'react-redux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {dismissOverlay} from 'src/shared/actions/overlayTechnology'
interface Props {
OverlayNode?: Component<any>
dismissOnClickOutside?: boolean
dismissOnEscape?: boolean
transitionTime?: number
handleDismissOverlay: () => void
}
interface State {
visible: boolean
}
export const OverlayContext = React.createContext()
@ErrorHandling
class Overlay extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = {
dismissOnClickOutside: false,
dismissOnEscape: false,
transitionTime: 300,
}
private animationTimer: number
constructor(props) {
super(props)
this.state = {
visible: false,
}
}
public componentDidUpdate(prevProps) {
if (prevProps.OverlayNode === null && this.props.OverlayNode) {
return this.setState({visible: true})
}
}
public render() {
const {OverlayNode} = this.props
return (
<OverlayContext.Provider
value={{
onDismissOverlay: this.handleAnimateDismiss,
}}
>
<div className={this.overlayClass}>
<div className="overlay--dialog">{OverlayNode}</div>
<div className="overlay--mask" onClick={this.handleClickOutside} />
</div>
</OverlayContext.Provider>
)
}
private get overlayClass(): string {
const {visible} = this.state
return `overlay-tech ${visible ? 'show' : ''}`
}
public handleClickOutside = () => {
const {handleDismissOverlay, dismissOnClickOutside} = this.props
if (dismissOnClickOutside) {
handleDismissOverlay()
}
}
public handleAnimateDismiss = () => {
const {transitionTime} = this.props
this.setState({visible: false})
this.animationTimer = window.setTimeout(this.handleDismiss, transitionTime)
}
public handleDismiss = () => {
const {handleDismissOverlay} = this.props
handleDismissOverlay()
clearTimeout(this.animationTimer)
}
}
const mapStateToProps = ({
overlayTechnology: {
OverlayNode,
options: {dismissOnClickOutside, dismissOnEscape, transitionTime},
},
}) => ({
OverlayNode,
dismissOnClickOutside,
dismissOnEscape,
transitionTime,
})
const mapDispatchToProps = {
handleDismissOverlay: dismissOverlay,
}
export default connect(mapStateToProps, mapDispatchToProps)(Overlay)

View File

@ -1,12 +0,0 @@
import React, {SFC} from 'react'
const SimpleOverlayTechnology: SFC = ({children}) => {
return (
<div className="overlay-tech show">
<div className="overlay--dialog">{children}</div>
<div className="overlay--mask" />
</div>
)
}
export default SimpleOverlayTechnology

View File

@ -1,29 +0,0 @@
const initialState = {
options: {
dismissOnClickOutside: false,
dismissOnEscape: false,
transitionTime: 300,
},
OverlayNode: null,
}
export default function overlayTechnology(state = initialState, action) {
switch (action.type) {
case 'SHOW_OVERLAY': {
const {OverlayNode, options} = action.payload
return {...state, OverlayNode, options}
}
case 'DISMISS_OVERLAY': {
const {options} = initialState
return {
...state,
OverlayNode: null,
options,
}
}
}
return state
}

View File

@ -14,7 +14,6 @@ import adminReducers from 'src/admin/reducers'
import kapacitorReducers from 'src/kapacitor/reducers'
import dashboardUI from 'src/dashboards/reducers/ui'
import cellEditorOverlay from 'src/dashboards/reducers/cellEditorOverlay'
import overlayTechnology from 'src/shared/reducers/overlayTechnology'
import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1'
import persistStateEnhancer from './persistStateEnhancer'
import servicesReducer from 'src/shared/reducers/services'
@ -28,7 +27,6 @@ const rootReducer = combineReducers({
...adminReducers,
dashboardUI,
cellEditorOverlay,
overlayTechnology,
dashTimeV1,
logs: logsReducer,
routing: routerReducer,

View File

@ -1,85 +1,29 @@
$padding: 20px 30px;
.edit-temp-var {
margin: 0 auto;
width: 650px;
}
.edit-temp-var--header {
background-color: $g0-obsidian;
padding: $padding;
display: flex;
align-items: center;
justify-content: space-between;
}
.edit-temp-var--header > h1 {
color: #eeeff2;
letter-spacing: 0;
font-size: 19px;
font-weight: 400;
}
.edit-temp-var--header-controls > button {
display: inline-block;
margin: 0 5px;
}
.edit-temp-var--body {
@include gradient-v($g3-castle, $g1-raven);
padding: $padding;
.delete {
margin: 20px auto 10px auto;
width: 90px;
}
}
.edit-temp-var--body-row {
display: flex;
.name {
flex: 1 1 50%;
padding-right: 10px;
input {
color: $c-potassium;
font-weight: bold;
}
}
.template-type {
flex: 1 1 50%;
padding-left: 10px;
}
.dropdown {
display: block;
width: 100%;
}
.dropdown-toggle, .dropdown-placeholder {
width: 100%;
}
}
.temp-builder {
width: 100%;
}
/*
Create / Edit Template Variable Overlay
------------------------------------------------------------------------------
*/
.temp-builder--mq-controls {
background: $g3-castle;
border-radius: $radius-small;
display: flex;
padding: 10px 10px 0 10px;
padding: 2px 10px;
&:last-child {
padding-bottom: 10px;
&:first-of-type {
padding-top: 10px;
border-top-left-radius: $radius;
border-top-right-radius: $radius;
}
> .temp-builder--mq-text, > .dropdown, .dropdown-placeholder {
margin-right: 5px;
&:last-of-type {
padding-bottom: 10px;
border-bottom-left-radius: $radius;
border-bottom-right-radius: $radius;
}
> .temp-builder--mq-text,
> .dropdown,
.dropdown-placeholder {
margin-right: 4px;
flex-grow: 1;
&:last-child {
@ -92,18 +36,16 @@ $padding: 20px 30px;
@include no-user-select();
background-color: $g5-pepper;
border-radius: $radius-small;
padding: 8px;
padding: 0 8px;
height: 30px;
line-height: 30px;
white-space: nowrap;
color: $c-pool;
font-size: 14px;
font-size: 13px;
font-weight: 600;
font-family: $code-font;
}
.temp-builder .temp-builder-results {
margin-top: 30px;
}
@keyframes pulse {
0% {
color: $g19-ghost;
@ -118,18 +60,21 @@ $padding: 20px 30px;
}
}
.temp-builder-results > p {
.temp-builder--validation {
@include no-user-select();
text-align: center;
font-weight: bold;
color: $g19-ghost;
margin: 15px 0;
font-weight: 500;
color: $g13-mist;
margin: 0 0 8px 0;
&.error {
color: $c-fire;
font-weight: 600;
}
&.warning {
color: $c-pineapple;
font-weight: 600;
}
&.loading {
@ -137,24 +82,34 @@ $padding: 20px 30px;
}
> strong {
color: $c-potassium;
color: $c-comet;
}
&:only-child {
margin-bottom: 0;
}
}
.temp-builder-results--list {
.temp-builder--results {
margin-top: 22px;
}
.temp-builder--results-list {
max-height: 250px;
padding: 0;
margin: 0;
li {
background-color: $g3-castle;
padding: 0 10px;
display: flex;
align-items: center;
border-radius: $radius-small;
margin: 0;
color: $g19-ghost;
font-weight: bold;
list-style: none;
}
}
.temp-builder--results-item {
@include no-user-select();
background-color: $g3-castle;
padding: 0 10px;
display: flex;
align-items: center;
border-radius: $radius;
font-size: 12px;
margin: 0;
color: $g13-mist;
font-weight: 600;
list-style: none;
}

View File

@ -551,12 +551,6 @@ $rule-builder--radius-lg: 5px;
padding-bottom: $rule-builder--padding-lg;
}
}
.endpoint-tab--parameters .faux-form {
margin-left: -6px;
margin-right: -6px;
width: calc(100% + 12px);
display: inline-block;
}
.endpoint-tab--parameters--empty {
align-items: center;
justify-content: center;

View File

@ -8,4 +8,21 @@
@import '../components/time-machine/flux-builder';
@import '../components/time-machine/flux-explorer';
@import '../components/time-machine/visualization';
@import '../components/time-machine/add-func-button';
@import '../components/time-machine/add-func-button';
// Flux Page Empty state
.flux-empty {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
display: inline-flex;
flex-direction: column;
align-items: center;
> p {
color: $g11-sidewalk;
font-size: 16px;
@include no-user-select();
}
}

View File

@ -423,3 +423,42 @@ button.btn-link-alert {
$c-thunder
);
}
/*
Buttons Groups
-----------------------------------------------------------------------------
*/
.btn-group--left,
.btn-group--center,
.btn-group--right {
display: flex;
align-items: center;
}
.btn-group--left > .btn {
margin-right: 4px;
&:last-child {
margin-right: 0;
}
}
.btn-group--center > .btn {
margin-left: 2px;
margin-right: 2px;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
.btn-group--right > .btn {
margin-left: 4px;
&:first-child {
margin-left: 0;
}
}

View File

@ -43,7 +43,7 @@ $grid--col-12: 100%;
}
.form-group-submit {
margin-top: 30px;
margin-top: 22px;
}
.col {
@ -220,3 +220,68 @@ $grid--col-12: 100%;
&-11 { margin-left: $grid--col-11; }
}
}
// Wrapp form sets to ensure proper spacing
// ----------------------------------------------------------------------------
div.faux-form {
width: calc(100% + 12px);
margin-left: -6px;
margin-right: -6px;
display: inline-block;
.form-group.col-xs-1,
.form-group.col-xs-2,
.form-group.col-xs-3,
.form-group.col-xs-4,
.form-group.col-xs-5,
.form-group.col-xs-6,
.form-group.col-xs-7,
.form-group.col-xs-8,
.form-group.col-xs-9,
.form-group.col-xs-10,
.form-group.col-xs-11,
.form-group.col-xs-12,
.form-group.col-sm-1,
.form-group.col-sm-2,
.form-group.col-sm-3,
.form-group.col-sm-4,
.form-group.col-sm-5,
.form-group.col-sm-6,
.form-group.col-sm-7,
.form-group.col-sm-8,
.form-group.col-sm-9,
.form-group.col-sm-10,
.form-group.col-sm-11,
.form-group.col-sm-12,
.form-group.col-md-1,
.form-group.col-md-2,
.form-group.col-md-3,
.form-group.col-md-4,
.form-group.col-md-5,
.form-group.col-md-6,
.form-group.col-md-7,
.form-group.col-md-8,
.form-group.col-md-9,
.form-group.col-md-10,
.form-group.col-md-11,
.form-group.col-md-12,
.form-group.col-lg-1,
.form-group.col-lg-2,
.form-group.col-lg-3,
.form-group.col-lg-4,
.form-group.col-lg-5,
.form-group.col-lg-6,
.form-group.col-lg-7,
.form-group.col-lg-8,
.form-group.col-lg-9,
.form-group.col-lg-10,
.form-group.col-lg-11,
.form-group.col-lg-12 {
padding-left: 6px;
padding-right: 6px;
}
> *:last-child {
margin-bottom: 0;
}
}

View File

@ -484,65 +484,6 @@ $dash-editable-header-padding: 7px;
}
}
/*
Fake form padding without <form>
*/
div.faux-form {
.form-group.col-xs-1,
.form-group.col-xs-2,
.form-group.col-xs-3,
.form-group.col-xs-4,
.form-group.col-xs-5,
.form-group.col-xs-6,
.form-group.col-xs-7,
.form-group.col-xs-8,
.form-group.col-xs-9,
.form-group.col-xs-10,
.form-group.col-xs-11,
.form-group.col-xs-12,
.form-group.col-sm-1,
.form-group.col-sm-2,
.form-group.col-sm-3,
.form-group.col-sm-4,
.form-group.col-sm-5,
.form-group.col-sm-6,
.form-group.col-sm-7,
.form-group.col-sm-8,
.form-group.col-sm-9,
.form-group.col-sm-10,
.form-group.col-sm-11,
.form-group.col-sm-12,
.form-group.col-md-1,
.form-group.col-md-2,
.form-group.col-md-3,
.form-group.col-md-4,
.form-group.col-md-5,
.form-group.col-md-6,
.form-group.col-md-7,
.form-group.col-md-8,
.form-group.col-md-9,
.form-group.col-md-10,
.form-group.col-md-11,
.form-group.col-md-12,
.form-group.col-lg-1,
.form-group.col-lg-2,
.form-group.col-lg-3,
.form-group.col-lg-4,
.form-group.col-lg-5,
.form-group.col-lg-6,
.form-group.col-lg-7,
.form-group.col-lg-8,
.form-group.col-lg-9,
.form-group.col-lg-10,
.form-group.col-lg-11,
.form-group.col-lg-12 {
padding-left: 6px;
padding-right: 6px;
}
}
/*
Stretch to fit Dropdowns
-----------------------------------------------------------------------------

View File

@ -28,20 +28,20 @@ class CSVTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
const pluralizer = templateValues.length === 1 ? '' : 's'
return (
<div className="temp-builder csv-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Comma Separated Values</label>
<div className="temp-builder--mq-controls">
<textarea
className="form-control"
className="form-control input-sm"
value={templateValuesString}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
</div>
</div>
<div className="temp-builder-results">
<p>
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation">
CSV contains <strong>{templateValues.length}</strong> value{
pluralizer
}
@ -50,7 +50,7 @@ class CSVTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
<TemplatePreviewList items={templateValues} />
)}
</div>
</div>
</>
)
}

View File

@ -38,8 +38,8 @@ class DatabasesTemplateBuilder extends PureComponent<
const {databases, databasesStatus} = this.state
return (
<div className="temp-builder databases-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<div className="temp-builder--mq-text">SHOW DATABASES</div>
@ -49,7 +49,7 @@ class DatabasesTemplateBuilder extends PureComponent<
items={databases}
loadingStatus={databasesStatus}
/>
</div>
</>
)
}

View File

@ -77,8 +77,8 @@ class KeysTemplateBuilder extends PureComponent<Props, State> {
} = this.state
return (
<div className="temp-builder measurements-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<div className="temp-builder--mq-text">{queryPrefix}</div>
@ -87,7 +87,8 @@ class KeysTemplateBuilder extends PureComponent<Props, State> {
items={databases.map(text => ({text}))}
onChoose={this.handleChooseDatabaseDropdown}
selected={selectedDatabase}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
<div className="temp-builder--mq-text">FROM</div>
@ -96,13 +97,14 @@ class KeysTemplateBuilder extends PureComponent<Props, State> {
items={measurements.map(text => ({text}))}
onChoose={this.handleChooseMeasurementDropdown}
selected={selectedMeasurement}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
</div>
</div>
<TemplateMetaQueryPreview items={keys} loadingStatus={keysStatus} />
</div>
</>
)
}

View File

@ -57,8 +57,8 @@ class MeasurementsTemplateBuilder extends PureComponent<
} = this.state
return (
<div className="temp-builder measurements-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<div className="temp-builder--mq-text">SHOW MEASUREMENTS ON</div>
@ -67,7 +67,8 @@ class MeasurementsTemplateBuilder extends PureComponent<
items={databases.map(text => ({text}))}
onChoose={this.handleChooseDatabaseDropdown}
selected={selectedDatabase}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
</div>
@ -76,7 +77,7 @@ class MeasurementsTemplateBuilder extends PureComponent<
items={measurements}
loadingStatus={measurementsStatus}
/>
</div>
</>
)
}

View File

@ -58,12 +58,12 @@ class CustomMetaQueryTemplateBuilder extends PureComponent<
const {metaQueryInput} = this.state
return (
<div className="temp-builder csv-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<textarea
className="form-control"
className="form-control input-sm"
value={metaQueryInput}
onChange={this.handleMetaQueryInputChange}
onBlur={this.handleMetaQueryChange}
@ -71,7 +71,7 @@ class CustomMetaQueryTemplateBuilder extends PureComponent<
</div>
</div>
{this.renderResults()}
</div>
</>
)
}
@ -80,8 +80,10 @@ class CustomMetaQueryTemplateBuilder extends PureComponent<
if (this.showInvalidMetaQueryMessage) {
return (
<div className="temp-builder-results">
<p className="error">Meta Query is not valid.</p>
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation error">
Meta Query is not valid.
</p>
</div>
)
}

View File

@ -79,8 +79,8 @@ class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
} = this.state
return (
<div className="temp-builder measurements-temp-builder">
<div className="form-group">
<>
<div className="form-group col-xs-12">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<div className="temp-builder--mq-text">SHOW TAG VALUES ON</div>
@ -89,7 +89,8 @@ class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
items={databases.map(text => ({text}))}
onChoose={this.handleChooseDatabaseDropdown}
selected={selectedDatabase}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
</div>
@ -100,7 +101,8 @@ class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
items={measurements.map(text => ({text}))}
onChoose={this.handleChooseMeasurementDropdown}
selected={selectedMeasurement}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
<div className="temp-builder--mq-text">WITH KEY</div>
@ -109,7 +111,8 @@ class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
items={tagKeys.map(text => ({text}))}
onChoose={this.handleChooseTagKeyDropdown}
selected={selectedTagKey}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</DropdownLoadingPlaceholder>
</div>
@ -118,7 +121,7 @@ class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
items={tagValues}
loadingStatus={tagValuesStatus}
/>
</div>
</>
)
}

View File

@ -2,7 +2,7 @@ import React, {Component} from 'react'
import classnames from 'classnames'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
@ -64,15 +64,13 @@ class TemplateControlBar extends Component<Props, State> {
<strong>Template Variables</strong>
</div>
)}
{isAdding && (
<SimpleOverlayTechnology>
<TemplateVariableEditor
source={source}
onCreate={this.handleCreateTemplate}
onCancel={this.handleCancelAddVariable}
/>
</SimpleOverlayTechnology>
)}
<OverlayTechnology visible={isAdding}>
<TemplateVariableEditor
source={source}
onCreate={this.handleCreateTemplate}
onCancel={this.handleCancelAddVariable}
/>
</OverlayTechnology>
</div>
<Authorized requiredRole={EDITOR_ROLE}>
<button

View File

@ -1,7 +1,7 @@
import React, {PureComponent} from 'react'
import Dropdown from 'src/shared/components/Dropdown'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import {calculateDropdownWidth} from 'src/dashboards/constants/templateControlBar'
import Authorized, {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
@ -76,18 +76,16 @@ class TemplateControlDropdown extends PureComponent<Props, State> {
/>
</label>
</Authorized>
{isEditing && (
<SimpleOverlayTechnology>
<TemplateVariableEditor
template={template}
source={source}
onCreate={onCreateTemplate}
onUpdate={this.handleUpdateTemplate}
onDelete={this.handleDelete}
onCancel={this.handleHideSettings}
/>
</SimpleOverlayTechnology>
)}
<OverlayTechnology visible={isEditing}>
<TemplateVariableEditor
template={template}
source={source}
onCreate={onCreateTemplate}
onUpdate={this.handleUpdateTemplate}
onDelete={this.handleDelete}
onCancel={this.handleHideSettings}
/>
</OverlayTechnology>
</div>
)
}

View File

@ -16,29 +16,33 @@ class TemplateMetaQueryPreview extends PureComponent<Props> {
const {items, loadingStatus} = this.props
if (loadingStatus === RemoteDataState.NotStarted) {
return <div className="temp-builder-results" />
return null
}
if (loadingStatus === RemoteDataState.Loading) {
return (
<div className="temp-builder-results">
<p className="loading">Loading meta query preview...</p>
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation loading">
Loading Meta Query preview...
</p>
</div>
)
}
if (loadingStatus === RemoteDataState.Error) {
return (
<div className="temp-builder-results">
<p className="error">Meta Query failed to execute</p>
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation error">
Meta Query failed to execute
</p>
</div>
)
}
if (items.length === 0) {
return (
<div className="temp-builder-results">
<p className="warning">
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation warning">
Meta Query is syntactically correct but returned no results
</p>
</div>
@ -48,8 +52,8 @@ class TemplateMetaQueryPreview extends PureComponent<Props> {
const pluralizer = items.length === 1 ? '' : 's'
return (
<div className="temp-builder-results">
<p>
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation">
Meta Query returned <strong>{items.length}</strong> value{pluralizer}
</p>
{items.length > 0 && <TemplatePreviewList items={items} />}

View File

@ -4,8 +4,8 @@ import uuid from 'uuid'
import {ErrorHandling} from 'src/shared/decorators/errors'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
const LI_HEIGHT = 35
const LI_MARGIN_BOTTOM = 3
const LI_HEIGHT = 28
const LI_MARGIN_BOTTOM = 2
const RESULTS_TO_DISPLAY = 10
interface Props {
@ -19,13 +19,14 @@ class TemplatePreviewList extends PureComponent<Props> {
return (
<ul
className="temp-builder-results--list"
className="temp-builder--results-list"
style={{height: `${this.resultsListHeight}px`}}
>
<FancyScrollbar>
<FancyScrollbar autoHide={false}>
{items.map(db => {
return (
<li
className="temp-builder--results-item"
key={uuid.v4()}
style={{
height: `${LI_HEIGHT}px`,

View File

@ -7,6 +7,9 @@ import React, {
import {connect} from 'react-redux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import OverlayContainer from 'src/reusable_ui/components/overlays/OverlayContainer'
import OverlayHeading from 'src/reusable_ui/components/overlays/OverlayHeading'
import OverlayBody from 'src/reusable_ui/components/overlays/OverlayBody'
import Dropdown from 'src/shared/components/Dropdown'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import {getDeep} from 'src/utils/wrappers'
@ -63,7 +66,7 @@ const TEMPLATE_BUILDERS = {
[TemplateType.MetaQuery]: MetaQueryTemplateBuilder,
}
const formatName = name => `:${name.replace(/:/g, '')}:`
const formatName = name => `:${name.replace(/:/g, '').replace(/\s/g, '')}:`
const DEFAULT_TEMPLATE = DEFAULT_TEMPLATES[TemplateType.Databases]
@ -100,19 +103,18 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
const TemplateBuilder = this.templateBuilder
return (
<div className="edit-temp-var">
<div className="edit-temp-var--header">
<h1>{isNew ? 'Create' : 'Edit'} Template Variable</h1>
<div className="edit-temp-var--header-controls">
<OverlayContainer maxWidth={650}>
<OverlayHeading title={this.title}>
<div className="btn-group--right">
<button
className="btn btn-default"
className="btn btn-default btn-sm"
type="button"
onClick={onCancel}
>
Cancel
</button>
<button
className="btn btn-success"
className="btn btn-success btn-sm"
type="button"
onClick={this.handleSave}
disabled={!this.canSave}
@ -120,50 +122,72 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
{this.saveButtonText}
</button>
</div>
</div>
<div className="edit-temp-var--body">
<div className="edit-temp-var--body-row">
<div className="form-group name">
</OverlayHeading>
<OverlayBody>
<div className="faux-form">
<div className="form-group col-sm-6">
<label>Name</label>
<input
type="text"
className="form-control"
className="form-control input-sm form-astronaut"
value={nextTemplate.tempVar}
onChange={this.handleChangeName}
onKeyPress={this.handleNameKeyPress}
onBlur={this.formatName}
spellCheck={false}
/>
</div>
<div className="form-group template-type">
<div className="form-group col-sm-6">
<label>Type</label>
<Dropdown
items={TEMPLATE_TYPES_LIST}
onChoose={this.handleChooseType}
selected={this.dropdownSelection}
buttonSize=""
buttonSize="btn-sm"
className="dropdown-stretch"
/>
</div>
</div>
<div className="edit-temp-var--body-row">
<TemplateBuilder
template={nextTemplate}
source={source}
onUpdateTemplate={this.handleUpdateTemplate}
/>
<div className="form-group text-center form-group-submit col-xs-12">
<ConfirmButton
text={this.confirmText}
confirmAction={this.handleDelete}
type="btn-danger"
size="btn-sm"
customClass="delete"
disabled={isNew || this.isDeleting}
/>
</div>
</div>
<ConfirmButton
text={this.isDeleting ? 'Deleting...' : 'Delete'}
confirmAction={this.handleDelete}
type="btn-danger"
size="btn-xs"
customClass="delete"
disabled={isNew || this.isDeleting}
/>
</div>
</div>
</OverlayBody>
</OverlayContainer>
)
}
private get confirmText(): string {
if (this.isDeleting) {
return 'Deleting...'
}
return 'Delete'
}
private get title(): string {
const {isNew} = this.state
let prefix = 'Edit'
if (isNew) {
prefix = 'Create'
}
return `${prefix} Template Variable`
}
private get templateBuilder(): ComponentClass<TemplateBuilderProps> {
const {
nextTemplate: {type},

View File

@ -4,7 +4,7 @@ import {shallow} from 'enzyme'
import TemplateControlBar from 'src/tempVars/components/TemplateControlBar'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import {source} from 'test/resources'
import {TemplateType, TemplateValueType} from 'src/types'
@ -61,14 +61,24 @@ describe('TemplateControlBar', () => {
it('renders an TemplateVariableEditor overlay when adding a template variable', () => {
const props = {...defaultProps}
const wrapper = shallow(<TemplateControlBar {...props} />)
const wrapper = shallow(<TemplateControlBar {...props} />, {
context: {
store: {},
},
})
expect(wrapper.find(SimpleOverlayTechnology)).toHaveLength(0)
const children = wrapper
.find(OverlayTechnology)
.dive()
.find("[data-test='overlay-children']")
.children()
expect(children).toHaveLength(0)
wrapper.find('[data-test="add-template-variable"]').simulate('click')
const elements = wrapper
.find(SimpleOverlayTechnology)
.find(OverlayTechnology)
.dive()
.find(TemplateVariableEditor)

View File

@ -1,7 +1,7 @@
import React from 'react'
import {shallow} from 'enzyme'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import {source} from 'test/resources'
@ -33,14 +33,24 @@ const defaultProps = {
describe('TemplateControlDropdown', () => {
it('should show a TemplateVariableEditor overlay when the settings icon is clicked', () => {
const wrapper = shallow(<TemplateControlDropdown {...defaultProps} />)
const wrapper = shallow(<TemplateControlDropdown {...defaultProps} />, {
context: {
store: {},
},
})
expect(wrapper.find(SimpleOverlayTechnology)).toHaveLength(0)
const children = wrapper
.find(OverlayTechnology)
.dive()
.find("[data-test='overlay-children']")
.children()
expect(children).toHaveLength(0)
wrapper.find("[data-test='edit']").simulate('click')
const elements = wrapper
.find(SimpleOverlayTechnology)
.find(OverlayTechnology)
.dive()
.find(TemplateVariableEditor)