Moar Polish (#11255)

* Allow for custom classnames in panels

* Rename "Collectors" to "Telegraf Configurations"

* Allow query builder blocks to always be removable

Use standard margin variables in query builder

* Fix alignment of labels in tasks list

* Amend

* Simplify context menu items

* Ensure cloned cells have same dimensions as source

* Update test snapshots
pull/11259/head
alexpaxton 2019-01-17 16:18:55 -08:00 committed by GitHub
parent e357f4bdbf
commit 250c8df997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 233 additions and 124 deletions

View File

@ -4,7 +4,8 @@ import classnames from 'classnames'
interface Props { interface Props {
label: string label: string
action: () => void action: (value?: any) => void
value?: any
onCollapseMenu?: () => void onCollapseMenu?: () => void
disabled?: boolean disabled?: boolean
} }
@ -33,14 +34,14 @@ class ContextMenuItem extends Component<Props> {
} }
private handleClick = (): void => { private handleClick = (): void => {
const {action, onCollapseMenu} = this.props const {action, onCollapseMenu, value} = this.props
if (!onCollapseMenu) { if (!onCollapseMenu) {
return return
} }
onCollapseMenu() onCollapseMenu()
action() action(value)
} }
} }

View File

@ -1,6 +1,7 @@
// Libraries // Libraries
import React, {Component, ComponentClass} from 'react' import React, {Component, ComponentClass} from 'react'
import _ from 'lodash' import _ from 'lodash'
import classnames from 'classnames'
// Components // Components
import PanelHeader from 'src/clockface/components/panel/PanelHeader' import PanelHeader from 'src/clockface/components/panel/PanelHeader'
@ -11,6 +12,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props { interface Props {
children: JSX.Element[] | JSX.Element children: JSX.Element[] | JSX.Element
className?: string
} }
@ErrorHandling @ErrorHandling
@ -35,11 +37,15 @@ class Panel extends Component<Props> {
).join(', ') ).join(', ')
public render() { public render() {
const {children} = this.props const {children, className} = this.props
this.validateChildren() this.validateChildren()
return <div className="panel">{children}</div> return (
<div className={classnames('panel', {[`${className}`]: className})}>
{children}
</div>
)
} }
private validateChildren = (): void => { private validateChildren = (): void => {

View File

@ -284,10 +284,11 @@ export const addCellAsync = (dashboard: Dashboard) => async (
export const createCellWithView = ( export const createCellWithView = (
dashboard: Dashboard, dashboard: Dashboard,
view: NewView view: NewView,
clonedCell?: Cell
) => async (dispatch: Dispatch<Action>): Promise<void> => { ) => async (dispatch: Dispatch<Action>): Promise<void> => {
try { try {
const cell: CreateCell = getNewDashboardCell(dashboard) const cell: CreateCell = getNewDashboardCell(dashboard, clonedCell)
const createdCell = await addCellAJAX(dashboard.id, cell) const createdCell = await addCellAJAX(dashboard.id, cell)
const updatedView = await updateViewAJAX(dashboard.id, createdCell.id, view) const updatedView = await updateViewAJAX(dashboard.id, createdCell.id, view)

View File

@ -325,7 +325,7 @@ class DashboardPage extends Component<Props, State> {
const {dashboard, onCreateCellWithView, views} = this.props const {dashboard, onCreateCellWithView, views} = this.props
const viewEntry = views[cell.id] const viewEntry = views[cell.id]
if (viewEntry && viewEntry.view) { if (viewEntry && viewEntry.view) {
await onCreateCellWithView(dashboard, viewEntry.view) await onCreateCellWithView(dashboard, viewEntry.view, cell)
} }
} }

View File

@ -53,7 +53,10 @@ const getNextAvailablePosition = (dashboard, newCell) => {
} }
} }
export const getNewDashboardCell = (dashboard: Dashboard): NewCell => { export const getNewDashboardCell = (
dashboard: Dashboard,
clonedCell?: Cell
): NewCell => {
const defaultCell = { const defaultCell = {
x: 0, x: 0,
y: 0, y: 0,
@ -76,12 +79,20 @@ export const getNewDashboardCell = (dashboard: Dashboard): NewCell => {
const mostCommonCellWidth = getMostCommonValue(existingCellWidths) const mostCommonCellWidth = getMostCommonValue(existingCellWidths)
const mostCommonCellHeight = getMostCommonValue(existingCellHeights) const mostCommonCellHeight = getMostCommonValue(existingCellHeights)
const newCell = { let newCell = {
...defaultCell, ...defaultCell,
w: mostCommonCellWidth, w: mostCommonCellWidth,
h: mostCommonCellHeight, h: mostCommonCellHeight,
} }
if (clonedCell) {
newCell = {
...defaultCell,
w: clonedCell.w,
h: clonedCell.h,
}
}
const {x, y} = getNextAvailablePosition(dashboard, newCell) const {x, y} = getNextAvailablePosition(dashboard, newCell)
return { return {

View File

@ -7,12 +7,15 @@ import {downloadTextFile} from 'src/shared/utils/download'
// Components // Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader' import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import CollectorList from 'src/organizations/components/CollectorList' import CollectorList from 'src/organizations/components/CollectorList'
import TelegrafExplainer from 'src/organizations/components/TelegrafExplainer'
import { import {
Button, Button,
ComponentColor, ComponentColor,
IconFont, IconFont,
ComponentSize, ComponentSize,
EmptyState, EmptyState,
Grid,
Columns,
} from 'src/clockface' } from 'src/clockface'
// Actions // Actions
@ -34,35 +37,60 @@ interface Props {
collectors: Telegraf[] collectors: Telegraf[]
onChange: () => void onChange: () => void
notify: NotificationsActions.PublishNotificationActionCreator notify: NotificationsActions.PublishNotificationActionCreator
orgName: string
} }
@ErrorHandling @ErrorHandling
export default class OrgOptions extends PureComponent<Props> { export default class Collectors extends PureComponent<Props> {
public render() { public render() {
const {collectors} = this.props const {collectors} = this.props
return ( return (
<> <>
<TabbedPageHeader> <TabbedPageHeader>
<h1>Collectors</h1> <h1>Telegraf Configurations</h1>
<Button <Button
text="Create Collector" text="Create Configuration"
icon={IconFont.Plus} icon={IconFont.Plus}
color={ComponentColor.Primary} color={ComponentColor.Primary}
/> />
</TabbedPageHeader> </TabbedPageHeader>
<CollectorList <Grid>
collectors={collectors} <Grid.Row>
emptyState={this.emptyState} <Grid.Column widthSM={Columns.Twelve}>
onDownloadConfig={this.handleDownloadConfig} <CollectorList
onDelete={this.handleDeleteTelegraf} collectors={collectors}
/> emptyState={this.emptyState}
onDownloadConfig={this.handleDownloadConfig}
onDelete={this.handleDeleteTelegraf}
/>
</Grid.Column>
<Grid.Column
widthSM={Columns.Six}
widthMD={Columns.Four}
offsetSM={Columns.Three}
offsetMD={Columns.Four}
>
<TelegrafExplainer />
</Grid.Column>
</Grid.Row>
</Grid>
</> </>
) )
} }
private get emptyState(): JSX.Element { private get emptyState(): JSX.Element {
const {orgName} = this.props
return ( return (
<EmptyState size={ComponentSize.Medium}> <EmptyState size={ComponentSize.Medium}>
<EmptyState.Text text="No Collectors match your query" /> <EmptyState.Text
text={`${orgName} does not own any Telegraf Configurations , why not create one?`}
highlightWords={['Telegraf', 'Configurations']}
/>
<Button
text="Create Configuration"
icon={IconFont.Plus}
color={ComponentColor.Primary}
/>
</EmptyState> </EmptyState>
) )
} }

View File

@ -96,7 +96,8 @@ export default class Scrapers extends PureComponent<Props, State> {
return ( return (
<EmptyState size={ComponentSize.Medium}> <EmptyState size={ComponentSize.Medium}>
<EmptyState.Text <EmptyState.Text
text={`${orgName} does not own any scrapers, why not create one?`} text={`${orgName} does not own any Scrapers , why not create one?`}
highlightWords={['Scrapers']}
/> />
{this.createScraperButton} {this.createScraperButton}
</EmptyState> </EmptyState>

View File

@ -0,0 +1,8 @@
@import 'src/style/modules';
.telegraf-explainer {
margin-top: $ix-marg-d;
color: $g13-mist;
user-select: none;
}

View File

@ -0,0 +1,30 @@
// Libraries
import React, {SFC} from 'react'
// Components
import {Panel} from 'src/clockface'
// Styles
import 'src/organizations/components/TelegrafExplainer.scss'
const TelegrafExplainer: SFC = () => (
<Panel className="telegraf-explainer">
<Panel.Header title="What is Telegraf?" />
<Panel.Body>
<p>
Telegraf is an agent written in Go for collecting metrics and writing
them into <strong>InfluxDB</strong> or other possible outputs.
<br />
Here's a handy guide for{' '}
<a
href="https://docs.influxdata.com/telegraf/v1.9/introduction/getting-started/"
target="_blank"
>
Getting Started with Telegraf
</a>
</p>
</Panel.Body>
</Panel>
)
export default TelegrafExplainer

View File

@ -156,7 +156,7 @@ class OrganizationView extends PureComponent<Props> {
<TabbedPageSection <TabbedPageSection
id="org-view-tab--collectors" id="org-view-tab--collectors"
url="collectors_tab" url="collectors_tab"
title="Collectors" title="Telegraf"
> >
<GetOrgResources<Telegraf[]> <GetOrgResources<Telegraf[]>
organization={org} organization={org}
@ -168,6 +168,7 @@ class OrganizationView extends PureComponent<Props> {
collectors={collectors} collectors={collectors}
onChange={fetch} onChange={fetch}
notify={notify} notify={notify}
orgName={org.name}
/> />
</Spinner> </Spinner>
)} )}

View File

@ -3,33 +3,32 @@
.tag-selector { .tag-selector {
min-height: 130px; min-height: 130px;
background: $g4-onyx; background: $g4-onyx;
border-radius: 4px; border-radius: $radius;
padding: 10px 0;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 13px; font-size: $form-sm-font;
position: relative;
input {
margin-top: 5px;
}
} }
.tag-selector--top, .tag-selector--search { .tag-selector--top,
margin: 0 10px; .tag-selector--search {
margin: $ix-marg-b;
} }
.tag-selector--top { .tag-selector--top {
display: flex; display: flex;
justify-content: flex-end;
} }
.tag-selector--search { .tag-selector--search {
margin-bottom: 10px; margin-top: 0;
width: initial !important; width: initial !important;
} }
button.tag-selector--remove { button.tag-selector--remove {
margin-left: 3px; flex: 0 0 $form-sm-height;
margin-left: $ix-marg-b;
} }
.tag-selector .selector-list { .tag-selector .selector-list {
@ -38,7 +37,8 @@ button.tag-selector--remove {
.tag-selector--empty { .tag-selector--empty {
flex: 1 1 0; position: absolute;
pointer-events: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
@ -49,5 +49,5 @@ button.tag-selector--remove {
font-size: 16px; font-size: 16px;
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
padding: 10px; padding: $ix-marg-b + $ix-marg-a;
} }

View File

@ -87,11 +87,35 @@ class TagSelector extends PureComponent<Props> {
} }
if (keysStatus === RemoteDataState.Error) { if (keysStatus === RemoteDataState.Error) {
return <div className="tag-selector--empty">Failed to load tag keys</div> return (
<>
<div className="tag-selector--top">
<Button
shape={ButtonShape.Square}
icon={IconFont.Remove}
onClick={this.handleRemoveTagSelector}
customClass="tag-selector--remove"
/>
</div>
<div className="tag-selector--empty">Failed to load tag keys</div>
</>
)
} }
if (keysStatus === RemoteDataState.Done && !keys.length) { if (keysStatus === RemoteDataState.Done && !keys.length) {
return <div className="tag-selector--empty">No more tag keys found</div> return (
<>
<div className="tag-selector--top">
<Button
shape={ButtonShape.Square}
icon={IconFont.Remove}
onClick={this.handleRemoveTagSelector}
customClass="tag-selector--remove"
/>
</div>
<div className="tag-selector--empty">No more tag keys found</div>
</>
)
} }
return ( return (

View File

@ -36,7 +36,7 @@
height: 100%; height: 100%;
.tag-selector { .tag-selector {
margin-right: 5px; margin-right: $ix-marg-a;
flex: 0 0 250px; flex: 0 0 250px;
&:last-child { &:last-child {

View File

@ -32,6 +32,8 @@ type Props = DispatchProps & OwnProps
@ErrorHandling @ErrorHandling
class CellContext extends PureComponent<Props> { class CellContext extends PureComponent<Props> {
public render() { public render() {
const {cell, onDeleteCell, onCloneCell} = this.props
return ( return (
<Context className="cell--context"> <Context className="cell--context">
<Context.Menu icon={IconFont.Pencil}>{this.editMenuItems}</Context.Menu> <Context.Menu icon={IconFont.Pencil}>{this.editMenuItems}</Context.Menu>
@ -39,10 +41,10 @@ class CellContext extends PureComponent<Props> {
icon={IconFont.Duplicate} icon={IconFont.Duplicate}
color={ComponentColor.Secondary} color={ComponentColor.Secondary}
> >
<Context.Item label="Clone" action={this.handleCloneCell} /> <Context.Item label="Clone" action={onCloneCell} value={cell} />
</Context.Menu> </Context.Menu>
<Context.Menu icon={IconFont.Trash} color={ComponentColor.Danger}> <Context.Menu icon={IconFont.Trash} color={ComponentColor.Danger}>
<Context.Item label="Delete" action={this.handleDeleteCell} /> <Context.Item label="Delete" action={onDeleteCell} value={cell} />
</Context.Menu> </Context.Menu>
</Context> </Context>
) )
@ -73,18 +75,6 @@ class CellContext extends PureComponent<Props> {
] ]
} }
private handleDeleteCell = () => {
const {cell, onDeleteCell} = this.props
onDeleteCell(cell)
}
private handleCloneCell = () => {
const {cell, onCloneCell} = this.props
onCloneCell(cell)
}
private handleEditNote = () => { private handleEditNote = () => {
const {onOpenNoteEditor, view} = this.props const {onOpenNoteEditor, view} = this.props

View File

@ -41,13 +41,25 @@ export class TaskRow extends PureComponent<Props & WithRouterProps> {
return ( return (
<IndexList.Row disabled={!this.isTaskActive}> <IndexList.Row disabled={!this.isTaskActive}>
<IndexList.Cell> <IndexList.Cell>
<ComponentSpacer stackChildren={Stack.Rows} align={Alignment.Left}> <ComponentSpacer
<a href="#" onClick={this.handleClick}> stackChildren={Stack.Columns}
align={Alignment.Right}
>
<a
href="#"
onClick={this.handleClick}
className="index-list--resource-name"
>
{task.name} {task.name}
</a> </a>
{this.labels} {this.labels}
</ComponentSpacer> </ComponentSpacer>
</IndexList.Cell> </IndexList.Cell>
<IndexList.Cell>
<a href="" onClick={this.handleOrgClick}>
{task.organization.name}
</a>
</IndexList.Cell>
<IndexList.Cell> <IndexList.Cell>
<SlideToggle <SlideToggle
active={this.isTaskActive} active={this.isTaskActive}
@ -56,11 +68,6 @@ export class TaskRow extends PureComponent<Props & WithRouterProps> {
/> />
</IndexList.Cell> </IndexList.Cell>
<IndexList.Cell>{this.schedule}</IndexList.Cell> <IndexList.Cell>{this.schedule}</IndexList.Cell>
<IndexList.Cell>
<a href="" onClick={this.handleOrgClick}>
{task.organization.name}
</a>
</IndexList.Cell>
<IndexList.Cell alignment={Alignment.Right} revealOnHover={true}> <IndexList.Cell alignment={Alignment.Right} revealOnHover={true}>
<ComponentSpacer align={Alignment.Right}> <ComponentSpacer align={Alignment.Right}>
<Button <Button

View File

@ -69,25 +69,11 @@ export default class TasksList extends PureComponent<Props, State> {
<IndexList.Header> <IndexList.Header>
<IndexList.HeaderCell <IndexList.HeaderCell
columnName="Name" columnName="Name"
width="20%" width="55%"
sortKey={headerKeys[0]} sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None} sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
onClick={this.handleClickColumn} onClick={this.handleClickColumn}
/> />
<IndexList.HeaderCell
columnName="Active"
width="10%"
sortKey={headerKeys[1]}
sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Schedule"
width="20%"
sortKey={headerKeys[2]}
sort={sortKey === headerKeys[2] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell <IndexList.HeaderCell
columnName="Owner" columnName="Owner"
width="15%" width="15%"
@ -95,7 +81,21 @@ export default class TasksList extends PureComponent<Props, State> {
sort={sortKey === headerKeys[3] ? sortDirection : Sort.None} sort={sortKey === headerKeys[3] ? sortDirection : Sort.None}
onClick={this.handleClickColumn} onClick={this.handleClickColumn}
/> />
<IndexList.HeaderCell columnName="" width="35%" /> <IndexList.HeaderCell
columnName="Active"
width="5%"
sortKey={headerKeys[1]}
sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell
columnName="Schedule"
width="15%"
sortKey={headerKeys[2]}
sort={sortKey === headerKeys[2] ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<IndexList.HeaderCell columnName="" width="10%" />
</IndexList.Header> </IndexList.Header>
<IndexList.Body <IndexList.Body
emptyState={ emptyState={

View File

@ -9,10 +9,11 @@ exports[`Tasks.Components.TaskRow renders 1`] = `
revealOnHover={false} revealOnHover={false}
> >
<ComponentSpacer <ComponentSpacer
align="left" align="right"
stackChildren="rows" stackChildren="columns"
> >
<a <a
className="index-list--resource-name"
href="#" href="#"
onClick={[Function]} onClick={[Function]}
> >
@ -26,6 +27,17 @@ exports[`Tasks.Components.TaskRow renders 1`] = `
/> />
</ComponentSpacer> </ComponentSpacer>
</IndexListRowCell> </IndexListRowCell>
<IndexListRowCell
alignment="left"
revealOnHover={false}
>
<a
href=""
onClick={[Function]}
>
RadicalOrganization
</a>
</IndexListRowCell>
<IndexListRowCell <IndexListRowCell
alignment="left" alignment="left"
revealOnHover={false} revealOnHover={false}
@ -45,17 +57,6 @@ exports[`Tasks.Components.TaskRow renders 1`] = `
> >
2 0 * * * 2 0 * * *
</IndexListRowCell> </IndexListRowCell>
<IndexListRowCell
alignment="left"
revealOnHover={false}
>
<a
href=""
onClick={[Function]}
>
RadicalOrganization
</a>
</IndexListRowCell>
<IndexListRowCell <IndexListRowCell
alignment="right" alignment="right"
revealOnHover={true} revealOnHover={true}

View File

@ -10,23 +10,7 @@ exports[`TasksList rendering renders 1`] = `
onClick={[Function]} onClick={[Function]}
sort="none" sort="none"
sortKey="name" sortKey="name"
width="20%" width="55%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="10%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/> />
<IndexListHeaderCell <IndexListHeaderCell
alignment="left" alignment="left"
@ -36,10 +20,26 @@ exports[`TasksList rendering renders 1`] = `
sortKey="organization.name" sortKey="organization.name"
width="15%" width="15%"
/> />
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="5%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="15%"
/>
<IndexListHeaderCell <IndexListHeaderCell
alignment="left" alignment="left"
columnName="" columnName=""
width="35%" width="10%"
/> />
</IndexListHeader> </IndexListHeader>
<IndexListBody <IndexListBody

View File

@ -10,23 +10,7 @@ exports[`TasksList rendering renders 1`] = `
onClick={[Function]} onClick={[Function]}
sort="none" sort="none"
sortKey="name" sortKey="name"
width="20%" width="55%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="10%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="20%"
/> />
<IndexListHeaderCell <IndexListHeaderCell
alignment="left" alignment="left"
@ -36,10 +20,26 @@ exports[`TasksList rendering renders 1`] = `
sortKey="organization.name" sortKey="organization.name"
width="15%" width="15%"
/> />
<IndexListHeaderCell
alignment="left"
columnName="Active"
onClick={[Function]}
sort="none"
sortKey="status"
width="5%"
/>
<IndexListHeaderCell
alignment="left"
columnName="Schedule"
onClick={[Function]}
sort="none"
sortKey="every"
width="15%"
/>
<IndexListHeaderCell <IndexListHeaderCell
alignment="left" alignment="left"
columnName="" columnName=""
width="35%" width="10%"
/> />
</IndexListHeader> </IndexListHeader>
<IndexListBody <IndexListBody