feat(ui): resource sorting dropdowns (#17321)

* feat(ui): replace sortable dashboard table headers with sort dropdown

* refactor(ui): remove unecessary components

* refactor(ui): add testIDs to dropdown

* refactor(ui): move tasks filter to page control bar

* fix(ui): remove unused import

* feat(ui): replace tasks sortable headers with sort dropdown

* fix(ui): make search widget more responsive

* fix(ui): make dashboard sort dropdown maintain size

* refactor(ui): consolidate resource sorting into a single component

* refactor(ui): standardize tabbed page headers

* fix: use correct import paths

* refactor(ui): implement resource sorting dropdown for variables

* refactor(ui): implement sorting dropdown on labels list

* chore: delete unused stylesheet

* refactor: implement sort dropdown for templates list

* refactor: implement sort dropdown on buckets list

* refactor: update design of "what is a bucket?" card

* refactor: implement sort dropdowns in telegrafs list

* fix: appease linter

* refactor: implement sort dropdown on scrapers list

* chore: add testIDs to resource sorter

* fix: remove unused code

* fix: update buckets and telegraf e2e tests

* fix: update labels e2e test

* fix: update variables e2e test

* refactor: move dashboards list empty state into own component

* fix: oops derp ayyyy

* chore: changelog

* refactor: use more resource specific types for sort keys
pull/17728/head
alexpaxton 2020-04-13 15:38:03 -07:00 committed by GitHub
parent 0d092d3dbb
commit 8130aa07a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1083 additions and 636 deletions

View File

@ -1,4 +1,4 @@
## v2.0.0-beta.9
## v2.0.0-beta.9 [unreleased]
### Features
@ -7,6 +7,7 @@
### UI Improvements
1. [17714](https://github.com/influxdata/influxdb/pull/17714): Cloud environments no longer render markdown images, for security reasons.
1. [17321](https://github.com/influxdata/influxdb/pull/17321): Improve UI for sorting resources
## v2.0.0-beta.8 [2020-04-10]

View File

@ -75,8 +75,11 @@ describe('Buckets', () => {
describe('Searching and Sorting', () => {
it('can sort by name and retention', () => {
const buckets = ['defbuck', '_tasks', '_monitoring']
cy.getByTestID('name-sorter')
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--name-desc').click()
})
.then(() => {
cy.get('[data-testid*="bucket-card"]').each((val, index) => {
const testID = val.attr('data-testid')
@ -84,8 +87,13 @@ describe('Buckets', () => {
})
})
cy.getByTestID('name-sorter')
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID(
'resource-sorter--retentionRules[0].everySeconds-desc'
).click()
})
.then(() => {
const asc_buckets = buckets.slice().sort()
cy.get('[data-testid*="bucket-card"]').each((val, index) => {

View File

@ -225,8 +225,11 @@ describe('Collectors', () => {
})
})
cy.getByTestID('name-sorter')
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--name-desc').click()
})
.then(() => {
// NOTE: this then is just here to let me scope this variable (alex)
const teletubbies = telegrafs

View File

@ -284,7 +284,11 @@ describe('labels', () => {
}
})
cy.getByTestID('sorter--name').click()
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--name-desc').click()
})
// check sort desc
cy.getByTestIDSubStr('label--pill').then(labels => {
@ -296,7 +300,11 @@ describe('labels', () => {
})
// reset to asc
cy.getByTestID('sorter--name').click()
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--name-asc').click()
})
cy.getByTestIDSubStr('label--pill').then(labels => {
for (let i = 0; i < labels.length; i++) {
@ -332,27 +340,32 @@ describe('labels', () => {
a.description < b.description ? -1 : a.description > b.description ? 1 : 0
)
// check sort asc
cy.getByTestID('sorter--desc').click()
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--properties.description-asc').click()
})
cy.getByTestIDSubStr('resource-card').then(labels => {
cy.getByTestID('label-card').then(labels => {
for (let i = 0; i < labels.length; i++) {
cy.getByTestIDSubStr('resource-card')
cy.getByTestID('label-card--description')
.eq(i)
.should('have.text', 'Description: ' + names[i].description)
.should('have.text', names[i].description)
}
})
// check sort desc
cy.getByTestID('sorter--desc').click()
cy.getByTestID('resource-sorter--button')
.click()
.then(() => {
cy.getByTestID('resource-sorter--properties.description-desc').click()
})
cy.getByTestIDSubStr('resource-card').then(labels => {
cy.getByTestID('label-card').then(labels => {
for (let i = 0; i < labels.length; i++) {
cy.getByTestIDSubStr('resource-card')
cy.getByTestID('label-card--description')
.eq(i)
.should(
'have.text',
'Description: ' + names[labels.length - (i + 1)].description
)
.should('have.text', names[labels.length - (i + 1)].description)
}
})
})

View File

@ -42,7 +42,7 @@ describe('Variables', () => {
})
it('keeps user input in text area when attempting to import invalid JSON', () => {
cy.get('.tabbed-page-section--header').within(() => {
cy.getByTestID('tabbed-page--header').within(() => {
cy.contains('Create').click()
})

View File

@ -55,20 +55,28 @@ class TokensTab extends PureComponent<Props, State> {
const {searchTerm, sortKey, sortDirection, sortType} = this.state
const {tokens} = this.props
const leftHeaderItems = (
<SearchWidget
searchTerm={searchTerm}
placeholderText="Filter Tokens..."
onSearch={this.handleChangeSearchTerm}
testID="input-field--filter"
/>
)
const rightHeaderItems = (
<GenerateTokenDropdown
onSelectAllAccess={this.handleGenerateAllAccess}
onSelectReadWrite={this.handleGenerateReadWrite}
/>
)
return (
<>
<TabbedPageHeader>
<SearchWidget
searchTerm={searchTerm}
placeholderText="Filter Tokens..."
onSearch={this.handleChangeSearchTerm}
testID="input-field--filter"
/>
<GenerateTokenDropdown
onSelectAllAccess={this.handleGenerateAllAccess}
onSelectReadWrite={this.handleGenerateReadWrite}
/>
</TabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={rightHeaderItems}
/>
<FilterAuthorizations
list={tokens}
searchTerm={searchTerm}

View File

@ -54,12 +54,3 @@
.system-bucket {
color: mix($c-honeydew, $g13-mist);
}
.buckets-buttons-wrap {
display: flex;
box-sizing: border-box;
text-align: right;
@media screen and (max-width: 680px) {
margin: 8px auto 0;
}
}

View File

@ -2,10 +2,10 @@
import React, {FunctionComponent} from 'react'
// Components
import {Panel, InfluxColors} from '@influxdata/clockface'
import {Panel, Gradients} from '@influxdata/clockface'
const BucketExplainer: FunctionComponent = () => (
<Panel backgroundColor={InfluxColors.Smoke} style={{marginTop: '32px'}}>
<Panel gradient={Gradients.PolarExpress} border={true}>
<Panel.Header>
<h5>What is a Bucket?</h5>
</Panel.Header>

View File

@ -18,8 +18,6 @@ import {Sort} from '@influxdata/clockface'
// Utils
import {SortTypes} from 'src/shared/utils/sort'
type SortKey = keyof Bucket | 'retentionRules[0].everySeconds'
interface Props {
buckets: Bucket[]
emptyState: JSX.Element
@ -29,9 +27,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (
sortType: SortTypes
) => (nextSort: Sort, sortKey: SortKey) => void
}
class BucketList extends PureComponent<Props & WithRouterProps> {
@ -40,38 +35,15 @@ class BucketList extends PureComponent<Props & WithRouterProps> {
)
public render() {
const {sortKey, sortDirection, onClickColumn} = this.props
return (
<>
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name="Name"
sortKey={this.headerKeys[0]}
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn(SortTypes.String)}
testID="name-sorter"
/>
<ResourceList.Sorter
name="Retention"
sortKey={this.headerKeys[1]}
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
onClick={onClickColumn(SortTypes.Float)}
testID="retention-sorter"
/>
</ResourceList.Header>
<ResourceList.Body emptyState={this.props.emptyState}>
{this.listBuckets}
</ResourceList.Body>
</ResourceList>
</>
<ResourceList>
<ResourceList.Body emptyState={this.props.emptyState}>
{this.listBuckets}
</ResourceList.Body>
</ResourceList>
)
}
private get headerKeys(): SortKey[] {
return ['name', 'retentionRules[0].everySeconds']
}
private get listBuckets(): JSX.Element[] {
const {
buckets,

View File

@ -18,13 +18,15 @@ import {
Overlay,
} from '@influxdata/clockface'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import SettingsTabbedPageHeader from 'src/settings/components/SettingsTabbedPageHeader'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import FilterList from 'src/shared/components/FilterList'
import BucketList from 'src/buckets/components/BucketList'
import CreateBucketOverlay from 'src/buckets/components/CreateBucketOverlay'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
import BucketExplainer from 'src/buckets/components/BucketExplainer'
import DemoDataDropdown from 'src/buckets/components/DemoDataDropdown'
import {FeatureFlag} from 'src/shared/utils/featureFlag'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
// Actions
import {
@ -58,6 +60,7 @@ import {
ResourceType,
OwnBucket,
} from 'src/types'
import {BucketSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
interface StateProps {
org: Organization
@ -78,15 +81,13 @@ interface DispatchProps {
interface State {
searchTerm: string
overlayState: OverlayState
sortKey: SortKey
sortKey: BucketSortKey
sortDirection: Sort
sortType: SortTypes
}
type Props = DispatchProps & StateProps
type SortKey = keyof Bucket
const FilterBuckets = FilterList<Bucket>()
@ErrorHandling
@ -126,6 +127,46 @@ class BucketsTab extends PureComponent<Props, State> {
sortType,
} = this.state
const leftHeaderItems = (
<>
<SearchWidget
placeholderText="Filter buckets..."
searchTerm={searchTerm}
onSearch={this.handleFilterUpdate}
/>
<ResourceSortDropdown
resourceType={ResourceType.Buckets}
sortDirection={sortDirection}
sortKey={sortKey}
sortType={sortType}
onSelect={this.handleSort}
width={238}
/>
</>
)
const rightHeaderItems = (
<>
<FeatureFlag name="demodata">
{demoDataBuckets.length > 0 && (
<DemoDataDropdown
buckets={demoDataBuckets}
getMembership={getDemoDataBucketMembership}
/>
)}
</FeatureFlag>
<Button
text="Create Bucket"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={this.handleOpenModal}
testID="Create Bucket"
status={this.createButtonStatus}
titleText={this.createButtonTitleText}
/>
</>
)
return (
<>
<AssetLimitAlert
@ -133,30 +174,10 @@ class BucketsTab extends PureComponent<Props, State> {
limitStatus={limitStatus}
className="load-data--asset-alert"
/>
<SettingsTabbedPageHeader>
<SearchWidget
placeholderText="Filter buckets..."
searchTerm={searchTerm}
onSearch={this.handleFilterUpdate}
/>
<div className="buckets-buttons-wrap">
{isFlagEnabled('demodata') && demoDataBuckets.length > 0 && (
<DemoDataDropdown
buckets={demoDataBuckets}
getMembership={getDemoDataBucketMembership}
/>
)}
<Button
text="Create Bucket"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={this.handleOpenModal}
testID="Create Bucket"
status={this.createButtonStatus}
titleText={this.createButtonTitleText}
/>
</div>
</SettingsTabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={rightHeaderItems}
/>
<Grid>
<Grid.Row>
<Grid.Column
@ -179,7 +200,6 @@ class BucketsTab extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)}
</FilterBuckets>
@ -204,11 +224,12 @@ class BucketsTab extends PureComponent<Props, State> {
)
}
private handleClickColumn = (sortType: SortTypes) => (
nextSort: Sort,
sortKey: SortKey
) => {
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: BucketSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private handleDeleteBucket = ({id, name}: OwnBucket) => {

View File

@ -13,6 +13,7 @@ import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import GetAssetLimits from 'src/cloud/components/GetAssetLimits'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
// Utils
@ -23,9 +24,11 @@ import {extractDashboardLimits} from 'src/cloud/utils/limits'
import {createDashboard as createDashboardAction} from 'src/dashboards/actions/thunks'
// Types
import {AppState} from 'src/types'
import {AppState, ResourceType} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits'
import {ComponentStatus} from '@influxdata/clockface'
import {ComponentStatus, Sort} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort'
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
interface DispatchProps {
createDashboard: typeof createDashboardAction
@ -44,6 +47,9 @@ type Props = DispatchProps & StateProps & OwnProps
interface State {
searchTerm: string
sortDirection: Sort
sortType: SortTypes
sortKey: DashboardSortKey
}
@ErrorHandling
@ -53,12 +59,15 @@ class DashboardIndex extends PureComponent<Props, State> {
this.state = {
searchTerm: '',
sortDirection: Sort.Ascending,
sortType: SortTypes.String,
sortKey: 'name',
}
}
public render() {
const {createDashboard, limitStatus} = this.props
const {searchTerm} = this.state
const {searchTerm, sortDirection, sortType, sortKey} = this.state
return (
<>
<Page
@ -76,6 +85,13 @@ class DashboardIndex extends PureComponent<Props, State> {
onSearch={this.handleFilterDashboards}
searchTerm={searchTerm}
/>
<ResourceSortDropdown
resourceType={ResourceType.Dashboards}
sortDirection={sortDirection}
sortKey={sortKey}
sortType={sortType}
onSelect={this.handleSort}
/>
</Page.ControlBarLeft>
<Page.ControlBarRight>
<AddResourceDropdown
@ -101,6 +117,9 @@ class DashboardIndex extends PureComponent<Props, State> {
<DashboardsIndexContents
searchTerm={searchTerm}
onFilterChange={this.handleFilterDashboards}
sortDirection={sortDirection}
sortType={sortType}
sortKey={sortKey}
/>
</GetAssetLimits>
</Page.Contents>
@ -110,6 +129,14 @@ class DashboardIndex extends PureComponent<Props, State> {
)
}
private handleSort = (
sortKey: DashboardSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private handleFilterDashboards = (searchTerm: string): void => {
this.setState({searchTerm})
}

View File

@ -15,12 +15,18 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
// Types
import {Dashboard, AppState, RemoteDataState, ResourceType} from 'src/types'
import {Sort} from '@influxdata/clockface'
import {getAll} from 'src/resources/selectors'
import {SortTypes} from 'src/shared/utils/sort'
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
interface OwnProps {
onFilterChange: (searchTerm: string) => void
searchTerm: string
filterComponent?: JSX.Element
sortDirection: Sort
sortType: SortTypes
sortKey: DashboardSortKey
}
interface DispatchProps {
@ -48,7 +54,15 @@ class DashboardsIndexContents extends Component<Props> {
}
public render() {
const {searchTerm, dashboards, filterComponent, onFilterChange} = this.props
const {
searchTerm,
dashboards,
filterComponent,
onFilterChange,
sortDirection,
sortType,
sortKey,
} = this.props
return (
<FilterDashboards
@ -63,6 +77,9 @@ class DashboardsIndexContents extends Component<Props> {
filterComponent={filterComponent}
dashboards={filteredDashboards}
onFilterChange={onFilterChange}
sortDirection={sortDirection}
sortType={sortType}
sortKey={sortKey}
/>
)}
</FilterDashboards>

View File

@ -0,0 +1,48 @@
// Libraries
import React, {FC} from 'react'
// Components
import {EmptyState, ComponentSize} from '@influxdata/clockface'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
// Actions
import {createDashboard} from 'src/dashboards/actions/thunks'
interface ComponentProps {
searchTerm?: string
onCreateDashboard: typeof createDashboard
summonImportOverlay: () => void
summonImportFromTemplateOverlay: () => void
}
const DashboardsTableEmpty: FC<ComponentProps> = ({
searchTerm,
onCreateDashboard,
summonImportOverlay,
summonImportFromTemplateOverlay,
}) => {
if (searchTerm) {
return (
<EmptyState size={ComponentSize.Large} testID="empty-dashboards-list">
<EmptyState.Text>No Dashboards match your search term</EmptyState.Text>
</EmptyState>
)
}
return (
<EmptyState size={ComponentSize.Large} testID="empty-dashboards-list">
<EmptyState.Text>
Looks like you don't have any <b>Dashboards</b>, why not create one?
</EmptyState.Text>
<AddResourceDropdown
onSelectNew={onCreateDashboard}
onSelectImport={summonImportOverlay}
onSelectTemplate={summonImportFromTemplateOverlay}
resourceName="Dashboard"
canImportFromTemplate={true}
/>
</EmptyState>
)
}
export default DashboardsTableEmpty

View File

@ -5,15 +5,19 @@ import {withRouter, WithRouterProps} from 'react-router'
import _ from 'lodash'
// Components
import {EmptyState, ResourceList} from '@influxdata/clockface'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import DashboardCards from 'src/dashboards/components/dashboard_index/DashboardCards'
import {createDashboard, getDashboards} from 'src/dashboards/actions/thunks'
import DashobardsTableEmpty from 'src/dashboards/components/dashboard_index/DashboardsTableEmpty'
// Utilities
import {getLabels} from 'src/labels/actions/thunks'
// Actions
import {createDashboard, getDashboards} from 'src/dashboards/actions/thunks'
// Types
import {AppState, Dashboard, RemoteDataState} from 'src/types'
import {Sort, ComponentSize} from '@influxdata/clockface'
import {Sort} from '@influxdata/clockface'
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
import {SortTypes} from 'src/shared/utils/sort'
interface OwnProps {
@ -21,11 +25,8 @@ interface OwnProps {
onFilterChange: (searchTerm: string) => void
filterComponent?: JSX.Element
dashboards: Dashboard[]
}
interface State {
sortKey: SortKey
sortDirection: Sort
sortKey: DashboardSortKey
sortType: SortTypes
}
@ -39,79 +40,48 @@ interface DispatchProps {
getLabels: typeof getLabels
}
type SortKey = keyof Dashboard | 'meta.updatedAt'
type Props = OwnProps & StateProps & DispatchProps & WithRouterProps
class DashboardsTable extends PureComponent<Props, State> {
state: State = {
sortKey: 'name',
sortDirection: Sort.Ascending,
sortType: SortTypes.String,
}
class DashboardsTable extends PureComponent<Props> {
public componentDidMount() {
this.props.getDashboards()
this.props.getLabels()
}
public render() {
const {status, dashboards, filterComponent, onFilterChange} = this.props
const {sortKey, sortDirection, sortType} = this.state
let body
const {
status,
dashboards,
onFilterChange,
sortKey,
sortDirection,
sortType,
onCreateDashboard,
searchTerm,
} = this.props
if (status === RemoteDataState.Done && !dashboards.length) {
body = (
<ResourceList.Body emptyState={null}>
{this.emptyState}
</ResourceList.Body>
)
} else {
body = (
<ResourceList.Body style={{height: '100%'}} emptyState={null}>
<DashboardCards
dashboards={dashboards}
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onFilterChange={onFilterChange}
/>
</ResourceList.Body>
return (
<DashobardsTableEmpty
searchTerm={searchTerm}
onCreateDashboard={onCreateDashboard}
summonImportFromTemplateOverlay={this.summonImportFromTemplateOverlay}
summonImportOverlay={this.summonImportOverlay}
/>
)
}
return (
<ResourceList>
<ResourceList.Header filterComponent={filterComponent}>
<ResourceList.Sorter
name="name"
sortKey="name"
sort={sortKey === 'name' ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
<ResourceList.Sorter
name="modified"
sortKey="meta.updatedAt"
sort={sortKey === 'meta.updatedAt' ? sortDirection : Sort.None}
onClick={this.handleClickColumn}
/>
</ResourceList.Header>
{body}
</ResourceList>
<DashboardCards
dashboards={dashboards}
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onFilterChange={onFilterChange}
/>
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
let sortType = SortTypes.String
if (sortKey === 'meta.updatedAt') {
sortType = SortTypes.Date
}
this.setState({sortKey, sortDirection: nextSort, sortType})
}
private summonImportOverlay = (): void => {
const {
router,
@ -127,35 +97,6 @@ class DashboardsTable extends PureComponent<Props, State> {
} = this.props
router.push(`/orgs/${orgID}/dashboards/import/template`)
}
private get emptyState(): JSX.Element {
const {onCreateDashboard, searchTerm} = this.props
if (searchTerm) {
return (
<EmptyState size={ComponentSize.Large} testID="empty-dashboards-list">
<EmptyState.Text>
No Dashboards match your search term
</EmptyState.Text>
</EmptyState>
)
}
return (
<EmptyState size={ComponentSize.Large} testID="empty-dashboards-list">
<EmptyState.Text>
Looks like you don't have any <b>Dashboards</b>, why not create one?
</EmptyState.Text>
<AddResourceDropdown
onSelectNew={onCreateDashboard}
onSelectImport={this.summonImportOverlay}
onSelectTemplate={this.summonImportFromTemplateOverlay}
resourceName="Dashboard"
canImportFromTemplate={true}
/>
</EmptyState>
)
}
}
const mstp = (state: AppState): StateProps => {

View File

@ -0,0 +1,19 @@
.label-card {
.cf-resource-card--contents {
flex-direction: row;
align-items: center;
}
.cf-resource-card--row {
margin-right: $cf-marg-c;
margin-bottom: 0;
}
}
.label-card--description {
font-weight: $cf-font-weight--medium;
}
.label-card--description__untitled {
color: $g8-storm;
font-style: italic;
}

View File

@ -1,5 +1,6 @@
// Libraries
import React, {PureComponent} from 'react'
import classnames from 'classnames'
// Components
import {
@ -26,24 +27,39 @@ export default class LabelCard extends PureComponent<Props> {
public render() {
const {label, onDelete} = this.props
const labelHasDescription = !!label.properties.description
const descriptionClassName = classnames('label-card--description', {
'label-card--description__untitled': !labelHasDescription,
})
const description = labelHasDescription
? label.properties.description
: 'No description'
return (
<>
<ResourceCard
testID="label-card"
contextMenu={<LabelContextMenu label={label} onDelete={onDelete} />}
name={
<LabelComponent
id={label.id}
name={label.name}
color={label.properties.color}
description={label.properties.description}
size={ComponentSize.Small}
onClick={this.handleClick}
/>
}
metaData={[<>Description: {label.properties.description}</>]}
/>
</>
<ResourceCard
className="label-card"
testID="label-card"
contextMenu={<LabelContextMenu label={label} onDelete={onDelete} />}
name={
<LabelComponent
id={label.id}
name={label.name}
color={label.properties.color}
description={label.properties.description}
size={ComponentSize.Small}
onClick={this.handleClick}
/>
}
>
<p
className={descriptionClassName}
data-testid="label-card--description"
>
{description}
</p>
</ResourceCard>
)
}

View File

@ -29,7 +29,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (mextSort: Sort, sortKey: string) => void
}
interface State {
@ -49,27 +48,9 @@ export default class LabelList extends PureComponent<Props, State> {
}
public render() {
const {sortKey, sortDirection, onClickColumn} = this.props
const headerKeys = ['name', 'properties.description']
return (
<>
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name={headerKeys[0]}
sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
testID="sorter--name"
/>
<ResourceList.Sorter
name="Description"
sortKey={headerKeys[1]}
sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
onClick={onClickColumn}
testID="sorter--desc"
/>
</ResourceList.Header>
<ResourceList.Body emptyState={this.props.emptyState}>
{this.rows}
</ResourceList.Body>

View File

@ -9,6 +9,7 @@ import CreateLabelOverlay from 'src/labels/components/CreateLabelOverlay'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import LabelList from 'src/labels/components/LabelList'
import FilterList from 'src/shared/components/FilterList'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
// Actions
import {createLabel, updateLabel, deleteLabel} from 'src/labels/actions/thunks'
@ -28,6 +29,7 @@ import {
Sort,
} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort'
import {LabelSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -39,7 +41,7 @@ interface StateProps {
interface State {
searchTerm: string
isOverlayVisible: boolean
sortKey: SortKey
sortKey: LabelSortKey
sortDirection: Sort
sortType: SortTypes
}
@ -52,8 +54,6 @@ interface DispatchProps {
type Props = DispatchProps & StateProps
type SortKey = keyof Label
const FilterLabels = FilterList<Label>()
@ErrorHandling
class Labels extends PureComponent<Props, State> {
@ -79,22 +79,39 @@ class Labels extends PureComponent<Props, State> {
sortType,
} = this.state
const leftHeaderItems = (
<>
<SearchWidget
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
placeholderText="Filter Labels..."
/>
<ResourceSortDropdown
resourceType={ResourceType.Labels}
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onSelect={this.handleSort}
/>
</>
)
const rightHeaderItems = (
<Button
text="Create Label"
color={ComponentColor.Primary}
icon={IconFont.Plus}
onClick={this.handleShowOverlay}
testID="button-create"
/>
)
return (
<>
<TabbedPageHeader>
<SearchWidget
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
placeholderText="Filter Labels..."
/>
<Button
text="Create Label"
color={ComponentColor.Primary}
icon={IconFont.Plus}
onClick={this.handleShowOverlay}
testID="button-create"
/>
</TabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={rightHeaderItems}
/>
<FilterLabels
list={labels}
searchKeys={['name', 'properties.description']}
@ -109,7 +126,6 @@ class Labels extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)}
</FilterLabels>
@ -123,9 +139,12 @@ class Labels extends PureComponent<Props, State> {
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
const sortType = SortTypes.String
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: LabelSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private handleShowOverlay = (): void => {

View File

@ -1,18 +0,0 @@
/*
RandomLabelColor Styles
------------------------------------------------------------------------------
*/
.random-color--button {
display: flex;
align-items: center;
justify-content: center;
.label-colors--swatch {
margin-right: $ix-marg-b;
}
> span.button-icon {
margin-right: 0;
}
}

View File

@ -6,7 +6,7 @@ import {connect} from 'react-redux'
import {withRouter, WithRouterProps} from 'react-router'
// Components
import SettingsTabbedPageHeader from 'src/settings/components/SettingsTabbedPageHeader'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import {EmptyState, Sort} from '@influxdata/clockface'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import MemberList from 'src/members/components/MemberList'
@ -59,13 +59,15 @@ class Members extends PureComponent<Props & WithRouterProps, State> {
return (
<>
<SettingsTabbedPageHeader>
<SearchWidget
placeholderText="Filter members..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
</SettingsTabbedPageHeader>
<TabbedPageHeader
childrenLeft={
<SearchWidget
placeholderText="Filter members..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
}
/>
<FilterMembers
list={this.props.members}
searchKeys={['name']}

View File

@ -0,0 +1,56 @@
.tabbed-page--header {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: $cf-marg-d;
}
.tabbed-page--header-left,
.tabbed-page--header-right {
display: flex;
align-items: center;
justify-content: center;
& > * {
margin-right: $cf-marg-a;
&:last-child {
margin-right: 0;
}
}
}
.tabbed-page--header-right {
order: 1;
margin-bottom: $cf-marg-b;
&:only-child {
margin-bottom: 0;
}
}
.tabbed-page--header-left {
order: 2;
}
@media screen and (min-width: $cf-grid--breakpoint-sm) {
.tabbed-page--header {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.tabbed-page--header-left {
flex: 1 0 0;
order: 1;
justify-content: flex-start;
}
.tabbed-page--header-right {
flex: 1 0 0;
order: 2;
justify-content: flex-end;
margin-bottom: 0;
}
}

View File

@ -14,8 +14,6 @@ import {Sort} from '@influxdata/clockface'
// Selectors
import {getSortedResources} from 'src/shared/utils/sort'
type SortKey = keyof Scraper
interface Props {
scrapers: Scraper[]
emptyState: JSX.Element
@ -24,7 +22,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
}
export default class ScraperList extends PureComponent<Props> {
@ -33,43 +30,17 @@ export default class ScraperList extends PureComponent<Props> {
)
public render() {
const {emptyState, sortKey, sortDirection, onClickColumn} = this.props
const {emptyState} = this.props
return (
<>
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name={this.headerKeys[0]}
sortKey={this.headerKeys[0]}
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name={this.headerKeys[1]}
sortKey={this.headerKeys[1]}
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name={this.headerKeys[2]}
sortKey={this.headerKeys[2]}
sort={sortKey === this.headerKeys[2] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
</ResourceList.Header>
<ResourceList.Body emptyState={emptyState}>
{this.scrapersList}
</ResourceList.Body>
</ResourceList>
</>
<ResourceList>
<ResourceList.Body emptyState={emptyState}>
{this.scrapersList}
</ResourceList.Body>
</ResourceList>
)
}
private get headerKeys(): SortKey[] {
return ['name', 'url', 'bucket']
}
public get scrapersList(): JSX.Element[] {
const {
scrapers,

View File

@ -7,10 +7,11 @@ import {isEmpty} from 'lodash'
// Components
import {Button, EmptyState, Sort} from '@influxdata/clockface'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import SettingsTabbedPageHeader from 'src/settings/components/SettingsTabbedPageHeader'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import ScraperList from 'src/scrapers/components/ScraperList'
import NoBucketsWarning from 'src/buckets/components/NoBucketsWarning'
import FilterList from 'src/shared/components/FilterList'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
// Actions
import {updateScraper, deleteScraper} from 'src/scrapers/actions/thunks'
@ -27,6 +28,7 @@ import {
ComponentStatus,
} from '@influxdata/clockface'
import {AppState, Bucket, Scraper, Organization, ResourceType} from 'src/types'
import {ScraperSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Selectors
import {getOrg} from 'src/organizations/selectors'
@ -47,13 +49,11 @@ type Props = StateProps & DispatchProps & WithRouterProps
interface State {
searchTerm: string
sortKey: SortKey
sortKey: ScraperSortKey
sortDirection: Sort
sortType: SortTypes
}
type SortKey = keyof Scraper
const FilterScrapers = FilterList<Scraper>()
@ErrorHandling
@ -73,16 +73,31 @@ class Scrapers extends PureComponent<Props, State> {
const {searchTerm, sortKey, sortDirection, sortType} = this.state
const {scrapers} = this.props
const leftHeaderItems = (
<>
<SearchWidget
placeholderText="Filter scrapers..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
<ResourceSortDropdown
resourceType={ResourceType.Scrapers}
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onSelect={this.handleSort}
/>
</>
)
return (
<>
<SettingsTabbedPageHeader>
<SearchWidget
placeholderText="Filter scrapers..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
{this.createScraperButton('create-scraper-button-header')}
</SettingsTabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={this.createScraperButton(
'create-scraper-button-header'
)}
/>
<NoBucketsWarning visible={this.hasNoBuckets} resourceName="Scrapers" />
<FilterScrapers
searchTerm={searchTerm}
@ -98,7 +113,6 @@ class Scrapers extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)}
</FilterScrapers>
@ -106,9 +120,12 @@ class Scrapers extends PureComponent<Props, State> {
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
const sortType = SortTypes.String
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: ScraperSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private get hasNoBuckets(): boolean {

View File

@ -1,23 +0,0 @@
// Libraries
import React, {SFC} from 'react'
// Components
import {FlexBox, FlexDirection, JustifyContent} from '@influxdata/clockface'
interface Props {
children: JSX.Element[] | JSX.Element
className?: string
}
const SettingsTabbedPageHeader: SFC<Props> = ({children, className}) => (
<FlexBox
direction={FlexDirection.Row}
justifyContent={JustifyContent.SpaceBetween}
style={{marginBottom: '32px'}}
className={className}
>
{children}
</FlexBox>
)
export default SettingsTabbedPageHeader

View File

@ -0,0 +1,90 @@
// Libraries
import React from 'react'
// Components
import {Dropdown} from '@influxdata/clockface'
// Utilities
import {generateSortItems} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Types
import {Sort} from '@influxdata/clockface'
import {
SortKey,
SortDropdownItem,
} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
import {SortTypes} from 'src/shared/utils/sort'
import {ResourceType} from 'src/types'
interface ComponentProps {
resourceType: ResourceType
sortDirection: Sort
sortKey: SortKey
sortType: SortTypes
onSelect: (sortKey: SortKey, sortDirection: Sort, sortType: SortTypes) => void
width?: number
}
function ResourceSortDropdown({
sortDirection,
sortKey,
sortType,
onSelect,
resourceType,
width = 210,
}: ComponentProps) {
const sortDropdownItems = generateSortItems(resourceType)
const {label} = sortDropdownItems.find(
item =>
item.sortKey === sortKey &&
item.sortDirection === sortDirection &&
item.sortType === sortType
)
const handleItemClick = (item: SortDropdownItem): void => {
const {sortKey, sortDirection, sortType} = item
onSelect(sortKey, sortDirection, sortType)
}
const button = (active, onClick) => (
<Dropdown.Button
onClick={onClick}
active={active}
testID="resource-sorter--button"
>
{`Sort by ${label}`}
</Dropdown.Button>
)
const menu = onCollapse => (
<Dropdown.Menu onCollapse={onCollapse}>
{sortDropdownItems.map(item => (
<Dropdown.Item
key={`${item.sortKey}${item.sortDirection}`}
value={item}
onClick={handleItemClick}
testID={`resource-sorter--${item.sortKey}-${item.sortDirection}`}
selected={
item.sortKey === sortKey &&
item.sortType === sortType &&
item.sortDirection === sortDirection
}
>
{item.label}
</Dropdown.Item>
))}
</Dropdown.Menu>
)
return (
<Dropdown
button={button}
menu={menu}
style={{flexBasis: `${width}px`, width: `${width}px`}}
testID="resource-sorter"
/>
)
}
export default ResourceSortDropdown

View File

@ -0,0 +1,298 @@
import {Sort} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort'
import {
ResourceType,
Dashboard,
Task,
Variable,
Label,
Template,
Bucket,
Telegraf,
Scraper,
} from 'src/types'
export type DashboardSortKey = keyof Dashboard | 'meta.updatedAt'
export type TaskSortKey = keyof Task
export type VariableSortKey = keyof Variable | 'arguments.type'
export type LabelSortKey = keyof Label | 'properties.description'
export type TemplateSortKey = keyof Template | 'meta.name' | 'meta.description'
export type BucketSortKey = keyof Bucket | 'retentionRules[0].everySeconds'
export type TelegrafSortKey = keyof Telegraf
export type ScraperSortKey = keyof Scraper
export type SortKey =
| DashboardSortKey
| TaskSortKey
| VariableSortKey
| LabelSortKey
| TemplateSortKey
| BucketSortKey
| TelegrafSortKey
| ScraperSortKey
export interface SortDropdownItem {
label: string
sortKey: SortKey
sortType: SortTypes
sortDirection: Sort
}
export const generateSortItems = (
resourceType: ResourceType
): SortDropdownItem[] => {
switch (resourceType) {
case ResourceType.Dashboards:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Modified (Oldest)',
sortKey: 'meta.updatedAt',
sortType: SortTypes.Date,
sortDirection: Sort.Ascending,
},
{
label: 'Modified (Newest)',
sortKey: 'meta.updatedAt',
sortType: SortTypes.Date,
sortDirection: Sort.Descending,
},
]
case ResourceType.Tasks:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Active',
sortKey: 'status',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Inactive',
sortKey: 'status',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Completed (Oldest)',
sortKey: 'latestCompleted',
sortType: SortTypes.Date,
sortDirection: Sort.Ascending,
},
{
label: 'Completed (Newest)',
sortKey: 'latestCompleted',
sortType: SortTypes.Date,
sortDirection: Sort.Descending,
},
{
label: 'Schedule (Most Often)',
sortKey: 'every',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Schedule (Least Often)',
sortKey: 'every',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Variables:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Type (A → Z)',
sortKey: 'arguments.type',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Type (Z → A)',
sortKey: 'arguments.type',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Labels:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Description (A → Z)',
sortKey: 'properties.description',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Description (Z → A)',
sortKey: 'properties.description',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Templates:
return [
{
label: 'Name (A → Z)',
sortKey: 'meta.name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'meta.name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Description (A → Z)',
sortKey: 'meta.description',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Description (Z → A)',
sortKey: 'meta.description',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Buckets:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Retention (Ascending)',
sortKey: 'retentionRules[0].everySeconds',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Retention (Descending)',
sortKey: 'retentionRules[0].everySeconds',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Telegrafs:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Description (Ascending)',
sortKey: 'description',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Description (Descending)',
sortKey: 'description',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
case ResourceType.Scrapers:
return [
{
label: 'Name (A → Z)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Name (Z → A)',
sortKey: 'name',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'URL (Ascending)',
sortKey: 'url',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'URL (Descending)',
sortKey: 'url',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
{
label: 'Bucket (Ascending)',
sortKey: 'bucket',
sortType: SortTypes.String,
sortDirection: Sort.Ascending,
},
{
label: 'Bucket (Descending)',
sortKey: 'bucket',
sortType: SortTypes.String,
sortDirection: Sort.Descending,
},
]
}
}

View File

@ -0,0 +1,3 @@
.search-widget-input {
flex: 1 0 80px;
}

View File

@ -13,7 +13,6 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
onSearch: (searchTerm: string) => void
widthPixels: number
placeholderText: string
searchTerm: string
testID: string
@ -50,18 +49,18 @@ class SearchWidget extends Component<Props, State> {
}
public render() {
const {placeholderText, widthPixels, testID} = this.props
const {placeholderText, testID} = this.props
const {searchTerm} = this.state
return (
<Input
icon={IconFont.Search}
placeholder={placeholderText}
style={{width: `${widthPixels}px`}}
value={searchTerm}
onChange={this.handleChange}
onBlur={this.handleBlur}
testID={testID}
className="search-widget-input"
/>
)
}

View File

@ -2,11 +2,30 @@
import React, {SFC} from 'react'
interface Props {
children: JSX.Element[] | JSX.Element
childrenLeft?: JSX.Element[] | JSX.Element
childrenRight?: JSX.Element[] | JSX.Element
}
const TabbedPageHeader: SFC<Props> = ({children}) => (
<div className="tabbed-page-section--header">{children}</div>
)
const TabbedPageHeader: SFC<Props> = ({childrenLeft, childrenRight}) => {
let leftHeader = <></>
let rightHeader = <></>
if (childrenLeft) {
leftHeader = <div className="tabbed-page--header-left">{childrenLeft}</div>
}
if (childrenRight) {
rightHeader = (
<div className="tabbed-page--header-right">{childrenRight}</div>
)
}
return (
<div className="tabbed-page--header" data-testid="tabbed-page--header">
{leftHeader}
{rightHeader}
</div>
)
}
export default TabbedPageHeader

View File

@ -51,7 +51,6 @@
@import 'src/buckets/components/Retention.scss';
@import 'src/buckets/components/BucketAddDataButton.scss';
@import 'src/buckets/components/NoBucketsWarning.scss';
@import 'src/telegrafs/components/Collectors.scss';
@import 'src/telegrafs/components/TelegrafConfigOverlay.scss';
@import 'src/variables/components/CreateVariableOverlay.scss';
@import 'src/variables/components/VariableDropdown.scss';
@ -62,7 +61,6 @@
@import 'src/tasks/components/TaskForm.scss';
@import 'src/tasks/components/TasksPage.scss';
@import 'src/labels/components/LabelOverlayForm.scss';
@import 'src/labels/components/RandomLabelColor.scss';
@import 'src/dataExplorer/components/SaveAsButton.scss';
@import 'src/dataExplorer/components/DataExplorer.scss';
@import 'src/dataExplorer/components/SaveAsButton.scss';
@ -76,6 +74,7 @@
@import 'src/me/graphics/CollectorGraphic.scss';
@import 'src/pageLayout/components/RenamablePageTitle.scss';
@import 'src/pageLayout/components/OrgSwitcherOverlay.scss';
@import 'src/pageLayout/components/TabbedPage.scss';
@import 'src/timeMachine/components/SelectorList.scss';
@import 'src/timeMachine/components/Queries.scss';
@import 'src/timeMachine/components/EditorShortcutsTooltip.scss';
@ -98,6 +97,7 @@
@import 'src/shared/components/AutoDomainInput.scss';
@import 'src/shared/components/Checkbox.scss';
@import 'src/shared/components/dapperScrollbars/DapperScrollbars.scss';
@import 'src/shared/components/search_widget/SearchWidget.scss';
@import 'src/templates/components/createFromTemplateOverlay/CreateFromTemplateOverlay.scss';
@import 'src/onboarding/components/SigninForm.scss';
@import 'src/onboarding/containers/LoginPage.scss';
@ -118,6 +118,7 @@
@import 'src/alerting/components/LevelTableField.scss';
@import 'src/alerting/components/SentTableField.scss';
@import 'src/notifications/rules/components/RuleOverlayFooter.scss';
@import 'src/labels/components/LabelCard.scss';
@import 'src/alerting/components/SearchBar.scss';
@import 'src/clientLibraries/components/ClientLibraryOverlay.scss';
@import 'src/dashboards/components/DashboardsCardGrid.scss';

View File

@ -8,12 +8,21 @@ import {
ComponentSize,
ComponentStatus,
Page,
Sort,
FlexBox,
FlexDirection,
} from '@influxdata/clockface'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
// Types
import {LimitStatus} from 'src/cloud/actions/limits'
import {setSearchTerm as setSearchTermAction} from 'src/tasks/actions/creators'
import {TaskSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
import {SortTypes} from 'src/shared/utils/sort'
import {ResourceType} from 'src/types'
interface Props {
onCreateTask: () => void
@ -22,6 +31,16 @@ interface Props {
onImportTask: () => void
limitStatus: LimitStatus
onImportFromTemplate: () => void
searchTerm: string
setSearchTerm: typeof setSearchTermAction
sortKey: TaskSortKey
sortDirection: Sort
sortType: SortTypes
onSort: (
sortKey: TaskSortKey,
sortDirection: Sort,
sortType: SortTypes
) => void
}
export default class TasksHeader extends PureComponent<Props> {
@ -32,6 +51,12 @@ export default class TasksHeader extends PureComponent<Props> {
showInactive,
onImportTask,
onImportFromTemplate,
setSearchTerm,
searchTerm,
sortKey,
sortType,
sortDirection,
onSort,
} = this.props
return (
@ -42,14 +67,31 @@ export default class TasksHeader extends PureComponent<Props> {
</Page.Header>
<Page.ControlBar fullWidth={false}>
<Page.ControlBarLeft>
<InputLabel>Show Inactive</InputLabel>
<SlideToggle
active={showInactive}
size={ComponentSize.ExtraSmall}
onChange={setShowInactive}
<SearchWidget
placeholderText="Filter tasks..."
onSearch={setSearchTerm}
searchTerm={searchTerm}
/>
<ResourceSortDropdown
resourceType={ResourceType.Tasks}
sortKey={sortKey}
sortType={sortType}
sortDirection={sortDirection}
onSelect={onSort}
/>
</Page.ControlBarLeft>
<Page.ControlBarRight>
<FlexBox
direction={FlexDirection.Row}
margin={ComponentSize.Medium}
>
<InputLabel>Show Inactive</InputLabel>
<SlideToggle
active={showInactive}
size={ComponentSize.ExtraSmall}
onChange={setShowInactive}
/>
</FlexBox>
<AddResourceDropdown
canImportFromTemplate
onSelectNew={onCreateTask}

View File

@ -11,9 +11,9 @@ import EmptyTasksList from 'src/tasks/components/EmptyTasksList'
import {Task} from 'src/types'
import {SortTypes} from 'src/shared/utils/sort'
import {Sort} from '@influxdata/clockface'
import {selectTask, addTaskLabel, runTask} from 'src/tasks/actions/thunks'
import {checkTaskLimits as checkTaskLimitsAction} from 'src/cloud/actions/limits'
import {TaskSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Selectors
import {getSortedResources} from 'src/shared/utils/sort'
@ -33,16 +33,13 @@ interface Props {
onUpdate: (name: string, taskID: string) => void
filterComponent?: JSX.Element
onImportTask: () => void
sortKey: string
sortKey: TaskSortKey
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
checkTaskLimits: typeof checkTaskLimitsAction
onImportFromTemplate: () => void
}
type SortKey = keyof Task
interface State {
taskLabelsEdit: Task
isEditingTaskLabels: boolean
@ -70,45 +67,13 @@ export default class TasksList extends PureComponent<Props, State> {
searchTerm,
onCreate,
totalCount,
filterComponent,
onImportTask,
sortKey,
sortDirection,
onClickColumn,
onImportFromTemplate,
} = this.props
const headerKeys: SortKey[] = ['name', 'status', 'every', 'latestCompleted']
return (
<>
<ResourceList>
<ResourceList.Header filterComponent={filterComponent}>
<ResourceList.Sorter
name="Name"
sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name="Active"
sortKey={headerKeys[1]}
sort={sortKey === headerKeys[1] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name="Schedule"
sortKey={headerKeys[2]}
sort={sortKey === headerKeys[2] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name="Last Completed"
sortKey={headerKeys[3]}
sort={sortKey === headerKeys[3] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
</ResourceList.Header>
<ResourceList.Body
emptyState={
<EmptyTasksList

View File

@ -8,7 +8,6 @@ import TasksList from 'src/tasks/components/TasksList'
import {Page} from '@influxdata/clockface'
import {ErrorHandling} from 'src/shared/decorators/errors'
import FilterList from 'src/shared/components/FilterList'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import GetResources from 'src/resources/components/GetResources'
import GetAssetLimits from 'src/cloud/components/GetAssetLimits'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
@ -43,6 +42,7 @@ import {InjectedRouter, WithRouterProps} from 'react-router'
import {Sort} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort'
import {extractTaskLimits} from 'src/cloud/utils/limits'
import {TaskSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Selectors
import {getAll} from 'src/resources/selectors'
@ -80,13 +80,11 @@ type Props = ConnectedDispatchProps &
interface State {
isImporting: boolean
taskLabelsEdit: Task
sortKey: SortKey
sortKey: TaskSortKey
sortDirection: Sort
sortType: SortTypes
}
type SortKey = keyof Task
const Filter = FilterList<Task>()
@ErrorHandling
@ -134,6 +132,12 @@ class TasksPage extends PureComponent<Props, State> {
onImportTask={this.summonImportOverlay}
onImportFromTemplate={this.summonImportFromTemplateOverlay}
limitStatus={limitStatus}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onSort={this.handleSort}
/>
<Page.Contents fullWidth={false} scrollable={true}>
<GetResources resources={[ResourceType.Tasks, ResourceType.Labels]}>
@ -160,7 +164,6 @@ class TasksPage extends PureComponent<Props, State> {
onAddTaskLabel={onAddTaskLabel}
onRunTask={onRunTask}
onFilterChange={setSearchTerm}
filterComponent={this.search}
onUpdate={updateTaskName}
onImportTask={this.summonImportOverlay}
onImportFromTemplate={
@ -169,7 +172,6 @@ class TasksPage extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
checkTaskLimits={checkTaskLimits}
/>
)}
@ -184,14 +186,12 @@ class TasksPage extends PureComponent<Props, State> {
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
let sortType = SortTypes.String
if (sortKey === 'latestCompleted') {
sortType = SortTypes.Date
}
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: TaskSortKey,
sortDirection: Sort,
sortType: SortTypes
) => {
this.setState({sortKey, sortDirection, sortType})
}
private handleActivate = (task: Task) => {
@ -233,18 +233,6 @@ class TasksPage extends PureComponent<Props, State> {
router.push(`/orgs/${orgID}/tasks/import`)
}
private get search(): JSX.Element {
const {setSearchTerm, searchTerm} = this.props
return (
<SearchWidget
placeholderText="Filter tasks..."
onSearch={setSearchTerm}
searchTerm={searchTerm}
/>
)
}
private get filteredTasks(): Task[] {
const {tasks, showInactive} = this.props
const matchingTasks = tasks.filter(t => {

View File

@ -17,14 +17,11 @@ import {updateTelegraf, deleteTelegraf} from 'src/telegrafs/actions/thunks'
// Selectors
import {getAll} from 'src/resources/selectors'
type SortKey = keyof Telegraf
interface OwnProps {
emptyState: JSX.Element
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
onFilterChange: (searchTerm: string) => void
}
@ -45,19 +42,10 @@ class CollectorList extends PureComponent<Props> {
)
public render() {
const {emptyState, sortKey, sortDirection, onClickColumn} = this.props
const {emptyState} = this.props
return (
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
sortKey="name"
sort={sortKey === 'name' ? sortDirection : Sort.None}
name="Name"
onClick={onClickColumn}
testID="name-sorter"
/>
</ResourceList.Header>
<ResourceList.Body emptyState={emptyState}>
{this.collectorsList}
</ResourceList.Body>
@ -130,7 +118,6 @@ class FilteredCollectorList extends PureComponent<FilteredProps> {
sortKey,
sortDirection,
sortType,
onClickColumn,
onUpdateTelegraf,
onDeleteTelegraf,
} = this.props
@ -148,7 +135,6 @@ class FilteredCollectorList extends PureComponent<FilteredProps> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={onClickColumn}
onUpdateTelegraf={onUpdateTelegraf}
onDeleteTelegraf={onDeleteTelegraf}
/>

View File

@ -1,21 +0,0 @@
.telegraf-collectors--header {
min-width: 530px;
@media screen and (max-width: 680px) {
// willing to take suggestions about removing this
display: block !important;
> .cf-input {
width: 100%;
margin: 0 auto;
}
}
}
.telegraf-collectors-button-wrap {
display: flex;
box-sizing: border-box;
text-align: right;
@media screen and (max-width: 680px) {
margin: 8px auto 0;
}
}

View File

@ -17,11 +17,12 @@ import {
ComponentStatus,
} from '@influxdata/clockface'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import SettingsTabbedPageHeader from 'src/settings/components/SettingsTabbedPageHeader'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import {FilteredList} from 'src/telegrafs/components/CollectorList'
import TelegrafExplainer from 'src/telegrafs/components/TelegrafExplainer'
import NoBucketsWarning from 'src/buckets/components/NoBucketsWarning'
import GetResources from 'src/resources/components/GetResources'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
// Actions
import {updateTelegraf, deleteTelegraf} from 'src/telegrafs/actions/thunks'
@ -30,13 +31,14 @@ import {updateTelegraf, deleteTelegraf} from 'src/telegrafs/actions/thunks'
import {ErrorHandling} from 'src/shared/decorators/errors'
// Types
import {Telegraf, OverlayState, AppState, Bucket, ResourceType} from 'src/types'
import {OverlayState, AppState, Bucket, ResourceType} from 'src/types'
import {
setTelegrafConfigID,
setTelegrafConfigName,
clearDataLoaders,
} from 'src/dataLoaders/actions/dataLoaders'
import {SortTypes} from 'src/shared/utils/sort'
import {TelegrafSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
// Selectors
import {getOrg} from 'src/organizations/selectors'
@ -63,13 +65,11 @@ interface State {
searchTerm: string
instructionsOverlay: OverlayState
collectorID?: string
sortKey: SortKey
sortKey: TelegrafSortKey
sortDirection: Sort
sortType: SortTypes
}
type SortKey = keyof Telegraf
@ErrorHandling
class Collectors extends PureComponent<Props, State> {
constructor(props: Props) {
@ -89,32 +89,48 @@ class Collectors extends PureComponent<Props, State> {
public render() {
const {hasTelegrafs} = this.props
const {searchTerm, sortKey, sortDirection, sortType} = this.state
const collecorsLeftHeaderItems = (
<>
<SearchWidget
placeholderText="Filter telegraf configurations..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
<ResourceSortDropdown
resourceType={ResourceType.Telegrafs}
sortDirection={sortDirection}
sortKey={sortKey}
sortType={sortType}
onSelect={this.handleSort}
/>
</>
)
const collecorsRightHeaderItems = (
<>
<Button
text="InfluxDB Output Plugin"
icon={IconFont.Eye}
color={ComponentColor.Secondary}
onClick={this.handleJustTheOutput}
titleText="Output section of telegraf.conf for V2"
testID="button--output-only"
/>
{this.createButton}
</>
)
return (
<>
<NoBucketsWarning
visible={this.hasNoBuckets}
resourceName="Telegraf Configurations"
/>
<SettingsTabbedPageHeader className="telegraf-collectors--header">
<SearchWidget
placeholderText="Filter telegraf configurations..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
<div className="telegraf-collectors-button-wrap">
<Button
text="InfluxDB Output Plugin"
icon={IconFont.Eye}
color={ComponentColor.Secondary}
style={{marginRight: '8px'}}
onClick={this.handleJustTheOutput}
titleText="Output section of telegraf.conf for V2"
testID="button--output-only"
/>
{this.createButton}
</div>
</SettingsTabbedPageHeader>
<TabbedPageHeader
childrenLeft={collecorsLeftHeaderItems}
childrenRight={collecorsRightHeaderItems}
/>
<Grid>
<Grid.Row>
<Grid.Column
@ -130,7 +146,6 @@ class Collectors extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
</GetResources>
</Grid.Column>
@ -149,9 +164,12 @@ class Collectors extends PureComponent<Props, State> {
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
const sortType = SortTypes.String
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: TelegrafSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private get hasNoBuckets(): boolean {

View File

@ -6,6 +6,7 @@ import {
Panel,
EmptyState,
InfluxColors,
Gradients,
ComponentSize,
} from '@influxdata/clockface'
import {TextAlignProperty} from 'csstype'
@ -21,10 +22,7 @@ const TelegrafExplainer: FunctionComponent<Props> = ({
textAlign = 'inherit',
bodySize,
}) => (
<Panel
backgroundColor={InfluxColors.Smoke}
style={{textAlign, marginTop: 32}}
>
<Panel gradient={Gradients.PolarExpress} border={true} style={{textAlign}}>
{hasNoTelegrafs && (
<EmptyState.Text style={{color: InfluxColors.Platinum, marginTop: 16}}>
What is Telegraf?

View File

@ -15,8 +15,6 @@ import {Sort} from 'src/clockface'
// Selectors
import {getSortedResources} from 'src/shared/utils/sort'
type SortKey = 'meta.name'
export type TemplateOrSummary = Template | TemplateSummary
export interface StaticTemplate {
@ -32,7 +30,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
}
export default class StaticTemplatesList extends PureComponent<Props> {
@ -41,26 +38,10 @@ export default class StaticTemplatesList extends PureComponent<Props> {
)
public render() {
const {
searchTerm,
onImport,
sortKey,
sortDirection,
onClickColumn,
} = this.props
const headerKeys: SortKey[] = ['meta.name']
const {searchTerm, onImport} = this.props
return (
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name="Name"
sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
</ResourceList.Header>
<ResourceList.Body
emptyState={
<EmptyTemplatesList searchTerm={searchTerm} onImport={onImport} />

View File

@ -16,8 +16,6 @@ import {Sort} from 'src/clockface'
// Selectors
import {getSortedResources} from 'src/shared/utils/sort'
type SortKey = 'meta.name'
interface Props {
templates: TemplateSummary[]
searchTerm: string
@ -26,7 +24,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
}
export default class TemplatesList extends PureComponent<Props> {
@ -35,27 +32,11 @@ export default class TemplatesList extends PureComponent<Props> {
)
public render() {
const {
searchTerm,
onImport,
sortKey,
sortDirection,
onClickColumn,
} = this.props
const headerKeys: SortKey[] = ['meta.name']
const {searchTerm, onImport} = this.props
return (
<>
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name="Name"
sortKey={headerKeys[0]}
sort={sortKey === headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
</ResourceList.Header>
<ResourceList.Body
emptyState={
<EmptyTemplatesList searchTerm={searchTerm} onImport={onImport} />

View File

@ -13,7 +13,8 @@ import StaticTemplatesList, {
import {ErrorHandling} from 'src/shared/decorators/errors'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import GetResources from 'src/resources/components/GetResources'
import SettingsTabbedPageHeader from 'src/settings/components/SettingsTabbedPageHeader'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
// Types
import {AppState, ResourceType, TemplateSummary} from 'src/types'
@ -24,16 +25,14 @@ import {
ComponentColor,
IconFont,
SelectGroup,
FlexBox,
FlexDirection,
ComponentSize,
} from '@influxdata/clockface'
import {TemplateSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
import {staticTemplates as statics} from 'src/templates/constants/defaultTemplates'
// Selectors
import {getAll} from 'src/resources/selectors/getAll'
// Constants
const staticTemplates: StaticTemplate[] = _.map(statics, (template, name) => ({
name,
template: template as TemplateOrSummary,
@ -51,14 +50,12 @@ type Props = OwnProps & StateProps
interface State {
searchTerm: string
sortKey: SortKey
sortKey: TemplateSortKey
sortDirection: Sort
sortType: SortTypes
activeTab: string
}
type SortKey = 'meta.name'
const FilterStaticTemplates = FilterList<StaticTemplate>()
const FilterTemplateSummaries = FilterList<TemplateSummary>()
@ -78,43 +75,56 @@ class TemplatesPage extends PureComponent<Props, State> {
public render() {
const {onImport} = this.props
const {activeTab} = this.state
const {activeTab, sortType, sortKey, sortDirection} = this.state
const leftHeaderItems = (
<>
{this.filterComponent}
<SelectGroup>
<SelectGroup.Option
name="template-type"
id="static-templates"
active={activeTab === 'static-templates'}
value="static-templates"
onClick={this.handleClickTab}
titleText="Static Templates"
>
Static Templates
</SelectGroup.Option>
<SelectGroup.Option
name="template-type"
id="user-templates"
active={activeTab === 'user-templates'}
value="user-templates"
onClick={this.handleClickTab}
titleText="User Templates"
>
User Templates
</SelectGroup.Option>
</SelectGroup>
<ResourceSortDropdown
resourceType={ResourceType.Templates}
sortType={sortType}
sortKey={sortKey}
sortDirection={sortDirection}
onSelect={this.handleSort}
/>
</>
)
return (
<>
<SettingsTabbedPageHeader>
<FlexBox direction={FlexDirection.Row} margin={ComponentSize.Small}>
{this.filterComponent}
<SelectGroup>
<SelectGroup.Option
name="template-type"
id="static-templates"
active={activeTab === 'static-templates'}
value="static-templates"
onClick={this.handleClickTab}
titleText="Static Templates"
>
Static Templates
</SelectGroup.Option>
<SelectGroup.Option
name="template-type"
id="user-templates"
active={activeTab === 'user-templates'}
value="user-templates"
onClick={this.handleClickTab}
titleText="User Templates"
>
User Templates
</SelectGroup.Option>
</SelectGroup>
</FlexBox>
<Button
text="Import Template"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={onImport}
/>
</SettingsTabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={
<Button
text="Import Template"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={onImport}
/>
}
/>
{this.templatesList}
</>
)
@ -124,9 +134,12 @@ class TemplatesPage extends PureComponent<Props, State> {
this.setState({activeTab: val})
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
const sortType = SortTypes.String
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: TemplateSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private get templatesList(): JSX.Element {
@ -150,7 +163,6 @@ class TemplatesPage extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)
}}
@ -176,7 +188,6 @@ class TemplatesPage extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)
}}

View File

@ -14,8 +14,6 @@ import {Sort} from '@influxdata/clockface'
// Selectors
import {getSortedResources} from 'src/shared/utils/sort'
type SortKey = keyof Variable | 'arguments.type'
interface Props {
variables: Variable[]
emptyState: JSX.Element
@ -24,7 +22,6 @@ interface Props {
sortKey: string
sortDirection: Sort
sortType: SortTypes
onClickColumn: (nextSort: Sort, sortKey: SortKey) => void
}
interface State {
@ -49,25 +46,11 @@ export default class VariableList extends PureComponent<Props, State> {
}
public render() {
const {emptyState, sortKey, sortDirection, onClickColumn} = this.props
const {emptyState} = this.props
return (
<>
<ResourceList>
<ResourceList.Header>
<ResourceList.Sorter
name="Name"
sortKey={this.headerKeys[0]}
sort={sortKey === this.headerKeys[0] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
<ResourceList.Sorter
name="Type"
sortKey={this.headerKeys[1]}
sort={sortKey === this.headerKeys[1] ? sortDirection : Sort.None}
onClick={onClickColumn}
/>
</ResourceList.Header>
<ResourceList.Body emptyState={emptyState}>
{this.rows}
</ResourceList.Body>
@ -76,10 +59,6 @@ export default class VariableList extends PureComponent<Props, State> {
)
}
private get headerKeys(): SortKey[] {
return ['name', 'arguments.type']
}
private get rows(): JSX.Element[] {
const {
variables,

View File

@ -15,6 +15,7 @@ import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader
import VariableList from 'src/variables/components/VariableList'
import Filter from 'src/shared/components/FilterList'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
import GetResources from 'src/resources/components/GetResources'
import {Sort} from '@influxdata/clockface'
@ -22,6 +23,7 @@ import {Sort} from '@influxdata/clockface'
import {AppState, OverlayState, ResourceType, Variable} from 'src/types'
import {ComponentSize} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort'
import {VariableSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
interface StateProps {
variables: Variable[]
@ -36,13 +38,11 @@ type Props = StateProps & DispatchProps & WithRouterProps
interface State {
searchTerm: string
importOverlayState: OverlayState
sortKey: SortKey
sortKey: VariableSortKey
sortDirection: Sort
sortType: SortTypes
}
type SortKey = keyof Variable
const FilterList = Filter<Variable>()
class VariablesTab extends PureComponent<Props, State> {
@ -58,20 +58,37 @@ class VariablesTab extends PureComponent<Props, State> {
const {variables} = this.props
const {searchTerm, sortKey, sortDirection, sortType} = this.state
const leftHeaderItems = (
<>
<SearchWidget
placeholderText="Filter variables..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
<ResourceSortDropdown
onSelect={this.handleSort}
resourceType={ResourceType.Variables}
sortDirection={sortDirection}
sortKey={sortKey}
sortType={sortType}
/>
</>
)
const rightHeaderItems = (
<AddResourceDropdown
resourceName="Variable"
onSelectImport={this.handleOpenImportOverlay}
onSelectNew={this.handleOpenCreateOverlay}
/>
)
return (
<>
<TabbedPageHeader>
<SearchWidget
placeholderText="Filter variables..."
searchTerm={searchTerm}
onSearch={this.handleFilterChange}
/>
<AddResourceDropdown
resourceName="Variable"
onSelectImport={this.handleOpenImportOverlay}
onSelectNew={this.handleOpenCreateOverlay}
/>
</TabbedPageHeader>
<TabbedPageHeader
childrenLeft={leftHeaderItems}
childrenRight={rightHeaderItems}
/>
<GetResources resources={[ResourceType.Labels]}>
<FilterList
searchTerm={searchTerm}
@ -87,7 +104,6 @@ class VariablesTab extends PureComponent<Props, State> {
sortKey={sortKey}
sortDirection={sortDirection}
sortType={sortType}
onClickColumn={this.handleClickColumn}
/>
)}
</FilterList>
@ -96,9 +112,12 @@ class VariablesTab extends PureComponent<Props, State> {
)
}
private handleClickColumn = (nextSort: Sort, sortKey: SortKey) => {
const sortType = SortTypes.String
this.setState({sortKey, sortDirection: nextSort, sortType})
private handleSort = (
sortKey: VariableSortKey,
sortDirection: Sort,
sortType: SortTypes
): void => {
this.setState({sortKey, sortDirection, sortType})
}
private get emptyState(): JSX.Element {