refactor(ui): Polish appearance of Alerting UI (#14602)

* refactor(ui): polish appearance of alerting index view

* refactor(ui): ensure alert check editor header has proper padding

* refactor(ui): emphasize alerting button more

* refactor(ui): style check builder control bar

* refactor(ui): allow buidler card to have a customizable width

* refactor(ui): expose autohide prop from dapperscrollbars within buildercardbody

* refactor(ui): allow dashed button to have configurable color and size

* refactor(ui): add placeholder buttons in to thresholds card

* refactor(ui): add custom styles for alert builder cards

* refactor(ui): increase height of status message template

* refactor(ui): add empty state to matching rules card

* fix(ui): reconnect checks list to redux

* refactor(ui): use more appropriate copy in endpoints empty state
pull/14603/head
alexpaxton 2019-08-08 16:45:48 -07:00 committed by GitHub
parent e66bc650c4
commit fb94fd8ffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 383 additions and 122 deletions

View File

@ -0,0 +1,30 @@
.alerting-index {
> .page-contents > .container-fluid,
> .page-contents > .container-fluid > .row,
> .page-contents > .container-fluid > .row > .col-xs-12,
> .page-contents > .container-fluid > .row > .col-xs-12 > .cf-grid--container,
> .page-contents > .container-fluid > .row > .col-xs-12 > .cf-grid--container > .cf-grid--row,
> .page-contents > .container-fluid > .row > .col-xs-12 > .cf-grid--container > .cf-grid--row > .cf-grid--column {
height: 100%;
}
}
.alerting-index--column {
height: 100%;
}
.alert-column--empty {
min-height: 160px;
justify-content: center;
}
.alerting-index--column-body {
flex: 1 0 0;
position: relative;
}
.alerting-index--list,
.alerting-index--search {
padding: 18px;
padding-top: 0;
}

View File

@ -0,0 +1,62 @@
// Libraries
import React, {FC} from 'react'
// Components
import {
Button,
ComponentColor,
Panel,
InfluxColors,
DapperScrollbars,
Input,
IconFont,
} from '@influxdata/clockface'
interface Props {
title: string
testID?: string
onCreate: () => void
}
const AlertsColumnHeader: FC<Props> = ({
children,
onCreate,
title,
testID = '',
}) => {
return (
<Panel
backgroundColor={InfluxColors.Kevlar}
className="alerting-index--column"
>
<Panel.Header title={title}>
<Button
text="Create"
icon={IconFont.Plus}
onClick={onCreate}
color={ComponentColor.Primary}
testID={`alert-column--header ${testID}`}
/>
</Panel.Header>
<div className="alerting-index--search">
<Input
icon={IconFont.Search}
placeholder={`Filter ${title}...`}
value=""
onChange={() => {}}
/>
</div>
<div className="alerting-index--column-body">
<DapperScrollbars
autoSize={false}
autoHide={true}
style={{width: '100%', height: '100%'}}
>
<div className="alerting-index--list">{children}</div>
</DapperScrollbars>
</div>
</Panel>
)
}
export default AlertsColumnHeader

View File

@ -1,38 +0,0 @@
// Libraries
import React, {FC} from 'react'
// Components
import {
Button,
IconFont,
ComponentSpacer,
JustifyContent,
AlignItems,
FlexDirection,
} from '@influxdata/clockface'
interface Props {
title: string
testID?: string
onCreate: () => void
}
const AlertsColumnHeader: FC<Props> = ({onCreate, title, testID = ''}) => {
return (
<ComponentSpacer
direction={FlexDirection.Row}
justifyContent={JustifyContent.SpaceBetween}
alignItems={AlignItems.Center}
>
<div>{title}</div>
<Button
text="Create"
icon={IconFont.AddCell}
onClick={onCreate}
testID={`alert-column--header ${testID}`}
/>
</ComponentSpacer>
)
}
export default AlertsColumnHeader

View File

@ -3,7 +3,7 @@ import React, {FunctionComponent} from 'react'
import {connect} from 'react-redux'
// Components
import {Button} from '@influxdata/clockface'
import {Button, ComponentColor, IconFont} from '@influxdata/clockface'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
@ -28,23 +28,25 @@ const CheckAlertingButton: FunctionComponent<Props> = ({
setActiveTab,
activeTab,
}) => {
const handleClickAlerting = () => {
setActiveTab('alerting')
}
const handleClickQueries = () => {
setActiveTab('queries')
}
if (activeTab === 'alerting') {
return <Button text="Queries" onClick={handleClickQueries} />
const handleClick = () => {
if (activeTab === 'alerting') {
setActiveTab('queries')
} else {
setActiveTab('alerting')
}
}
return (
<Button
icon={IconFont.BellSolid}
color={
activeTab === 'alerting'
? ComponentColor.Secondary
: ComponentColor.Default
}
titleText="Add alerting to this query"
text="Alerting"
onClick={handleClickAlerting}
onClick={handleClick}
/>
)
}

View File

@ -29,7 +29,7 @@ const CheckCards: FunctionComponent<Props> = ({checks}) => {
const EmptyChecksList: FunctionComponent = () => {
return (
<EmptyState size={ComponentSize.ExtraSmall}>
<EmptyState size={ComponentSize.ExtraSmall} className="alert-column--empty">
<EmptyState.Text
text="Looks like you dont have any Checks , why not create one?"
highlightWords={['Checks']}

View File

@ -5,7 +5,7 @@ import {connect} from 'react-redux'
// Components
import CheckCards from 'src/alerting/components/CheckCards'
import AlertsColumnHeader from 'src/alerting/components/AlertsColumnHeader'
import AlertsColumnHeader from 'src/alerting/components/AlertsColumn'
// Types
import {Check, AppState} from 'src/types'
@ -25,10 +25,9 @@ const ChecksColumn: FunctionComponent<Props> = ({
router.push(`/orgs/${orgID}/alerting/checks/new`)
}
return (
<>
<AlertsColumnHeader title="Checks" onCreate={handleClick} />
<AlertsColumnHeader title="Checks" onCreate={handleClick}>
<CheckCards checks={checks} />
</>
</AlertsColumnHeader>
)
}

View File

@ -1,7 +1,28 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import {EmptyState, ComponentSize} from '@influxdata/clockface'
import AlertsColumn from 'src/alerting/components/AlertsColumn'
const EndpointsColumn: FunctionComponent = () => {
return <div>Endpoints</div>
return (
<AlertsColumn
title="Endpoints"
testID="create-endpoint"
onCreate={() => {}}
>
<EmptyState
size={ComponentSize.ExtraSmall}
className="alert-column--empty"
>
<EmptyState.Text
text="Looks like you dont have any Endpoints , why not create one?"
highlightWords={['Endpoints']}
/>
</EmptyState>
</AlertsColumn>
)
}
export default EndpointsColumn

View File

@ -0,0 +1,29 @@
/*
Alert Builder Styles
------------------------------------------------------------------------------
*/
.alert-builder--card {
.builder-card--contents {
min-height: 100%;
padding: 16px;
}
.builder-card--header {
justify-content: center;
}
}
.cf-empty-state.alert-builder--card__empty {
padding: 32px;
height: 100%;
justify-content: center;
}
.alert-builder--message-template {
.cf-text-area--container.cf-input-md {
height: auto;
}
textarea {
height: 180px;
}
}

View File

@ -14,19 +14,31 @@ const AlertBuilder: FC = () => {
<div className="query-builder--cards">
<FancyScrollbar>
<div className="builder-card--list">
<BuilderCard testID="builder-meta">
<BuilderCard
testID="builder-meta"
widthPixels={340}
className="alert-builder--card"
>
<BuilderCard.Header title="Meta" />
<BuilderCard.Body addPadding={true}>
<BuilderCard.Body addPadding={true} autoHideScrollbars={true}>
<CheckMetaCard />
</BuilderCard.Body>
</BuilderCard>
<BuilderCard testID="builder-meta">
<BuilderCard
testID="builder-meta"
widthPixels={510}
className="alert-builder--card"
>
<BuilderCard.Header title="Thresholds" />
<BuilderCard.Body addPadding={true}>
<CheckThresholdsCard />
</BuilderCard.Body>
</BuilderCard>
<BuilderCard testID="builder-meta">
<BuilderCard
testID="builder-meta"
widthPixels={420}
className="alert-builder--card"
>
<BuilderCard.Header title="Matching Notification Rules" />
<BuilderCard.Body addPadding={true}>
<CheckMatchingRulesCard />

View File

@ -1,8 +1,19 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import {EmptyState, ComponentSize} from '@influxdata/clockface'
const CheckMatchingRulesCard: FunctionComponent = () => {
return <div>Matching Rules go here</div>
return (
<EmptyState
size={ComponentSize.Small}
className="alert-builder--card__empty"
>
<EmptyState.Text text="Notification Rules configured to act on tag sets matching this Alert Check will automatically show up here" />
<EmptyState.Text text="Looks like no notification rules match the tag set defined in this Alert Check" />
</EmptyState>
)
}
export default CheckMatchingRulesCard

View File

@ -108,7 +108,10 @@ const CheckMetaCard: FC<Props> = ({updateCurrentCheck, check}) => {
value={check.name}
/>
</Form.Element>
<Form.Element label="Status Message Template">
<Form.Element
label="Status Message Template"
className="alert-builder--message-template"
>
<TextArea
autoFocus={false}
autocomplete={AutoComplete.Off}

View File

@ -1,8 +1,43 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import {
ComponentSpacer,
FlexDirection,
AlignItems,
ComponentColor,
ComponentSize,
} from '@influxdata/clockface'
import DashedButton from 'src/shared/components/dashed_button/DashedButton'
const CheckThresholdsCard: FunctionComponent = () => {
return <div>Thresholds go here</div>
return (
<ComponentSpacer
direction={FlexDirection.Column}
alignItems={AlignItems.Stretch}
margin={ComponentSize.Medium}
>
<DashedButton
text="+ INFO"
color={ComponentColor.Success}
size={ComponentSize.Large}
onClick={() => {}}
/>
<DashedButton
text="+ WARN"
color={ComponentColor.Warning}
size={ComponentSize.Large}
onClick={() => {}}
/>
<DashedButton
text="+ CRIT"
color={ComponentColor.Danger}
size={ComponentSize.Large}
onClick={() => {}}
/>
</ComponentSpacer>
)
}
export default CheckThresholdsCard

View File

@ -15,24 +15,22 @@ interface Props {
const NotificationRuleCards: FunctionComponent<Props> = ({rules}) => {
return (
<>
<ResourceList>
<ResourceList.Body emptyState={<EmptyNotificationRulesList />}>
{rules.map(nr => (
<NotificationRuleCard key={nr.id} rule={nr} />
))}
</ResourceList.Body>
</ResourceList>
</>
<ResourceList>
<ResourceList.Body emptyState={<EmptyNotificationRulesList />}>
{rules.map(nr => (
<NotificationRuleCard key={nr.id} rule={nr} />
))}
</ResourceList.Body>
</ResourceList>
)
}
const EmptyNotificationRulesList: FunctionComponent = () => {
return (
<EmptyState size={ComponentSize.ExtraSmall}>
<EmptyState size={ComponentSize.ExtraSmall} className="alert-column--empty">
<EmptyState.Text
text="Looks like you dont have any Notification Rules, why not create one?"
highlightWords={['Notification Rules']}
text="Looks like you dont have any Notification Rules , why not create one?"
highlightWords={['Notification', 'Rules']}
/>
</EmptyState>
)

View File

@ -8,6 +8,7 @@ import {
ComponentSpacer,
FlexDirection,
ComponentSize,
ComponentColor,
AlignItems,
} from '@influxdata/clockface'
import StatusRuleComponent from 'src/alerting/components/notifications/StatusRule'
@ -57,7 +58,12 @@ const RuleConditions: FC<Props> = ({rule}) => {
>
{statuses}
{tags}
<DashedButton text="+ Tag Rule" onClick={addTagRule} />
<DashedButton
text="+ Tag Rule"
onClick={addTagRule}
color={ComponentColor.Primary}
size={ComponentSize.Small}
/>
</ComponentSpacer>
</Grid.Column>
<Grid.Column>

View File

@ -5,8 +5,10 @@ import {withRouter, WithRouterProps} from 'react-router'
// Types
import {NotificationRuleDraft, AppState} from 'src/types'
// Components
import NotificationRuleCards from 'src/alerting/components/notifications/RuleCards'
import AlertsColumnHeader from 'src/alerting/components/AlertsColumnHeader'
import AlertsColumn from 'src/alerting/components/AlertsColumn'
interface StateProps {
rules: NotificationRuleDraft[]
@ -25,14 +27,13 @@ const NotificationRulesColumn: FunctionComponent<Props> = ({
}
return (
<>
<AlertsColumnHeader
title="Notification Rules"
testID="create-rule"
onCreate={handleOpenOverlay}
/>
<AlertsColumn
title="Notification Rules"
testID="create-rule"
onCreate={handleOpenOverlay}
>
<NotificationRuleCards rules={rules} />
</>
</AlertsColumn>
)
}

View File

@ -2,7 +2,8 @@
import React, {FunctionComponent} from 'react'
//Components
import {Page, Grid, GridRow, GridColumn} from '@influxdata/clockface'
import {Grid, GridRow, GridColumn} from '@influxdata/clockface'
import {Page} from 'src/pageLayout'
import PageTitleWithOrg from 'src/shared/components/PageTitleWithOrg'
import ChecksColumn from 'src/alerting/components/ChecksColumn'
import RulesColumn from 'src/alerting/components/notifications/RulesColumn'
@ -12,30 +13,33 @@ import GetResources, {ResourceTypes} from 'src/shared/components/GetResources'
const AlertingIndex: FunctionComponent = ({children}) => {
return (
<>
<Page titleTag="Alerting">
<Page titleTag="Alerting" className="alerting-index">
<Page.Header fullWidth={false}>
<Page.Header.Left>
<PageTitleWithOrg title="Alerting" />
</Page.Header.Left>
<Page.Header.Right />
</Page.Header>
<Page.Contents fullWidth={true} scrollable={true}>
<Grid>
<GridRow testID="grid--row">
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<GetResources resource={ResourceTypes.Checks}>
<ChecksColumn />
</GetResources>
</GridColumn>
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<GetResources resource={ResourceTypes.NotificationRules}>
<RulesColumn />
</GetResources>
</GridColumn>
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<EndpointsColumn />
</GridColumn>
</GridRow>
</Grid>
<Page.Contents fullWidth={false} scrollable={false}>
<div className="col-xs-12">
<Grid>
<GridRow testID="grid--row">
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<GetResources resource={ResourceTypes.Checks}>
<ChecksColumn />
</GetResources>
</GridColumn>
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<GetResources resource={ResourceTypes.NotificationRules}>
<RulesColumn />
</GetResources>
</GridColumn>
<GridColumn widthLG={4} widthMD={4} widthSM={4} widthXS={12}>
<EndpointsColumn />
</GridColumn>
</GridRow>
</Grid>
</div>
</Page.Contents>
</Page>
{children}

View File

@ -13,6 +13,7 @@
border-radius: 0 0 $radius $radius;
.page-header--container,
.cf-page-header--container,
.veo-contents {
padding-left: $ix-marg-d;
padding-right: $ix-marg-d;

View File

@ -1,12 +1,10 @@
.dashed-button {
width: 100%;
border: $ix-border dashed $g4-onyx;
border: $ix-border dashed $g5-pepper;
background-color: rgba($g5-pepper, 0);
color: $g9-mountain;
height: 54px;
transition: background-color 0.25s ease, color 0.25s ease,
border-color 0.25s ease;
font-size: 13px;
border-color 0.25s ease;
font-weight: 700;
outline: none;
border-radius: $ix-radius;
@ -17,9 +15,64 @@
border-color: $g6-smoke;
color: $g13-mist;
}
&:active {
border-color: $c-pool;
color: $c-pool;
}
}
/*
Colors
------------------------------------------------------------------------------
*/
.dashed-button__default:active {
border-color: $g9-mountain;
color: $g16-pearl;
}
.dashed-button__primary:active {
border-color: $c-pool;
color: $c-pool;
}
.dashed-button__secondary:active {
border-color: $c-star;
color: $c-star;
}
.dashed-button__success:active {
border-color: $c-rainforest;
color: $c-rainforest;
}
.dashed-button__warning:active {
border-color: $c-thunder;
color: $c-thunder;
}
.dashed-button__danger:active {
border-color: $c-dreamsicle;
color: $c-dreamsicle;
}
/*
Sizes
------------------------------------------------------------------------------
*/
.dashed-button__xs {
height: 32px;
font-size: 11px;
}
.dashed-button__sm {
height: 54px;
font-size: 12px;
}
.dashed-button__md {
height: 76px;
font-size: 13px;
}
.dashed-button__lg {
height: 98px;
font-size: 14px;
}

View File

@ -1,14 +1,30 @@
// Libraries
import React, {FC, MouseEvent} from 'react'
import classnames from 'classnames'
// Types
import {ComponentColor, ComponentSize} from '@influxdata/clockface'
interface Props {
text: string
onClick: (e: MouseEvent) => void
color: ComponentColor
size: ComponentSize
}
const DashedButton: FC<Props> = ({text, onClick}) => {
const DashedButton: FC<Props> = ({
text,
onClick,
color = ComponentColor.Primary,
size = ComponentSize.Medium,
}) => {
const classname = classnames('dashed-button', {
[`dashed-button__${color}`]: color,
[`dashed-button__${size}`]: size,
})
return (
<button className="dashed-button" onClick={onClick}>
<button className={classname} onClick={onClick}>
{text}
</button>
)

View File

@ -114,6 +114,8 @@
@import 'src/shared/components/cloud/CloudOnly.scss';
@import 'src/alerting/components/notifications/NewRuleOverlay.scss';
@import 'src/shared/components/dashed_button/DashedButton.scss';
@import 'src/alerting/components/AlertingIndex.scss';
@import 'src/alerting/components/builder/AlertBuilder.scss';
// External
@import '../../node_modules/@influxdata/react-custom-scrollbars/dist/styles.css';

View File

@ -57,4 +57,13 @@
right: 0;
bottom: 0;
left: 0;
}
}
.time-machine--editor-title {
height: 38px;
user-select: none;
font-weight: 500;
line-height: 30px;
padding-left: 6px;
color: $g13-mist;
}

View File

@ -23,7 +23,7 @@ const TimeMachineAlerting: FunctionComponent<Props> = ({
return (
<div className="time-machine-queries">
<div className="time-machine-queries--controls">
Check Builder
<div className="time-machine--editor-title">Check Builder</div>
<div className="time-machine-queries--buttons">
<ComponentSpacer
direction={FlexDirection.Row}

View File

@ -1,4 +1,3 @@
$builder-card--width: 228px;
$builder-card--margin: $ix-marg-b;
$builder-card--header-height: 30px;
$builder-card--header-margin: $ix-marg-b + $ix-border;
@ -31,7 +30,6 @@ $builder-card--header-margin: $ix-marg-b + $ix-border;
flex-direction: column;
align-items: stretch;
position: relative;
flex: 0 0 $builder-card--width;
}
.builder-card--header {

View File

@ -11,6 +11,7 @@ import BuilderCardEmpty from 'src/timeMachine/components/builderCard/BuilderCard
interface Props {
testID: string
className?: string
widthPixels: number
}
export default class BuilderCard extends PureComponent<Props> {
@ -21,15 +22,18 @@ export default class BuilderCard extends PureComponent<Props> {
public static defaultProps = {
testID: 'builder-card',
widthPixels: 228,
}
public render() {
const {children, testID, className} = this.props
const {children, testID, className, widthPixels} = this.props
const classname = classnames('builder-card', {[`${className}`]: className})
const style = {flex: `0 0 ${widthPixels}px`}
return (
<div className={classname} data-testid={testID}>
<div className={classname} data-testid={testID} style={style}>
{children}
</div>
)

View File

@ -8,6 +8,7 @@ interface Props {
scrollable: boolean
addPadding: boolean
testID: string
autoHideScrollbars: boolean
}
export default class BuilderCardBody extends PureComponent<Props> {
@ -15,10 +16,11 @@ export default class BuilderCardBody extends PureComponent<Props> {
scrollable: true,
addPadding: true,
testID: 'builder-card--body',
autoHideScrollbars: false,
}
public render() {
const {scrollable, testID} = this.props
const {scrollable, testID, autoHideScrollbars} = this.props
if (scrollable) {
return (
@ -26,6 +28,7 @@ export default class BuilderCardBody extends PureComponent<Props> {
className="builder-card--body"
style={{maxWidth: '100%', maxHeight: '100%'}}
testID={testID}
autoHide={autoHideScrollbars}
>
{this.children}
</DapperScrollbars>