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 {
label: string
action: () => void
action: (value?: any) => void
value?: any
onCollapseMenu?: () => void
disabled?: boolean
}
@ -33,14 +34,14 @@ class ContextMenuItem extends Component<Props> {
}
private handleClick = (): void => {
const {action, onCollapseMenu} = this.props
const {action, onCollapseMenu, value} = this.props
if (!onCollapseMenu) {
return
}
onCollapseMenu()
action()
action(value)
}
}

View File

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

View File

@ -284,10 +284,11 @@ export const addCellAsync = (dashboard: Dashboard) => async (
export const createCellWithView = (
dashboard: Dashboard,
view: NewView
view: NewView,
clonedCell?: Cell
) => async (dispatch: Dispatch<Action>): Promise<void> => {
try {
const cell: CreateCell = getNewDashboardCell(dashboard)
const cell: CreateCell = getNewDashboardCell(dashboard, clonedCell)
const createdCell = await addCellAJAX(dashboard.id, cell)
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 viewEntry = views[cell.id]
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 = {
x: 0,
y: 0,
@ -76,12 +79,20 @@ export const getNewDashboardCell = (dashboard: Dashboard): NewCell => {
const mostCommonCellWidth = getMostCommonValue(existingCellWidths)
const mostCommonCellHeight = getMostCommonValue(existingCellHeights)
const newCell = {
let newCell = {
...defaultCell,
w: mostCommonCellWidth,
h: mostCommonCellHeight,
}
if (clonedCell) {
newCell = {
...defaultCell,
w: clonedCell.w,
h: clonedCell.h,
}
}
const {x, y} = getNextAvailablePosition(dashboard, newCell)
return {

View File

@ -7,12 +7,15 @@ import {downloadTextFile} from 'src/shared/utils/download'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import CollectorList from 'src/organizations/components/CollectorList'
import TelegrafExplainer from 'src/organizations/components/TelegrafExplainer'
import {
Button,
ComponentColor,
IconFont,
ComponentSize,
EmptyState,
Grid,
Columns,
} from 'src/clockface'
// Actions
@ -34,35 +37,60 @@ interface Props {
collectors: Telegraf[]
onChange: () => void
notify: NotificationsActions.PublishNotificationActionCreator
orgName: string
}
@ErrorHandling
export default class OrgOptions extends PureComponent<Props> {
export default class Collectors extends PureComponent<Props> {
public render() {
const {collectors} = this.props
return (
<>
<TabbedPageHeader>
<h1>Collectors</h1>
<h1>Telegraf Configurations</h1>
<Button
text="Create Collector"
text="Create Configuration"
icon={IconFont.Plus}
color={ComponentColor.Primary}
/>
</TabbedPageHeader>
<CollectorList
collectors={collectors}
emptyState={this.emptyState}
onDownloadConfig={this.handleDownloadConfig}
onDelete={this.handleDeleteTelegraf}
/>
<Grid>
<Grid.Row>
<Grid.Column widthSM={Columns.Twelve}>
<CollectorList
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 {
const {orgName} = this.props
return (
<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>
)
}

View File

@ -96,7 +96,8 @@ export default class Scrapers extends PureComponent<Props, State> {
return (
<EmptyState size={ComponentSize.Medium}>
<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}
</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
id="org-view-tab--collectors"
url="collectors_tab"
title="Collectors"
title="Telegraf"
>
<GetOrgResources<Telegraf[]>
organization={org}
@ -168,6 +168,7 @@ class OrganizationView extends PureComponent<Props> {
collectors={collectors}
onChange={fetch}
notify={notify}
orgName={org.name}
/>
</Spinner>
)}

View File

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

View File

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

View File

@ -32,6 +32,8 @@ type Props = DispatchProps & OwnProps
@ErrorHandling
class CellContext extends PureComponent<Props> {
public render() {
const {cell, onDeleteCell, onCloneCell} = this.props
return (
<Context className="cell--context">
<Context.Menu icon={IconFont.Pencil}>{this.editMenuItems}</Context.Menu>
@ -39,10 +41,10 @@ class CellContext extends PureComponent<Props> {
icon={IconFont.Duplicate}
color={ComponentColor.Secondary}
>
<Context.Item label="Clone" action={this.handleCloneCell} />
<Context.Item label="Clone" action={onCloneCell} value={cell} />
</Context.Menu>
<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>
)
@ -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 = () => {
const {onOpenNoteEditor, view} = this.props

View File

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

View File

@ -69,25 +69,11 @@ export default class TasksList extends PureComponent<Props, State> {
<IndexList.Header>
<IndexList.HeaderCell
columnName="Name"
width="20%"
width="55%"
sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
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
columnName="Owner"
width="15%"
@ -95,7 +81,21 @@ export default class TasksList extends PureComponent<Props, State> {
sort={sortKey === headerKeys[3] ? sortDirection : Sort.None}
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.Body
emptyState={

View File

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

View File

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

View File

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