feat(ui): redesign asset & rate limit alerts (#19032)

* feat(ui): update rate limit alert

* feat(ui): update to clockface 2.3.0

* feat(ui): update limit alert components

* feat(ui): add home page limit alert

* feat(ui): replace alert page limit alerts

* feat(ui): relace load data limit alerts

* feat(ui): replace dashboards limit alerts

* feat(ui): replace data explorer limit alerts

* feat(ui): replace tasks limit alerts

* feat(ui): replace settings limit alerts

* fix(ui): prettier

* feat(ui): update changelog

* fix(ui): update clockface to 2.3.1 and fix tests

* fix(ui): change test id
pull/19055/head
Daniel Campbell 2020-07-23 15:29:57 -07:00 committed by GitHub
parent 17391d6924
commit 4df52d0cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 284 additions and 311 deletions

View File

@ -21,6 +21,7 @@
1. [19029](https://github.com/influxdata/influxdb/pull/19029): Navigating away from a dashboard cancels all pending queries 1. [19029](https://github.com/influxdata/influxdb/pull/19029): Navigating away from a dashboard cancels all pending queries
1. [19003](https://github.com/influxdata/influxdb/pull/19003): Upgrade to Flux v0.74.0 1. [19003](https://github.com/influxdata/influxdb/pull/19003): Upgrade to Flux v0.74.0
1. [19040](https://github.com/influxdata/influxdb/pull/19040): Drop the REPL command from influx CLI 1. [19040](https://github.com/influxdata/influxdb/pull/19040): Drop the REPL command from influx CLI
1. [19032](https://github.com/influxdata/influxdb/pull/19032): Redesign asset & rate limit alerts
### Bug Fixes ### Bug Fixes

View File

@ -802,12 +802,12 @@ describe('DataExplorer', () => {
cy.getByTestID('raw-data--toggle').click() cy.getByTestID('raw-data--toggle').click()
cy.get('.time-machine--view').within(() => { cy.get('.time-machine--view').within(() => {
cy.get('.cf-dapper-scrollbars--thumb-y') // TODO(zoe): replace with test ids https://github.com/influxdata/clockface/issues/507 cy.getByTestID('rawdata-table--scrollbar--thumb-y')
.trigger('mousedown', {force: true}) .trigger('mousedown', {force: true})
.trigger('mousemove', {clientY: 5000}) .trigger('mousemove', {clientY: 5000})
.trigger('mouseup') .trigger('mouseup')
cy.get('.cf-dapper-scrollbars--thumb-x') // TODO(zoe): replace with test ids https://github.com/influxdata/clockface/issues/507 cy.getByTestID('rawdata-table--scrollbar--thumb-x')
.trigger('mousedown', {force: true}) .trigger('mousedown', {force: true})
.trigger('mousemove', {clientX: 1000}) .trigger('mousemove', {clientX: 1000})
.trigger('mouseup') .trigger('mouseup')
@ -831,7 +831,7 @@ describe('DataExplorer', () => {
cy.getByTestID(`view-type--table`).click() cy.getByTestID(`view-type--table`).click()
cy.get('.time-machine--view').within(() => { cy.get('.time-machine--view').within(() => {
cy.get('.cf-dapper-scrollbars--thumb-y') // TODO(zoe): replace with test ids https://github.com/influxdata/clockface/issues/507 cy.getByTestID('dapper-scrollbars--thumb-y')
.trigger('mousedown', {force: true}) .trigger('mousedown', {force: true})
.trigger('mousemove', {clientY: 5000}) .trigger('mousemove', {clientY: 5000})
.trigger('mouseup') .trigger('mouseup')

View File

@ -133,7 +133,7 @@
"webpack-merge": "^4.2.1" "webpack-merge": "^4.2.1"
}, },
"dependencies": { "dependencies": {
"@influxdata/clockface": "2.2.0", "@influxdata/clockface": "2.3.1",
"@influxdata/flux": "^0.5.1", "@influxdata/flux": "^0.5.1",
"@influxdata/flux-lsp-browser": "^0.5.11", "@influxdata/flux-lsp-browser": "^0.5.11",
"@influxdata/giraffe": "0.23.0", "@influxdata/giraffe": "0.23.0",

View File

@ -9,7 +9,7 @@ import EventTable from 'src/eventViewer/components/EventTable'
import AlertHistoryControls from 'src/alerting/components/AlertHistoryControls' import AlertHistoryControls from 'src/alerting/components/AlertHistoryControls'
import AlertHistoryQueryParams from 'src/alerting/components/AlertHistoryQueryParams' import AlertHistoryQueryParams from 'src/alerting/components/AlertHistoryQueryParams'
import GetResources from 'src/resources/components/GetResources' import GetResources from 'src/resources/components/GetResources'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
// Constants // Constants
import { import {
@ -80,7 +80,7 @@ const AlertHistoryIndex: FC<Props> = ({
title="Check Statuses" title="Check Statuses"
testID="alert-history-title" testID="alert-history-title"
/> />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.ControlBar fullWidth={true}> <Page.ControlBar fullWidth={true}>
<AlertHistoryQueryParams <AlertHistoryQueryParams

View File

@ -1,6 +1,5 @@
// Libraries // Libraries
import React, {FunctionComponent, useState} from 'react' import React, {FunctionComponent, useState} from 'react'
import {connect} from 'react-redux'
import {Switch, Route} from 'react-router-dom' import {Switch, Route} from 'react-router-dom'
//Components //Components
@ -9,9 +8,8 @@ import ChecksColumn from 'src/checks/components/ChecksColumn'
import RulesColumn from 'src/notifications/rules/components/RulesColumn' import RulesColumn from 'src/notifications/rules/components/RulesColumn'
import EndpointsColumn from 'src/notifications/endpoints/components/EndpointsColumn' import EndpointsColumn from 'src/notifications/endpoints/components/EndpointsColumn'
import GetAssetLimits from 'src/cloud/components/GetAssetLimits' import GetAssetLimits from 'src/cloud/components/GetAssetLimits'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
import GetResources from 'src/resources/components/GetResources' import GetResources from 'src/resources/components/GetResources'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
import NewThresholdCheckEO from 'src/checks/components/NewThresholdCheckEO' import NewThresholdCheckEO from 'src/checks/components/NewThresholdCheckEO'
import NewDeadmanCheckEO from 'src/checks/components/NewDeadmanCheckEO' import NewDeadmanCheckEO from 'src/checks/components/NewDeadmanCheckEO'
import EditCheckEO from 'src/checks/components/EditCheckEO' import EditCheckEO from 'src/checks/components/EditCheckEO'
@ -22,28 +20,15 @@ import EditEndpointOverlay from 'src/notifications/endpoints/components/EditEndp
// Utils // Utils
import {pageTitleSuffixer} from 'src/shared/utils/pageTitles' import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
import {
extractMonitoringLimitStatus,
extractLimitedMonitoringResources,
} from 'src/cloud/utils/limits'
// Types // Types
import {AppState, ResourceType} from 'src/types' import {ResourceType} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits'
const alertsPath = '/orgs/:orgID/alerting' const alertsPath = '/orgs/:orgID/alerting'
interface StateProps {
limitStatus: LimitStatus
limitedResources: string
}
type ActiveColumn = 'checks' | 'endpoints' | 'rules' type ActiveColumn = 'checks' | 'endpoints' | 'rules'
const AlertingIndex: FunctionComponent<StateProps> = ({ const AlertingIndex: FunctionComponent = () => {
limitStatus,
limitedResources,
}) => {
const [activeColumn, setActiveColumn] = useState<ActiveColumn>('checks') const [activeColumn, setActiveColumn] = useState<ActiveColumn>('checks')
const pageContentsClassName = `alerting-index alerting-index__${activeColumn}` const pageContentsClassName = `alerting-index alerting-index__${activeColumn}`
@ -57,7 +42,7 @@ const AlertingIndex: FunctionComponent<StateProps> = ({
<Page titleTag={pageTitleSuffixer(['Alerts'])}> <Page titleTag={pageTitleSuffixer(['Alerts'])}>
<Page.Header fullWidth={true} testID="alerts-page--header"> <Page.Header fullWidth={true} testID="alerts-page--header">
<Page.Title title="Alerts" /> <Page.Title title="Alerts" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.Contents <Page.Contents
fullWidth={true} fullWidth={true}
@ -66,11 +51,6 @@ const AlertingIndex: FunctionComponent<StateProps> = ({
> >
<GetResources resources={[ResourceType.Labels, ResourceType.Buckets]}> <GetResources resources={[ResourceType.Labels, ResourceType.Buckets]}>
<GetAssetLimits> <GetAssetLimits>
<AssetLimitAlert
resourceName={limitedResources}
limitStatus={limitStatus}
className="load-data--asset-alert"
/>
<SelectGroup <SelectGroup
className="alerting-index--selector" className="alerting-index--selector"
shape={ButtonShape.StretchToFit} shape={ButtonShape.StretchToFit}
@ -152,11 +132,4 @@ const AlertingIndex: FunctionComponent<StateProps> = ({
) )
} }
const mstp = ({cloud: {limits}}: AppState) => { export default AlertingIndex
return {
limitStatus: extractMonitoringLimitStatus(limits),
limitedResources: extractLimitedMonitoringResources(limits),
}
}
export default connect(mstp)(AlertingIndex)

View File

@ -1,8 +1,10 @@
// Libraries // Libraries
import React, {FC, ReactChild, useState} from 'react' import React, {FC, ReactChild, useState} from 'react'
import {connect} from 'react-redux'
// Types // Types
import {ResourceType} from 'src/types' import {AppState, ResourceType} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits'
// Components // Components
import { import {
@ -17,13 +19,17 @@ import {
QuestionMarkTooltip, QuestionMarkTooltip,
ComponentColor, ComponentColor,
} from '@influxdata/clockface' } from '@influxdata/clockface'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
// Utils
import {extractMonitoringLimitStatus} from 'src/cloud/utils/limits'
type ColumnTypes = type ColumnTypes =
| ResourceType.NotificationRules | ResourceType.NotificationRules
| ResourceType.NotificationEndpoints | ResourceType.NotificationEndpoints
| ResourceType.Checks | ResourceType.Checks
interface Props { interface OwnProps {
type: ColumnTypes type: ColumnTypes
title: string title: string
createButton: JSX.Element createButton: JSX.Element
@ -31,10 +37,15 @@ interface Props {
children: (searchTerm: string) => ReactChild children: (searchTerm: string) => ReactChild
} }
const AlertsColumnHeader: FC<Props> = ({ interface StateProps {
limitStatus: LimitStatus
}
const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
type, type,
children, children,
title, title,
limitStatus,
createButton, createButton,
questionMarkTooltipContents, questionMarkTooltipContents,
}) => { }) => {
@ -75,11 +86,20 @@ const AlertsColumnHeader: FC<Props> = ({
autoHide={true} autoHide={true}
style={{width: '100%', height: '100%'}} style={{width: '100%', height: '100%'}}
> >
<div className="alerting-index--list">{children(searchTerm)}</div> <div className="alerting-index--list">
{children(searchTerm)}
<AssetLimitAlert resourceName={title} limitStatus={limitStatus} />
</div>
</DapperScrollbars> </DapperScrollbars>
</div> </div>
</Panel> </Panel>
) )
} }
export default AlertsColumnHeader const mstp = ({cloud: {limits}}: AppState) => {
return {
limitStatus: extractMonitoringLimitStatus(limits),
}
}
export default connect(mstp)(AlertsColumnHeader)

View File

@ -102,11 +102,6 @@ class BucketsTab extends PureComponent<Props, State> {
return ( return (
<> <>
<AssetLimitAlert
resourceName="buckets"
limitStatus={limitStatus}
className="load-data--asset-alert"
/>
<TabbedPageHeader <TabbedPageHeader
childrenLeft={leftHeaderItems} childrenLeft={leftHeaderItems}
childrenRight={rightHeaderItems} childrenRight={rightHeaderItems}
@ -136,6 +131,11 @@ class BucketsTab extends PureComponent<Props, State> {
/> />
)} )}
</FilterBuckets> </FilterBuckets>
<AssetLimitAlert
resourceName="buckets"
limitStatus={limitStatus}
className="load-data--asset-alert"
/>
</Grid.Column> </Grid.Column>
<Grid.Column <Grid.Column
widthXS={Columns.Twelve} widthXS={Columns.Twelve}

View File

@ -11,22 +11,15 @@ import BucketsTab from 'src/buckets/components/BucketsTab'
import GetResources from 'src/resources/components/GetResources' import GetResources from 'src/resources/components/GetResources'
import GetAssetLimits from 'src/cloud/components/GetAssetLimits' import GetAssetLimits from 'src/cloud/components/GetAssetLimits'
import LimitChecker from 'src/cloud/components/LimitChecker' import LimitChecker from 'src/cloud/components/LimitChecker'
import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
import LineProtocolWizard from 'src/dataLoaders/components/lineProtocolWizard/LineProtocolWizard' import LineProtocolWizard from 'src/dataLoaders/components/lineProtocolWizard/LineProtocolWizard'
import CollectorsWizard from 'src/dataLoaders/components/collectorsWizard/CollectorsWizard' import CollectorsWizard from 'src/dataLoaders/components/collectorsWizard/CollectorsWizard'
import UpdateBucketOverlay from 'src/buckets/components/UpdateBucketOverlay' import UpdateBucketOverlay from 'src/buckets/components/UpdateBucketOverlay'
import RenameBucketOverlay from 'src/buckets/components/RenameBucketOverlay' import RenameBucketOverlay from 'src/buckets/components/RenameBucketOverlay'
import CreateScraperOverlay from 'src/scrapers/components/CreateScraperOverlay' import CreateScraperOverlay from 'src/scrapers/components/CreateScraperOverlay'
import DeleteDataOverlay from 'src/shared/components/DeleteDataOverlay' import DeleteDataOverlay from 'src/shared/components/DeleteDataOverlay'
import { import {Page} from '@influxdata/clockface'
FlexBox,
FlexDirection,
JustifyContent,
Page,
} from '@influxdata/clockface'
// Utils // Utils
import {extractRateLimitResources} from 'src/cloud/utils/limits'
import {pageTitleSuffixer} from 'src/shared/utils/pageTitles' import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
import {getOrg} from 'src/organizations/selectors' import {getOrg} from 'src/organizations/selectors'
@ -38,7 +31,6 @@ import {AppState, Organization, ResourceType} from 'src/types'
interface StateProps { interface StateProps {
org: Organization org: Organization
limitedResources: string[]
} }
const bucketsPath = `/${ORGS}/${ORG_ID}/load-data/${BUCKETS}/${BUCKET_ID}` const bucketsPath = `/${ORGS}/${ORG_ID}/load-data/${BUCKETS}/${BUCKET_ID}`
@ -53,14 +45,6 @@ class BucketsIndex extends Component<StateProps> {
<Page titleTag={pageTitleSuffixer(['Buckets', 'Load Data'])}> <Page titleTag={pageTitleSuffixer(['Buckets', 'Load Data'])}>
<LimitChecker> <LimitChecker>
<LoadDataHeader /> <LoadDataHeader />
<FlexBox
direction={FlexDirection.Row}
justifyContent={JustifyContent.Center}
>
{this.isCardinalityExceeded && (
<RateLimitAlert className="load-data--rate-alert" />
)}
</FlexBox>
<LoadDataTabbedPage activeTab="buckets" orgID={org.id}> <LoadDataTabbedPage activeTab="buckets" orgID={org.id}>
<GetResources <GetResources
resources={[ resources={[
@ -102,22 +86,12 @@ class BucketsIndex extends Component<StateProps> {
</> </>
) )
} }
private get isCardinalityExceeded(): boolean {
const {limitedResources} = this.props
return limitedResources.includes('cardinality')
}
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {
const {
cloud: {limits},
} = state
const org = getOrg(state) const org = getOrg(state)
const limitedResources = extractRateLimitResources(limits)
return {org, limitedResources} return {org}
} }
export default connect<StateProps, {}, {}>(mstp, null)(BucketsIndex) export default connect<StateProps, {}, {}>(mstp, null)(BucketsIndex)

View File

@ -1,63 +1,61 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {FC} from 'react'
// Components // Components
import { import {
FlexBox, FlexBox,
FlexDirection,
AlignItems,
ComponentSize,
IconFont,
ComponentColor,
Alert,
JustifyContent, JustifyContent,
Gradients,
InfluxColors,
GradientBox,
Panel,
Heading,
HeadingElement,
AlignItems,
} from '@influxdata/clockface' } from '@influxdata/clockface'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
// Constants // Constants
import {CLOUD} from 'src/shared/constants' import {CLOUD} from 'src/shared/constants'
// Types // Types
import {LimitStatus} from 'src/cloud/actions/limits' import {LimitStatus} from 'src/cloud/actions/limits'
import CheckoutButton from 'src/cloud/components/CheckoutButton'
interface Props { interface Props {
resourceName: string
limitStatus: LimitStatus limitStatus: LimitStatus
resourceName: string
className?: string className?: string
} }
export default class AssetLimitAlert extends PureComponent<Props> { const AssetLimitAlert: FC<Props> = ({limitStatus, resourceName, className}) => {
public render() {
const {limitStatus, resourceName, className} = this.props
if (CLOUD && limitStatus === LimitStatus.EXCEEDED) { if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
return ( return (
<FlexBox <GradientBox
direction={FlexDirection.Column} borderGradient={Gradients.MiyazakiSky}
alignItems={AlignItems.Center} borderColor={InfluxColors.Raven}
margin={ComponentSize.Large}
stretchToFitWidth={true}
className={className} className={className}
> >
<Alert icon={IconFont.Cloud} color={ComponentColor.Primary}> <Panel backgroundColor={InfluxColors.Raven} className="asset-alert">
<Panel.Header>
<Heading element={HeadingElement.H4}>
Need more {resourceName}?
</Heading>
</Panel.Header>
<Panel.Body className="asset-alert--contents">
<FlexBox <FlexBox
alignItems={AlignItems.Center} justifyContent={JustifyContent.FlexEnd}
direction={FlexDirection.Row} alignItems={AlignItems.FlexEnd}
justifyContent={JustifyContent.SpaceBetween} stretchToFitHeight={true}
margin={ComponentSize.Medium}
> >
<div> <CloudUpgradeButton buttonText={`Get more ${resourceName}`} />
{`Hey there, looks like you have reached the maximum number of
${resourceName} you can create as part of your plan.`}
<br />
</div>
<CheckoutButton />
</FlexBox>
</Alert>
</FlexBox> </FlexBox>
</Panel.Body>
</Panel>
</GradientBox>
) )
} }
return null return null
}
} }
export default AssetLimitAlert

View File

@ -1,42 +0,0 @@
import React, {FunctionComponent} from 'react'
// Components
import {FeatureFlag} from 'src/shared/utils/featureFlag'
import {
Button,
ComponentColor,
ComponentSize,
FlexBox,
FlexDirection,
JustifyContent,
} from '@influxdata/clockface'
// Constants
import {CLOUD_CHECKOUT_PATH, CLOUD_URL} from 'src/shared/constants'
const CheckoutButton: FunctionComponent<{}> = () => {
const checkoutURL = `${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`
const onClick = () => {
window.open(checkoutURL, '_self')
}
return (
<FeatureFlag name="cloudBilling">
<FlexBox
direction={FlexDirection.Row}
justifyContent={JustifyContent.SpaceAround}
margin={ComponentSize.Small}
>
<div>Want to remove these limits?</div>
<Button
color={ComponentColor.Primary}
onClick={onClick}
text="Upgrade Now"
size={ComponentSize.Small}
/>
</FlexBox>
</FeatureFlag>
)
}
export default CheckoutButton

View File

@ -1,6 +1,7 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {FC} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import classnames from 'classnames'
// Components // Components
import { import {
@ -9,10 +10,12 @@ import {
AlignItems, AlignItems,
ComponentSize, ComponentSize,
IconFont, IconFont,
ComponentColor,
Alert,
JustifyContent, JustifyContent,
Gradients,
InfluxColors,
BannerPanel,
} from '@influxdata/clockface' } from '@influxdata/clockface'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
// Utils // Utils
import { import {
@ -26,7 +29,6 @@ import {CLOUD} from 'src/shared/constants'
// Types // Types
import {AppState} from 'src/types' import {AppState} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits' import {LimitStatus} from 'src/cloud/actions/limits'
import CheckoutButton from 'src/cloud/components/CheckoutButton'
interface StateProps { interface StateProps {
resources: string[] resources: string[]
@ -37,65 +39,58 @@ interface OwnProps {
} }
type Props = StateProps & OwnProps type Props = StateProps & OwnProps
class RateLimitAlert extends PureComponent<Props> { const RateLimitAlert: FC<Props> = ({status, className, resources}) => {
public render() { const rateLimitAlertClass = classnames('rate-alert', {
const {status, className} = this.props [`${className}`]: className,
})
if (CLOUD && status === LimitStatus.EXCEEDED) { if (
CLOUD &&
status === LimitStatus.EXCEEDED &&
resources.includes('cardinality')
) {
return ( return (
<FlexBox <FlexBox
direction={FlexDirection.Column} direction={FlexDirection.Column}
alignItems={AlignItems.Center} alignItems={AlignItems.Center}
margin={ComponentSize.Large} margin={ComponentSize.Large}
stretchToFitWidth={true} className={rateLimitAlertClass}
className={className}
> >
<Alert icon={IconFont.Cloud} color={ComponentColor.Primary}> <BannerPanel
size={ComponentSize.ExtraSmall}
gradient={Gradients.PolarExpress}
icon={IconFont.Cloud}
hideMobileIcon={true}
textColor={InfluxColors.Yeti}
>
<div className="rate-alert--content">
<span>
You've reached the maximum{' '}
<a
href="https://v2.docs.influxdata.com/v2.0/reference/glossary/#series-cardinality"
target="_blank"
>
series cardinality
</a>{' '}
available in your plan. Need to write more data?
</span>
<FlexBox <FlexBox
alignItems={AlignItems.Center} justifyContent={JustifyContent.Center}
direction={FlexDirection.Row} className="rate-alert--button"
justifyContent={JustifyContent.SpaceBetween}
margin={ComponentSize.Medium}
> >
<div> <CloudUpgradeButton />
{this.message}
<br />
</div>
<CheckoutButton />
</FlexBox> </FlexBox>
</Alert> </div>
</BannerPanel>
</FlexBox> </FlexBox>
) )
} }
if (CLOUD) {
return <CloudUpgradeButton />
}
return null return null
}
private get message(): string {
return `Hey there, it looks like you have exceeded your plan's ${this.resourceName} limits.${this.additionalMessage}`
}
private get additionalMessage(): string {
if (this.props.resources.includes('cardinality')) {
return ' Your writes will be rejected until resolved.'
}
return ''
}
private get resourceName(): string {
const {resources} = this.props
const renamedResources = resources.map(resource => {
if (resource === 'cardinality') {
return 'total series'
}
return resource
})
return renamedResources.join(' and ')
}
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {

View File

@ -62,7 +62,7 @@ class DashboardPage extends Component<Props> {
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
onManualRefresh={onManualRefresh} onManualRefresh={onManualRefresh}
/> />
<RateLimitAlert className="dashboard--rate-alert" /> <RateLimitAlert />
<VariablesControlBar /> <VariablesControlBar />
<DashboardComponent manualRefresh={manualRefresh} /> <DashboardComponent manualRefresh={manualRefresh} />
</HoverTimeProvider> </HoverTimeProvider>

View File

@ -1,19 +1,29 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import memoizeOne from 'memoize-one' import memoizeOne from 'memoize-one'
// Components // Components
import DashboardCard from 'src/dashboards/components/dashboard_index/DashboardCard' import DashboardCard from 'src/dashboards/components/dashboard_index/DashboardCard'
import {TechnoSpinner} from '@influxdata/clockface' import {TechnoSpinner} from '@influxdata/clockface'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
// Selectors // Selectors
import {getSortedResources, SortTypes} from 'src/shared/utils/sort' import {getSortedResources, SortTypes} from 'src/shared/utils/sort'
// Types // Types
import {Dashboard, RemoteDataState} from 'src/types' import {AppState, Dashboard, RemoteDataState} from 'src/types'
import {Sort} from 'src/clockface' import {Sort} from 'src/clockface'
import {LimitStatus} from 'src/cloud/actions/limits'
interface Props { // Utils
import {extractDashboardLimits} from 'src/cloud/utils/limits'
interface StateProps {
limitStatus: LimitStatus
}
interface OwnProps {
dashboards: Dashboard[] dashboards: Dashboard[]
sortKey: string sortKey: string
sortDirection: Sort sortDirection: Sort
@ -21,7 +31,7 @@ interface Props {
onFilterChange: (searchTerm: string) => void onFilterChange: (searchTerm: string) => void
} }
export default class DashboardCards extends PureComponent<Props> { class DashboardCards extends PureComponent<OwnProps & StateProps> {
private _observer private _observer
private _spinner private _spinner
@ -110,6 +120,11 @@ export default class DashboardCards extends PureComponent<Props> {
onFilterChange={onFilterChange} onFilterChange={onFilterChange}
/> />
))} ))}
<AssetLimitAlert
className="dashboards--asset-alert"
resourceName="dashboards"
limitStatus={this.props.limitStatus}
/>
</div> </div>
{windowSize * pages < dashboards.length && ( {windowSize * pages < dashboards.length && (
<div <div
@ -123,3 +138,15 @@ export default class DashboardCards extends PureComponent<Props> {
) )
} }
} }
const mstp = (state: AppState) => {
const {
cloud: {limits},
} = state
return {
limitStatus: extractDashboardLimits(limits),
}
}
export default connect(mstp)(DashboardCards)

View File

@ -13,9 +13,8 @@ import {Page} from '@influxdata/clockface'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget' import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown' import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import GetAssetLimits from 'src/cloud/components/GetAssetLimits' import GetAssetLimits from 'src/cloud/components/GetAssetLimits'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown' import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
import DashboardImportOverlay from 'src/dashboards/components/DashboardImportOverlay' import DashboardImportOverlay from 'src/dashboards/components/DashboardImportOverlay'
import CreateFromTemplateOverlay from 'src/templates/components/createFromTemplateOverlay/CreateFromTemplateOverlay' import CreateFromTemplateOverlay from 'src/templates/components/createFromTemplateOverlay/CreateFromTemplateOverlay'
import DashboardExportOverlay from 'src/dashboards/components/DashboardExportOverlay' import DashboardExportOverlay from 'src/dashboards/components/DashboardExportOverlay'
@ -53,8 +52,9 @@ class DashboardIndex extends PureComponent<Props, State> {
} }
public render() { public render() {
const {createDashboard, limitStatus, sortOptions} = this.props const {createDashboard, sortOptions} = this.props
const {searchTerm} = this.state const {searchTerm} = this.state
return ( return (
<> <>
<Page <Page
@ -63,7 +63,7 @@ class DashboardIndex extends PureComponent<Props, State> {
> >
<Page.Header fullWidth={false}> <Page.Header fullWidth={false}>
<Page.Title title="Dashboards" /> <Page.Title title="Dashboards" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.ControlBar fullWidth={false}> <Page.ControlBar fullWidth={false}>
<Page.ControlBarLeft> <Page.ControlBarLeft>
@ -97,10 +97,6 @@ class DashboardIndex extends PureComponent<Props, State> {
scrollable={true} scrollable={true}
> >
<GetAssetLimits> <GetAssetLimits>
<AssetLimitAlert
resourceName="dashboards"
limitStatus={limitStatus}
/>
<DashboardsIndexContents <DashboardsIndexContents
searchTerm={searchTerm} searchTerm={searchTerm}
onFilterChange={this.handleFilterDashboards} onFilterChange={this.handleFilterDashboards}

View File

@ -79,15 +79,8 @@ class DashboardsIndexContents extends Component<Props> {
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {
const {
cloud: {
limits: {status},
},
} = state
return { return {
dashboards: getAll<Dashboard>(state, ResourceType.Dashboards), dashboards: getAll<Dashboard>(state, ResourceType.Dashboards),
limitStatus: status,
} }
} }

View File

@ -5,7 +5,6 @@ import {useDispatch} from 'react-redux'
// Components // Components
import TimeMachine from 'src/timeMachine/components/TimeMachine' import TimeMachine from 'src/timeMachine/components/TimeMachine'
import LimitChecker from 'src/cloud/components/LimitChecker' import LimitChecker from 'src/cloud/components/LimitChecker'
import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
// Actions // Actions
import {setActiveTimeMachine} from 'src/timeMachine/actions' import {setActiveTimeMachine} from 'src/timeMachine/actions'
@ -28,7 +27,6 @@ const DataExplorer: FC = () => {
return ( return (
<LimitChecker> <LimitChecker>
<RateLimitAlert />
<div className="data-explorer"> <div className="data-explorer">
<HoverTimeProvider> <HoverTimeProvider>
<TimeMachine /> <TimeMachine />

View File

@ -11,7 +11,7 @@ import ViewTypeDropdown from 'src/timeMachine/components/view_options/ViewTypeDr
import GetResources from 'src/resources/components/GetResources' import GetResources from 'src/resources/components/GetResources'
import TimeZoneDropdown from 'src/shared/components/TimeZoneDropdown' import TimeZoneDropdown from 'src/shared/components/TimeZoneDropdown'
import DeleteDataButton from 'src/dataExplorer/components/DeleteDataButton' import DeleteDataButton from 'src/dataExplorer/components/DeleteDataButton'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
import SaveAsOverlay from 'src/dataExplorer/components/SaveAsOverlay' import SaveAsOverlay from 'src/dataExplorer/components/SaveAsOverlay'
import DEDeleteDataOverlay from 'src/dataExplorer/components/DeleteDataOverlay' import DEDeleteDataOverlay from 'src/dataExplorer/components/DeleteDataOverlay'
@ -40,7 +40,7 @@ const DataExplorerPage: FC = () => {
<GetResources resources={[ResourceType.Variables]}> <GetResources resources={[ResourceType.Variables]}>
<Page.Header fullWidth={true} testID="data-explorer--header"> <Page.Header fullWidth={true} testID="data-explorer--header">
<Page.Title title="Data Explorer" /> <Page.Title title="Data Explorer" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.ControlBar fullWidth={true}> <Page.ControlBar fullWidth={true}>
<Page.ControlBarLeft> <Page.ControlBarLeft>

View File

@ -16,7 +16,7 @@ import {
import Resources from 'src/me/components/Resources' import Resources from 'src/me/components/Resources'
import Docs from 'src/me/components/Docs' import Docs from 'src/me/components/Docs'
import GettingStarted from 'src/me/components/GettingStarted' import GettingStarted from 'src/me/components/GettingStarted'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
// Utils // Utils
import {pageTitleSuffixer} from 'src/shared/utils/pageTitles' import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
@ -39,7 +39,7 @@ export class MePage extends PureComponent<Props> {
<Page titleTag={pageTitleSuffixer(['Home'])}> <Page titleTag={pageTitleSuffixer(['Home'])}>
<Page.Header fullWidth={false}> <Page.Header fullWidth={false}>
<Page.Title title="Getting Started" testID="home-page--header" /> <Page.Title title="Getting Started" testID="home-page--header" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.Contents fullWidth={false} scrollable={true}> <Page.Contents fullWidth={false} scrollable={true}>
<Grid> <Grid>

View File

@ -1,18 +1,16 @@
import React, {Component} from 'react' import React, {FC} from 'react'
// Components // Components
import {Page} from '@influxdata/clockface' import {Page} from '@influxdata/clockface'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
class LoadDataHeader extends Component { const LoadDataHeader: FC = () => {
public render() {
return ( return (
<Page.Header fullWidth={false} testID="load-data--header"> <Page.Header fullWidth={false} testID="load-data--header">
<Page.Title title="Load Data" /> <Page.Title title="Load Data" />
<CloudUpgradeButton /> <RateLimitAlert className="load-data--rate-alert" />
</Page.Header> </Page.Header>
) )
}
} }
export default LoadDataHeader export default LoadDataHeader

View File

@ -2,14 +2,14 @@ import React, {Component} from 'react'
// Components // Components
import {Page} from '@influxdata/clockface' import {Page} from '@influxdata/clockface'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
class SettingsHeader extends Component { class SettingsHeader extends Component {
public render() { public render() {
return ( return (
<Page.Header fullWidth={false}> <Page.Header fullWidth={false}>
<Page.Title title="Settings" testID="settings-page--header" /> <Page.Title title="Settings" testID="settings-page--header" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
) )
} }

View File

@ -2,8 +2,15 @@
import React, {FC} from 'react' import React, {FC} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {get, find} from 'lodash' import {get, find} from 'lodash'
import classnames from 'classnames'
// Components // Components
import {
LinkButton,
ComponentColor,
ComponentSize,
ButtonShape,
} from '@influxdata/clockface'
import CloudOnly from 'src/shared/components/cloud/CloudOnly' import CloudOnly from 'src/shared/components/cloud/CloudOnly'
// Constants // Constants
@ -20,17 +27,32 @@ interface StateProps {
inView: boolean inView: boolean
} }
const CloudUpgradeButton: FC<StateProps> = ({inView}) => { interface OwnProps {
className?: string
buttonText?: string
}
const CloudUpgradeButton: FC<StateProps & OwnProps> = ({
inView,
className,
buttonText = 'Upgrade Now',
}) => {
const cloudUpgradeButtonClass = classnames('upgrade-payg--button', {
[`${className}`]: className,
})
return ( return (
<CloudOnly> <CloudOnly>
{inView && ( {inView && (
<a <LinkButton
className="cf-button cf-button-sm cf-button-success upgrade-payg--button" className={cloudUpgradeButtonClass}
color={ComponentColor.Success}
size={ComponentSize.Small}
shape={ButtonShape.Default}
href={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`} href={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self" target="_self"
> text={buttonText}
Upgrade Now />
</a>
)} )}
</CloudOnly> </CloudOnly>
) )

View File

@ -1,20 +1,67 @@
.dashboard--rate-alert {
padding: 0 $page-gutter;
width: 100%;
padding-bottom: 16px;
}
.load-data--rate-alert {
padding: 0 $page-gutter;
padding-bottom: 16px;
max-width: 1608px;
}
.load-data--asset-alert { .load-data--asset-alert {
padding-bottom: 16px; margin-bottom: $cf-marg-d;
} }
// Might need to remove this eventually .cf-page-control-bar {
.cf-page-header--fixed { + .rate-alert {
justify-content: space-between; margin: 0 $cf-marg-c $cf-marg-c;
}
}
.rate-alert--content {
display: inline-flex;
flex-direction: column;
font-weight: 500;
.rate-alert--button {
margin-top: $cf-marg-b;
}
}
.cf-page--title {
flex-shrink: 0;
}
.asset-alert {
height: 100%;
background-position: bottom -20px left -20px;
background-size: contain;
background-repeat: no-repeat;
background-image: url('../../assets/images/dashboard-empty--dark.svg');
}
.dashboards--asset-alert {
.asset-alert--contents {
position: absolute;
bottom: 0;
}
}
a.upgrade-payg--button {
@include gradient-diag-up($c-pool, $c-rainforest);
}
@media screen and (min-width: $cf-nav-menu--breakpoint) {
.rate-alert {
margin-left: $cf-marg-d;
}
.rate-alert--content {
flex-direction: row;
width: 100%;
height: 100%;
align-items: center;
justify-content: space-between;
.rate-alert--button {
margin-top: 0;
margin-left: $cf-marg-c;
}
}
.cf-page-control-bar {
+ .rate-alert {
margin: 0 $cf-marg-d $cf-marg-c;
}
}
} }

View File

@ -9,7 +9,6 @@ $nav-size: 50px;
$nav-breakpoint: 800px; $nav-breakpoint: 800px;
$page-header-size: 66px; $page-header-size: 66px;
$page-max-width: 1608px; $page-max-width: 1608px;
$page-gutter: 54px;
$page-title-size: 21px; $page-title-size: 21px;
$page-title-weight: 400; $page-title-weight: 400;
$sidebar--width: 50px; //delete this later $sidebar--width: 50px; //delete this later

View File

@ -15,7 +15,7 @@ import {
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown' import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import SearchWidget from 'src/shared/components/search_widget/SearchWidget' import SearchWidget from 'src/shared/components/search_widget/SearchWidget'
import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown' import ResourceSortDropdown from 'src/shared/components/resource_sort_dropdown/ResourceSortDropdown'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton' import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
// Types // Types
import {LimitStatus} from 'src/cloud/actions/limits' import {LimitStatus} from 'src/cloud/actions/limits'
@ -63,7 +63,7 @@ export default class TasksHeader extends PureComponent<Props> {
<> <>
<Page.Header fullWidth={false} testID="tasks-page--header"> <Page.Header fullWidth={false} testID="tasks-page--header">
<Page.Title title="Tasks" /> <Page.Title title="Tasks" />
<CloudUpgradeButton /> <RateLimitAlert />
</Page.Header> </Page.Header>
<Page.ControlBar fullWidth={false}> <Page.ControlBar fullWidth={false}>
<Page.ControlBarLeft> <Page.ControlBarLeft>

View File

@ -113,10 +113,6 @@ class TasksPage extends PureComponent<Props, State> {
<Page.Contents fullWidth={false} scrollable={true}> <Page.Contents fullWidth={false} scrollable={true}>
<GetResources resources={[ResourceType.Tasks, ResourceType.Labels]}> <GetResources resources={[ResourceType.Tasks, ResourceType.Labels]}>
<GetAssetLimits> <GetAssetLimits>
<AssetLimitAlert
resourceName="tasks"
limitStatus={limitStatus}
/>
<Filter <Filter
list={this.filteredTasks} list={this.filteredTasks}
searchTerm={searchTerm} searchTerm={searchTerm}
@ -147,6 +143,10 @@ class TasksPage extends PureComponent<Props, State> {
)} )}
</Filter> </Filter>
{this.hiddenTaskAlert} {this.hiddenTaskAlert}
<AssetLimitAlert
resourceName="tasks"
limitStatus={limitStatus}
/>
</GetAssetLimits> </GetAssetLimits>
</GetResources> </GetResources>
</Page.Contents> </Page.Contents>

View File

@ -10,15 +10,9 @@ import LoadDataHeader from 'src/settings/components/LoadDataHeader'
import Collectors from 'src/telegrafs/components/Collectors' import Collectors from 'src/telegrafs/components/Collectors'
import GetResources from 'src/resources/components/GetResources' import GetResources from 'src/resources/components/GetResources'
import LimitChecker from 'src/cloud/components/LimitChecker' import LimitChecker from 'src/cloud/components/LimitChecker'
import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
import TelegrafInstructionsOverlay from 'src/telegrafs/components/TelegrafInstructionsOverlay' import TelegrafInstructionsOverlay from 'src/telegrafs/components/TelegrafInstructionsOverlay'
import CollectorsWizard from 'src/dataLoaders/components/collectorsWizard/CollectorsWizard' import CollectorsWizard from 'src/dataLoaders/components/collectorsWizard/CollectorsWizard'
import { import {Page} from '@influxdata/clockface'
FlexBox,
FlexDirection,
JustifyContent,
Page,
} from '@influxdata/clockface'
import OverlayHandler, { import OverlayHandler, {
RouteOverlay, RouteOverlay,
} from 'src/overlays/components/RouteOverlay' } from 'src/overlays/components/RouteOverlay'
@ -39,7 +33,6 @@ const TelegrafOutputOverlay = RouteOverlay(
) )
// Utils // Utils
import {extractRateLimitResources} from 'src/cloud/utils/limits'
import {pageTitleSuffixer} from 'src/shared/utils/pageTitles' import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
import {getOrg} from 'src/organizations/selectors' import {getOrg} from 'src/organizations/selectors'
@ -51,7 +44,6 @@ import {ORGS, ORG_ID, TELEGRAFS} from 'src/shared/constants/routes'
interface StateProps { interface StateProps {
org: Organization org: Organization
limitedResources: string[]
} }
const telegrafsPath = `/${ORGS}/${ORG_ID}/load-data/${TELEGRAFS}` const telegrafsPath = `/${ORGS}/${ORG_ID}/load-data/${TELEGRAFS}`
@ -66,14 +58,6 @@ class TelegrafsPage extends PureComponent<StateProps> {
<Page titleTag={pageTitleSuffixer(['Telegraf', 'Load Data'])}> <Page titleTag={pageTitleSuffixer(['Telegraf', 'Load Data'])}>
<LimitChecker> <LimitChecker>
<LoadDataHeader /> <LoadDataHeader />
<FlexBox
direction={FlexDirection.Row}
justifyContent={JustifyContent.Center}
>
{this.isCardinalityExceeded && (
<RateLimitAlert className="load-data--rate-alert" />
)}
</FlexBox>
<LoadDataTabbedPage activeTab="telegrafs" orgID={org.id}> <LoadDataTabbedPage activeTab="telegrafs" orgID={org.id}>
<GetResources <GetResources
resources={[ResourceType.Buckets, ResourceType.Telegrafs]} resources={[ResourceType.Buckets, ResourceType.Telegrafs]}
@ -101,21 +85,11 @@ class TelegrafsPage extends PureComponent<StateProps> {
</> </>
) )
} }
private get isCardinalityExceeded(): boolean {
const {limitedResources} = this.props
return limitedResources.includes('cardinality')
}
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {
const org = getOrg(state) const org = getOrg(state)
const { return {org}
cloud: {limits},
} = state
const limitedResources = extractRateLimitResources(limits)
return {org, limitedResources}
} }
export default connect<StateProps>(mstp)(TelegrafsPage) export default connect<StateProps>(mstp)(TelegrafsPage)

View File

@ -747,10 +747,10 @@
debug "^3.1.0" debug "^3.1.0"
lodash.once "^4.1.1" lodash.once "^4.1.1"
"@influxdata/clockface@2.2.0": "@influxdata/clockface@2.3.1":
version "2.2.0" version "2.3.1"
resolved "https://registry.yarnpkg.com/@influxdata/clockface/-/clockface-2.2.0.tgz#73f09f4832d6b6bad53af029844a11dd6562527e" resolved "https://registry.yarnpkg.com/@influxdata/clockface/-/clockface-2.3.1.tgz#562a218e62b50ba16cd4a4e1e88b2e335289c595"
integrity sha512-pIQPJXjvVgzcryhAjgZPSoC5BRLbQb1sIIY9l6KQCg4DWJkxqFC/sPI7qJItRXd8kiPXbfbHvXGAwqIY+TdWNQ== integrity sha512-NqPaCT/vUOCEnjAekfECAvUS3OiSwHREwG/5YuawCw5EoswcUc9h6sMSo2spPxXQuiVAB4RcsxeX3+hqP6pU7w==
"@influxdata/flux-lsp-browser@^0.5.11": "@influxdata/flux-lsp-browser@^0.5.11":
version "0.5.11" version "0.5.11"