fix: cell errors (#14168)
* fix: add tooltip for error message * wip: styles for error * chore: christmas time * feat: introduce error formatting * style(veo): scrollable errors * style(cell): error tooltip * chore: remove comment * style: put styles on childrenpull/14176/head
parent
89acf847ea
commit
5dfba47d15
|
@ -4,20 +4,89 @@ $box-tooltip--caret-size: 6px;
|
|||
position: fixed;
|
||||
visibility: hidden;
|
||||
z-index: $z--dygraph-legend;
|
||||
border: $ix-border solid $c-pool;
|
||||
}
|
||||
|
||||
.box-tooltip--contents {
|
||||
border-width: $ix-border;
|
||||
border-style: solid;
|
||||
background-color: $g1-raven;
|
||||
border-radius: $radius;
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
width: calc(100% - #{$box-tooltip--caret-size * 2});
|
||||
|
||||
pre {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.box-tooltip.left .box-tooltip--caret {
|
||||
.box-tooltip__primary {
|
||||
.box-tooltip--contents {
|
||||
border-color: $c-pool;
|
||||
}
|
||||
|
||||
&.right .box-tooltip--caret {
|
||||
border-right-color: $c-pool;
|
||||
}
|
||||
|
||||
&.left .box-tooltip--caret {
|
||||
border-left-color: $c-pool;
|
||||
}
|
||||
}
|
||||
|
||||
.box-tooltip__danger {
|
||||
.box-tooltip--contents {
|
||||
border-color: $c-dreamsicle;
|
||||
}
|
||||
|
||||
&.right .box-tooltip--caret {
|
||||
border-right-color: $c-dreamsicle;
|
||||
}
|
||||
|
||||
&.left .box-tooltip--caret {
|
||||
border-left-color: $c-dreamsicle;
|
||||
}
|
||||
}
|
||||
|
||||
.box-tooltip--caret {
|
||||
position: absolute;
|
||||
right: -$box-tooltip--caret-size;
|
||||
transform: translate(0,-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: $box-tooltip--caret-size solid transparent;
|
||||
border-bottom: $box-tooltip--caret-size solid transparent;
|
||||
border-left: $box-tooltip--caret-size solid $c-pool;
|
||||
border: $box-tooltip--caret-size solid transparent;
|
||||
transform: translate(0,-50%);
|
||||
}
|
||||
|
||||
.box-tooltip--caret-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: $box-tooltip--caret-size * 2;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box-tooltip.left {
|
||||
.box-tooltip--contents {
|
||||
margin-right: $box-tooltip--caret-size * 2;
|
||||
}
|
||||
.box-tooltip--caret-container {
|
||||
right: 0;
|
||||
}
|
||||
.box-tooltip--caret {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.box-tooltip.right {
|
||||
.box-tooltip--contents {
|
||||
margin-left: $box-tooltip--caret-size * 2;
|
||||
}
|
||||
.box-tooltip--caret-container {
|
||||
left: 0;
|
||||
}
|
||||
.box-tooltip--caret {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,22 @@ import {createPortal} from 'react-dom'
|
|||
// Constants
|
||||
import {TOOLTIP_PORTAL_ID} from 'src/portals/TooltipPortal'
|
||||
|
||||
// Types
|
||||
import {ComponentColor} from '@influxdata/clockface'
|
||||
|
||||
interface Props {
|
||||
triggerRect: DOMRect
|
||||
children: JSX.Element
|
||||
maxWidth?: number
|
||||
color?: ComponentColor
|
||||
}
|
||||
|
||||
const BoxTooltip: FunctionComponent<Props> = ({triggerRect, children}) => {
|
||||
const BoxTooltip: FunctionComponent<Props> = ({
|
||||
triggerRect,
|
||||
children,
|
||||
maxWidth = 300,
|
||||
color = ComponentColor.Primary,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Position the tooltip after it has rendered, taking into account its size
|
||||
|
@ -23,8 +33,15 @@ const BoxTooltip: FunctionComponent<Props> = ({triggerRect, children}) => {
|
|||
|
||||
const rect = el.getBoundingClientRect()
|
||||
|
||||
// Always position the tooltip to the left of the trigger position
|
||||
const left = triggerRect.left - rect.width
|
||||
let left = triggerRect.left - rect.width
|
||||
let caretClassName = 'left'
|
||||
|
||||
// If the width of the tooltip causes it to overflow left
|
||||
// position it to the right of the trigger element
|
||||
if (left < 0) {
|
||||
left = triggerRect.left + triggerRect.width
|
||||
caretClassName = 'right'
|
||||
}
|
||||
|
||||
// Attempt to position the vertical midpoint of tooltip next to the
|
||||
// vertical midpoint of the trigger rectangle
|
||||
|
@ -44,7 +61,7 @@ const BoxTooltip: FunctionComponent<Props> = ({triggerRect, children}) => {
|
|||
|
||||
el.setAttribute(
|
||||
'style',
|
||||
`visibility: visible; top: ${top}px; left: ${left}px;`
|
||||
`visibility: visible; top: ${top}px; left: ${left}px; max-width: ${maxWidth}px;`
|
||||
)
|
||||
|
||||
// Position the caret (little arrow on the side of the tooltip) so that it
|
||||
|
@ -55,12 +72,20 @@ const BoxTooltip: FunctionComponent<Props> = ({triggerRect, children}) => {
|
|||
'style',
|
||||
`top: ${caretTop}px;`
|
||||
)
|
||||
|
||||
el.className = `box-tooltip box-tooltip__${color} ${caretClassName}`
|
||||
})
|
||||
|
||||
return createPortal(
|
||||
<div className="box-tooltip left" ref={ref}>
|
||||
<div
|
||||
className={`box-tooltip box-tooltip__${color}`}
|
||||
ref={ref}
|
||||
style={{maxWidth}}
|
||||
>
|
||||
{children}
|
||||
<div className="box-tooltip--caret" />
|
||||
<div className="box-tooltip--caret-container">
|
||||
<div className="box-tooltip--caret" />
|
||||
</div>
|
||||
</div>,
|
||||
document.querySelector(`#${TOOLTIP_PORTAL_ID}`)
|
||||
)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.empty-graph-error {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: $radius;
|
||||
background: $g2-kevlar;
|
||||
position: relative;
|
||||
color: $c-dreamsicle;
|
||||
border: $ix-border solid $g5-pepper;
|
||||
|
||||
pre {
|
||||
pre {
|
||||
font-size: 14px;
|
||||
padding: $ix-marg-c;
|
||||
user-select: text !important;
|
||||
|
@ -13,19 +14,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.empty-graph-error--icon {
|
||||
.empty-graph-error--icon {
|
||||
display: inline-block;
|
||||
margin-right: $ix-marg-b;
|
||||
}
|
||||
|
||||
.empty-graph-error--copy {
|
||||
position: absolute;
|
||||
top: $ix-marg-b;
|
||||
right: $ix-marg-b;
|
||||
opacity: 0.9;
|
||||
display: none;
|
||||
|
||||
.empty-graph-error:hover & {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// Libraries
|
||||
import React, {useState, useRef, FunctionComponent} from 'react'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
|
||||
// Components
|
||||
import {
|
||||
Button,
|
||||
ComponentSize,
|
||||
ComponentColor,
|
||||
IconFont,
|
||||
} from '@influxdata/clockface'
|
||||
import BoxTooltip from 'src/shared/components/BoxTooltip'
|
||||
|
||||
interface Props {
|
||||
message: string
|
||||
testID?: string
|
||||
}
|
||||
|
||||
const EmptyGraphError: FunctionComponent<Props> = ({message, testID}) => {
|
||||
const [didCopy, setDidCopy] = useState(false)
|
||||
|
||||
const buttonText = didCopy ? 'Copied!' : 'Copy'
|
||||
const buttonColor = didCopy ? ComponentColor.Success : ComponentColor.Default
|
||||
|
||||
const onClick = () => {
|
||||
setDidCopy(true)
|
||||
setTimeout(() => setDidCopy(false), 2000)
|
||||
}
|
||||
|
||||
const trigger = useRef<HTMLDivElement>(null)
|
||||
const [tooltipVisible, setTooltipVisible] = useState(false)
|
||||
|
||||
let triggerRect: DOMRect = null
|
||||
|
||||
if (trigger.current) {
|
||||
triggerRect = trigger.current.getBoundingClientRect() as DOMRect
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cell--view-empty error" data-testid={testID}>
|
||||
<div
|
||||
onMouseEnter={() => setTooltipVisible(true)}
|
||||
onMouseLeave={() => setTooltipVisible(false)}
|
||||
ref={trigger}
|
||||
>
|
||||
<span
|
||||
className={`icon ${IconFont.AlertTriangle} empty-graph-error--icon`}
|
||||
/>
|
||||
{tooltipVisible && (
|
||||
<BoxTooltip
|
||||
triggerRect={triggerRect as DOMRect}
|
||||
color={ComponentColor.Danger}
|
||||
>
|
||||
<div className="box-tooltip--contents">
|
||||
<pre>
|
||||
<CopyToClipboard text={message}>
|
||||
<Button
|
||||
size={ComponentSize.ExtraSmall}
|
||||
color={buttonColor}
|
||||
titleText={buttonText}
|
||||
text={buttonText}
|
||||
onClick={onClick}
|
||||
className="empty-graph-error--copy"
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
<code>{message}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</BoxTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmptyGraphError
|
|
@ -3,6 +3,7 @@ import React, {PureComponent} from 'react'
|
|||
|
||||
// Components
|
||||
import EmptyGraphMessage from 'src/shared/components/EmptyGraphMessage'
|
||||
import EmptyGraphErrorTooltip from 'src/shared/components/EmptyGraphErrorTooltip'
|
||||
import EmptyGraphError from 'src/shared/components/EmptyGraphError'
|
||||
import Markdown from 'src/shared/components/views/Markdown'
|
||||
|
||||
|
@ -13,8 +14,14 @@ import {emptyGraphCopy} from 'src/shared/copy/cell'
|
|||
import {RemoteDataState} from 'src/types'
|
||||
import {DashboardQuery} from 'src/types'
|
||||
|
||||
export enum ErrorFormat {
|
||||
Tooltip = 'tooltip',
|
||||
Scroll = 'scroll',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
errorMessage: string
|
||||
errorFormat: ErrorFormat
|
||||
isInitialFetch: boolean
|
||||
loading: RemoteDataState
|
||||
hasResults: boolean
|
||||
|
@ -31,6 +38,7 @@ export default class EmptyQueryView extends PureComponent<Props> {
|
|||
queries,
|
||||
fallbackNote,
|
||||
hasResults,
|
||||
errorFormat,
|
||||
} = this.props
|
||||
|
||||
if (loading === RemoteDataState.NotStarted || !queries.length) {
|
||||
|
@ -43,9 +51,18 @@ export default class EmptyQueryView extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<EmptyGraphError message={errorMessage} testID="empty-graph--error" />
|
||||
)
|
||||
if (errorFormat === ErrorFormat.Tooltip)
|
||||
return (
|
||||
<EmptyGraphErrorTooltip
|
||||
message={errorMessage}
|
||||
testID="empty-graph--error"
|
||||
/>
|
||||
)
|
||||
|
||||
if (errorFormat === ErrorFormat.Scroll)
|
||||
return (
|
||||
<EmptyGraphError message={errorMessage} testID="empty-graph--error" />
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
@ -4,7 +4,7 @@ import {connect} from 'react-redux'
|
|||
|
||||
// Components
|
||||
import TimeSeries from 'src/shared/components/TimeSeries'
|
||||
import EmptyQueryView from 'src/shared/components/EmptyQueryView'
|
||||
import EmptyQueryView, {ErrorFormat} from 'src/shared/components/EmptyQueryView'
|
||||
import ViewSwitcher from 'src/shared/components/ViewSwitcher'
|
||||
|
||||
// Utils
|
||||
|
@ -73,6 +73,7 @@ class RefreshingView extends PureComponent<Props, State> {
|
|||
{({giraffeResult, files, loading, errorMessage, isInitialFetch}) => {
|
||||
return (
|
||||
<EmptyQueryView
|
||||
errorFormat={ErrorFormat.Tooltip}
|
||||
errorMessage={errorMessage}
|
||||
hasResults={checkResultsLength(giraffeResult)}
|
||||
loading={loading}
|
||||
|
|
|
@ -35,6 +35,11 @@ $cell--header-size: 36px;
|
|||
}
|
||||
}
|
||||
|
||||
.empty-graph-error--copy {
|
||||
z-index: 1;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.cell--view-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -46,22 +51,10 @@ $cell--header-size: 36px;
|
|||
@extend %no-user-select;
|
||||
padding: 0 $ix-marg-d $ix-marg-d $ix-marg-d;
|
||||
overflow: hidden;
|
||||
|
||||
.empty-graph-error {
|
||||
position: absolute;
|
||||
top: $ix-marg-c;
|
||||
right: $ix-marg-c;
|
||||
bottom: $ix-marg-c;
|
||||
left: $ix-marg-c;
|
||||
}
|
||||
|
||||
.empty-graph-error--scroll {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.empty-graph-error--copy {
|
||||
z-index: 1;
|
||||
&.error {
|
||||
font-size: 40px;
|
||||
color: $c-dreamsicle;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +101,7 @@ $cell--header-size: 36px;
|
|||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
|
||||
.cell--header-note & {
|
||||
padding-left: $cell--header-size;
|
||||
}
|
||||
|
|
|
@ -49,4 +49,8 @@
|
|||
> .single-stat {
|
||||
border-radius: 0 0 $radius $radius;
|
||||
}
|
||||
|
||||
> .cell--view-empty {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {FromFluxResult} from '@influxdata/giraffe'
|
|||
import {AutoSizer} from 'react-virtualized'
|
||||
|
||||
// Components
|
||||
import EmptyQueryView from 'src/shared/components/EmptyQueryView'
|
||||
import EmptyQueryView, {ErrorFormat} from 'src/shared/components/EmptyQueryView'
|
||||
import ViewSwitcher from 'src/shared/components/ViewSwitcher'
|
||||
import RawFluxDataTable from 'src/timeMachine/components/RawFluxDataTable'
|
||||
|
||||
|
@ -68,11 +68,12 @@ const TimeMachineVis: SFC<Props> = ({
|
|||
return (
|
||||
<div className="time-machine--view">
|
||||
<EmptyQueryView
|
||||
errorMessage={errorMessage}
|
||||
hasResults={checkResultsLength(giraffeResult)}
|
||||
loading={loading}
|
||||
errorFormat={ErrorFormat.Scroll}
|
||||
errorMessage={errorMessage}
|
||||
isInitialFetch={isInitialFetch}
|
||||
queries={viewProperties.queries}
|
||||
hasResults={checkResultsLength(giraffeResult)}
|
||||
>
|
||||
{isViewingRawData ? (
|
||||
<AutoSizer>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
align-items: center;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
dt {
|
||||
background-color: $g6-smoke;
|
||||
font-weight: 600;
|
||||
|
@ -46,17 +46,6 @@
|
|||
padding: 15px;
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--tooltip-contents {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
article {
|
||||
margin-bottom: $ix-marg-c;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--heading {
|
||||
font-weight: 600;
|
||||
margin-bottom: $ix-marg-a;
|
||||
|
|
|
@ -21,7 +21,7 @@ const FunctionTooltipContents: FunctionComponent<Props> = ({
|
|||
func: {desc, args, example, link},
|
||||
}) => {
|
||||
return (
|
||||
<div className="flux-functions-toolbar--tooltip-contents">
|
||||
<div className="box-tooltip--contents">
|
||||
<FancyScrollbar autoHeight={true} maxHeight={MAX_HEIGHT} autoHide={false}>
|
||||
<TooltipDescription description={desc} />
|
||||
<TooltipArguments argsList={args} />
|
||||
|
|
|
@ -59,7 +59,7 @@ const VariableTooltipContents: FunctionComponent<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="variable-tooltip--contents" onMouseEnter={handleMouseEnter}>
|
||||
<div className="box-tooltip--contents" onMouseEnter={handleMouseEnter}>
|
||||
<Form.Element label="Value">
|
||||
<Dropdown
|
||||
selectedID={get(values, 'selectedValue')}
|
||||
|
|
Loading…
Reference in New Issue